diff --git a/kdevplatform/shell/mainwindow.cpp b/kdevplatform/shell/mainwindow.cpp index 71aef56993..a21801e373 100644 --- a/kdevplatform/shell/mainwindow.cpp +++ b/kdevplatform/shell/mainwindow.cpp @@ -1,529 +1,535 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.h" #include "mainwindow_p.h" #include "qtcompat_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "projectcontroller.h" #include "uicontroller.h" #include "documentcontroller.h" #include "workingsetcontroller.h" #include "sessioncontroller.h" #include "sourceformattercontroller.h" #include "areadisplay.h" #include "project.h" #include "debug.h" #include "uiconfig.h" #include "ktexteditorpluginintegration.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QColor defaultColor(const QPalette& palette) { return palette.foreground().color(); } QColor colorForDocument(const QUrl& url, const QPalette& palette, const QColor& defaultColor) { auto project = Core::self()->projectController()->findProjectForUrl(url); if (!project) return defaultColor; return WidgetColorizer::colorForId(qHash(project->path()), palette); } } void MainWindow::applyMainWindowSettings(const KConfigGroup& config) { if(!d->changingActiveView()) KXmlGuiWindow::applyMainWindowSettings(config); } void MainWindow::createGUI(KParts::Part* part) { //TODO remove if-clause once KF5 >= 5.24 is required #if KPARTS_VERSION_MINOR >= 24 Sublime::MainWindow::setWindowTitleHandling(false); Sublime::MainWindow::createGUI(part); #else Sublime::MainWindow::createGUI(part); if (part) { // Don't let the Part control the main window caption -- we take care of that disconnect(part, SIGNAL(setWindowCaption(QString)), this, SLOT(setCaption(QString))); } #endif } void MainWindow::initializeCorners() { const KConfigGroup cg = KSharedConfig::openConfig()->group( "UiSettings" ); const int bottomleft = cg.readEntry( "BottomLeftCornerOwner", 0 ); const int bottomright = cg.readEntry( "BottomRightCornerOwner", 0 ); qCDebug(SHELL) << "Bottom Left:" << bottomleft; qCDebug(SHELL) << "Bottom Right:" << bottomright; // 0 means vertical dock (left, right), 1 means horizontal dock( top, bottom ) if( bottomleft == 0 ) setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea ); else if( bottomleft == 1 ) setCorner( Qt::BottomLeftCorner, Qt::BottomDockWidgetArea ); if( bottomright == 0 ) setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea ); else if( bottomright == 1 ) setCorner( Qt::BottomRightCorner, Qt::BottomDockWidgetArea ); } MainWindow::MainWindow( Sublime::Controller *parent, Qt::WindowFlags flags ) : Sublime::MainWindow( parent, flags ) { QDBusConnection::sessionBus().registerObject( QStringLiteral("/kdevelop/MainWindow"), this, QDBusConnection::ExportScriptableSlots ); setAcceptDrops( true ); initializeCorners(); setObjectName( QStringLiteral("MainWindow") ); d = new MainWindowPrivate(this); setStandardToolBarMenuEnabled( true ); d->setupActions(); if( !ShellExtension::getInstance()->xmlFile().isEmpty() ) { setXMLFile( ShellExtension::getInstance() ->xmlFile() ); } menuBar()->setCornerWidget(new AreaDisplay(this), Qt::TopRightCorner); } MainWindow::~ MainWindow() { if (memberList().count() == 1) { // We're closing down... Core::self()->shutdown(); } delete d; } KTextEditorIntegration::MainWindow *MainWindow::kateWrapper() const { return d->kateWrapper(); } void MainWindow::split(Qt::Orientation orientation) { d->split(orientation); } void MainWindow::ensureVisible() { if (isMinimized()) { if (isMaximized()) { showMaximized(); } else { showNormal(); } } KWindowSystem::forceActiveWindow(winId()); } QAction* MainWindow::createCustomElement(QWidget* parent, int index, const QDomElement& element) { QAction* before = nullptr; if (index > 0 && index < parent->actions().count()) before = parent->actions().at(index); //KDevelop needs to ensure that separators defined as //are always shown in the menubar. For those, we create special disabled actions //instead of calling QMenuBar::addSeparator() because menubar separators are ignored if (element.tagName().toLower() == QLatin1String("separator") && element.attribute(QStringLiteral("style")) == QLatin1String("visible")) { if ( QMenuBar* bar = qobject_cast( parent ) ) { QAction *separatorAction = new QAction(QStringLiteral("|"), this); bar->insertAction( before, separatorAction ); separatorAction->setDisabled(true); return separatorAction; } } return KXMLGUIBuilder::createCustomElement(parent, index, element); } bool KDevelop::MainWindow::event( QEvent* ev ) { if ( ev->type() == QEvent::PaletteChange ) updateAllTabColors(); return Sublime::MainWindow::event(ev); } void MainWindow::dragEnterEvent( QDragEnterEvent* ev ) { const QMimeData* mimeData = ev->mimeData(); if (mimeData->hasUrls()) { ev->acceptProposedAction(); } else if (mimeData->hasText()) { // also take text which contains a URL const QUrl url = QUrl::fromUserInput(mimeData->text()); if (url.isValid()) { ev->acceptProposedAction(); } } } void MainWindow::dropEvent( QDropEvent* ev ) { Sublime::View* dropToView = viewForPosition(mapToGlobal(ev->pos())); if(dropToView) activateView(dropToView); QList urls; const QMimeData* mimeData = ev->mimeData(); if (mimeData->hasUrls()) { urls = mimeData->urls(); } else if (mimeData->hasText()) { const QUrl url = QUrl::fromUserInput(mimeData->text()); if (url.isValid()) { urls << url; } } bool eventUsed = false; if (urls.size() == 1) { eventUsed = Core::self()->projectControllerInternal()->fetchProjectFromUrl(urls.at(0)); } if (!eventUsed) { for(const auto& url : qAsConst(urls)) { Core::self()->documentController()->openDocument(url); } } ev->acceptProposedAction(); } void MainWindow::loadSettings() { qCDebug(SHELL) << "Loading Settings"; initializeCorners(); updateAllTabColors(); Sublime::MainWindow::loadSettings(); } void MainWindow::configureShortcuts() { ///Workaround for a problem with the actions: Always start the shortcut-configuration in the first mainwindow, then propagate the updated ///settings into the other windows // We need to bring up the shortcut dialog ourself instead of // Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->configureShortcuts(); // so we can connect to the saved() signal to propagate changes in the editor shortcuts KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); foreach (KXMLGUIClient *client, Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients()) { if(client && !client->xmlFile().isEmpty()) dlg.addCollection( client->actionCollection() ); } connect(&dlg, &KShortcutsDialog::saved, this, &MainWindow::shortcutsChanged); dlg.configure(true); QMap shortcuts; foreach(KXMLGUIClient* client, Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients()) { foreach(QAction* action, client->actionCollection()->actions()) { if(!action->objectName().isEmpty()) { shortcuts[action->objectName()] = action->shortcut(); } } } for(int a = 1; a < Core::self()->uiControllerInternal()->mainWindows().size(); ++a) { foreach(KXMLGUIClient* client, Core::self()->uiControllerInternal()->mainWindows()[a]->guiFactory()->clients()) { foreach(QAction* action, client->actionCollection()->actions()) { qCDebug(SHELL) << "transferring setting shortcut for" << action->objectName(); if(shortcuts.contains(action->objectName())) { action->setShortcut(shortcuts[action->objectName()]); } } } } } void MainWindow::shortcutsChanged() { KTextEditor::View *activeClient = Core::self()->documentController()->activeTextDocumentView(); if(!activeClient) return; foreach(IDocument * doc, Core::self()->documentController()->openDocuments()) { KTextEditor::Document *textDocument = doc->textDocument(); if (textDocument) { foreach(KTextEditor::View *client, textDocument->views()) { if (client != activeClient) { client->reloadXML(); } } } } } void MainWindow::initialize() { KStandardAction::keyBindings(this, SLOT(configureShortcuts()), actionCollection()); setupGUI( KXmlGuiWindow::ToolBar | KXmlGuiWindow::Create | KXmlGuiWindow::Save ); Core::self()->partController()->addManagedTopLevelWidget(this); qCDebug(SHELL) << "Adding plugin-added connection"; connect( Core::self()->pluginController(), &IPluginController::pluginLoaded, d, &MainWindowPrivate::addPlugin); connect( Core::self()->pluginController(), &IPluginController::pluginUnloaded, d, &MainWindowPrivate::removePlugin); connect( Core::self()->partController(), &IPartController::activePartChanged, 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()); 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. Core::self()->sessionController()->updateXmlGuiActionList(); d->setupGui(); + qRegisterMetaType>(); + //Queued so we process it with some delay, to make sure the rest of the UI has already adapted connect(Core::self()->documentController(), &IDocumentController::documentActivated, // Use a queued connection, because otherwise the view is not yet fully set up // but wrap the document in a smart pointer to guard against crashes when it // gets deleted in the meantime this, [this](IDocument *doc) { const auto textDocument = QPointer(doc->textDocument()); - QMetaObject::invokeMethod(this, [this, textDocument](){ - updateCaption(); - - // update active document connection - disconnect(d->activeDocumentReadWriteConnection); - if (textDocument) { - d->activeDocumentReadWriteConnection = connect(textDocument, &KTextEditor::Document::readWriteChanged, - this, &MainWindow::updateCaption); - } - }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "documentActivated", Qt::QueuedConnection, + Q_ARG(QPointer, textDocument)); }); connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->sessionController()->activeSession(), &ISession::sessionUpdated, this, &MainWindow::updateCaption); connect(Core::self()->documentController(), &IDocumentController::documentOpened, this, &MainWindow::updateTabColor); connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateTabColor); connect(this, &Sublime::MainWindow::viewAdded, this, &MainWindow::updateAllTabColors); connect(Core::self()->projectController(), &ProjectController::projectOpened, this, &MainWindow::updateAllTabColors, Qt::QueuedConnection); updateCaption(); } void MainWindow::cleanup() { } void MainWindow::setVisible( bool visible ) { KXmlGuiWindow::setVisible( visible ); emit finishedLoading(); } bool MainWindow::queryClose() { if (!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(this, IDocument::Default)) return false; return Sublime::MainWindow::queryClose(); } +void MainWindow::documentActivated(const QPointer& textDocument) +{ + updateCaption(); + + // update active document connection + disconnect(d->activeDocumentReadWriteConnection); + if (textDocument) { + d->activeDocumentReadWriteConnection = connect(textDocument, &KTextEditor::Document::readWriteChanged, + this, &MainWindow::updateCaption); + } +} + void MainWindow::updateCaption() { const auto activeSession = Core::self()->sessionController()->activeSession(); QString title = activeSession ? activeSession->description() : QString(); QString localFilePath; if(area()->activeView()) { if(!title.isEmpty()) title += QLatin1String(" - [ "); Sublime::Document* doc = area()->activeView()->document(); Sublime::UrlDocument* urlDoc = dynamic_cast(doc); if(urlDoc) { if (urlDoc->url().isLocalFile()) { localFilePath = urlDoc->url().toLocalFile(); } title += Core::self()->projectController()->prettyFileName(urlDoc->url(), KDevelop::IProjectController::FormatPlain); } else title += doc->title(); auto activeDocument = Core::self()->documentController()->activeDocument(); if (activeDocument && activeDocument->textDocument() && !activeDocument->textDocument()->isReadWrite()) title += i18n(" (read only)"); title += QLatin1String(" ]"); } setWindowFilePath(localFilePath); setCaption(title); } void MainWindow::updateAllTabColors() { auto documentController = Core::self()->documentController(); if (!documentController) return; const auto defaultColor = ::defaultColor(palette()); if (UiConfig::colorizeByProject()) { QHash viewColors; foreach (auto container, containers()) { const auto views = container->views(); viewColors.reserve(views.size()); viewColors.clear(); for (auto view : views) { const auto urlDoc = qobject_cast(view->document()); if (urlDoc) { viewColors[view] = colorForDocument(urlDoc->url(), palette(), defaultColor); } } container->setTabColors(viewColors); } } else { foreach (auto container, containers()) { container->resetTabColors(defaultColor); } } } void MainWindow::updateTabColor(IDocument* doc) { if (!UiConfig::self()->colorizeByProject()) return; const auto color = colorForDocument(doc->url(), palette(), defaultColor(palette())); foreach (auto container, containers()) { foreach (auto view, container->views()) { const auto urlDoc = qobject_cast(view->document()); if (urlDoc && urlDoc->url() == doc->url()) { container->setTabColor(view, color); } } } } void MainWindow::registerStatus(QObject* status) { d->registerStatus(status); } void MainWindow::initializeStatusBar() { d->setupStatusBar(); } void MainWindow::showErrorMessage(const QString& message, int timeout) { d->showErrorMessage(message, timeout); } void MainWindow::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { Sublime::MainWindow::tabContextMenuRequested(view, menu); d->tabContextMenuRequested(view, menu); } void MainWindow::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { d->tabToolTipRequested(view, container, tab); } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { d->dockBarContextMenuRequested(area, position); } void MainWindow::newTabRequested() { Sublime::MainWindow::newTabRequested(); d->fileNew(); } diff --git a/kdevplatform/shell/mainwindow.h b/kdevplatform/shell/mainwindow.h index 0f1edbfdb3..b4e7b61330 100644 --- a/kdevplatform/shell/mainwindow.h +++ b/kdevplatform/shell/mainwindow.h @@ -1,117 +1,119 @@ /* This file is part of the KDevelop project Copyright 2003 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2003 Amilcar do Carmo Lucas Copyright 2004, 2007 Alexander Dymo Copyright 2006 Adam Treat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_MAINWINDOW_H #define KDEVPLATFORM_MAINWINDOW_H #include #include "shellexport.h" namespace KTextEditor { class View; +class Document; } namespace KTextEditorIntegration { class MainWindow; } namespace KDevelop { class IDocument; /** KDevelop main window. Provides methods to control the main window of an application. */ class KDEVPLATFORMSHELL_EXPORT MainWindow: public Sublime::MainWindow { friend class UiController; Q_OBJECT Q_CLASSINFO( "D-Bus Interface", "org.kdevelop.MainWindow" ) public: explicit MainWindow(Sublime::Controller *parent = nullptr, Qt::WindowFlags flags = {}); ~MainWindow() override; /*! @p status must implement KDevelop::IStatus */ void registerStatus(QObject* status); KTextEditorIntegration::MainWindow *kateWrapper() const; void split(Qt::Orientation orientation); public Q_SLOTS: /*! Shows an error message in the status bar. @p message The message @p timeout The timeout in milliseconds how long to show the message */ void showErrorMessage(const QString& message, int timeout); virtual Q_SCRIPTABLE void ensureVisible(); virtual Q_SCRIPTABLE QString windowTitle() { return Sublime::MainWindow::windowTitle(); } void setVisible( bool visible ) override; void configureShortcuts(); void loadSettings() override; Q_SIGNALS: void finishedLoading(); protected: //FIXME DOCUMENT!!! queryClose() must call all of the Core cleanup() methods! bool queryClose() override; //reimplemented from KXMLGUIBuilder to support visible menubar separators QAction *createCustomElement(QWidget *parent, int index, const QDomElement &element) override; virtual void initialize(); virtual void cleanup(); void initializeStatusBar() override; bool event( QEvent* ) override; void dragEnterEvent( QDragEnterEvent* ) override; void dropEvent( QDropEvent* ) override; void applyMainWindowSettings(const KConfigGroup& config) override; void createGUI(KParts::Part* part); protected Q_SLOTS: void tabContextMenuRequested(Sublime::View* , QMenu* ) override; void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) override; void dockBarContextMenuRequested(Qt::DockWidgetArea, const QPoint&) override; void newTabRequested() override; private Q_SLOTS: + void documentActivated(const QPointer& textDocument); void updateCaption(); void updateTabColor(IDocument* doc); void updateAllTabColors(); void shortcutsChanged(); private: void initializeCorners(); class MainWindowPrivate *d; friend class MainWindowPrivate; }; } #endif diff --git a/kdevplatform/shell/sourceformattercontroller.cpp b/kdevplatform/shell/sourceformattercontroller.cpp index e6f2039ac4..6880029b6a 100644 --- a/kdevplatform/shell/sourceformattercontroller.cpp +++ b/kdevplatform/shell/sourceformattercontroller.cpp @@ -1,750 +1,750 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sourceformattercontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "debug.h" #include "plugincontroller.h" #include "sourceformatterjob.h" #include "textdocument.h" namespace { namespace Strings { QString SourceFormatter() { return QStringLiteral("SourceFormatter"); } QString UseDefault() { return QStringLiteral("UseDefault"); } } } namespace KDevelop { class SourceFormatterControllerPrivate { public: // cache of formatter plugins, to avoid querying plugincontroller QVector sourceFormatters; // GUI actions QAction* formatTextAction; QAction* formatFilesAction; QAction* formatLine; QList prjItems; QList urls; bool enabled = true; }; QString SourceFormatterController::kateModeLineConfigKey() { return QStringLiteral("ModelinesEnabled"); } QString SourceFormatterController::kateOverrideIndentationConfigKey() { return QStringLiteral("OverrideKateIndentation"); } QString SourceFormatterController::styleCaptionKey() { return QStringLiteral("Caption"); } QString SourceFormatterController::styleContentKey() { return QStringLiteral("Content"); } QString SourceFormatterController::styleMimeTypesKey() { return QStringLiteral("MimeTypes"); } QString SourceFormatterController::styleSampleKey() { return QStringLiteral("StyleSample"); } SourceFormatterController::SourceFormatterController(QObject *parent) : ISourceFormatterController(parent) , d(new SourceFormatterControllerPrivate) { setObjectName(QStringLiteral("SourceFormatterController")); setComponentName(QStringLiteral("kdevsourceformatter"), i18n("Source Formatter")); setXMLFile(QStringLiteral("kdevsourceformatter.rc")); if (Core::self()->setupFlags() & Core::NoUi) return; d->formatTextAction = actionCollection()->addAction(QStringLiteral("edit_reformat_source")); 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, this, &SourceFormatterController::updateFormatTextAction); connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &SourceFormatterController::updateFormatTextAction); + + qRegisterMetaType>(); connect(Core::self()->documentController(), &IDocumentController::documentLoaded, // Use a queued connection, because otherwise the view is not yet fully set up // but wrap the document in a smart pointer to guard against crashes when it // gets deleted in the meantime this, [this](IDocument *doc) { const auto textDoc = QPointer(dynamic_cast(doc)); - QMetaObject::invokeMethod(this, [this, textDoc](){ - documentLoaded(textDoc); - }, Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "documentLoaded", Qt::QueuedConnection, Q_ARG(QPointer, textDoc)); }); updateFormatTextAction(); } -void SourceFormatterController::documentLoaded(IDocument* doc) +void SourceFormatterController::documentLoaded(const QPointer& doc) { // NOTE: explicitly check this here to prevent crashes on shutdown // when this slot gets called (note: delayed connection) // but the text document was already destroyed // there have been unit tests that failed due to that... if (!doc || !doc->textDocument()) { return; } const auto url = doc->url(); const auto mime = QMimeDatabase().mimeTypeForUrl(url); 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() { } SourceFormatterController::~SourceFormatterController() { } ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl &url) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); return formatterForUrl(url, mime); } KConfigGroup SourceFormatterController::configForUrl(const QUrl& url) const { auto core = KDevelop::Core::self(); auto project = core->projectController()->findProjectForUrl(url); if (project) { auto config = project->projectConfiguration()->group(Strings::SourceFormatter()); if (config.isValid() && !config.readEntry(Strings::UseDefault(), true)) { return config; } } return core->activeSession()->config()->group( Strings::SourceFormatter() ); } KConfigGroup SourceFormatterController::sessionConfig() const { return KDevelop::Core::self()->activeSession()->config()->group( Strings::SourceFormatter() ); } KConfigGroup SourceFormatterController::globalConfig() const { return KSharedConfig::openConfig()->group( Strings::SourceFormatter() ); } ISourceFormatter* SourceFormatterController::findFirstFormatterForMimeType(const QMimeType& mime ) const { static QHash knownFormatters; if (knownFormatters.contains(mime.name())) return knownFormatters[mime.name()]; foreach (ISourceFormatter* iformatter, d->sourceFormatters) { QSharedPointer formatter(createFormatterForPlugin(iformatter)); if( formatter->supportedMimeTypes().contains(mime.name()) ) { knownFormatters[mime.name()] = iformatter; return iformatter; } } knownFormatters[mime.name()] = nullptr; return nullptr; } static void populateStyleFromConfigGroup(SourceFormatterStyle* s, const KConfigGroup& stylegrp) { s->setCaption( stylegrp.readEntry( SourceFormatterController::styleCaptionKey(), QString() ) ); s->setContent( stylegrp.readEntry( SourceFormatterController::styleContentKey(), QString() ) ); s->setMimeTypes( stylegrp.readEntry( SourceFormatterController::styleMimeTypesKey(), QStringList() ) ); s->setOverrideSample( stylegrp.readEntry( SourceFormatterController::styleSampleKey(), QString() ) ); } SourceFormatter* SourceFormatterController::createFormatterForPlugin(ISourceFormatter *ifmt) const { SourceFormatter* formatter = new SourceFormatter(); formatter->formatter = ifmt; // Inserted a new formatter. Now fill it with styles foreach( const KDevelop::SourceFormatterStyle& style, ifmt->predefinedStyles() ) { formatter->styles[ style.name() ] = new SourceFormatterStyle(style); } KConfigGroup grp = globalConfig(); if( grp.hasGroup( ifmt->name() ) ) { KConfigGroup fmtgrp = grp.group( ifmt->name() ); foreach( const QString& subgroup, fmtgrp.groupList() ) { SourceFormatterStyle* s = new SourceFormatterStyle( subgroup ); KConfigGroup stylegrp = fmtgrp.group( subgroup ); populateStyleFromConfigGroup(s, stylegrp); formatter->styles[ s->name() ] = s; } } return formatter; } ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl& url, const QMimeType& mime) { if (!d->enabled || !isMimeTypeSupported(mime)) { return nullptr; } const auto formatter = configForUrl(url).readEntry(mime.name(), QString()); if( formatter.isEmpty() ) { return findFirstFormatterForMimeType( mime ); } QStringList formatterinfo = formatter.split( QStringLiteral("||"), QString::SkipEmptyParts ); if( formatterinfo.size() != 2 ) { qCDebug(SHELL) << "Broken formatting entry for mime:" << mime.name() << "current value:" << formatter; return nullptr; } foreach (ISourceFormatter* iformatter, d->sourceFormatters) { if (iformatter->name() == formatterinfo.first()) { return iformatter; } } return nullptr; } bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime) { if( findFirstFormatterForMimeType( mime ) ) { return true; } return false; } QString SourceFormatterController::indentationMode(const QMimeType& mime) { if (mime.inherits(QStringLiteral("text/x-c++src")) || mime.inherits(QStringLiteral("text/x-chdr")) || mime.inherits(QStringLiteral("text/x-c++hdr")) || mime.inherits(QStringLiteral("text/x-csrc")) || mime.inherits(QStringLiteral("text/x-java")) || mime.inherits(QStringLiteral("text/x-csharp"))) { return QStringLiteral("cstyle"); } return QStringLiteral("none"); } QString SourceFormatterController::addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType& mime) { if( !isMimeTypeSupported(mime) ) return input; QRegExp kateModelineWithNewline(QStringLiteral("\\s*\\n//\\s*kate:(.*)$")); // If there already is a modeline in the document, adapt it while formatting, even // if "add modeline" is disabled. if (!configForUrl(url).readEntry(SourceFormatterController::kateModeLineConfigKey(), false) && kateModelineWithNewline.indexIn( input ) == -1 ) return input; ISourceFormatter* fmt = formatterForUrl(url, mime); ISourceFormatter::Indentation indentation = fmt->indentation(url); if( !indentation.isValid() ) return input; QString output; QTextStream os(&output, QIODevice::WriteOnly); QTextStream is(&input, QIODevice::ReadOnly); Q_ASSERT(fmt); QString modeline(QStringLiteral("// kate: ") + QStringLiteral("indent-mode ") + indentationMode(mime) + QStringLiteral("; ")); if(indentation.indentWidth) // We know something about indentation-width modeline.append(QStringLiteral("indent-width %1; ").arg(indentation.indentWidth)); if(indentation.indentationTabWidth != 0) // We know something about tab-usage { modeline.append(QStringLiteral("replace-tabs %1; ").arg(QLatin1String((indentation.indentationTabWidth == -1) ? "on" : "off"))); if(indentation.indentationTabWidth > 0) modeline.append(QStringLiteral("tab-width %1; ").arg(indentation.indentationTabWidth)); } qCDebug(SHELL) << "created modeline: " << modeline << endl; QRegExp kateModeline(QStringLiteral("^\\s*//\\s*kate:(.*)$")); bool modelinefound = false; QRegExp knownOptions(QStringLiteral("\\s*(indent-width|space-indent|tab-width|indent-mode|replace-tabs)")); while (!is.atEnd()) { QString line = is.readLine(); // replace only the options we care about if (kateModeline.indexIn(line) >= 0) { // match qCDebug(SHELL) << "Found a kate modeline: " << line << endl; modelinefound = true; QString options = kateModeline.cap(1); const QStringList optionList = options.split(QLatin1Char(';'), QString::SkipEmptyParts); os << modeline; for (QString s : optionList) { if (knownOptions.indexIn(s) < 0) { // unknown option, add it if(s.startsWith(QLatin1Char(' '))) s.remove(0, 1); os << s << ";"; qCDebug(SHELL) << "Found unknown option: " << s << endl; } } os << endl; } else os << line << endl; } if (!modelinefound) os << modeline << endl; return output; } void SourceFormatterController::cleanup() { } void SourceFormatterController::updateFormatTextAction() { bool enabled = false; 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); d->formatTextAction->setEnabled(enabled); } void SourceFormatterController::beautifySource() { IDocument* idoc = KDevelop::ICore::self()->documentController()->activeDocument(); if (!idoc) return; KTextEditor::View* view = idoc->activeTextView(); if (!view) return; KTextEditor::Document* doc = view->document(); // load the appropriate formatter const auto url = idoc->url(); const auto mime = QMimeDatabase().mimeTypeForUrl(url); ISourceFormatter* formatter = formatterForUrl(url, mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } // Ignore the modeline, as the modeline will be changed anyway adaptEditorIndentationMode(doc, formatter, url, true); bool has_selection = view->selection(); if (has_selection) { QString original = view->selectionText(); QString output = formatter->formatSource(view->selectionText(), url, mime, doc->text(KTextEditor::Range(KTextEditor::Cursor(0,0),view->selectionRange().start())), doc->text(KTextEditor::Range(view->selectionRange().end(), doc->documentRange().end()))); //remove the final newline character, unless it should be there if (!original.endsWith(QLatin1Char('\n')) && output.endsWith(QLatin1Char('\n'))) output.resize(output.length() - 1); //there was a selection, so only change the part of the text related to it // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code( dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( view->selectionRange(), original, output ); } else { formatDocument(idoc, formatter, mime); } } void SourceFormatterController::beautifyLine() { KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->activeDocument(); if (!doc || !doc->isTextDocument()) return; KTextEditor::Document *tDoc = doc->textDocument(); KTextEditor::View* view = doc->activeTextView(); if (!view) return; // load the appropriate formatter const auto url = doc->url(); const auto mime = QMimeDatabase().mimeTypeForUrl(url); ISourceFormatter* formatter = formatterForUrl(url, mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } const KTextEditor::Cursor cursor = view->cursorPosition(); const QString line = tDoc->line(cursor.line()); const QString prev = tDoc->text(KTextEditor::Range(0, 0, cursor.line(), 0)); const QString post = QLatin1Char('\n') + tDoc->text(KTextEditor::Range(KTextEditor::Cursor(cursor.line() + 1, 0), tDoc->documentEnd())); const QString formatted = formatter->formatSource(line, doc->url(), mime, prev, post); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code(dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( KTextEditor::Range(cursor.line(), 0, cursor.line(), line.length()), line, formatted ); // advance cursor one line view->setCursorPosition(KTextEditor::Cursor(cursor.line() + 1, 0)); } void SourceFormatterController::formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime) { Q_ASSERT(doc); Q_ASSERT(formatter); qCDebug(SHELL) << "Running" << formatter->name() << "on" << doc->url(); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop CodeRepresentation::Ptr code = KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ); KTextEditor::Cursor cursor = doc->cursorPosition(); QString text = formatter->formatSource(code->text(), doc->url(), mime); text = addModelineForCurrentLang(text, doc->url(), mime); code->setText(text); doc->setCursorPosition(cursor); } void SourceFormatterController::settingsChanged() { foreach (KDevelop::IDocument* doc, ICore::self()->documentController()->openDocuments()) { adaptEditorIndentationMode(doc->textDocument(), formatterForUrl(doc->url()), doc->url()); } } /** * Kate commands: * Use spaces for indentation: * "set-replace-tabs 1" * Use tabs for indentation (eventually mixed): * "set-replace-tabs 0" * Indent width: * "set-indent-width X" * Tab width: * "set-tab-width X" * */ void SourceFormatterController::adaptEditorIndentationMode(KTextEditor::Document *doc, ISourceFormatter *formatter, const QUrl& url, bool ignoreModeline) { if (!formatter || !configForUrl(url).readEntry(SourceFormatterController::kateOverrideIndentationConfigKey(), false) || !doc) return; qCDebug(SHELL) << "adapting mode for" << url; QRegExp kateModelineWithNewline(QStringLiteral("\\s*\\n//\\s*kate:(.*)$")); // modelines should always take precedence if( !ignoreModeline && kateModelineWithNewline.indexIn( doc->text() ) != -1 ) { qCDebug(SHELL) << "ignoring because a kate modeline was found"; return; } ISourceFormatter::Indentation indentation = formatter->indentation(url); if(indentation.isValid()) { struct CommandCaller { explicit CommandCaller(KTextEditor::Document* _doc) : doc(_doc), editor(KTextEditor::Editor::instance()) { Q_ASSERT(editor); } void operator()(const QString& cmd) { KTextEditor::Command* command = editor->queryCommand( cmd ); Q_ASSERT(command); QString msg; qCDebug(SHELL) << "calling" << cmd; foreach(KTextEditor::View* view, doc->views()) if (!command->exec(view, cmd, msg)) qCWarning(SHELL) << "setting indentation width failed: " << msg; } KTextEditor::Document* doc; KTextEditor::Editor* editor; } call(doc); if( indentation.indentWidth ) // We know something about indentation-width call( QStringLiteral("set-indent-width %1").arg(indentation.indentWidth ) ); if( indentation.indentationTabWidth != 0 ) // We know something about tab-usage { call( QStringLiteral("set-replace-tabs %1").arg( (indentation.indentationTabWidth == -1) ? 1 : 0 ) ); if( indentation.indentationTabWidth > 0 ) call( QStringLiteral("set-tab-width %1").arg(indentation.indentationTabWidth ) ); } }else{ qCDebug(SHELL) << "found no valid indentation"; } } void SourceFormatterController::formatFiles() { if (d->prjItems.isEmpty() && d->urls.isEmpty()) return; //get a list of all files in this folder recursively QList folders; foreach (KDevelop::ProjectBaseItem *item, d->prjItems) { if (!item) continue; if (item->folder()) folders.append(item->folder()); else if (item->file()) d->urls.append(item->file()->path().toUrl()); else if (item->target()) { foreach(KDevelop::ProjectFileItem *f, item->fileList()) d->urls.append(f->path().toUrl()); } } while (!folders.isEmpty()) { KDevelop::ProjectFolderItem *item = folders.takeFirst(); foreach(KDevelop::ProjectFolderItem *f, item->folderList()) folders.append(f); foreach(KDevelop::ProjectTargetItem *f, item->targetList()) { foreach(KDevelop::ProjectFileItem *child, f->fileList()) d->urls.append(child->path().toUrl()); } foreach(KDevelop::ProjectFileItem *f, item->fileList()) d->urls.append(f->path().toUrl()); } auto win = ICore::self()->uiController()->activeMainWindow()->window(); QMessageBox msgBox(QMessageBox::Question, i18n("Reformat files?"), i18n("Reformat all files in the selected folder?"), QMessageBox::Ok|QMessageBox::Cancel, win); msgBox.setDefaultButton(QMessageBox::Cancel); auto okButton = msgBox.button(QMessageBox::Ok); okButton->setText(i18n("Reformat")); msgBox.exec(); if (msgBox.clickedButton() == okButton) { auto formatterJob = new SourceFormatterJob(this); formatterJob->setFiles(d->urls); ICore::self()->runController()->registerJob(formatterJob); } } KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { Q_UNUSED(parent); KDevelop::ContextMenuExtension ext; d->urls.clear(); d->prjItems.clear(); if (d->sourceFormatters.isEmpty()) { return ext; } if (context->hasType(KDevelop::Context::EditorContext)) { if (d->formatTextAction->isEnabled()) ext.addAction(KDevelop::ContextMenuExtension::EditGroup, d->formatTextAction); } else if (context->hasType(KDevelop::Context::FileContext)) { KDevelop::FileContext* filectx = static_cast(context); d->urls = filectx->urls(); ext.addAction(KDevelop::ContextMenuExtension::EditGroup, d->formatFilesAction); } else if (context->hasType(KDevelop::Context::CodeContext)) { } else if (context->hasType(KDevelop::Context::ProjectItemContext)) { KDevelop::ProjectItemContext* prjctx = static_cast(context); d->prjItems = prjctx->items(); if (!d->prjItems.isEmpty()) { ext.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, d->formatFilesAction); } } return ext; } SourceFormatterStyle SourceFormatterController::styleForUrl(const QUrl& url, const QMimeType& mime) { const auto formatter = configForUrl(url).readEntry(mime.name(), QString()).split(QStringLiteral("||"), QString::SkipEmptyParts); if( formatter.count() == 2 ) { SourceFormatterStyle s( formatter.at( 1 ) ); KConfigGroup fmtgrp = globalConfig().group( formatter.at(0) ); if( fmtgrp.hasGroup( formatter.at(1) ) ) { KConfigGroup stylegrp = fmtgrp.group( formatter.at(1) ); populateStyleFromConfigGroup(&s, stylegrp); } return s; } return SourceFormatterStyle(); } void SourceFormatterController::disableSourceFormatting(bool disable) { d->enabled = !disable; } bool SourceFormatterController::sourceFormattingEnabled() { 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/sourceformattercontroller.h b/kdevplatform/shell/sourceformattercontroller.h index a0430ef4af..5152f75e83 100644 --- a/kdevplatform/shell/sourceformattercontroller.h +++ b/kdevplatform/shell/sourceformattercontroller.h @@ -1,174 +1,175 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #define KDEVPLATFORM_SOURCEFORMATTERCONTROLLER_H #include #include #include #include #include #include #include #include "shellexport.h" class QUrl; namespace KTextEditor { class Document; } namespace KDevelop { class Context; class ContextMenuExtension; class IDocument; +class TextDocument; class ISourceFormatter; class IPlugin; struct SourceFormatter { KDevelop::ISourceFormatter* formatter; // style name -> style. style objects owned by this typedef QMap StyleMap; StyleMap styles; // Get a list of supported mime types from the style map. QSet supportedMimeTypes() const { QSet supported; for ( auto style: styles ) { foreach ( auto& item, style->mimeTypes() ) { supported.insert(item.mimeType); } } return supported; } ~SourceFormatter() { qDeleteAll(styles); }; }; /** \short A singleton class managing all source formatter plugins */ class KDEVPLATFORMSHELL_EXPORT SourceFormatterController : public ISourceFormatterController, public KXMLGUIClient { Q_OBJECT friend class SourceFormatterJob; public: static QString kateModeLineConfigKey(); static QString kateOverrideIndentationConfigKey(); static QString styleCaptionKey(); static QString styleContentKey(); static QString styleMimeTypesKey(); static QString styleSampleKey(); explicit SourceFormatterController(QObject *parent = nullptr); ~SourceFormatterController() override; void initialize(); void cleanup(); //----------------- Public API defined in interfaces ------------------- /** \return The formatter corresponding to the language * of the document corresponding to the \arg url. */ ISourceFormatter* formatterForUrl(const QUrl &url) override; /** Loads and returns a source formatter for this mime type. * The language is then activated and the style is loaded. * 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; /** * @brief Instantiate a Formatter for the given plugin and load its configuration. * * @param ifmt The ISourceFormatter interface of the plugin * @return KDevelop::SourceFormatter* the SourceFormatter instance for the plugin, including config items */ SourceFormatter* createFormatterForPlugin(KDevelop::ISourceFormatter* ifmt) const; /** * @brief Find the first formatter which supports a given mime type. */ ISourceFormatter* findFirstFormatterForMimeType(const QMimeType& mime) const; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent); KDevelop::SourceFormatterStyle styleForUrl(const QUrl& url, const QMimeType& mime) override; KConfigGroup configForUrl(const QUrl& url) const; KConfigGroup sessionConfig() const; KConfigGroup globalConfig() const; void settingsChanged(); 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 documentLoaded(const QPointer& doc); 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. */ QString addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType&); /** \return The name of kate indentation mode for the mime type. * examples are cstyle, python, etc. */ QString indentationMode(const QMimeType& mime); void formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime); // Adapts the mode of the editor regarding indentation-style void adaptEditorIndentationMode(KTextEditor::Document* doc, KDevelop::ISourceFormatter* formatter, const QUrl& url, bool ignoreModeline = false); void resetUi(); private: const QScopedPointer d; }; } #endif // KDEVPLATFORM_SOURCEFORMATTERMANAGER_H