diff --git a/kdevplatform/shell/ktexteditorpluginintegration.cpp b/kdevplatform/shell/ktexteditorpluginintegration.cpp index d24024d837..254d0d8cd2 100644 --- a/kdevplatform/shell/ktexteditorpluginintegration.cpp +++ b/kdevplatform/shell/ktexteditorpluginintegration.cpp @@ -1,459 +1,431 @@ /* Copyright 2015 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "ktexteditorpluginintegration.h" #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "documentcontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "textdocument.h" #include using namespace KDevelop; namespace { KTextEditor::MainWindow *toKteWrapper(KParts::MainWindow *window) { if (auto mainWindow = dynamic_cast(window)) { return mainWindow->kateWrapper() ? mainWindow->kateWrapper()->interface() : nullptr; } else { return nullptr; } } KTextEditor::View *toKteView(Sublime::View *view) { if (auto textView = dynamic_cast(view)) { return textView->textView(); } else { return nullptr; } } class ToolViewFactory; /** * This HACK is required to massage the KTextEditor plugin API into the * GUI concepts we apply in KDevelop. Kate does not allow the user to * delete tool views and then readd them. We do. To support our use case * we prevent the widget we return to KTextEditor plugins from * MainWindow::createToolView from getting destroyed. This widget class * unsets the parent of the so called container in its dtor. The * ToolViewFactory handles the ownership and destroys the kate widget * as needed. */ class KeepAliveWidget : public QWidget { Q_OBJECT public: explicit KeepAliveWidget(ToolViewFactory *factory, QWidget *parent = nullptr) : QWidget(parent) , m_factory(factory) { } ~KeepAliveWidget() override; private: ToolViewFactory *m_factory; }; class ToolViewFactory : public QObject, public KDevelop::IToolViewFactory { Q_OBJECT public: ToolViewFactory(const QString &text, const QIcon &icon, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos) : m_text(text) , m_icon(icon) , m_identifier(identifier) , m_container(new QWidget) , m_pos(pos) { m_container->setLayout(new QVBoxLayout); } ~ToolViewFactory() override { delete m_container; } QWidget *create(QWidget *parent = nullptr) override { auto widget = new KeepAliveWidget(this, parent); widget->setWindowTitle(m_text); widget->setWindowIcon(m_icon); widget->setLayout(new QVBoxLayout); widget->layout()->addWidget(m_container); widget->addActions(m_container->actions()); return widget; } Qt::DockWidgetArea defaultPosition() override { switch (m_pos) { case KTextEditor::MainWindow::Left: return Qt::LeftDockWidgetArea; case KTextEditor::MainWindow::Right: return Qt::RightDockWidgetArea; case KTextEditor::MainWindow::Top: return Qt::TopDockWidgetArea; case KTextEditor::MainWindow::Bottom: return Qt::BottomDockWidgetArea; } Q_UNREACHABLE(); } QString id() const override { return m_identifier; } QWidget *container() const { return m_container; } private: QString m_text; QIcon m_icon; QString m_identifier; QPointer m_container; KTextEditor::MainWindow::ToolViewPosition m_pos; friend class KeepAliveWidget; }; KeepAliveWidget::~KeepAliveWidget() { // if the container is still valid, unparent it to prevent it from getting deleted // this happens when the user removes a tool view // on shutdown, the container does get deleted, thus we must guard against that. if (m_factory->container()) { Q_ASSERT(m_factory->container()->parentWidget() == this); m_factory->container()->setParent(nullptr); } } } namespace KTextEditorIntegration { Application::Application(QObject *parent) : QObject(parent) { } Application::~Application() { KTextEditor::Editor::instance()->setApplication(nullptr); } KTextEditor::MainWindow *Application::activeMainWindow() const { return toKteWrapper(Core::self()->uiController()->activeMainWindow()); } QList Application::mainWindows() const { return {activeMainWindow()}; } bool Application::closeDocument(KTextEditor::Document *document) const { const auto& openDocuments = Core::self()->documentControllerInternal()->openDocuments(); for (auto doc : openDocuments) { if (doc->textDocument() == document) { return doc->close(); } } return false; } KTextEditor::Plugin *Application::plugin(const QString &id) const { auto kdevPlugin = Core::self()->pluginController()->loadPlugin(id); const auto plugin = dynamic_cast(kdevPlugin); return plugin ? plugin->interface() : nullptr; } QList Application::documents() { QList l; const auto openDocuments = Core::self()->documentControllerInternal()->openDocuments(); l.reserve(openDocuments.size()); for (auto* d : openDocuments) { l << d->textDocument(); } return l; } KTextEditor::Document *Application::openUrl(const QUrl &url, const QString &encoding) { Q_UNUSED(encoding); auto documentController = Core::self()->documentControllerInternal(); auto doc = url.isEmpty() ? documentController->openDocumentFromText(QString()) : documentController->openDocument(url); return doc->textDocument(); } MainWindow::MainWindow(KDevelop::MainWindow *mainWindow) - : m_mainWindow(mainWindow) + : QObject(mainWindow) + , m_mainWindow(mainWindow) , m_interface(new KTextEditor::MainWindow(this)) { connect(mainWindow, &Sublime::MainWindow::viewAdded, this, [this] (Sublime::View *view) { if (auto kteView = toKteView(view)) { emit m_interface->viewCreated(kteView); } }); connect(mainWindow, &Sublime::MainWindow::activeViewChanged, this, [this] (Sublime::View *view) { auto kteView = toKteView(view); emit m_interface->viewChanged(kteView); if (auto viewBar = m_viewBars.value(kteView)) { m_mainWindow->viewBarContainer()->setCurrentViewBar(viewBar); } }); } MainWindow::~MainWindow() = default; QWidget *MainWindow::createToolView(KTextEditor::Plugin* plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text) { auto factory = new ToolViewFactory(text, icon, identifier, pos); Core::self()->uiController()->addToolView(text, factory); connect(plugin, &QObject::destroyed, this, [=] { Core::self()->uiController()->removeToolView(factory); }); return factory->container(); } KXMLGUIFactory *MainWindow::guiFactory() const { - if (!m_mainWindow) { - return nullptr; - } return m_mainWindow->guiFactory(); } QWidget *MainWindow::window() const { return m_mainWindow; } QList MainWindow::views() const { QList views; - if (m_mainWindow) { - foreach (auto area, m_mainWindow->areas()) { - foreach (auto view, area->views()) { - if (auto kteView = toKteView(view)) { - views << kteView; - } + foreach (auto area, m_mainWindow->areas()) { + foreach (auto view, area->views()) { + if (auto kteView = toKteView(view)) { + views << kteView; } } } return views; } KTextEditor::View *MainWindow::activeView() const { - if (!m_mainWindow) { - return nullptr; - } return toKteView(m_mainWindow->activeView()); } KTextEditor::View *MainWindow::activateView(KTextEditor::Document *doc) { - if (!m_mainWindow) { - return nullptr; - } - foreach (auto area, m_mainWindow->areas()) { foreach (auto view, area->views()) { if (auto kteView = toKteView(view)) { if (kteView->document() == doc) { m_mainWindow->activateView(view); return kteView; } } } } return activeView(); } QObject *MainWindow::pluginView(const QString &id) const { return m_pluginViews.value(id); } QWidget *MainWindow::createViewBar(KTextEditor::View *view) { Q_UNUSED(view); - if (!m_mainWindow) { - return nullptr; - } + // we reuse the central view bar for every view return m_mainWindow->viewBarContainer(); } void MainWindow::deleteViewBar(KTextEditor::View *view) { auto viewBar = m_viewBars.take(view); - if (m_mainWindow) { - m_mainWindow->viewBarContainer()->removeViewBar(viewBar); - } + m_mainWindow->viewBarContainer()->removeViewBar(viewBar); delete viewBar; } void MainWindow::showViewBar(KTextEditor::View *view) { auto viewBar = m_viewBars.value(view); Q_ASSERT(viewBar); - if (m_mainWindow) { - m_mainWindow->viewBarContainer()->showViewBar(viewBar); - } + + m_mainWindow->viewBarContainer()->showViewBar(viewBar); } void MainWindow::hideViewBar(KTextEditor::View *view) { auto viewBar = m_viewBars.value(view); Q_ASSERT(viewBar); - if (m_mainWindow) { - m_mainWindow->viewBarContainer()->hideViewBar(viewBar); - } + m_mainWindow->viewBarContainer()->hideViewBar(viewBar); } void MainWindow::addWidgetToViewBar(KTextEditor::View *view, QWidget *widget) { Q_ASSERT(widget); m_viewBars[view] = widget; - if (m_mainWindow) { - m_mainWindow->viewBarContainer()->addViewBar(widget); - } + m_mainWindow->viewBarContainer()->addViewBar(widget); } KTextEditor::MainWindow *MainWindow::interface() const { return m_interface; } void MainWindow::addPluginView(const QString &id, QObject *view) { m_pluginViews.insert(id, view); emit m_interface->pluginViewCreated(id, view); } void MainWindow::removePluginView(const QString &id) { auto view = m_pluginViews.take(id).data(); delete view; emit m_interface->pluginViewDeleted(id, view); } -void MainWindow::startDestroy() -{ - m_mainWindow = nullptr; - deleteLater(); -} - Plugin::Plugin(KTextEditor::Plugin *plugin, QObject *parent) : IPlugin({}, parent) , m_plugin(plugin) , m_tracker(new ObjectListTracker(ObjectListTracker::CleanupWhenDone, this)) { } Plugin::~Plugin() = default; void Plugin::unload() { if (auto mainWindow = KTextEditor::Editor::instance()->application()->activeMainWindow()) { auto integration = dynamic_cast(mainWindow->parent()); if (integration) { integration->removePluginView(pluginId()); } } m_tracker->deleteAll(); delete m_plugin; } KXMLGUIClient *Plugin::createGUIForMainWindow(Sublime::MainWindow* window) { auto ret = IPlugin::createGUIForMainWindow(window); auto mainWindow = dynamic_cast(window); Q_ASSERT(mainWindow); auto wrapper = mainWindow->kateWrapper(); auto view = m_plugin->createView(wrapper->interface()); wrapper->addPluginView(pluginId(), view); // ensure that unloading the plugin kills all views m_tracker->append(view); return ret; } KTextEditor::Plugin *Plugin::interface() const { return m_plugin.data(); } QString Plugin::pluginId() const { return Core::self()->pluginController()->pluginInfo(this).pluginId(); } void initialize() { auto app = new KTextEditor::Application(new Application(Core::self())); KTextEditor::Editor::instance()->setApplication(app); } void MainWindow::splitView(Qt::Orientation orientation) { - if (m_mainWindow) { - m_mainWindow->split(orientation); - } + m_mainWindow->split(orientation); } } #include "ktexteditorpluginintegration.moc" diff --git a/kdevplatform/shell/ktexteditorpluginintegration.h b/kdevplatform/shell/ktexteditorpluginintegration.h index 545237c69d..2b1af0dfe3 100644 --- a/kdevplatform/shell/ktexteditorpluginintegration.h +++ b/kdevplatform/shell/ktexteditorpluginintegration.h @@ -1,139 +1,129 @@ /* Copyright 2015 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef KDEVPLATFORM_KTEXTEDITOR_PLUGIN_INTEGRATION_H #define KDEVPLATFORM_KTEXTEDITOR_PLUGIN_INTEGRATION_H #include #include #include #include #include namespace KDevelop { class ObjectListTracker; class MainWindow; } namespace KTextEditorIntegration { /** * Class mimicking the KTextEditor::Application interface */ class Application : public QObject { Q_OBJECT public: explicit Application(QObject *parent = nullptr); ~Application() override; public Q_SLOTS: KTextEditor::MainWindow *activeMainWindow() const; QList mainWindows() const; KTextEditor::Plugin *plugin(const QString &id) const; QList documents(); KTextEditor::Document *openUrl(const QUrl &url, const QString &encoding = QString()); bool closeDocument(KTextEditor::Document *document) const; }; class MainWindow : public QObject { Q_OBJECT public: explicit MainWindow(KDevelop::MainWindow *mainWindow); ~MainWindow() override; public Q_SLOTS: QWidget *createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text); KXMLGUIFactory *guiFactory() const; QWidget *window() const; QList views() const; KTextEditor::View *activeView() const; KTextEditor::View *activateView(KTextEditor::Document *doc); QObject *pluginView(const QString &id) const; void splitView(Qt::Orientation orientation); QWidget *createViewBar(KTextEditor::View *view); void deleteViewBar(KTextEditor::View *view); void showViewBar(KTextEditor::View *view); void hideViewBar(KTextEditor::View *view); void addWidgetToViewBar(KTextEditor::View *view, QWidget *widget); public: KTextEditor::MainWindow *interface() const; void addPluginView(const QString &id, QObject *pluginView); void removePluginView(const QString &id); - /** - * To be called when the actual mainwindow is destroyed. - * Some KTextEditor::View objects relying on this wrapper object at that point in time - * only have got called deleteLater() on them, so will still get their destruction - * done only in the next handling of QEvent::DeferredDelete. - * To outlive them, this method triggers deleteLater for this wrapper object as well, - * but already prepares for the actual mainwindow no longer being available. - */ - void startDestroy(); - private: KDevelop::MainWindow *m_mainWindow; KTextEditor::MainWindow *m_interface; QHash> m_pluginViews; QHash m_viewBars; }; class Plugin : public KDevelop::IPlugin { Q_OBJECT public: explicit Plugin(KTextEditor::Plugin *plugin, QObject *parent = nullptr); ~Plugin() override; KXMLGUIClient* createGUIForMainWindow(Sublime::MainWindow *window) override; void unload() override; KTextEditor::Plugin *interface() const; QString pluginId() const; private: QPointer m_plugin; // view objects and tool views that should get deleted when the plugin gets unloaded KDevelop::ObjectListTracker *m_tracker; }; void initialize(); } #endif diff --git a/kdevplatform/shell/mainwindow.cpp b/kdevplatform/shell/mainwindow.cpp index cf84f3d80f..1cce8259b9 100644 --- a/kdevplatform/shell/mainwindow.cpp +++ b/kdevplatform/shell/mainwindow.cpp @@ -1,525 +1,522 @@ /* 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(); } - // The window wrapper has to stay alive until the last KTextEditor::Views are gone - // but needs to know this mainwindow is next an ex-mainwindow. - d->kateWrapper()->startDestroy(); 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(); //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, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &MainWindow::updateActiveDocumentConnection, Qt::QueuedConnection); 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::updateActiveDocumentConnection(IDocument* document) { disconnect(d->activeDocumentReadWriteConnection); if (auto textDocument = document->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/tests/test_ktexteditorpluginintegration.cpp b/kdevplatform/shell/tests/test_ktexteditorpluginintegration.cpp index 0903b28e94..93f2c9aa53 100644 --- a/kdevplatform/shell/tests/test_ktexteditorpluginintegration.cpp +++ b/kdevplatform/shell/tests/test_ktexteditorpluginintegration.cpp @@ -1,212 +1,200 @@ /* Copyright 2015 Milian Wolff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "test_ktexteditorpluginintegration.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { template QPointer makeQPointer(T *ptr) { return {ptr}; } IToolViewFactory *findToolView(const QString &id) { const auto uiController = Core::self()->uiControllerInternal(); const auto map = uiController->factoryDocuments(); for (auto it = map.begin(); it != map.end(); ++it) { if (it.key()->id() == id) { return it.key(); } } return nullptr; } class TestPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit TestPlugin(QObject *parent) : Plugin(parent) { } QObject *createView(KTextEditor::MainWindow * mainWindow) override { return new QObject(mainWindow); } }; } void TestKTextEditorPluginIntegration::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\n")); AutoTestShell::init({QStringLiteral("katesnippetsplugin")}); TestCore::initialize(); QVERIFY(KTextEditor::Editor::instance()); } void TestKTextEditorPluginIntegration::cleanupTestCase() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); const auto editor = makeQPointer(KTextEditor::Editor::instance()); const auto application = makeQPointer(editor->application()); const auto window = makeQPointer(application->activeMainWindow()); TestCore::shutdown(); QVERIFY(!plugin); - // Test uncovers issue in shutdown behaviour when not triggered by last closed mainwindow, but directly: - // Core::shutdown() deletes itself via deleteLater, for which TestCore::shutdown() adds a QTest::qWait(1) - // so Core instance should be gone after the call returns. - // Core in its destructor deletes the Sublime::Controller instance. - // That one in the destructor deletes any still existing mainwindows, of which we have here in the test one. - // The KTE::MainWindow wrapper trying to outlive the KTE::View instances as needed now is only deleted with - // a deleteLater() from the mainwindow. Thus still living here. - QEXPECT_FAIL("", "Chain of deleteLater too long ATM", Continue); QVERIFY(!window); QVERIFY(!application); - // workaround for now, remove again if issue of too long deleteLater chain above is fixed - QTest::qWait(1); - QVERIFY(!window); - // editor lives by design until QCoreApplication terminates, then autodeletes } void TestKTextEditorPluginIntegration::testApplication() { auto app = KTextEditor::Editor::instance()->application(); QVERIFY(app); QVERIFY(app->parent()); QCOMPARE(app->parent()->metaObject()->className(), "KTextEditorIntegration::Application"); QVERIFY(app->activeMainWindow()); QCOMPARE(app->mainWindows().size(), 1); QVERIFY(app->mainWindows().contains(app->activeMainWindow())); } void TestKTextEditorPluginIntegration::testMainWindow() { auto window = KTextEditor::Editor::instance()->application()->activeMainWindow(); QVERIFY(window); QVERIFY(window->parent()); QCOMPARE(window->parent()->metaObject()->className(), "KTextEditorIntegration::MainWindow"); const auto id = QStringLiteral("kte_integration_toolview"); const auto icon = QIcon::fromTheme(QStringLiteral("kdevelop")); const auto text = QStringLiteral("some text"); QVERIFY(!findToolView(id)); auto plugin = new TestPlugin(this); auto toolView = makeQPointer(window->createToolView(plugin, id, KTextEditor::MainWindow::Bottom, icon, text)); QVERIFY(toolView); auto factory = findToolView(id); QVERIFY(factory); // we reuse the same view QWidget parent; auto kdevToolView = makeQPointer(factory->create(&parent)); QCOMPARE(kdevToolView->parentWidget(), &parent); QCOMPARE(toolView->parentWidget(), kdevToolView.data()); // the children are kept alive when the tool view gets destroyed delete kdevToolView; QVERIFY(toolView); kdevToolView = factory->create(&parent); // and we reuse the ktexteditor tool view for the new kdevelop tool view QCOMPARE(toolView->parentWidget(), kdevToolView.data()); delete toolView; delete kdevToolView; delete plugin; QVERIFY(!findToolView(id)); } void TestKTextEditorPluginIntegration::testPlugin() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); if (!plugin) { QSKIP("Cannot continue without katesnippetsplugin, install Kate"); } auto app = KTextEditor::Editor::instance()->application(); auto ktePlugin = makeQPointer(app->plugin(id)); QVERIFY(ktePlugin); auto view = makeQPointer(app->activeMainWindow()->pluginView(id)); QVERIFY(view); const auto rawView = view.data(); QSignalSpy spy(app->activeMainWindow(), &KTextEditor::MainWindow::pluginViewDeleted); QVERIFY(controller->unloadPlugin(id)); QVERIFY(!ktePlugin); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().count(), 2); QCOMPARE(spy.first().at(0), QVariant::fromValue(id)); QCOMPARE(spy.first().at(1), QVariant::fromValue(rawView)); QVERIFY(!view); } void TestKTextEditorPluginIntegration::testPluginUnload() { auto controller = Core::self()->pluginController(); const auto id = QStringLiteral("katesnippetsplugin"); auto plugin = makeQPointer(controller->loadPlugin(id)); if (!plugin) { QSKIP("Cannot continue without katesnippetsplugin, install Kate"); } auto app = KTextEditor::Editor::instance()->application(); auto ktePlugin = makeQPointer(app->plugin(id)); QVERIFY(ktePlugin); delete ktePlugin; // don't crash plugin->unload(); } QTEST_MAIN(TestKTextEditorPluginIntegration) #include diff --git a/kdevplatform/sublime/view.cpp b/kdevplatform/sublime/view.cpp index bb8ad72411..9f3486bfe3 100644 --- a/kdevplatform/sublime/view.cpp +++ b/kdevplatform/sublime/view.cpp @@ -1,138 +1,143 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "view.h" #include #include "document.h" #include "tooldocument.h" namespace Sublime { class View; class Document; class ViewPrivate { public: ViewPrivate(); Document *doc = nullptr; QWidget *widget = nullptr; void unsetWidget(); View::WidgetOwnership ws; }; ViewPrivate::ViewPrivate() { } void ViewPrivate::unsetWidget() { widget = nullptr; } View::View(Document *doc, WidgetOwnership ws ) :QObject(doc), d(new ViewPrivate) { d->doc = doc; d->ws = ws; } View::~View() { if (d->widget && d->ws == View::TakeOwnership ) { d->widget->hide(); d->widget->setParent(nullptr); - d->widget->deleteLater(); + delete d->widget; } } Document *View::document() const { return d->doc; } QWidget *View::widget(QWidget *parent) { if (!d->widget) { d->widget = createWidget(parent); + // if we own this widget, we will also delete it and ideally would disconnect + // the following connect before doing that. For that though we would need to store + // a reference to the connection. + // As the d object still exists in the destructor when we delete the widget + // this lambda method though can be still safely executed, so we spare ourselves such disconnect. connect(d->widget, &QWidget::destroyed, this, [&] { d->unsetWidget(); }); } return d->widget; } QWidget *View::createWidget(QWidget *parent) { return d->doc->createViewWidget(parent); } bool View::hasWidget() const { return d->widget != nullptr; } void View::requestRaise() { emit raise(this); } void View::readSessionConfig(KConfigGroup& config) { Q_UNUSED(config); } void View::writeSessionConfig(KConfigGroup& config) { Q_UNUSED(config); } QList View::toolBarActions() const { ToolDocument* tooldoc = dynamic_cast( document() ); if( tooldoc ) { return tooldoc->factory()->toolBarActions( d->widget ); } return QList(); } QList< QAction* > View::contextMenuActions() const { ToolDocument* tooldoc = dynamic_cast( document() ); if( tooldoc ) { return tooldoc->factory()->contextMenuActions( d->widget ); } return QList(); } QString View::viewStatus() const { return QString(); } void View::notifyPositionChanged(int newPositionInArea) { emit positionChanged(this, newPositionInArea); } } #include "moc_view.cpp"