diff --git a/kdevplatform/shell/mainwindow.cpp b/kdevplatform/shell/mainwindow.cpp --- a/kdevplatform/shell/mainwindow.cpp +++ b/kdevplatform/shell/mainwindow.cpp @@ -361,12 +361,16 @@ d, &MainWindowPrivate::activePartChanged); connect( this, &MainWindow::activeViewChanged, d, &MainWindowPrivate::changeActiveView); + connect(Core::self()->sourceFormatterControllerInternal(), &SourceFormatterController::hasFormattersChanged, + d, &MainWindowPrivate::updateSourceFormatterGuiClient); foreach(IPlugin* plugin, Core::self()->pluginController()->loadedPlugins()) d->addPlugin(plugin); guiFactory()->addClient(Core::self()->sessionController()); - guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal()); + if (Core::self()->sourceFormatterControllerInternal()->hasFormatters()) { + guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal()); + } // Needed to re-plug the actions from the sessioncontroller as xmlguiclients don't // seem to remember which actions where plugged in. diff --git a/kdevplatform/shell/mainwindow_p.h b/kdevplatform/shell/mainwindow_p.h --- a/kdevplatform/shell/mainwindow_p.h +++ b/kdevplatform/shell/mainwindow_p.h @@ -77,6 +77,7 @@ public Q_SLOTS: void addPlugin( KDevelop::IPlugin *plugin ); void removePlugin( KDevelop::IPlugin *plugin ); + void updateSourceFormatterGuiClient(bool hasFormatters); void activePartChanged(KParts::Part *part); void mergeView(Sublime::View *view); diff --git a/kdevplatform/shell/mainwindow_p.cpp b/kdevplatform/shell/mainwindow_p.cpp --- a/kdevplatform/shell/mainwindow_p.cpp +++ b/kdevplatform/shell/mainwindow_p.cpp @@ -50,6 +50,7 @@ #include "mainwindow.h" #include "textdocument.h" #include "sessioncontroller.h" +#include "sourceformattercontroller.h" #include "debug.h" #include "ktexteditorpluginintegration.h" #include "colorschemechooser.h" @@ -128,6 +129,17 @@ m_mainWindow->guiFactory()->removeClient( plugin ); } +void MainWindowPrivate::updateSourceFormatterGuiClient(bool hasFormatters) +{ + auto sourceFormatterController = Core::self()->sourceFormatterControllerInternal(); + auto guiFactory = m_mainWindow->guiFactory(); + if (hasFormatters) { + guiFactory->addClient(sourceFormatterController); + } else { + guiFactory->removeClient(sourceFormatterController); + } +} + void MainWindowPrivate::activePartChanged(KParts::Part *part) { if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow) diff --git a/kdevplatform/shell/sourceformattercontroller.h b/kdevplatform/shell/sourceformattercontroller.h --- a/kdevplatform/shell/sourceformattercontroller.h +++ b/kdevplatform/shell/sourceformattercontroller.h @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -132,12 +133,23 @@ void disableSourceFormatting(bool disable) override; bool sourceFormattingEnabled() override; + bool hasFormatters() const; + QVector formatters() const; + +Q_SIGNALS: + void hasFormattersChanged(bool hasFormatters); + void formatterLoaded(KDevelop::ISourceFormatter* ifmt); + void formatterUnloading(KDevelop::ISourceFormatter* ifmt); + private Q_SLOTS: void updateFormatTextAction(); void beautifySource(); void beautifyLine(); void formatFiles(); void documentLoaded( KDevelop::IDocument* ); + void pluginLoaded(KDevelop::IPlugin* plugin); + void unloadingPlugin(KDevelop::IPlugin* plugin); + private: /** \return A modeline string (to add at the end or the beginning of a file) * corresponding to the settings of the active language. diff --git a/kdevplatform/shell/sourceformattercontroller.cpp b/kdevplatform/shell/sourceformattercontroller.cpp --- a/kdevplatform/shell/sourceformattercontroller.cpp +++ b/kdevplatform/shell/sourceformattercontroller.cpp @@ -76,6 +76,8 @@ class SourceFormatterControllerPrivate { public: + // cache of formatter plugis, to avoid querying plugincontroller + QVector sourceFormatters; // GUI actions QAction* formatTextAction; QAction* formatFilesAction; @@ -129,20 +131,29 @@ d->formatTextAction->setText(i18n("&Reformat Source")); d->formatTextAction->setToolTip(i18n("Reformat source using AStyle")); d->formatTextAction->setWhatsThis(i18n("Source reformatting functionality using astyle library.")); + d->formatTextAction->setEnabled(false); connect(d->formatTextAction, &QAction::triggered, this, &SourceFormatterController::beautifySource); d->formatLine = actionCollection()->addAction(QStringLiteral("edit_reformat_line")); d->formatLine->setText(i18n("Reformat Line")); d->formatLine->setToolTip(i18n("Reformat current line using AStyle")); d->formatLine->setWhatsThis(i18n("Source reformatting of line under cursor using astyle library.")); + d->formatLine->setEnabled(false); connect(d->formatLine, &QAction::triggered, this, &SourceFormatterController::beautifyLine); d->formatFilesAction = actionCollection()->addAction(QStringLiteral("tools_astyle")); d->formatFilesAction->setText(i18n("Reformat Files...")); d->formatFilesAction->setToolTip(i18n("Format file(s) using the current theme")); d->formatFilesAction->setWhatsThis(i18n("Formatting functionality using astyle library.")); + d->formatFilesAction->setEnabled(false); connect(d->formatFilesAction, &QAction::triggered, this, static_cast(&SourceFormatterController::formatFiles)); + + connect(Core::self()->pluginController(), &IPluginController::pluginLoaded, + this, &SourceFormatterController::pluginLoaded); + connect(Core::self()->pluginController(), &IPluginController::unloadingPlugin, + this, &SourceFormatterController::unloadingPlugin); + // connect to both documentActivated & documentClosed, // otherwise we miss when the last document was closed connect(Core::self()->documentController(), &IDocumentController::documentActivated, @@ -170,6 +181,42 @@ adaptEditorIndentationMode(doc->textDocument(), formatterForUrl(url, mime), url); } +void SourceFormatterController::pluginLoaded(IPlugin* plugin) +{ + ISourceFormatter* sourceFormatter = plugin->extension(); + if (sourceFormatter) { + d->sourceFormatters << sourceFormatter; + + emit formatterLoaded(sourceFormatter); + + if (d->sourceFormatters.count() == 1) { + d->formatFilesAction->setEnabled(true); + updateFormatTextAction(); + emit hasFormattersChanged(true); + } + } +} + +void SourceFormatterController::unloadingPlugin(IPlugin* plugin) +{ + ISourceFormatter* sourceFormatter = plugin->extension(); + if (sourceFormatter) { + const int idx = d->sourceFormatters.indexOf(sourceFormatter); + Q_ASSERT(idx != -1); + d->sourceFormatters.remove(idx); + + emit formatterUnloading(sourceFormatter); + + if (d->sourceFormatters.isEmpty()) { + d->formatTextAction->setEnabled(false); + d->formatFilesAction->setEnabled(false); + d->formatLine->setEnabled(false); + emit hasFormattersChanged(false); + } + } +} + + void SourceFormatterController::initialize() { } @@ -214,9 +261,7 @@ if (knownFormatters.contains(mime.name())) return knownFormatters[mime.name()]; - QList plugins = Core::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") ); - foreach( IPlugin* p, plugins) { - ISourceFormatter *iformatter = p->extension(); + foreach (ISourceFormatter* iformatter, d->sourceFormatters) { QSharedPointer formatter(createFormatterForPlugin(iformatter)); if( formatter->supportedMimeTypes().contains(mime.name()) ) { knownFormatters[mime.name()] = iformatter; @@ -277,7 +322,13 @@ return nullptr; } - return Core::self()->pluginControllerInternal()->extensionForPlugin( QStringLiteral("org.kdevelop.ISourceFormatter"), formatterinfo.at(0) ); + foreach (ISourceFormatter* iformatter, d->sourceFormatters) { + if (iformatter->name() == formatterinfo.at(0)) { + return iformatter; + } + } + + return nullptr; } bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime) @@ -379,11 +430,13 @@ { bool enabled = false; - IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument(); - if (doc) { - QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); - if (isMimeTypeSupported(mime)) - enabled = true; + if (!d->sourceFormatters.isEmpty()) { + IDocument* doc = KDevelop::ICore::self()->documentController()->activeDocument(); + if (doc) { + QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); + if (isMimeTypeSupported(mime)) + enabled = true; + } } d->formatLine->setEnabled(enabled); @@ -618,6 +671,10 @@ d->urls.clear(); d->prjItems.clear(); + if (d->sourceFormatters.isEmpty()) { + return ext; + } + if (context->hasType(KDevelop::Context::EditorContext)) { if (d->formatTextAction->isEnabled()) @@ -663,4 +720,14 @@ return d->enabled; } +bool SourceFormatterController::hasFormatters() const +{ + return (!d->sourceFormatters.isEmpty()); +} + +QVector SourceFormatterController::formatters() const +{ + return d->sourceFormatters; +} + } diff --git a/kdevplatform/shell/sourceformatterselectionedit.h b/kdevplatform/shell/sourceformatterselectionedit.h --- a/kdevplatform/shell/sourceformatterselectionedit.h +++ b/kdevplatform/shell/sourceformatterselectionedit.h @@ -32,6 +32,7 @@ namespace KDevelop { class SourceFormatterStyle; +class ISourceFormatter; class KDEVPLATFORMSHELL_EXPORT SourceFormatterSelectionEdit : public QWidget { @@ -49,6 +50,9 @@ void changed(); private Q_SLOTS: + void addSourceFormatter(KDevelop::ISourceFormatter* ifmt); + void removeSourceFormatter(KDevelop::ISourceFormatter* ifmt); + void deleteStyle(); void editStyle(); void newStyle(); @@ -58,6 +62,7 @@ void styleNameChanged(QListWidgetItem* ); private: + void resetUi(); void updatePreview(); QListWidgetItem* addStyle(const KDevelop::SourceFormatterStyle& s); void enableStyleButtons(); diff --git a/kdevplatform/shell/sourceformatterselectionedit.cpp b/kdevplatform/shell/sourceformatterselectionedit.cpp --- a/kdevplatform/shell/sourceformatterselectionedit.cpp +++ b/kdevplatform/shell/sourceformatterselectionedit.cpp @@ -112,6 +112,15 @@ iface->setConfigValue(QStringLiteral("dynamic-word-wrap"), false); iface->setConfigValue(QStringLiteral("icon-bar"), false); } + + SourceFormatterController* controller = Core::self()->sourceFormatterControllerInternal(); + connect(controller, &SourceFormatterController::formatterLoaded, + this, &SourceFormatterSelectionEdit::addSourceFormatter); + connect(controller, &SourceFormatterController::formatterUnloading, + this, &SourceFormatterSelectionEdit::removeSourceFormatter); + for (auto formatter : controller->formatters()) { + addSourceFormatter(formatter); + } } SourceFormatterSelectionEdit::~SourceFormatterSelectionEdit() @@ -125,58 +134,76 @@ lang.selectedStyle = *lang.selectedFormatter->styles.begin(); } -void SourceFormatterSelectionEdit::loadSettings(const KConfigGroup& config) +void SourceFormatterSelectionEdit::addSourceFormatter(ISourceFormatter* ifmt) { - SourceFormatterController* fmtctrl = Core::self()->sourceFormatterControllerInternal(); - QList plugins = KDevelop::ICore::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") ); - foreach( KDevelop::IPlugin* plugin, plugins ) - { - KDevelop::ISourceFormatter* ifmt = plugin->extension(); - auto info = KDevelop::Core::self()->pluginControllerInternal()->pluginInfo( plugin ); - KDevelop::SourceFormatter* formatter; - FormatterMap::const_iterator iter = d->formatters.constFind(ifmt->name()); - if (iter == d->formatters.constEnd()) { - formatter = fmtctrl->createFormatterForPlugin(ifmt); - d->formatters[ifmt->name()] = formatter; - } else { - formatter = iter.value(); - } - foreach ( const SourceFormatterStyle* style, formatter->styles ) { - foreach ( const SourceFormatterStyle::MimeHighlightPair& item, style->mimeTypes() ) { - QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType); - if (!mime.isValid()) { - qCWarning(SHELL) << "plugin" << info.name() << "supports unknown mimetype entry" << item.mimeType; - continue; - } - QString languageName = item.highlightMode; - LanguageSettings& l = d->languages[languageName]; - l.mimetypes.append(mime); - l.formatters.insert( formatter ); + SourceFormatter* formatter; + FormatterMap::const_iterator iter = d->formatters.constFind(ifmt->name()); + if (iter == d->formatters.constEnd()) { + formatter = Core::self()->sourceFormatterControllerInternal()->createFormatterForPlugin(ifmt); + d->formatters[ifmt->name()] = formatter; + } else { + qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "loading which was already seen before by SourceFormatterSelectionEdit"; + return; + } + + foreach ( const SourceFormatterStyle* style, formatter->styles ) { + foreach ( const SourceFormatterStyle::MimeHighlightPair& item, style->mimeTypes() ) { + QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType); + if (!mime.isValid()) { + qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "supports unknown mimetype entry" << item.mimeType; + continue; + } + QString languageName = item.highlightMode; + LanguageSettings& l = d->languages[languageName]; + l.mimetypes.append(mime); + l.formatters.insert( formatter ); + // init selection if needed + if (!l.selectedFormatter) { + l.selectedFormatter = formatter; + selectAvailableStyle(l); } } } - // Sort the languages, preferring firstly active, then loaded languages - QList sortedLanguages; + resetUi(); +} - foreach(const auto language, - KDevelop::ICore::self()->languageController()->activeLanguages() + - KDevelop::ICore::self()->languageController()->loadedLanguages()) - { - if (d->languages.contains(language->name()) && !sortedLanguages.contains(language->name())) { - sortedLanguages.push_back( language->name() ); - } +void SourceFormatterSelectionEdit::removeSourceFormatter(ISourceFormatter* ifmt) +{ + auto iter = d->formatters.find(ifmt->name()); + if (iter == d->formatters.end()) { + qCWarning(SHELL) << "formatter plugin" << ifmt->name() << "unloading which was not seen before by SourceFormatterSelectionEdit"; + return; } + d->formatters.erase(iter); + auto formatter = iter.value(); - foreach (const QString& name, d->languages.keys()) { - if( !sortedLanguages.contains( name ) ) - sortedLanguages.push_back( name ); + auto languageIter = d->languages.begin(); + while (languageIter != d->languages.end()) { + LanguageSettings& l = languageIter.value(); + + l.formatters.remove(formatter); + if (l.formatters.isEmpty()) { + languageIter = d->languages.erase(languageIter); + } else { + // reset selected formatter if needed + if (l.selectedFormatter == formatter) { + l.selectedFormatter = *l.formatters.begin(); + selectAvailableStyle(l); + } + ++languageIter; + } } + delete formatter; - foreach( const QString& name, sortedLanguages ) - { + resetUi(); +} + +void SourceFormatterSelectionEdit::loadSettings(const KConfigGroup& config) +{ + for (auto languageIter = d->languages.begin(); languageIter != d->languages.end(); ++languageIter) { // Pick the first appropriate mimetype for this language - LanguageSettings& l = d->languages[name]; + LanguageSettings& l = languageIter.value(); const QList mimetypes = l.mimetypes; foreach (const QMimeType& mimetype, mimetypes) { QStringList formatterAndStyleName = config.readEntry(mimetype.name(), QString()).split(QStringLiteral("||"), QString::KeepEmptyParts); @@ -205,6 +232,29 @@ selectAvailableStyle(l); } } + + resetUi(); +} + +void SourceFormatterSelectionEdit::resetUi() +{ + // Sort the languages, preferring firstly active, then loaded languages + QList sortedLanguages; + + foreach(const auto language, + KDevelop::ICore::self()->languageController()->activeLanguages() + + KDevelop::ICore::self()->languageController()->loadedLanguages()) + { + if (d->languages.contains(language->name()) && !sortedLanguages.contains(language->name())) { + sortedLanguages.push_back( language->name() ); + } + } + + foreach (const QString& name, d->languages.keys()) { + if( !sortedLanguages.contains( name ) ) + sortedLanguages.push_back( name ); + } + bool b = blockSignals( true ); d->ui.cbLanguages->blockSignals(!b); d->ui.cbFormatters->blockSignals(!b); @@ -222,6 +272,7 @@ } else { d->ui.cbLanguages->setCurrentIndex(0); + d->ui.cbLanguages->setEnabled(true); selectLanguage( 0 ); } updatePreview(); diff --git a/plugins/astyle/kdevastyle.json b/plugins/astyle/kdevastyle.json --- a/plugins/astyle/kdevastyle.json +++ b/plugins/astyle/kdevastyle.json @@ -28,7 +28,7 @@ "Description[uk]": "Додаток для форматування коду у відповідності до вказаного набору правил", "Description[x-test]": "xxA plugin for formatting of sourcecode according to a specified set of rulesxx", "Description[zh_CN]": "一个根据指定规则设定格式化源代码的插件", - "Icon": "kdevelop", + "Icon": "text-field", "Id": "kdevastyle", "License": "LGPL", "Name": "AStyle Formatter Backend", @@ -59,6 +59,6 @@ "X-KDevelop-Interfaces": [ "org.kdevelop.ISourceFormatter" ], - "X-KDevelop-LoadMode": "AlwaysOn", + "X-KDevelop-Category": "Global", "X-KDevelop-Mode": "NoGUI" } diff --git a/plugins/customscript/kdevcustomscript.json b/plugins/customscript/kdevcustomscript.json --- a/plugins/customscript/kdevcustomscript.json +++ b/plugins/customscript/kdevcustomscript.json @@ -28,7 +28,7 @@ "Description[uk]": "Додаток для форматування файлів мовою C за допомогою нетипових скриптів", "Description[x-test]": "xxA plugin for formatting C files using custom scriptsxx", "Description[zh_CN]": "使用自定义脚本格式化 C 文件的插件", - "Icon": "kdevelop", + "Icon": "text-field", "Id": "kdevcustomscript", "License": "LGPL", "Name": "Custom Script Formatter Backend", @@ -59,6 +59,6 @@ "X-KDevelop-Interfaces": [ "org.kdevelop.ISourceFormatter" ], - "X-KDevelop-LoadMode": "AlwaysOn", + "X-KDevelop-Category": "Global", "X-KDevelop-Mode": "NoGUI" } diff --git a/plugins/sourceformatter/sourceformatterplugin.cpp b/plugins/sourceformatter/sourceformatterplugin.cpp --- a/plugins/sourceformatter/sourceformatterplugin.cpp +++ b/plugins/sourceformatter/sourceformatterplugin.cpp @@ -21,6 +21,8 @@ #include "config/projectconfigpage.h" +#include +#include #include #include @@ -38,7 +40,9 @@ int SourceFormatterPlugin::perProjectConfigPages() const { - return 1; + // exception of accessing internal API, to be solved by making sourceFormatterController a plugin + const auto hasFormatters = KDevelop::Core::self()->sourceFormatterControllerInternal()->hasFormatters(); + return hasFormatters ? 1 : 0; } KDevelop::ConfigPage* SourceFormatterPlugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent)