diff --git a/kdevplatform/interfaces/isourceformattercontroller.h b/kdevplatform/interfaces/isourceformattercontroller.h --- a/kdevplatform/interfaces/isourceformattercontroller.h +++ b/kdevplatform/interfaces/isourceformattercontroller.h @@ -58,6 +58,8 @@ * @param mime known mimetype of the url */ virtual ISourceFormatter* formatterForUrl(const QUrl& url, const QMimeType& mime) = 0; + ///\return @c true if there are formatters at all, @c false otherwise + virtual bool hasFormatters() const = 0; /** \return Whether this mime type is supported by any plugin. */ virtual bool isMimeTypeSupported(const QMimeType &mime) = 0; @@ -68,6 +70,9 @@ virtual void disableSourceFormatting(bool disable) = 0; ///\return Whether or not source formatting is enabled virtual bool sourceFormattingEnabled() = 0; + + Q_SIGNALS: + void hasFormattersChanged(bool hasFormatters); }; } diff --git a/kdevplatform/shell/mainwindow.cpp b/kdevplatform/shell/mainwindow.cpp --- a/kdevplatform/shell/mainwindow.cpp +++ b/kdevplatform/shell/mainwindow.cpp @@ -345,12 +345,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 @@ -47,6 +47,7 @@ #include "mainwindow.h" #include "textdocument.h" #include "sessioncontroller.h" +#include "sourceformattercontroller.h" #include "debug.h" #include "ktexteditorpluginintegration.h" #include "colorschemechooser.h" @@ -125,6 +126,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 @@ -102,6 +103,7 @@ * The source formatter is then ready to use on a file. */ ISourceFormatter* formatterForUrl(const QUrl& url, const QMimeType& mime) override; + bool hasFormatters() const override; /** \return Whether this mime type is supported by any plugin. */ bool isMimeTypeSupported(const QMimeType& mime) override; @@ -132,12 +134,21 @@ void disableSourceFormatting(bool disable) override; bool sourceFormattingEnabled() override; + QVector formatters() const; + +Q_SIGNALS: + 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. @@ -152,6 +163,8 @@ void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter, const QUrl& url, bool ignoreModeline = false); + void resetUi(); + private: const QScopedPointer d; }; diff --git a/kdevplatform/shell/sourceformattercontroller.cpp b/kdevplatform/shell/sourceformattercontroller.cpp --- a/kdevplatform/shell/sourceformattercontroller.cpp +++ b/kdevplatform/shell/sourceformattercontroller.cpp @@ -74,6 +74,8 @@ class SourceFormatterControllerPrivate { public: + // cache of formatter plugins, to avoid querying plugincontroller + QVector sourceFormatters; // GUI actions QAction* formatTextAction; QAction* formatFilesAction; @@ -127,20 +129,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, @@ -168,6 +179,46 @@ adaptEditorIndentationMode(doc->textDocument(), formatterForUrl(url, mime), url); } +void SourceFormatterController::pluginLoaded(IPlugin* plugin) +{ + ISourceFormatter* sourceFormatter = plugin->extension(); + + if (!sourceFormatter) { + return; + } + + d->sourceFormatters << sourceFormatter; + + resetUi(); + + emit formatterLoaded(sourceFormatter); + // with one plugin now added, hasFormatters turned to true, so report to listeners + if (d->sourceFormatters.size() == 1) { + emit hasFormattersChanged(true); + } +} + +void SourceFormatterController::unloadingPlugin(IPlugin* plugin) +{ + ISourceFormatter* sourceFormatter = plugin->extension(); + + if (!sourceFormatter) { + return; + } + + const int idx = d->sourceFormatters.indexOf(sourceFormatter); + Q_ASSERT(idx != -1); + d->sourceFormatters.remove(idx); + + resetUi(); + + emit formatterUnloading(sourceFormatter); + if (d->sourceFormatters.isEmpty()) { + emit hasFormattersChanged(false); + } +} + + void SourceFormatterController::initialize() { } @@ -212,9 +263,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; @@ -275,7 +324,13 @@ return nullptr; } - return Core::self()->pluginControllerInternal()->extensionForPlugin( QStringLiteral("org.kdevelop.ISourceFormatter"), formatterinfo.at(0) ); + foreach (ISourceFormatter* iformatter, d->sourceFormatters) { + if (iformatter->name() == formatterinfo.first()) { + return iformatter; + } + } + + return nullptr; } bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime) @@ -377,11 +432,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); @@ -616,6 +673,10 @@ d->urls.clear(); d->prjItems.clear(); + if (d->sourceFormatters.isEmpty()) { + return ext; + } + if (context->hasType(KDevelop::Context::EditorContext)) { if (d->formatTextAction->isEnabled()) @@ -661,4 +722,21 @@ return d->enabled; } +bool SourceFormatterController::hasFormatters() const +{ + return !d->sourceFormatters.isEmpty(); +} + +QVector SourceFormatterController::formatters() const +{ + return d->sourceFormatters; +} + +void SourceFormatterController::resetUi() +{ + d->formatFilesAction->setEnabled(!d->sourceFormatters.isEmpty()); + + updateFormatTextAction(); +} + } 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 @@ -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 @@ -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,8 @@ int SourceFormatterPlugin::perProjectConfigPages() const { - return 1; + const auto hasFormatters = KDevelop::ICore::self()->sourceFormatterController()->hasFormatters(); + return hasFormatters ? 1 : 0; } KDevelop::ConfigPage* SourceFormatterPlugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent)