#include "configdialog.h" #include "ui_configdialog.h" #include "uiconfig.h" #include "customconfigwidgetplugin.h" #include "services/pluginmanager.h" #include "formmanager.h" #include "services/codeformatter.h" #include "plugins/codeformatterplugin.h" #include "configwidgets/styleconfigwidget.h" #include "configwidgets/combodatawidget.h" #include "configwidgets/listtostringlisthash.h" #include "iconmanager.h" #include "common/userinputfilter.h" #include "multieditor/multieditorwidget.h" #include "multieditor/multieditorwidgetplugin.h" #include "plugins/confignotifiableplugin.h" #include "mainwindow.h" #include "common/unused.h" #include "configmapper.h" #include "datatype.h" #include "uiutils.h" #include "common/utils.h" #include "translations.h" #include "plugins/uiconfiguredplugin.h" #include "dbtree/dbtree.h" #include "common/compatibility.h" #include "windows/editorwindow.h" #include "syntaxhighlighterplugin.h" #include "sqleditor.h" #include "style.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GET_FILTER_STRING(Widget, WidgetType, Method) \ if (qobject_cast(Widget))\ return qobject_cast(Widget)->Method() + " " + Widget->toolTip();\ #define GET_FILTER_STRING2(Widget, WidgetType) \ WidgetType* w##WidgetType = qobject_cast(widget);\ if (w##WidgetType)\ return getFilterString(w##WidgetType) + " " + Widget->toolTip(); class ConfigDialogItemDelegate : public QItemDelegate { public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant userData = index.data(Qt::UserRole); bool isCategory = userData.isValid() && userData.toBool(); QSize size = QItemDelegate::sizeHint(option, index); return isCategory ? QSize(size.width(), size.height() * 1.7) : size; } }; ConfigDialog::ConfigDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ConfigDialog) { init(); } ConfigDialog::~ConfigDialog() { rollbackColorsConfig(); // Cancel transaction on CfgMain objects from plugins rollbackPluginConfigs(); // Notify plugins about dialog being closed UiConfiguredPlugin* cfgPlugin = nullptr; for (Plugin*& plugin : PLUGINS->getLoadedPlugins()) { cfgPlugin = dynamic_cast(plugin); if (!cfgPlugin) continue; cfgPlugin->configDialogClosed(); } // Delete UI and other resources qDeleteAll(colorPreviewEditors); delete ui; safe_delete(configMapper); for (ConfigMapper*& mapper : pluginConfigMappers) delete mapper; pluginConfigMappers.clear(); } void ConfigDialog::configureDataEditors(const QString& dataTypeString) { ui->categoriesWidget->setVisible(false); ui->stackedWidget->setCurrentWidget(ui->dataEditorsPage); for (int i = 0; i < ui->dataEditorsTypesList->count(); i++) { if (ui->dataEditorsTypesList->item(i)->text() == dataTypeString.toUpper()) { ui->dataEditorsTypesList->setCurrentRow(i); return; } } addDataType(dataTypeString.toUpper()); } QString ConfigDialog::getFilterString(QWidget *widget) { // Common code for widgets with single method call GET_FILTER_STRING(widget, QLabel, text); GET_FILTER_STRING(widget, QAbstractButton, text); GET_FILTER_STRING(widget, QLineEdit, text); GET_FILTER_STRING(widget, QTextEdit, toPlainText); GET_FILTER_STRING(widget, QPlainTextEdit, toPlainText); GET_FILTER_STRING(widget, QGroupBox, title); GET_FILTER_STRING(widget, QKeySequenceEdit, keySequence().toString); // Widgets needs a little more than single method call GET_FILTER_STRING2(widget, QComboBox); GET_FILTER_STRING2(widget, QTreeWidget); GET_FILTER_STRING2(widget, QListWidget); GET_FILTER_STRING2(widget, QTableWidget); return QString(); } QString ConfigDialog::getFilterString(QComboBox *widget) { QStringList items; for (int i = 0; i < widget->count(); i++) items << widget->itemText(i); return items.join(" "); } QString ConfigDialog::getFilterString(QTreeWidget *widget) { QList items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); QStringList strList; for (QTreeWidgetItem* item : items) for (int i = 0; i < widget->columnCount(); i++) strList << item->text(i) + " " + item->toolTip(0); return strList.join(" "); } QString ConfigDialog::getFilterString(QListWidget *widget) { QList items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); QStringList strList; for (QListWidgetItem* item : items) strList << item->text() + " " + item->toolTip(); return strList.join(" "); } QString ConfigDialog::getFilterString(QTableWidget *widget) { QList items = widget->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); QStringList strList; for (QTableWidgetItem* item : items) strList << item->text() + " " + item->toolTip(); return strList.join(" "); } void ConfigDialog::showEvent(QShowEvent* event) { UNUSED(event); ui->categoriesTree->resizeColumnToContents(0); int adjustedColumnWidth = ui->categoriesTree->columnWidth(0) + 4; if (adjustedColumnWidth > ui->categoriesTree->width()) { if (adjustedColumnWidth > (width() / 2)) adjustedColumnWidth = width() / 2; QList sizes = ui->splitter->sizes(); int rightPartSize = sizes[0] + sizes[1] - adjustedColumnWidth; sizes[0] = adjustedColumnWidth; sizes[1] = rightPartSize; ui->splitter->setSizes(sizes); } } void ConfigDialog::init() { ui->setupUi(this); setWindowIcon(ICONS.CONFIGURE); ui->categoriesTree->setCurrentItem(ui->categoriesTree->topLevelItem(0)); configMapper = new ConfigMapper(CfgMain::getPersistableInstances()); connectMapperSignals(configMapper); ui->categoriesFilter->setClearButtonEnabled(true); UserInputFilter* filter = new UserInputFilter(ui->categoriesFilter, this, SLOT(applyFilter(QString))); filter->setDelay(500); ui->stackedWidget->setCurrentWidget(ui->generalPage); initPageMap(); initInternalCustomConfigWidgets(); initPlugins(); initPluginsPage(); initFormatterPlugins(); initDataEditors(); initShortcuts(); initLangs(); initTooltips(); initColors(); connect(ui->categoriesTree, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(switchPage(QTreeWidgetItem*))); connect(ui->previewTabs, SIGNAL(currentChanged(int)), this, SLOT(updateStylePreview())); connect(ui->activeStyleCombo, SIGNAL(currentTextChanged(QString)), this, SLOT(updateStylePreview())); connect(ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply())); connect(ui->hideBuiltInPluginsCheck, SIGNAL(toggled(bool)), this, SLOT(updateBuiltInPluginsVisibility())); connect(ui->codeColorsResetBtn, SIGNAL(pressed()), this, SLOT(resetCodeSyntaxColors())); QList entries; entries << CFG_UI.General.SortObjects << CFG_UI.General.SortColumns << CFG_UI.General.ShowDbTreeLabels << CFG_UI.General.ShowRegularTableLabels << CFG_UI.General.ShowSystemObjects << CFG_UI.General.ShowVirtualTableLabels; for (CfgEntry*& cfg : entries) connect(cfg, SIGNAL(changed(QVariant)), this, SLOT(markRequiresSchemasRefresh())); QStringList styles = QStyleFactory::keys(); styles.sort(Qt::CaseInsensitive); ui->activeStyleCombo->addItems(styles); ui->activeStyleCombo->setCurrentText(STYLE->name()); connect(ui->stackedWidget, SIGNAL(currentChanged(int)), this, SLOT(pageSwitched())); ui->hideBuiltInPluginsCheck->setChecked(true); #ifndef PORTABLE_CONFIG ui->updatesGroup->setVisible(false); #endif resettingColors = true; load(); resettingColors = false; colorChanged(); updateStylePreview(); updateColorsAfterLoad(); ui->categoriesTree->expandAll(); } void ConfigDialog::load() { updatingDataEditorItem = true; configMapper->loadToWidget(ui->stackedWidget); updatingDataEditorItem = false; setModified(false); } void ConfigDialog::save() { if (MainWindow::getInstance()->currentStyle().compare(ui->activeStyleCombo->currentText(), Qt::CaseInsensitive) != 0) { QList unmodifiedColors = prepareCodeSyntaxColorsForStyle(); bool wasDark = STYLE->isDark(); MainWindow::getInstance()->setStyle(ui->activeStyleCombo->currentText()); if (STYLE->isDark() != wasDark) { resettingColors = true; // to avoid mass events of color change on syntax page adjustSyntaxColorsForStyle(unmodifiedColors); resettingColors = false; colorChanged(); } } QString loadedPlugins = collectLoadedPlugins(); storeSelectedFormatters(); CFG->beginMassSave(); CFG_CORE.General.LoadedPlugins.set(loadedPlugins); configMapper->saveFromWidget(ui->stackedWidget, true); commitPluginConfigs(); commitColorsConfig(); CFG->commitMassSave(); if (requiresSchemasRefresh) { requiresSchemasRefresh = false; DBTREE->refreshSchemas(); } MainWindow::getInstance()->updateCornerDocking(); } void ConfigDialog::storeSelectedFormatters() { CodeFormatterPlugin* plugin = nullptr; QTreeWidgetItem* item = nullptr; QComboBox* combo = nullptr; QString lang; QString pluginName; for (int i = 0, total = ui->formatterPluginsTree->topLevelItemCount(); i < total; ++i) { item = ui->formatterPluginsTree->topLevelItem(i); lang = item->text(0); combo = formatterLangToPluginComboMap[lang]; if (!combo) { qCritical() << "Could not find combo for lang " << lang << " in storeSelectedFormatters()"; continue; } pluginName = combo->currentData().toString(); plugin = dynamic_cast(PLUGINS->getLoadedPlugin(pluginName)); if (!plugin) { qCritical() << "Could not find plugin for lang " << lang << " in storeSelectedFormatters()"; continue; } FORMATTER->setFormatter(lang, plugin); } FORMATTER->storeCurrentSettings(); } void ConfigDialog::markModified() { setModified(true); } void ConfigDialog::setModified(bool modified) { modifiedFlag = modified; updateModified(); } void ConfigDialog::updateModified() { ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(modifiedFlag); } void ConfigDialog::applyFilter(const QString &filter) { QColor normalColor = ui->categoriesTree->palette().color(QPalette::Active, QPalette::WindowText); QColor disabledColor = ui->categoriesTree->palette().color(QPalette::Disabled, QPalette::WindowText); if (filter.isEmpty()) { for (QTreeWidgetItem*& item : getAllCategoryItems()) item->setForeground(0, normalColor); return; } QList widgets = ui->stackedWidget->findChildren(); QList matchedWidgets; for (QWidget* widget : widgets) { if (getFilterString(widget).contains(filter, Qt::CaseInsensitive)) matchedWidgets << widget; } QHash pageToCategoryItem = buildPageToCategoryItemMap(); QSet matchedCategories; for (auto it = pageToCategoryItem.keyBegin(), end = pageToCategoryItem.keyEnd(); it != end; ++it) { for (QWidget* matched : qAsConst(matchedWidgets)) { QWidget* page = *it; if (page->isAncestorOf(matched)) { if (!pageToCategoryItem.contains(page)) { qCritical() << "Page" << page << "not on page-to-category-item mapping."; continue; } matchedCategories << pageToCategoryItem[page]; break; } } } for (QTreeWidgetItem*& item : getAllCategoryItems()) item->setForeground(0, disabledColor); for (QTreeWidgetItem* item : qAsConst(matchedCategories)) { item->setForeground(0, normalColor); while ((item = item->parent()) != nullptr) item->setForeground(0, normalColor); } } QHash ConfigDialog::buildPageToCategoryItemMap() const { QHash pageNameToCategoryItem; for (QTreeWidgetItem*& item : getAllCategoryItems()) pageNameToCategoryItem[item->statusTip(0)] = item; QWidget* page = nullptr; QHash pageToCategoryItem; for (int i = 0; i < ui->stackedWidget->count(); i++) { page = ui->stackedWidget->widget(i); pageToCategoryItem[page] = pageNameToCategoryItem[page->objectName()]; } return pageToCategoryItem; } QList ConfigDialog::getAllCategoryItems() const { return ui->categoriesTree->findItems("*", Qt::MatchWildcard|Qt::MatchRecursive); } QList ConfigDialog::getDefaultEditorsForType(DataType::Enum dataType) { QList plugins = PLUGINS->getLoadedPlugins(); DataType modelDataType; modelDataType.setType(dataType); typedef QPair PluginWithPriority; QList sortedPlugins; PluginWithPriority editorWithPrio; for (MultiEditorWidgetPlugin* plugin : plugins) { if (!plugin->validFor(modelDataType)) continue; editorWithPrio.first = plugin->getPriority(modelDataType); editorWithPrio.second = plugin; sortedPlugins << editorWithPrio; } sSort(sortedPlugins, [=](const PluginWithPriority& p1, const PluginWithPriority& p2) -> bool { return p1.first < p2.first; }); QList results; for (PluginWithPriority& p: sortedPlugins) results << p.second; return results; } void ConfigDialog::pageSwitched() { if (ui->stackedWidget->currentWidget() == ui->dataEditorsPage) { updateDataTypeEditors(); return; } } void ConfigDialog::updateDataTypeEditors() { QString typeName = ui->dataEditorsTypesList->currentItem()->text(); DataType::Enum typeEnum = DataType::fromString(typeName); bool usingCustomOrder = false; QStringList editorsOrder = getPluginNamesFromDataTypeItem(ui->dataEditorsTypesList->currentItem(), &usingCustomOrder); QList sortedPlugins; while (ui->dataEditorsSelectedTabs->count() > 0) delete ui->dataEditorsSelectedTabs->widget(0); ui->dataEditorsAvailableList->clear(); if (usingCustomOrder) sortedPlugins = updateCustomDataTypeEditors(editorsOrder); else sortedPlugins = updateDefaultDataTypeEditors(typeEnum); ui->dataEditorsAvailableList->sortItems(); for (MultiEditorWidgetPlugin*& plugin : sortedPlugins) addDataTypeEditor(plugin); } QList ConfigDialog::updateCustomDataTypeEditors(const QStringList& editorsOrder) { // Building plugins list QList plugins = PLUGINS->getLoadedPlugins(); QList enabledPlugins; QListWidgetItem* item = nullptr; for (MultiEditorWidgetPlugin* plugin : plugins) { item = new QListWidgetItem(plugin->getTitle()); item->setFlags(item->flags()|Qt::ItemIsUserCheckable); item->setCheckState(editorsOrder.contains(plugin->getName()) ? Qt::Checked : Qt::Unchecked); item->setData(QListWidgetItem::UserType, plugin->getName()); if (item->checkState() == Qt::Checked) enabledPlugins << plugin; ui->dataEditorsAvailableList->addItem(item); } sSort(enabledPlugins, [=](MultiEditorWidgetPlugin* p1, MultiEditorWidgetPlugin* p2) -> bool { return editorsOrder.indexOf(p1->getName()) < editorsOrder.indexOf(p2->getName()); }); return enabledPlugins; } QList ConfigDialog::updateDefaultDataTypeEditors(DataType::Enum typeEnum) { // Building plugins list QList plugins = PLUGINS->getLoadedPlugins(); QList enabledPlugins = getDefaultEditorsForType(typeEnum); QListWidgetItem* item = nullptr; for (MultiEditorWidgetPlugin* plugin : plugins) { item = new QListWidgetItem(plugin->getTitle()); item->setFlags(item->flags()|Qt::ItemIsUserCheckable); item->setCheckState(enabledPlugins.contains(plugin) ? Qt::Checked : Qt::Unchecked); item->setData(QListWidgetItem::UserType, plugin->getName()); ui->dataEditorsAvailableList->addItem(item); } return enabledPlugins; } void ConfigDialog::addDataTypeEditor(const QString& pluginName) { MultiEditorWidgetPlugin* plugin = dynamic_cast(PLUGINS->getLoadedPlugin(pluginName)); if (!plugin) { qCritical() << "Could not find plugin" << pluginName << " in ConfigDialog::addDataTypeEditor()"; return; } addDataTypeEditor(plugin); } void ConfigDialog::addDataTypeEditor(MultiEditorWidgetPlugin* plugin) { MultiEditorWidget* editor = plugin->getInstance(); editor->setTabLabel(plugin->getTabLabel()); ui->dataEditorsSelectedTabs->addTab(editor, editor->getTabLabel().replace("&", "&&")); connect(editor, &MultiEditorWidget::aboutToBeDeleted, [this, editor]() { int idx = ui->dataEditorsSelectedTabs->indexOf(editor); ui->dataEditorsSelectedTabs->removeTab(idx); }); } void ConfigDialog::removeDataTypeEditor(QListWidgetItem* item, const QString& pluginName) { QStringList orderedPlugins = getPluginNamesFromDataTypeItem(item); int idx = orderedPlugins.indexOf(pluginName); removeDataTypeEditor(idx); } void ConfigDialog::removeDataTypeEditor(int idx) { if (idx < 0 || idx > (ui->dataEditorsSelectedTabs->count() - 1)) { qCritical() << "Index out of range in ConfigDialog::removeDataTypeEditor():" << idx << "(tabs:" << ui->dataEditorsSelectedTabs->count() << ")"; return; } delete ui->dataEditorsSelectedTabs->widget(idx); } void ConfigDialog::transformDataTypeEditorsToCustomList(QListWidgetItem* typeItem) { DataType::Enum dataType = DataType::fromString(typeItem->text()); QList plugins = getDefaultEditorsForType(dataType); QStringList pluginNames; for (MultiEditorWidgetPlugin* plugin : plugins) pluginNames << plugin->getName(); setPluginNamesForDataTypeItem(typeItem, pluginNames); } QStringList ConfigDialog::getPluginNamesFromDataTypeItem(QListWidgetItem* typeItem, bool* exists) { QVariant data = typeItem->data(QListWidgetItem::UserType); if (exists) *exists = data.isValid(); return data.toStringList(); } void ConfigDialog::setPluginNamesForDataTypeItem(QListWidgetItem* typeItem, const QStringList& pluginNames) { updatingDataEditorItem = true; typeItem->setData(QListWidgetItem::UserType, pluginNames); updatingDataEditorItem = false; } void ConfigDialog::addDataType(const QString& typeStr) { QListWidgetItem* item = new QListWidgetItem(typeStr); item->setFlags(item->flags()|Qt::ItemIsEditable); ui->dataEditorsTypesList->addItem(item); ui->dataEditorsTypesList->setCurrentRow(ui->dataEditorsTypesList->count() - 1, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); markModified(); } void ConfigDialog::rollbackPluginConfigs() { CfgMain* mainCfg = nullptr; for (UiConfiguredPlugin*& plugin : pluginConfigMappers.keys()) { mainCfg = plugin->getMainUiConfig(); if (mainCfg) mainCfg->rollback(); } } void ConfigDialog::rollbackColorsConfig() { CFG_UI.Colors.rollback(); } void ConfigDialog::commitPluginConfigs() { CfgMain* mainCfg = nullptr; for (auto it = pluginConfigMappers.keyBegin(), end = pluginConfigMappers.keyEnd(); it != end; ++it) { mainCfg = (*it)->getMainUiConfig(); if (mainCfg) { mainCfg->commit(); mainCfg->begin(); // be prepared for further changes after "Apply" } } auto it = highlightingPluginForPreviewEditor.iterator(); while (it.hasNext()) { auto item = it.next(); if (item.value()->getLanguageName() == "SQL") continue; item.value()->refreshFormats(); } } void ConfigDialog::commitColorsConfig() { CFG_UI.Colors.commit(); CFG_UI.Colors.begin(); } void ConfigDialog::connectMapperSignals(ConfigMapper* mapper) { connect(mapper, SIGNAL(modified(QWidget*)), this, SLOT(markModified())); connect(mapper, SIGNAL(notifyEnabledWidgetModified(QWidget*,CfgEntry*,QVariant)), this, SLOT(notifyPluginsAboutModification(QWidget*,CfgEntry*,QVariant))); } QList ConfigDialog::getShortcutsCfgMains() const { static const QString metaName = CFG_SHORTCUTS_METANAME; QList mains; for (CfgMain*& cfgMain : CfgMain::getInstances()) { if (cfgMain->getMetaName() != metaName) continue; mains << cfgMain; } return mains; } QList ConfigDialog::getShortcutsCfgCategories() const { QList categories; for (CfgMain*& cfgMain : getShortcutsCfgMains()) categories.append(cfgMain->getCategories().values()); return categories; } void ConfigDialog::updateDataTypeListState() { bool listEditingEnabled = ui->dataEditorsTypesList->selectedItems().size() > 0 && ui->dataEditorsTypesList->currentItem()->flags().testFlag(Qt::ItemIsEditable); dataEditRenameAction->setEnabled(listEditingEnabled); dataEditDeleteAction->setEnabled(listEditingEnabled); bool orderEditingEnabled = ui->dataEditorsTypesList->selectedItems().size() > 0; ui->dataEditorsAvailableList->setEnabled(orderEditingEnabled); ui->dataEditorsSelectedTabs->setEnabled(orderEditingEnabled); } void ConfigDialog::dataEditorItemEdited(QListWidgetItem* item) { if (updatingDataEditorItem) return; updatingDataEditorItem = true; QString txt = item->text().toUpper(); if (DataType::getAllNames().contains(txt)) txt += "_"; while (ui->dataEditorsTypesList->findItems(txt, Qt::MatchExactly).size() > 1) txt += "_"; item->setText(txt); updatingDataEditorItem = false; } void ConfigDialog::dataEditorAvailableChanged(QListWidgetItem* item) { QListWidgetItem* typeItem = ui->dataEditorsTypesList->currentItem(); if (!typeItem) return; bool exists = false; QStringList pluginNames = getPluginNamesFromDataTypeItem(typeItem, &exists); if (!exists) { transformDataTypeEditorsToCustomList(typeItem); pluginNames = getPluginNamesFromDataTypeItem(typeItem); } QString pluginName = item->data(QListWidgetItem::UserType).toString(); Qt::CheckState state = item->checkState(); if (pluginNames.contains(pluginName) && state == Qt::Unchecked) { removeDataTypeEditor(typeItem, pluginName); pluginNames.removeOne(pluginName); } else if (!pluginNames.contains(pluginName) && state == Qt::Checked) { addDataTypeEditor(pluginName); pluginNames << pluginName; } setPluginNamesForDataTypeItem(typeItem, pluginNames); } void ConfigDialog::dataEditorTabsOrderChanged(int from, int to) { QListWidgetItem* typeItem = ui->dataEditorsTypesList->currentItem(); if (!typeItem) return; bool exists = false; QStringList pluginNames = getPluginNamesFromDataTypeItem(typeItem, &exists); if (!exists) { transformDataTypeEditorsToCustomList(typeItem); pluginNames = getPluginNamesFromDataTypeItem(typeItem); } int pluginSize = pluginNames.size(); if (from >= pluginSize || to >= pluginSize) { qCritical() << "Tabse moved out of range. in ConfigDialog::dataEditorTabsOrderChanged(). Range was: " << pluginSize << "and indexes were:" << from << to; return; } QString pluginName = pluginNames[from]; pluginNames.removeAt(from); pluginNames.insert(to, pluginName); setPluginNamesForDataTypeItem(typeItem, pluginNames); } void ConfigDialog::addDataType() { addDataType(""); renameDataType(); } void ConfigDialog::renameDataType() { QListWidgetItem* item = ui->dataEditorsTypesList->currentItem(); if (!item) return; ui->dataEditorsTypesList->editItem(item); } void ConfigDialog::delDataType() { QListWidgetItem* item = ui->dataEditorsTypesList->currentItem(); if (!item) return; int row = ui->dataEditorsTypesList->currentRow(); delete ui->dataEditorsTypesList->takeItem(row); if (ui->dataEditorsTypesList->count() > 0) { if (ui->dataEditorsTypesList->count() <= row) { row--; if (row < 0) row = 0; } ui->dataEditorsTypesList->setCurrentRow(row, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); } updateDataTypeListState(); markModified(); } void ConfigDialog::dataTypesHelp() { static const QString url = QStringLiteral("https://github.com/pawelsalawa/sqlitestudio/wiki/User_Manual#customizing-data-type-editors"); QDesktopServices::openUrl(QUrl(url, QUrl::StrictMode)); } void ConfigDialog::updateActiveFormatterState() { CodeFormatterPlugin* plugin = nullptr; QTreeWidgetItem* item = nullptr; QComboBox* combo = nullptr; QToolButton* button = nullptr; QString lang; QString pluginName; for (int i = 0, total = ui->formatterPluginsTree->topLevelItemCount(); i < total; ++i) { item = ui->formatterPluginsTree->topLevelItem(i); lang = item->text(0); combo = formatterLangToPluginComboMap[lang]; button = formatterLangToConfigButtonMap[lang]; if (!button) { qCritical() << "Could not find button for lang " << lang << " in updateActiveFormatterState()"; continue; } if (!combo) { qCritical() << "Could not find combo for lang " << lang << " in updateActiveFormatterState()"; button->setEnabled(false); continue; } pluginName = combo->currentData().toString(); plugin = dynamic_cast(PLUGINS->getLoadedPlugin(pluginName)); if (!plugin) { qCritical() << "Could not find plugin for lang " << lang << " in updateActiveFormatterState()"; button->setEnabled(false); continue; } button->setEnabled(dynamic_cast(plugin)); } } void ConfigDialog::configureFormatter(const QString& pluginTitle) { QTreeWidgetItem* item = getItemByTitle(pluginTitle); if (!item) return; ui->categoriesTree->setCurrentItem(item); } void ConfigDialog::activeFormatterChanged() { markModified(); updateActiveFormatterState(); } void ConfigDialog::detailsClicked(const QString& pluginName) { static const QString details = QStringLiteral( "" "" "" "" "" "%2" "
%1
"); static const QString row = QStringLiteral("%1%2"); static const QString hline = QStringLiteral("
"); PluginType* type = PLUGINS->getPluginType(pluginName); Q_ASSERT(type != nullptr); // Rows QStringList rows; rows << row.arg(tr("Description:", "plugin details"), PLUGINS->getDescription(pluginName)); rows << row.arg(tr("Category:", "plugin details"), type->getTitle()); rows << row.arg(tr("Version:", "plugin details"), PLUGINS->getPrintableVersion(pluginName)); rows << row.arg(tr("Author:", "plugin details"), PLUGINS->getAuthor(pluginName)); rows << hline; rows << row.arg(tr("Internal name:", "plugin details"), pluginName); rows << row.arg(tr("Dependencies:", "plugin details"), PLUGINS->getDependencies(pluginName).join(", ")); rows << row.arg(tr("Conflicts:", "plugin details"), PLUGINS->getConflicts(pluginName).join(", ")); // Message QString pluginDetails = details.arg(PLUGINS->getTitle(pluginName), rows.join("")); QMessageBox::information(this, tr("Plugin details"), pluginDetails); } void ConfigDialog::failedToLoadPlugin(const QString& pluginName) { QTreeWidgetItem* theItem = pluginListItemToPluginNameMap.valueByRight(pluginName); if (!theItem) { qWarning() << "Plugin" << pluginName << "failed to load, but it could not be found on the plugins list in ConfigDialog."; return; } theItem->setCheckState(0, Qt::Unchecked); } void ConfigDialog::codeFormatterUnloaded() { refreshFormattersPage(); } void ConfigDialog::codeFormatterLoaded() { refreshFormattersPage(); } void ConfigDialog::loadUnloadPlugin(QTreeWidgetItem* item, int column) { if (column != 0) return; QString pluginName = pluginListItemToPluginNameMap.valueByLeft(item); if (PLUGINS->isBuiltIn(pluginName)) return; bool wasLoaded = PLUGINS->isLoaded(pluginName); if (wasLoaded == (item->checkState(0) == Qt::Checked)) return; if (wasLoaded) PLUGINS->unload(pluginName); else PLUGINS->load(pluginName); markModified(); } void ConfigDialog::pluginAboutToUnload(Plugin* plugin, PluginType* type) { // Deinit tree item QTreeWidgetItem* typeItem = getPluginsCategoryItem(type); QTreeWidgetItem* pluginItem = getPluginItem(plugin); if (pluginItem) { typeItem->removeChild(pluginItem); pluginToItemMap.remove(plugin); } // Notifiable plugin ConfigNotifiablePlugin* notifiablePlugin = dynamic_cast(plugin); if (notifiablePlugin && notifiablePlugins.contains(notifiablePlugin)) notifiablePlugins.removeOne(notifiablePlugin); // Update code colors page if (type->isForPluginType()) highlighterPluginUnloaded(dynamic_cast(plugin)); // Deinit page deinitPluginPage(plugin); // Update tree categories updatePluginCategoriesVisibility(); } void ConfigDialog::pluginLoaded(Plugin* plugin, PluginType* type, bool skipConfigLoading) { // Update formatters page if (type->isForPluginType()) codeFormatterLoaded(); // Update code colors page if (type->isForPluginType()) highlighterPluginLoaded(dynamic_cast(plugin)); QTreeWidgetItem* listItem = pluginListItemToPluginNameMap.valueByRight(plugin->getName()); if (listItem && listItem->checkState(0) == Qt::Unchecked) listItem->setCheckState(0, Qt::Checked); // Init page if (!initPluginPage(plugin, skipConfigLoading)) return; // Init tree item QTreeWidgetItem* typeItem = getPluginsCategoryItem(type); QTreeWidgetItem* pluginItem = new QTreeWidgetItem({plugin->getTitle()}); pluginItem->setStatusTip(0, plugin->getName()); typeItem->addChild(pluginItem); pluginToItemMap[plugin] = pluginItem; // Update tree categories updatePluginCategoriesVisibility(); // Notifiable plugin ConfigNotifiablePlugin* notifiablePlugin = dynamic_cast(plugin); if (notifiablePlugin) notifiablePlugins << notifiablePlugin; } void ConfigDialog::pluginUnloaded(const QString& pluginName, PluginType* type) { QTreeWidgetItem* item = pluginListItemToPluginNameMap.valueByRight(pluginName); if (item && item->checkState(0) == Qt::Checked) item->setCheckState(0, Qt::Unchecked); // Update formatters page if (type->isForPluginType()) codeFormatterUnloaded(); } void ConfigDialog::updatePluginCategoriesVisibility() { QTreeWidgetItem* categories = getPluginsCategoryItem(); for (int i = 0; i < categories->childCount(); i++) updatePluginCategoriesVisibility(categories->child(i)); } void ConfigDialog::updateBuiltInPluginsVisibility() { bool hideBuiltIn = ui->hideBuiltInPluginsCheck->isChecked(); QHashIterator it = pluginListItemToPluginNameMap.iterator(); while (it.hasNext()) { it.next(); if (PLUGINS->isBuiltIn(it.value())) it.key()->setHidden(hideBuiltIn); else it.key()->setHidden(false); } } void ConfigDialog::refreshColorsInSyntaxHighlighters() { auto it = highlightingPluginForPreviewEditor.iterator(); while (it.hasNext()) it.next().value()->refreshFormats(); } void ConfigDialog::applyShortcutsFilter(const QString &filter) { QTreeWidgetItem* categoryItem = nullptr; QTreeWidgetItem* item = nullptr; QKeySequenceEdit* seqEdit = nullptr; bool empty = filter.isEmpty(); bool visible = true; int foundInCategory = 0; for (int i = 0, total_i = ui->shortcutsTable->topLevelItemCount(); i < total_i; ++i) { foundInCategory = 0; categoryItem = ui->shortcutsTable->topLevelItem(i); for (int j = 0 , total_j = categoryItem->childCount(); j < total_j; ++j) { item = categoryItem->child(j); seqEdit = dynamic_cast(ui->shortcutsTable->itemWidget(item, 1)); visible = empty || item->text(0).contains(filter, Qt::CaseInsensitive) || seqEdit->keySequence().toString().contains(filter, Qt::CaseInsensitive); item->setHidden(!visible); if (visible) foundInCategory++; } categoryItem->setHidden(foundInCategory == 0); } } void ConfigDialog::markRequiresSchemasRefresh() { requiresSchemasRefresh = true; } void ConfigDialog::notifyPluginsAboutModification(QWidget*, CfgEntry* key, const QVariant& value) { for (ConfigNotifiablePlugin*& plugin : notifiablePlugins) plugin->configModified(key, value); } void ConfigDialog::resetCodeSyntaxColors() { resettingColors = true; for (QWidget*& widget : configMapper->getAllConfigWidgets(ui->commonCodeColorsGroup)) configMapper->applyConfigDefaultValueToWidget(widget); resettingColors = false; colorChanged(); } void ConfigDialog::colorChanged() { refreshColorsInSyntaxHighlighters(); for (QSyntaxHighlighter*& highligter : colorPreviewHighlighters) highligter->rehighlight(); if (codePreviewSqlEditor) codePreviewSqlEditor->colorsConfigChanged(); } void ConfigDialog::adjustSyntaxColorsForStyle(QList& unmodifiedColors) { for (QWidget*& w : unmodifiedColors) configMapper->applyConfigDefaultValueToWidget(w); } void ConfigDialog::highlighterPluginLoaded(SyntaxHighlighterPlugin* plugin) { QPlainTextEdit* editor = nullptr; if (plugin->getLanguageName() == "SQL") { // For SQL there is assumed just one, built-in plugin codePreviewSqlEditor = new SqlEditor(ui->codeColorsPreviewTabWidget); codePreviewSqlEditor->setShowLineNumbers(false); codePreviewSqlEditor->setCurrentQueryHighlighting(true); editor = codePreviewSqlEditor; } else { editor = new QPlainTextEdit(ui->codeColorsPreviewTabWidget); editor->setFont(CFG_UI.Fonts.SqlEditor.get()); colorPreviewHighlighters << plugin->createSyntaxHighlighter(editor); } editor->setPlainText(plugin->previewSampleCode()); editor->setReadOnly(true); colorPreviewEditors << editor; highlightingPluginForPreviewEditor.insert(editor, plugin); ui->codeColorsPreviewTabWidget->addTab(editor, plugin->getLanguageName()); } void ConfigDialog::highlighterPluginUnloaded(SyntaxHighlighterPlugin* plugin) { QPlainTextEdit* editor = highlightingPluginForPreviewEditor.valueByRight(plugin); if (!editor) { qCritical() << "Highlighter plugin unloaded for which there is no preview editor! Application crash is possible. Plugin:" << plugin->getName(); return; } QTextDocument* document = editor->document(); QSyntaxHighlighter* highlighter = findFirst(colorPreviewHighlighters, [document](auto highlighter) {return highlighter->document() == document;}); ui->codeColorsPreviewTabWidget->removeTab(ui->codeColorsPreviewTabWidget->indexOf(editor)); colorPreviewHighlighters.removeOne(highlighter); colorPreviewEditors.removeOne(editor); delete editor; highlightingPluginForPreviewEditor.removeRight(plugin); } QList ConfigDialog::prepareCodeSyntaxColorsForStyle() { QList unmodified; for (QWidget*& w : configMapper->getAllConfigWidgets(ui->commonCodeColorsGroup)) { CfgEntry* entry = configMapper->getConfigForWidget(w); if (entry->getDefaultValue() == entry->get()) unmodified << w; } return unmodified; } void ConfigDialog::initColors() { CFG_UI.Colors.begin(); connect(configMapper, &ConfigMapper::modified, this, [this](QWidget* widget) { CfgEntry* key = configMapper->getBindConfigForWidget(widget); if (!key) { qCritical() << "Missing CfgEntry in Colors configuration for widget" << widget->objectName(); return; } if (key->getCategory() != CFG_UI.Colors) return; configMapper->saveFromWidget(widget, key); if (key->getName().endsWith("Custom")) toggleColorButtonState(key); if (resettingColors) return; colorChanged(); }); } void ConfigDialog::updateColorsAfterLoad() { QHash entries = CFG_UI.Colors.getEntries(); auto it = entries.begin(); while (it != entries.end()) { if (it.key().endsWith("Custom")) toggleColorButtonState(it.value()); it++; } } void ConfigDialog::toggleColorButtonState(CfgEntry* colorCheckEntry) { CfgEntry* colorKey = colorCheckEntry->getCategory()->getEntryByName(colorCheckEntry->getName().chopped(6)); if (colorKey) { QWidget* button = configMapper->getBindWidgetForConfig(colorKey); if (button) button->setEnabled(colorCheckEntry->get().toBool()); } } void ConfigDialog::updatePluginCategoriesVisibility(QTreeWidgetItem* categoryItem) { categoryItem->setHidden(categoryItem->childCount() == 0); } QString ConfigDialog::collectLoadedPlugins() const { QStringList loaded; QHashIterator it = pluginListItemToPluginNameMap.iterator(); while (it.hasNext()) { it.next(); loaded << (it.value() + "=" + ((it.key()->checkState(0) == Qt::Checked) ? "1" : "0")); } return loaded.join(","); } void ConfigDialog::initPageMap() { int pages = ui->stackedWidget->count(); QWidget* widget = nullptr; for (int i = 0; i < pages; i++) { widget = ui->stackedWidget->widget(i); nameToPage[widget->objectName()] = widget; } } void ConfigDialog::initInternalCustomConfigWidgets() { QList customWidgets; customWidgets << new StyleConfigWidget(); customWidgets << new ListToStringListHash(&CFG_UI.General.DataEditorsOrder); customWidgets << new ComboDataWidget(&CFG_CORE.General.Language); configMapper->setInternalCustomConfigWidgets(customWidgets); } void ConfigDialog::initFormatterPlugins() { ui->formatterPluginsTree->header()->setSectionsMovable(false); ui->formatterPluginsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->formatterPluginsTree->resizeColumnToContents(1); ui->formatterPluginsTree->resizeColumnToContents(2); refreshFormattersPage(); } void ConfigDialog::refreshFormattersPage() { ui->formatterPluginsTree->clear(); QHash activeFormatters = CFG_CORE.General.ActiveCodeFormatter.get(); QList plugins = PLUGINS->getLoadedPlugins(); QHash> groupedPlugins; for (CodeFormatterPlugin* plugin : plugins) groupedPlugins[plugin->getLanguage()] << plugin; formatterLangToPluginComboMap.clear(); formatterLangToConfigButtonMap.clear(); int row = 0; QTreeWidgetItem* item = nullptr; QComboBox* combo = nullptr; QToolButton* configButton = nullptr; QStringList pluginTitles; QStringList pluginNames; QStringList sortedPluginNames; QString selectedPluginName; QModelIndex index; QString groupName; QHashIterator> it(groupedPlugins); while (it.hasNext()) { it.next(); groupName = it.key(); item = new QTreeWidgetItem({groupName}); ui->formatterPluginsTree->addTopLevelItem(item); pluginNames.clear(); pluginTitles.clear(); for (CodeFormatterPlugin* plugin : it.value()) { pluginNames << plugin->getName(); pluginTitles << plugin->getTitle(); } sortedPluginNames = pluginNames; sSort(sortedPluginNames); combo = new QComboBox(ui->formatterPluginsTree); for (int i = 0, total = pluginNames.size(); i < total; ++i) combo->addItem(pluginTitles[i], pluginNames[i]); connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(activeFormatterChanged())); index = ui->formatterPluginsTree->model()->index(row, 1); ui->formatterPluginsTree->setIndexWidget(index, combo); formatterLangToPluginComboMap[groupName] = combo; if (activeFormatters.contains(groupName) && pluginNames.contains(activeFormatters[groupName].toString())) { selectedPluginName = activeFormatters[groupName].toString(); } else { // Pick first from sorted list and put it to combobox selectedPluginName = sortedPluginNames.first(); } configButton = new QToolButton(ui->formatterPluginsTree); configButton->setIcon(ICONS.CONFIGURE); index = ui->formatterPluginsTree->model()->index(row, 2); ui->formatterPluginsTree->setIndexWidget(index, configButton); connect(configButton, &QToolButton::clicked, [this, combo]() {configureFormatter(combo->currentText());}); formatterLangToConfigButtonMap[groupName] = configButton; combo->setCurrentIndex(pluginNames.indexOf(selectedPluginName)); row++; } updateActiveFormatterState(); } void ConfigDialog::applyStyle(QWidget *widget, QStyle *style) { widget->setStyle(style); widget->setPalette(style->standardPalette()); for (QObject* child : widget->children()) { if (!qobject_cast(child)) continue; applyStyle(qobject_cast(child), style); } } QTreeWidgetItem* ConfigDialog::getPluginsCategoryItem() const { QTreeWidgetItem* item = nullptr; for (int i = 0; i < ui->categoriesTree->topLevelItemCount(); i++) { item = ui->categoriesTree->topLevelItem(i); if (item->statusTip(0) == ui->pluginsPage->objectName()) return item; } Q_ASSERT_X(true, "ConfigDialog", "No Plugins toplevel item in config categories tree!"); return nullptr; } QTreeWidgetItem* ConfigDialog::getPluginsCategoryItem(PluginType* type) const { if (!pluginTypeToItemMap.contains(type)) return nullptr; return pluginTypeToItemMap[type]; } QTreeWidgetItem* ConfigDialog::getPluginItem(Plugin* plugin) const { if (!pluginToItemMap.contains(plugin)) return nullptr; return pluginToItemMap[plugin]; } QTreeWidgetItem* ConfigDialog::createPluginsTypeItem(const QString& widgetName, const QString& title) const { if (FORMS->hasWidget(widgetName)) return new QTreeWidgetItem({title}); QTreeWidgetItem* pluginsCategoryItem = getPluginsCategoryItem(); QTreeWidgetItem* item = nullptr; for (int i = 0; i < pluginsCategoryItem->childCount(); i++) { item = pluginsCategoryItem->child(i); if (item->statusTip(0) == widgetName) return item; } return new QTreeWidgetItem({title}); } QTreeWidgetItem* ConfigDialog::getItemByTitle(const QString& title) const { QList items = ui->categoriesTree->findItems(title, Qt::MatchExactly|Qt::MatchRecursive); if (items.size() == 0) return nullptr; return items.first(); } void ConfigDialog::switchPage(QTreeWidgetItem *item) { if (isPluginCategoryItem((item))) { switchPageToPlugin(item); return; } QString name = item->statusTip(0); if (!nameToPage.contains(name)) { qWarning() << "Switched page to item" << name << "but there's no such named page defined in ConfigDialog."; return; } ui->stackedWidget->setCurrentWidget(nameToPage[name]); } void ConfigDialog::switchPageToPlugin(QTreeWidgetItem *item) { QString pluginName = item->statusTip(0); if (!nameToPage.contains(pluginName)) { qCritical() << "No plugin page available for plugin:" << pluginName; return; } ui->stackedWidget->setCurrentWidget(nameToPage[pluginName]); } void ConfigDialog::initPlugins() { QTreeWidgetItem *item = getPluginsCategoryItem(); // Recreate QTreeWidgetItem *typeItem = nullptr; for (PluginType*& pluginType : PLUGINS->getPluginTypes()) { typeItem = createPluginsTypeItem(pluginType->getConfigUiForm(), pluginType->getTitle()); item->addChild(typeItem); pluginTypeToItemMap[pluginType] = typeItem; for (Plugin*& plugin : pluginType->getLoadedPlugins()) pluginLoaded(plugin, pluginType, true); } updatePluginCategoriesVisibility(); // Using old connect() syntax, becase the pointer syntax does not work correctly with signals declared in an interface class. // At least that's the case with Qt 5.15.2. connect(PLUGINS, SIGNAL(loaded(Plugin*,PluginType*)), this, SLOT(pluginLoaded(Plugin*,PluginType*))); connect(PLUGINS, SIGNAL(aboutToUnload(Plugin*,PluginType*)), this, SLOT(pluginAboutToUnload(Plugin*,PluginType*))); connect(PLUGINS, SIGNAL(unloaded(QString,PluginType*)), this, SLOT(pluginUnloaded(QString,PluginType*))); } void ConfigDialog::initPluginsPage() { setValidStateTooltip(ui->pluginsList, tr("Plugins are loaded/unloaded immediately when checked/unchecked, " "but modified list of plugins to load at startup is not saved until " "you commit the whole configuration dialog.")); QTreeWidgetItem* category = nullptr; QTreeWidgetItem* item = nullptr; QFont font; QModelIndex categoryIndex; QModelIndex itemIndex; int itemRow; int categoryRow; bool builtIn; QLabel* detailsLabel = nullptr; QString title; QSize itemSize; QStringList pluginNames; // Font and metrics item = new QTreeWidgetItem({""}); font = item->font(0); QFontMetrics fm(font); itemSize = QSize(-1, (fm.ascent() + fm.descent() + 4)); delete item; // Creating... ui->pluginsList->header()->setSectionsMovable(false); ui->pluginsList->header()->setSectionResizeMode(0, QHeaderView::Stretch); QBrush categoryBg = ui->pluginsList->palette().button(); QBrush categoryFg = ui->pluginsList->palette().buttonText(); connect(ui->pluginsList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(loadUnloadPlugin(QTreeWidgetItem*,int))); connect(PLUGINS, SIGNAL(failedToLoad(QString)), this, SLOT(failedToLoadPlugin(QString))); categoryRow = 0; QList pluginTypes = PLUGINS->getPluginTypes(); sSort(pluginTypes, PluginType::nameLessThan); for (PluginType*& pluginType : pluginTypes) { category = new QTreeWidgetItem({pluginType->getTitle()}); font.setItalic(false); font.setBold(true); category->setFont(0, font); for (int i = 0; i < 2; i++) { category->setBackground(i, categoryBg); category->setForeground(i, categoryFg); } category->setSizeHint(0, itemSize); ui->pluginsList->addTopLevelItem(category); categoryIndex = ui->pluginsList->model()->index(categoryRow, 0); categoryRow++; itemRow = 0; pluginNames = pluginType->getAllPluginNames(); sSort(pluginNames); for (QString& pluginName : pluginNames) { builtIn = PLUGINS->isBuiltIn(pluginName); title = PLUGINS->getTitle(pluginName); if (builtIn) title = tr("%1 (built-in)", "plugins manager in configuration dialog").arg(title); item = new QTreeWidgetItem({title}); item->setCheckState(0, PLUGINS->isLoaded(pluginName) ? Qt::Checked : Qt::Unchecked); item->setSizeHint(0, itemSize); if (builtIn) item->setDisabled(true); category->addChild(item); pluginListItemToPluginNameMap.insert(item, pluginName); // Details button detailsLabel = new QLabel(QString("%2 ").arg(pluginName, tr("Details")), ui->pluginsList); detailsLabel->setAlignment(Qt::AlignRight); itemIndex = ui->pluginsList->model()->index(itemRow, 1, categoryIndex); ui->pluginsList->setIndexWidget(itemIndex, detailsLabel); connect(detailsLabel, SIGNAL(linkActivated(QString)), this, SLOT(detailsClicked(QString))); itemRow++; } if (itemRow == 0) { item = new QTreeWidgetItem({tr("No plugins in this category.")}); item->setDisabled(true); item->setSizeHint(0, itemSize); font.setItalic(true); font.setBold(false); item->setFont(0, font); category->addChild(item); } category->setExpanded(true); } } bool ConfigDialog::initPluginPage(Plugin* plugin, bool skipConfigLoading) { UiConfiguredPlugin* cfgPlugin = dynamic_cast(plugin); if (!cfgPlugin) return false; QString pluginName = plugin->getName(); QString formName = cfgPlugin->getConfigUiForm(); QWidget* widget = FORMS->createWidget(formName); if (!widget) { qWarning() << "Could not load plugin UI file" << formName << "for plugin:" << pluginName; return false; } nameToPage[pluginName] = widget; ui->stackedWidget->addWidget(widget); CfgMain* mainConfig = cfgPlugin->getMainUiConfig(); if (mainConfig) { pluginConfigMappers[cfgPlugin] = new ConfigMapper(mainConfig); pluginConfigMappers[cfgPlugin]->bindToConfig(widget); connectMapperSignals(pluginConfigMappers[cfgPlugin]); mainConfig->begin(); // Remove this config from global mapper (if present there), so it's handled per plugin mapper configMapper->removeMainCfgEntry(mainConfig); configMapper->ignoreWidget(widget); } else if (!skipConfigLoading) { configMapper->loadToWidget(widget); } cfgPlugin->configDialogOpen(); return true; } void ConfigDialog::deinitPluginPage(Plugin* plugin) { QString pluginName = plugin->getName(); if (!nameToPage.contains(pluginName)) return; if (dynamic_cast(plugin)) { UiConfiguredPlugin* cfgPlugin = dynamic_cast(plugin); CfgMain* mainCfg = cfgPlugin->getMainUiConfig(); if (mainCfg) mainCfg->rollback(); cfgPlugin->configDialogClosed(); if (pluginConfigMappers.contains(cfgPlugin)) { delete pluginConfigMappers[cfgPlugin]; pluginConfigMappers.remove(cfgPlugin); } } QWidget* widget = nameToPage[pluginName]; nameToPage.remove(pluginName); configMapper->removeIgnoredWidget(widget); ui->stackedWidget->removeWidget(widget); delete widget; } void ConfigDialog::initDataEditors() { ui->dataEditorsAvailableList->setSpacing(1); QHash editorsOrder = CFG_UI.General.DataEditorsOrder.get(); QSet dataTypeSet = toSet(editorsOrder.keys()); dataTypeSet += toSet(DataType::getAllNames()); QStringList dataTypeList = dataTypeSet.values(); sSort(dataTypeList); QListWidgetItem* item = nullptr; for (QString& type : dataTypeList) { item = new QListWidgetItem(type); if (!DataType::getAllNames().contains(type)) item->setFlags(item->flags()|Qt::ItemIsEditable); ui->dataEditorsTypesList->addItem(item); } QAction* act = new QAction(ICONS.INSERT_DATATYPE, tr("Add new data type"), ui->dataEditorsTypesToolbar); connect(act, SIGNAL(triggered()), this, SLOT(addDataType())); ui->dataEditorsTypesToolbar->addAction(act); dataEditRenameAction = new QAction(ICONS.RENAME_DATATYPE, tr("Rename selected data type"), ui->dataEditorsTypesToolbar); connect(dataEditRenameAction, SIGNAL(triggered()), this, SLOT(renameDataType())); ui->dataEditorsTypesToolbar->addAction(dataEditRenameAction); dataEditDeleteAction = new QAction(ICONS.DELETE_DATATYPE, tr("Delete selected data type"), ui->dataEditorsTypesToolbar); connect(dataEditDeleteAction, SIGNAL(triggered()), this, SLOT(delDataType())); ui->dataEditorsTypesToolbar->addAction(dataEditDeleteAction); act = new QAction(ICONS.HELP, tr("Help for configuring data type editors"), ui->dataEditorsTypesToolbar); connect(act, SIGNAL(triggered()), this, SLOT(dataTypesHelp())); ui->dataEditorsTypesToolbar->addAction(act); connect(ui->dataEditorsTypesList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateDataTypeEditors())); connect(ui->dataEditorsTypesList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(updateDataTypeListState())); connect(ui->dataEditorsTypesList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(dataEditorItemEdited(QListWidgetItem*))); connect(ui->dataEditorsAvailableList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(dataEditorAvailableChanged(QListWidgetItem*))); connect(ui->dataEditorsSelectedTabs->tabBar(), SIGNAL(tabMoved(int,int)), this, SLOT(dataEditorTabsOrderChanged(int,int))); ui->dataEditorsTypesList->setCurrentRow(0, QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent); updateDataTypeListState(); } void ConfigDialog::initShortcuts() { ui->shortcutsTable->header()->setSectionsMovable(false); ui->shortcutsTable->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->shortcutsTable->header()->setSectionResizeMode(1, QHeaderView::Fixed); ui->shortcutsTable->header()->setSectionResizeMode(2, QHeaderView::Fixed); ui->shortcutsTable->header()->resizeSection(1, 150); ui->shortcutsTable->header()->resizeSection(2, 26); ui->shortcutsTable->header()->resizeSection(3, 26); ui->shortcutsFilterEdit->setClearButtonEnabled(true); new UserInputFilter(ui->shortcutsFilterEdit, this, SLOT(applyShortcutsFilter(QString))); QList categories = getShortcutsCfgCategories(); ui->shortcutsTable->setItemDelegate(new ConfigDialogItemDelegate()); sSort(categories, [](CfgCategory* cat1, CfgCategory* cat2) -> bool { return cat1->getTitle().compare(cat2->getTitle()) < 0; }); for (CfgCategory*& cat : categories) initShortcuts(cat); } void ConfigDialog::initShortcuts(CfgCategory *cfgCategory) { // Font and metrics QTreeWidgetItem item({""}); QFont font = item.font(0); // QFontMetrics fm(font); // QSize itemSize = QSize(-1, -1); // Creating... QBrush categoryBg = ui->shortcutsTable->palette().button(); QBrush categoryFg = ui->shortcutsTable->palette().buttonText(); QTreeWidgetItem* category = new QTreeWidgetItem({cfgCategory->getTitle()}); font.setItalic(false); font.setBold(true); category->setFont(0, font); for (int i = 0; i < 4; i++) { category->setData(i, Qt::UserRole, true); category->setBackground(i, categoryBg); category->setForeground(i, categoryFg); } // category->setSizeHint(0, itemSize); category->setFlags(category->flags() ^ Qt::ItemIsSelectable); ui->shortcutsTable->addTopLevelItem(category); int categoryRow = ui->shortcutsTable->topLevelItemCount() - 1; QModelIndex categoryIndex = ui->shortcutsTable->model()->index(categoryRow, 0); int itemRow = 0; QStringList entryNames = cfgCategory->getEntries().keys(); sSort(entryNames); for (QString& entryName : entryNames) { CfgEntry* cfgEntry = cfgCategory->getEntries()[entryName]; // Title QString title = cfgEntry->getTitle(); new QTreeWidgetItem(category, {title}); // Key edit QKeySequenceEdit* sequenceEdit = new QKeySequenceEdit(ui->shortcutsTable); sequenceEdit->setFixedWidth(150); sequenceEdit->setProperty("cfg", cfgEntry->getFullKey()); QModelIndex itemIndex = ui->shortcutsTable->model()->index(itemRow, 1, categoryIndex); ui->shortcutsTable->setIndexWidget(itemIndex, sequenceEdit); configMapper->addExtraWidget(sequenceEdit); // Clear button QToolButton* clearButton = new QToolButton(ui->shortcutsTable); clearButton->setIcon(ICONS.CLEAR_LINEEDIT); clearButton->setToolTip(tr("Clear hotkey for this action")); connect(clearButton, &QToolButton::clicked, this, [this, sequenceEdit]() { sequenceEdit->clear(); this->markModified(); }); itemIndex = ui->shortcutsTable->model()->index(itemRow, 2, categoryIndex); ui->shortcutsTable->setIndexWidget(itemIndex, clearButton); // Restore default value button QToolButton* defaultButton = new QToolButton(ui->shortcutsTable); defaultButton->setIcon(ICONS.RESTORE_DEFAULT); defaultButton->setToolTip(tr("Restore original hotkey for this action")); connect(defaultButton, &QToolButton::clicked, this, [this, sequenceEdit, cfgEntry]() { cfgEntry->reset(); sequenceEdit->setKeySequence(QKeySequence::fromString(cfgEntry->get().toString())); this->markModified(); }); itemIndex = ui->shortcutsTable->model()->index(itemRow, 3, categoryIndex); ui->shortcutsTable->setIndexWidget(itemIndex, defaultButton); itemRow++; } category->setExpanded(true); } void ConfigDialog::initLangs() { QMap langs = getAvailableLanguages(); int idx = 0; int selected = -1; QStringList langList = langs.keys(); strSort(langList, Qt::CaseInsensitive); for (QString& lang : langList) { ui->langCombo->addItem(lang, langs[lang]); if (langs[lang] == SQLITESTUDIO->getCurrentLang()) selected = idx; idx++; } ui->langCombo->setCurrentIndex(selected); } void ConfigDialog::initTooltips() { ui->execQueryUnderCursorCheck->setToolTip(ui->execQueryUnderCursorCheck->toolTip().arg( GET_SHORTCUT_ENTRY(EditorWindow, EXEC_ONE_QUERY)->get().toString(), GET_SHORTCUT_ENTRY(EditorWindow, EXEC_ALL_QUERIES)->get().toString() )); setValidStateTooltip(ui->commonCodeColorsGroup, tr("Here you can configure colors for code syntax highlighting. " "They are shared across different languages - not only for SQL, but also JavaScript and others. " "By default a theme-based color is used. To define your own color, enable a custom color " "by selecting a checkbox next to a particular color.")); } bool ConfigDialog::isPluginCategoryItem(QTreeWidgetItem *item) const { return item->parent() && item->parent()->parent() && item->parent()->parent() == getPluginsCategoryItem(); } void ConfigDialog::updateStylePreview() { ui->previewWidget->parentWidget()->layout()->removeWidget(ui->previewWidget); ui->previewTabs->currentWidget()->layout()->addWidget(ui->previewWidget); ui->previewWidget->setEnabled(ui->previewTabs->currentIndex() == 0); QStyle* previousStyle = previewStyle; previewStyle = QStyleFactory::create(ui->activeStyleCombo->currentText()); if (!previewStyle) { qWarning() << "Could not create style:" << ui->activeStyleCombo->currentText(); return; } applyStyle(ui->activeStylePreviewGroup, previewStyle); if (previousStyle) delete previousStyle; } void ConfigDialog::apply() { if (modifiedFlag) save(); setModified(false); } void ConfigDialog::accept() { apply(); QDialog::accept(); }