QtCanpool 3.3.0
载入中...
搜索中...
未找到
qxpluginmanagerdialog.cpp
浏览该文件的文档.
1
7
10
11#include <QtCore/QAbstractItemModel>
12#include <QtCore/QCoreApplication>
13#include <QtCore/QDebug>
14#include <QtWidgets/QAbstractButton>
15#include <QtWidgets/QDialogButtonBox>
16#include <QtWidgets/QFormLayout>
17#include <QtWidgets/QGroupBox>
18#include <QtWidgets/QHBoxLayout>
19#include <QtWidgets/QHeaderView>
20#include <QtWidgets/QLabel>
21#include <QtWidgets/QPushButton>
22#include <QtWidgets/QTreeWidget>
23#include <QtWidgets/QTreeWidgetItem>
24#include <QtWidgets/QVBoxLayout>
25
26#include <algorithm>
27#include <utility>
28
30
33
34namespace
35{
36
37/* Column of the load order, of the switch, of the version and of the state. */
38const int kOrderColumn = 0;
39const int kPluginColumn = 1;
40const int kVersionColumn = 2;
41const int kStatusColumn = 3;
42
44QString disabledKey()
45{
46 return QStringLiteral("plugins/disabled");
47}
48
49QString enabledKey()
50{
51 return QStringLiteral("plugins/enabled");
52}
53
55QString emptyMark()
56{
57 return QStringLiteral("-");
58}
59
60QString joined(const QStringList &values)
61{
62 return values.isEmpty() ? emptyMark() : values.join(QStringLiteral(", "));
63}
64
70QString stateTextOf(QxPluginSpec *spec)
71{
72 const auto text = [](const char *source) {
73 return QCoreApplication::translate("QxPluginManagerDialog", source);
74 };
75
76 switch (spec->state()) {
77 case QxPluginState::Invalid:
78 return text("Metadata is invalid");
79 case QxPluginState::Read:
80 return text("Not resolved");
81 case QxPluginState::Resolved:
82 return text("Resolved");
83 case QxPluginState::Loaded:
84 return text("Loaded");
85 case QxPluginState::Initialized:
86 return text("Running");
87 case QxPluginState::Stopped:
88 return text("Stopped");
89 case QxPluginState::Disabled:
90 return spec->error().isEmpty() ? text("Switched off") : text("Failed");
91 }
92 return QString();
93}
94
96QLabel *addValueRow(QFormLayout *form, const QString &title)
97{
98 QLabel *value = new QLabel;
99 value->setWordWrap(true);
100 value->setTextInteractionFlags(Qt::TextSelectableByMouse);
101 form->addRow(title, value);
102 return value;
103}
104
105} // namespace
106
107QxPluginManagerDialogPrivate::QxPluginManagerDialogPrivate() = default;
108
109void QxPluginManagerDialogPrivate::init()
110{
112
113 q->setWindowTitle(QxPluginManagerDialog::tr("Plugins"));
114 q->resize(840, 540);
115
116 QVBoxLayout *outer = new QVBoxLayout(q);
117
118 QHBoxLayout *body = new QHBoxLayout();
119 outer->addLayout(body, 1);
120
121 m_tree = new QTreeWidget(q);
122 m_tree->setRootIsDecorated(false);
123 m_tree->setUniformRowHeights(true);
124 m_tree->setAllColumnsShowFocus(true);
125 m_tree->setSelectionMode(QAbstractItemView::SingleSelection);
126 m_tree->setColumnCount(4);
127 m_tree->setHeaderLabels(QStringList{QxPluginManagerDialog::tr("Order"), QxPluginManagerDialog::tr("Plugin"),
128 QxPluginManagerDialog::tr("Version"), QxPluginManagerDialog::tr("Status")});
129 m_tree->header()->setSectionResizeMode(kPluginColumn, QHeaderView::Stretch);
130 m_tree->setColumnWidth(kOrderColumn, 64);
131 m_tree->setColumnWidth(kVersionColumn, 90);
132 m_tree->setColumnWidth(kStatusColumn, 130);
133 body->addWidget(m_tree, 3);
134
135 QVBoxLayout *side = new QVBoxLayout();
136 body->addLayout(side, 2);
137
138 QGroupBox *details = new QGroupBox(QxPluginManagerDialog::tr("Details"), q);
139 QFormLayout *form = new QFormLayout(details);
140 m_id = addValueRow(form, QxPluginManagerDialog::tr("Plugin"));
141 m_description = addValueRow(form, QxPluginManagerDialog::tr("Description"));
142 m_version = addValueRow(form, QxPluginManagerDialog::tr("Version"));
143 m_vendor = addValueRow(form, QxPluginManagerDialog::tr("Vendor"));
144 m_file = addValueRow(form, QxPluginManagerDialog::tr("File"));
145 m_order = addValueRow(form, QxPluginManagerDialog::tr("Load order"));
146 m_state = addValueRow(form, QxPluginManagerDialog::tr("State"));
147 m_dependencies = addValueRow(form, QxPluginManagerDialog::tr("Depends on"));
148 m_optional = addValueRow(form, QxPluginManagerDialog::tr("Optional"));
149 m_dependents = addValueRow(form, QxPluginManagerDialog::tr("Required by"));
150 m_error = addValueRow(form, QxPluginManagerDialog::tr("Error"));
151 side->addWidget(details);
152
153 QGroupBox *diagnosticsBox = new QGroupBox(QxPluginManagerDialog::tr("Diagnostics"), q);
154 QVBoxLayout *diagnosticsLayout = new QVBoxLayout(diagnosticsBox);
155 m_diagnostics = new QLabel(diagnosticsBox);
156 m_diagnostics->setWordWrap(true);
157 m_diagnostics->setTextInteractionFlags(Qt::TextSelectableByMouse);
158 m_diagnostics->setAlignment(Qt::AlignLeft | Qt::AlignTop);
159 diagnosticsLayout->addWidget(m_diagnostics);
160 side->addWidget(diagnosticsBox, 1);
161
162 QLabel *hint = new QLabel(
163 QxPluginManagerDialog::tr("Switching a plugin on or off takes effect the next time the application starts."),
164 q);
165 hint->setWordWrap(true);
166 outer->addWidget(hint);
167
168 m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply, q);
169 outer->addWidget(m_buttons);
170
171 QObject::connect(m_buttons, &QDialogButtonBox::accepted, q, [q]() {
172 q->accept();
173 });
174 QObject::connect(m_buttons, &QDialogButtonBox::rejected, q, [q]() {
175 q->reject();
176 });
177 // Applied is called through the dialog rather than bound straight to the
178 // button: clicked() carries a checked argument apply() does not take.
179 QObject::connect(m_buttons->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, [q]() {
180 q->apply();
181 });
182
183 QObject::connect(m_tree, &QTreeWidget::currentItemChanged, q, [this](QTreeWidgetItem *current, QTreeWidgetItem *) {
184 const int row = current ? m_tree->indexOfTopLevelItem(current) : -1;
185 showSpec(m_specs.value(row, Q_NULLPTR));
186 });
187 QObject::connect(m_tree, &QTreeWidget::itemChanged, q, [this](QTreeWidgetItem *item) {
188 noteSwitch(item);
189 });
190
191 updateButtons();
192}
193
194QxPluginSpec *QxPluginManagerDialogPrivate::specById(const QString &id) const
195{
196 for (QxPluginSpec *spec : std::as_const(m_specs)) {
197 if (spec->id() == id)
198 return spec;
199 }
200 return Q_NULLPTR;
201}
202
203bool QxPluginManagerDialogPrivate::savedState(QxPluginSpec *spec) const
204{
205 if (!spec)
206 return false;
207 if (m_savedDisabled.contains(spec->id()))
208 return false;
209 if (m_savedEnabled.contains(spec->id()))
210 return true;
211 return spec->isEnabledByDefault();
212}
213
214bool QxPluginManagerDialogPrivate::isModified() const
215{
216 for (QxPluginSpec *spec : std::as_const(m_specs)) {
217 if (m_switch.value(spec->id(), spec->isEnabledByDefault()) != savedState(spec))
218 return true;
219 }
220 return false;
221}
222
223void QxPluginManagerDialogPrivate::updateButtons()
224{
225 if (!m_buttons)
226 return;
227 if (QAbstractButton *apply = m_buttons->button(QDialogButtonBox::Apply))
228 apply->setEnabled(isModified());
229}
230
231void QxPluginManagerDialogPrivate::noteSwitch(QTreeWidgetItem *item)
232{
233 if (m_updating || !item)
234 return;
235 const int row = m_tree->indexOfTopLevelItem(item);
236 if (row < 0 || row >= m_specs.count())
237 return;
238 m_switch.insert(m_specs.at(row)->id(), item->checkState(kPluginColumn) == Qt::Checked);
239 updateButtons();
240}
241
242void QxPluginManagerDialogPrivate::rebuild()
243{
244 if (!m_tree)
245 return;
246
247 // Filling the list moves the switches about, and every move is a change the
248 // user did not make; m_updating keeps those out of m_switch.
249 m_updating = true;
250 m_tree->clear();
251 m_specs.clear();
252 m_switch.clear();
253
254 QList<QxPluginSpec *> specs;
255 QHash<QxPluginSpec *, int> orderOf;
256 if (!m_manager.isNull()) {
257 specs = m_manager->allSpecs();
258 const QList<QxPluginSpec *> ordered = m_manager->specs();
259 for (int i = 0; i < ordered.count(); ++i)
260 orderOf.insert(ordered.at(i), i);
261 }
262
263 // The plugins that load come first, in the order they load in; everything
264 // else - switched off, failed, unresolved - follows by name.
265 const auto rank = [&orderOf](QxPluginSpec *spec) {
266 return orderOf.value(spec, -1);
267 };
268 std::stable_sort(specs.begin(), specs.end(), [&rank](QxPluginSpec *left, QxPluginSpec *right) {
269 const int leftRank = rank(left);
270 const int rightRank = rank(right);
271 if (leftRank >= 0 && rightRank < 0)
272 return true;
273 if (leftRank < 0 && rightRank >= 0)
274 return false;
275 if (leftRank >= 0 && rightRank >= 0 && leftRank != rightRank)
276 return leftRank < rightRank;
277 return QString::compare(left->id(), right->id(), Qt::CaseInsensitive) < 0;
278 });
279
280 for (QxPluginSpec *spec : std::as_const(specs)) {
281 const int order = orderOf.value(spec, -1);
282 QTreeWidgetItem *item = new QTreeWidgetItem(m_tree);
283 item->setText(kOrderColumn, order < 0 ? emptyMark() : QString::number(order + 1));
284 item->setText(kPluginColumn, spec->id());
285 item->setText(kVersionColumn, spec->version().toString());
286 item->setText(kStatusColumn, stateTextOf(spec));
287 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
288 const bool on = savedState(spec);
289 item->setCheckState(kPluginColumn, on ? Qt::Checked : Qt::Unchecked);
290 if (!spec->error().isEmpty())
291 item->setToolTip(kPluginColumn, spec->error());
292
293 m_specs.append(spec);
294 m_switch.insert(spec->id(), on);
295 }
296 m_updating = false;
297
298 if (!m_specs.isEmpty())
299 m_tree->setCurrentItem(m_tree->topLevelItem(0));
300 else
301 showSpec(Q_NULLPTR);
302
303 m_diagnostics->setText(!m_manager.isNull() && m_manager->hasError() ? m_manager->errorString()
304 : QxPluginManagerDialog::tr("No errors."));
305 updateButtons();
306}
307
308void QxPluginManagerDialogPrivate::showSpec(QxPluginSpec *spec)
309{
310 const QList<QLabel *> labels{m_id, m_description, m_version, m_vendor, m_file, m_order,
311 m_state, m_dependencies, m_optional, m_dependents, m_error};
312
313 if (!spec) {
314 for (QLabel *label : labels)
315 label->clear();
316 return;
317 }
318
319 int order = -1;
320 if (!m_manager.isNull())
321 order = m_manager->specs().indexOf(spec);
322
323 // What depends on this one: the other half of the picture the detail pane is
324 // for, and the thing a reader cannot get by looking at one plugin alone.
325 QStringList dependents;
326 for (QxPluginSpec *other : std::as_const(m_specs)) {
327 if (other == spec)
328 continue;
329 if (other->dependencies().contains(spec->id()))
330 dependents.append(other->id());
331 else if (other->optionalDependencies().contains(spec->id()))
332 dependents.append(QxPluginManagerDialog::tr("%1 (optional)").arg(other->id()));
333 }
334
335 m_id->setText(spec->id());
336 m_description->setText(spec->description().isEmpty() ? emptyMark() : spec->description());
337 if (spec->compatVersion() == spec->version())
338 m_version->setText(spec->version().toString());
339 else
340 m_version->setText(QxPluginManagerDialog::tr("%1 (host compatible from %2)")
341 .arg(spec->version().toString(), spec->compatVersion().toString()));
342 m_vendor->setText(spec->vendor().isEmpty() ? emptyMark() : spec->vendor());
343 m_file->setText(spec->filePath().isEmpty() ? emptyMark() : spec->filePath());
344 m_order->setText(order < 0 ? emptyMark() : QString::number(order + 1));
345 m_state->setText(stateTextOf(spec));
346 m_dependencies->setText(joined(spec->dependencies()));
347 m_optional->setText(joined(spec->optionalDependencies()));
348 m_dependents->setText(joined(dependents));
349 m_error->setText(spec->error().isEmpty() ? emptyMark() : spec->error());
350}
351
352QxPluginManagerDialog::QxPluginManagerDialog(QxPluginManager *manager, QxSettings *settings, QWidget *parent)
353 : QDialog(parent)
354 , d_ptr(new QxPluginManagerDialogPrivate())
355{
357 d->setPublic(this);
358 d->m_manager = manager;
359 d->m_settings = settings;
360 d->init();
361 // Loaded here as well as on show, so a caller that never exec()s it still
362 // gets a dialog that tells the truth.
363 reload();
364}
365
367{
369 // The tree is a child of this dialog and outlives the private half, which
370 // QX_FINI_PRIVATE deletes right now. Tearing the tree down would otherwise
371 // call back into state that has just gone away.
372 if (d->m_tree)
373 d->m_tree->blockSignals(true);
375}
376
378{
379 return settings ? settings->stringListValue(disabledKey()) : QStringList();
380}
381
382void QxPluginManagerDialog::setDisabledPlugins(QxSettings *settings, const QStringList &ids)
383{
384 if (!settings)
385 return;
386 if (ids.isEmpty())
387 settings->remove(disabledKey());
388 else
389 settings->setValue(disabledKey(), ids);
390}
391
393{
394 return settings ? settings->stringListValue(enabledKey()) : QStringList();
395}
396
397void QxPluginManagerDialog::setEnabledPlugins(QxSettings *settings, const QStringList &ids)
398{
399 if (!settings)
400 return;
401 if (ids.isEmpty())
402 settings->remove(enabledKey());
403 else
404 settings->setValue(enabledKey(), ids);
405}
406
408{
409 Q_D(const QxPluginManagerDialog);
410 return d->m_manager;
411}
412
413int QxPluginManagerDialog::rowOf(const QString &id) const
414{
415 Q_D(const QxPluginManagerDialog);
416 for (int i = 0; i < d->m_specs.count(); ++i) {
417 if (d->m_specs.at(i)->id() == id)
418 return i;
419 }
420 return -1;
421}
422
424{
425 Q_D(const QxPluginManagerDialog);
426 return d->m_specs.count();
427}
428
429bool QxPluginManagerDialog::isChecked(const QString &id) const
430{
431 Q_D(const QxPluginManagerDialog);
432 QxPluginSpec *spec = d->specById(id);
433 if (!spec)
434 return false;
435 return d->m_switch.value(id, spec->isEnabledByDefault());
436}
437
438void QxPluginManagerDialog::setChecked(const QString &id, bool checked)
439{
441 const int row = rowOf(id);
442 if (row < 0)
443 return;
444 d->m_updating = true;
445 d->m_tree->topLevelItem(row)->setCheckState(kPluginColumn, checked ? Qt::Checked : Qt::Unchecked);
446 d->m_updating = false;
447 d->m_switch.insert(id, checked);
448 d->updateButtons();
449}
450
452{
454 const int row = rowOf(id);
455 if (row < 0)
456 return;
457 // Going through the tree keeps the selection, the detail pane and the
458 // selectionChanged handler in step.
459 d->m_tree->setCurrentItem(d->m_tree->topLevelItem(row));
460}
461
463{
464 Q_D(const QxPluginManagerDialog);
465 return d->m_diagnostics ? d->m_diagnostics->text() : QString();
466}
467
469{
470 Q_D(const QxPluginManagerDialog);
471 return d->isModified();
472}
473
475{
477 d->m_savedDisabled = disabledPlugins(d->m_settings);
478 d->m_savedEnabled = enabledPlugins(d->m_settings);
479 d->rebuild();
480}
481
483{
485 d->m_updating = true;
486 for (int i = 0; i < d->m_specs.count(); ++i) {
487 QxPluginSpec *spec = d->m_specs.at(i);
488 const bool on = d->savedState(spec);
489 d->m_tree->topLevelItem(i)->setCheckState(kPluginColumn, on ? Qt::Checked : Qt::Unchecked);
490 d->m_switch.insert(spec->id(), on);
491 }
492 d->m_updating = false;
493 d->updateButtons();
494}
495
497{
499 if (!d->m_settings) {
500 qWarning("QxPluginManagerDialog: there is no settings object to apply to");
501 return;
502 }
503
504 QStringList disabled;
505 QStringList enabled;
506 for (QxPluginSpec *spec : std::as_const(d->m_specs)) {
507 const bool want = d->m_switch.value(spec->id(), spec->isEnabledByDefault());
508 // Only the deviations are stored: a plugin that agrees with its metadata
509 // belongs in neither list, which keeps both lists small and keeps the
510 // metadata the default a fresh installation starts from.
511 if (want == spec->isEnabledByDefault())
512 continue;
513 if (want)
514 enabled.append(spec->id());
515 else
516 disabled.append(spec->id());
517 }
518
519 setDisabledPlugins(d->m_settings, disabled);
520 setEnabledPlugins(d->m_settings, enabled);
521 d->m_settings->sync();
522
523 // What is stored is the new baseline, so the apply button goes dark again.
524 d->m_savedDisabled = disabled;
525 d->m_savedEnabled = enabled;
526 d->updateButtons();
527 Q_EMIT applied();
528}
529
531{
532 apply();
533 QDialog::accept();
534}
535
537{
538 revert();
539 QDialog::reject();
540}
541
542void QxPluginManagerDialog::showEvent(QShowEvent *event)
543{
545 // Shown, not constructed: a later loadPlugins() finds plugins this dialog has
546 // not seen, and one opened twice has to show what is stored now rather than
547 // what was edited and discarded last time.
548 reload();
549 QDialog::showEvent(event);
550}
551
The state behind QxPluginManagerDialog: the list, the switches it shows and the two lists the setting...
Lists the plugins the manager found and lets their switches be set.
void reload()
Rebuilds the list from the manager and the switches from the settings.
int count() const
Number of rows, one per plugin the manager knows about.
void setChecked(const QString &id, bool checked)
Sets the switch of the plugin id on screen; unknown id: no-op.
static void setEnabledPlugins(QxCore ::QxSettings *settings, const QStringList &ids)
Stores the ids that must start; an empty list takes the key out again.
void showEvent(QShowEvent *event) override
void revert()
Puts every switch back to what the settings hold.
bool isModified() const
Whether the switches on screen differ from the ones the settings hold.
static QStringList enabledPlugins(QxCore ::QxSettings *settings)
Ids stored as switched on even though their metadata says they are off by default.
int rowOf(const QString &id) const
Row of the plugin id, or -1 when the list does not hold it.
void reject() override
Discards and closes.
void accept() override
Applies and closes.
QString diagnostics() const
The manager's errorString(), or a placeholder when there is none.
::QxPlugin::QxPluginManager * manager() const
void applied()
The switches have been written and synced.
void apply()
Writes the switches to the settings.
static QStringList disabledPlugins(QxCore ::QxSettings *settings)
Ids stored as switched off; empty for a null settings.
static void setDisabledPlugins(QxCore ::QxSettings *settings, const QStringList &ids)
Stores the ids that must not start; an empty list takes the key out again.
bool isChecked(const QString &id) const
The switch of the plugin id as it is on screen; false for an unknown id.
void setCurrentPlugin(const QString &id)
Brings the row of the plugin id into view and shows its details.
Thin wrapper around QSettings that keeps call sites free of the usual boilerplate: typed accessors wi...
Definition qxsettings.h:32
QStringList stringListValue(const QString &key, const QStringList &defaultValue=QStringList()) const
void remove(const QString &key)
void setValue(const QString &key, const QVariant &value)
Discovers, orders, loads and isolates QtCanpool plugins.
A read-only view of one plugin's metadata, plus the runtime state the manager attaches to it.
QString error() const
Why the plugin is not running; empty when it runs or is only skipped by default.
QVersionNumber version() const
QString description() const
bool isEnabledByDefault() const
True when the plugin starts unless explicitly disabled (EnabledByDefault).
QString filePath() const
Path of the metadata file (dynamic) or the registration key (static).
QStringList optionalDependencies() const
Optional dependency ids (Type == "optional" in the metadata), in declaration order.
QString vendor() const
QString id() const
Stable plugin id; also the base name of its library and metadata file.
QStringList dependencies() const
Required dependency ids, in declaration order.
QVersionNumber compatVersion() const
Lowest host version the plugin still supports; the host must be in [compatVersion,...
#define QX_APP_END_NAMESPACE
#define Q_NULLPTR
Copyleft (C) 2025 maminjie canpool@163.com SPDX-License-Identifier: MulanPSL-2.0
#define QX_APP_BEGIN_NAMESPACE
#define QX_FINI_PRIVATE()
#define QX_CORE_USE_NAMESPACE
bool enabled
Definition qxlogger.cpp:27
#define QX_PLUGIN_USE_NAMESPACE
const char * id
Definition qxtheme.cpp:24
QColor text