diff --git a/rkward/windows/katepluginintegration.cpp b/rkward/windows/katepluginintegration.cpp index f475696a..be7dd1a8 100644 --- a/rkward/windows/katepluginintegration.cpp +++ b/rkward/windows/katepluginintegration.cpp @@ -1,489 +1,520 @@ /*************************************************************************** katepluginintegration - description ------------------- begin : Mon Jun 12 2017 copyright : (C) 2017-2020 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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) any later version. * * * ***************************************************************************/ #include "katepluginintegration.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../rkward.h" #include "rkworkplace.h" #include "rkworkplaceview.h" #include "rkcommandeditorwindow.h" #include "../misc/rkdummypart.h" #include "../settings/rksettingsmodulecommandeditor.h" #include "../debug.h" /// BEGIN KTextEditor::Application interface KatePluginIntegrationApp::KatePluginIntegrationApp(QObject *parent) : QObject (parent) { RK_TRACE (APP); dummy_view = 0; window = new KatePluginIntegrationWindow(this); app = new KTextEditor::Application(this); KTextEditor::Editor::instance()->setApplication(app); // enumerate all available kate plugins QVector plugins = KPluginLoader::findPlugins(QStringLiteral ("ktexteditor"), [](const KPluginMetaData &md) { return md.serviceTypes().contains(QLatin1String("KTextEditor/Plugin")); }); for (int i = plugins.size() -1; i >= 0; --i) { PluginInfo info; info.plugin = 0; info.data = plugins[i]; // Note: creates a lookup-table *and* eliminates potential dupes later in the search path known_plugins.insert(idForPlugin(info.data), info); // TODO: remove me qDebug ("%s", qPrintable(info.data.fileName())); } // NOTE: Destructor is too late for this, esp. As plugin destructors will try to unregister from the guiFactory(), and such. connect(RKWardMainWindow::getMain(), &RKWardMainWindow::aboutToQuitRKWard, this, &KatePluginIntegrationApp::saveConfigAndUnload); } KatePluginIntegrationApp::~KatePluginIntegrationApp() { RK_TRACE (APP); } KTextEditor::View *KatePluginIntegrationApp::dummyView() { if (!dummy_view) { RK_TRACE (APP); KTextEditor::Document *doc = KTextEditor::Editor::instance()->createDocument (this); dummy_view = doc->createView(0); dummy_view->hide(); // Make sure it does not accumulate cruft. connect(doc, &KTextEditor::Document::textChanged, doc, &KTextEditor::Document::clear); } return dummy_view; } +QString KatePluginIntegrationApp::idForPlugin(const KTextEditor::Plugin *plugin) const { + for (auto it = known_plugins.constBegin(); it != known_plugins.constEnd(); ++it) { + if (it.value().plugin == plugin) return it.key(); + } + return QString(); +} + QString KatePluginIntegrationApp::idForPlugin(const KPluginMetaData &plugin) const { return QFileInfo(plugin.fileName()).baseName(); } QObject* KatePluginIntegrationApp::loadPlugin (const QString& identifier) { RK_TRACE (APP); if (!known_plugins.contains (identifier)) { RK_DEBUG (APP, DL_WARNING, "Plugin %s is not known", qPrintable (identifier)); return 0; } KPluginFactory *factory = KPluginLoader(known_plugins[identifier].data.fileName ()).factory (); if (factory) { KTextEditor::Plugin *plugin = factory->create(this, QVariantList () << identifier); if (plugin) { known_plugins[identifier].plugin = plugin; emit KTextEditor::Editor::instance()->application()->pluginCreated(identifier, plugin); QObject* created = mainWindow()->createPluginView(plugin); if (created) { emit mainWindow()->main->pluginViewCreated(identifier, created); KTextEditor::SessionConfigInterface *interface = qobject_cast(created); if (interface) { // NOTE: Some plugins (noteably the Search in files plugin) will misbehave, unless readSessionConfig has been called! KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("KatePlugin:%1:").arg(identifier)); interface->readSessionConfig(group); } } return plugin; } } return 0; } void KatePluginIntegrationApp::saveConfigAndUnload() { RK_TRACE (APP); for (auto it = known_plugins.constBegin(); it != known_plugins.constEnd(); ++it) { KTextEditor::Plugin* plugin = it.value().plugin; if (!plugin) continue; QObject* view = mainWindow()->pluginView(it.key()); if (view) { KTextEditor::SessionConfigInterface* interface = qobject_cast(view); if (interface) { KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("KatePlugin:%1:").arg(it.key())); interface->writeSessionConfig(group); } emit mainWindow()->main->pluginViewDeleted(it.key(), view); delete view; } emit app->pluginDeleted(it.key(), plugin); delete plugin; } known_plugins.clear(); } QList KatePluginIntegrationApp::mainWindows() { RK_TRACE (APP); QList ret; ret.append (window->main); return ret; } KTextEditor::MainWindow *KatePluginIntegrationApp::activeMainWindow() { RK_TRACE (APP); return window->main; } RKCommandEditorWindow* findWindowForView(KTextEditor::View *view) { RK_TRACE (APP); QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); for (int i = 0; i < w.size(); ++i) { KTextEditor::View *v = static_cast(w[i])->getView(); if (v && (v == view)) { return static_cast(w[i]); } } return 0; } RKCommandEditorWindow* findWindowForDocument(KTextEditor::Document *document) { RK_TRACE (APP); QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); for (int i = 0; i < w.size(); ++i) { KTextEditor::View *v = static_cast(w[i])->getView(); if (v && (v->document() == document)) { return static_cast(w[i]); } } return 0; } QList KatePluginIntegrationApp::documents() { RK_TRACE (APP); QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); QList ret; for (int i = 0; i < w.size (); ++i) { KTextEditor::View *v = static_cast(w[i])->getView(); if (v) ret.append(v->document()); } if (ret.isEmpty()) { // See the NOTE in KatePluginIntegrationWindow::activeView() ret.append(dummyView()->document()); } return ret; } KTextEditor::Document *KatePluginIntegrationApp::findUrl(const QUrl &url) { RK_TRACE (APP); QUrl _url = url.adjusted(QUrl::NormalizePathSegments); // Needed? QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); for (int i = 0; i < w.size (); ++i) { if (_url == static_cast(w[i])->url().adjusted(QUrl::NormalizePathSegments)) { KTextEditor::View *v = static_cast(w[i])->getView(); if (v) return v->document(); } } return 0; } KTextEditor::Document *KatePluginIntegrationApp::openUrl(const QUrl &url, const QString &encoding) { RK_TRACE (APP); KTextEditor::View *v = window->openUrl(url, encoding); if (v) return v->document(); return 0; } bool KatePluginIntegrationApp::closeDocument(KTextEditor::Document *document) { RK_TRACE (APP); RKMDIWindow *w = findWindowForDocument(document); if (w) return RKWorkplace::mainWorkplace()->closeWindow(w); // NOTE: Closes only a single view of the document return false; } bool KatePluginIntegrationApp::closeDocuments(const QList &documents) { RK_TRACE (APP); bool allfound = true; QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); QList toclose; for (int i = 0; i < documents.size(); ++i) { bool found = false; for (int j = 0; j < w.size(); ++j) { KTextEditor::View *v = static_cast(w[j])->getView(); if (v && v->document() == documents[i]) { toclose.append(w[i]); found = true; break; } } if (!found) allfound = false; } return RKWorkplace::mainWorkplace()->closeWindows(toclose) && allfound; } KTextEditor::Plugin *KatePluginIntegrationApp::plugin(const QString &name) { RK_TRACE (APP); if (known_plugins.contains(name)) { return known_plugins[name].plugin; } return 0; } /// END KTextEditor::Application interface /// BEGIN KTextEditor::MainWindow interface KatePluginIntegrationWindow::KatePluginIntegrationWindow (KatePluginIntegrationApp *parent) : QObject (parent), KXMLGUIClient () { RK_TRACE (APP); // This one is passed to each created plugin main = new KTextEditor::MainWindow(this); // While this one may be accessed from plugins via KTextEditor::Editor::instance()->application() app = parent; + active_plugin = 0; } class KatePluginToolWindow : public RKMDIWindow { Q_OBJECT public: - KatePluginToolWindow(QWidget *parent, RKMDIWindow::Type type) : RKMDIWindow(parent, type, true) { + KatePluginToolWindow(QWidget *parent) : RKMDIWindow(parent, RKMDIWindow::KatePluginWindow, true) { RK_TRACE (APP); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setPart(new RKDummyPart(this, this)); initializeActivationSignals(); setFocusPolicy(Qt::ClickFocus); } ~KatePluginToolWindow() { RK_TRACE (APP); } /** This is a bit lame, but the plugin does not add itself to the parent widget's layout by itself. So we need this override * to do that. Where did the good old KVBox go? */ void childEvent(QChildEvent *ev) override { if ((ev->type() == QEvent::ChildAdded) && qobject_cast(ev->child())) { QWidget *widget = qobject_cast(ev->child()); layout()->addWidget(widget); setFocusProxy(widget); } QWidget::childEvent(ev); } }; -QWidget * KatePluginIntegrationWindow::createToolView (KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text) { +QWidget* KatePluginIntegrationWindow::createToolView (KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text) { RK_TRACE (APP); + RK_DEBUG(APP, DL_DEBUG, "createToolView for %p, %s, position %d, %s", plugin, qPrintable(identifier), pos, qPrintable(text)); // TODO: Set proper RKMDIWindow:type - KatePluginToolWindow *window = new KatePluginToolWindow(RKWorkplace::mainWorkplace()->view(), RKMDIWindow::ConsoleWindow); + KatePluginToolWindow *window = new KatePluginToolWindow(RKWorkplace::mainWorkplace()->view()); window->setCaption(text); window->setWindowIcon(icon); RKWorkplace::mainWorkplace()->placeInToolWindowBar(window, pos); - created_tool_views.append(window->getPart()); + RKToolWindowList::registerToolWindow(window, identifier, (RKToolWindowList::Placement) pos, 0); + plugin_resources[plugin].windows.append(window); return window; } bool KatePluginIntegrationWindow::showToolView (QWidget *widget) { RK_TRACE (APP); RKMDIWindow *w = qobject_cast(widget); if (w) w->activate(); else { RK_DEBUG(APP, DL_ERROR, "Failed to find mdi window for (plugin) tool view %p", widget); RK_ASSERT(w); widget->show(); } return true; } KXMLGUIFactory *KatePluginIntegrationWindow::guiFactory () { RK_TRACE (APP); // NOTE: We'd rather like to add the plugin to our own RKMDIWindows, rather than // allowing it direct access to the guiFactory() // See the HACK in createPluginView(). return factory (); } QWidget *KatePluginIntegrationWindow::window() { RK_TRACE (APP); return RKWorkplace::mainWorkplace()->view()->window(); } QList KatePluginIntegrationWindow::views() { RK_TRACE (APP); QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); QList ret; for (int i = 0; i < w.size (); ++i) { ret.append (static_cast(w[i])->getView()); } return ret; } KTextEditor::View *KatePluginIntegrationWindow::activeView() { RK_TRACE (APP); RKMDIWindow *w = RKWorkplace::mainWorkplace()->activeWindow(RKMDIWindow::AnyWindowState); if (w && w->isType (RKMDIWindow::CommandEditorWindow)) { return static_cast(w)->getView(); } // NOTE: As far as RKWard is concerned, the active window will most likely be the tool window at this point, while the // intention will be to get an active window that the tool should operate on. So get the last used window from // history. (Another option would be to check which window is on top in the view area, but this will be difficult // for split views. RKMDIWindow* candidate = RKWorkplace::getHistory()->previousDocumentWindow(); if (candidate && candidate->isType(RKMDIWindow::CommandEditorWindow)) return static_cast(candidate)->getView(); // NOTE: It looks like some plugins assume this cannot return 0. That's a bug in the plugin, but still one that could // be quite prevalent, as in kate, that assumption holds. So, to be safe, we create a dummy window on the fly. return app->dummyView(); } KTextEditor::View *KatePluginIntegrationWindow::activateView(KTextEditor::Document *document) { RK_TRACE (APP); RKCommandEditorWindow* w = findWindowForDocument(document); if (w) { w->activate(); return w->getView(); } if (app->dummy_view && document == app->dummy_view->document()) return app->dummy_view; return 0; } KTextEditor::View *KatePluginIntegrationWindow::openUrl(const QUrl &url, const QString &encoding) { RK_TRACE (APP); RKMDIWindow *w = RKWorkplace::mainWorkplace()->openScriptEditor(url, encoding, RKSettingsModuleCommandEditor::matchesScriptFileFilter(url.fileName())); if (w) return static_cast(w)->getView(); RK_ASSERT(w); // should not happen return 0; } QObject *KatePluginIntegrationWindow::pluginView(const QString &name) { RK_TRACE (APP); - return plugin_views.value(app->plugin(name)); + return plugin_resources.value(app->plugin(name)).view; } bool KatePluginIntegrationWindow::closeSplitView(KTextEditor::View* view) { RK_TRACE (APP); // TODO: This should close the area that this view is in, not necessarily the view itself. However, if the same doc // is also present in the area to merge into, then close this view, keeping the other. return closeView(view); } bool KatePluginIntegrationWindow::closeView(KTextEditor::View* view) { RK_TRACE (APP); RKMDIWindow *w = findWindowForView(view); if (w) return RKWorkplace::mainWorkplace()->closeWindow(w); return false; } bool KatePluginIntegrationWindow::hideToolView(QWidget* widget) { RK_TRACE (APP); RKMDIWindow *w = qobject_cast(widget); if (w) w->close(false); else { RK_ASSERT(w); widget->hide(); } return true; } /* These appear to be truly optional, so let's disable them for now. void KatePluginIntegrationWindow::hideViewBar(KTextEditor::View* view) {} void KatePluginIntegrationWindow::showViewBar(KTextEditor::View* view) {} void KatePluginIntegrationWindow::deleteViewBar(KTextEditor::View* view) {} void KatePluginIntegrationWindow::addWidgetToViewBar(KTextEditor::View* view, QWidget* bar) {} QWidget *KatePluginIntegrationWindow::createViewBar(KTextEditor::View *view) {} */ bool KatePluginIntegrationWindow::moveToolView(QWidget* widget, KTextEditor::MainWindow::ToolViewPosition pos) { RK_TRACE (APP); RKMDIWindow *w = qobject_cast(widget); if (w) { RKWorkplace::mainWorkplace ()->placeInToolWindowBar (w, pos); return true; } return false; } void KatePluginIntegrationWindow::splitView(Qt::Orientation orientation) { RK_TRACE (APP); RKWorkplace::mainWorkplace()->view()->splitView(orientation); } bool KatePluginIntegrationWindow::viewsInSameSplitView(KTextEditor::View* view1, KTextEditor::View* view2) { RK_TRACE (APP); // TODO not sure what the semantics of this really are. The two views are in the same view area (not visible, simultaneously), or in two areas split side-by-side? // However, this is essentially unused in kate. return false; } +void fixupPluginUI(const QString &id, int num_of_client, KXMLGUIClient* client, RKMDIWindow* window) { + RK_TRACE (APP); + + if (num_of_client == 0) { + if (id == QStringLiteral("katesearchplugin")) { + window->setCaption("Search in Scripts"); + // TODO + } + } +} + QObject* KatePluginIntegrationWindow::createPluginView(KTextEditor::Plugin* plugin) { RK_TRACE (APP); // HACK: Currently, plugins will add themselves to the main window's UI, without asking. We don't want that, as // our MDI windows are enabled / disabled on activation. To hack around this, the catch the added clients, // and put them, where they belong. connect(factory(), &KXMLGUIFactory::clientAdded, this, &KatePluginIntegrationWindow::catchXMLGUIClientsHack); - QObject *view = plugin->createView(main); + active_plugin = plugin; + PluginResources& resources = plugin_resources.insert(plugin, PluginResources()).value(); + resources.view = plugin->createView(main); + active_plugin = 0; disconnect(factory(), &KXMLGUIFactory::clientAdded, this, &KatePluginIntegrationWindow::catchXMLGUIClientsHack); KXMLGUIClient* hacked_parent = this; - for (int i = 0; i < caught_clients.size(); ++i) { - if (i < created_tool_views.size()) { - hacked_parent = created_tool_views[i]; + QString id = app->idForPlugin(plugin); + for (int i = 0; i < resources.clients.size(); ++i) { + KXMLGUIClient* client = resources.clients[i]; + RKMDIWindow* window = resources.windows.value(i); + if (window) { + hacked_parent = window->getPart();; } - factory()->removeClient(caught_clients[i]); - hacked_parent->insertChildClient(caught_clients[i]); + factory()->removeClient(client); + fixupPluginUI(id, i, client, window); + hacked_parent->insertChildClient(client); } - caught_clients.clear(); - created_tool_views.clear(); + // TODO: If child clients were added to the window, itself, we need to tell the main window to rebuild. - plugin_views.insert(plugin, view); - connect(plugin, &QObject::destroyed, [&]() { plugin_views.remove(plugin); }); - return view; + connect(plugin, &QObject::destroyed, [&]() { plugin_resources.remove(plugin); }); + return resources.view; } void KatePluginIntegrationWindow::catchXMLGUIClientsHack(KXMLGUIClient* client) { RK_TRACE (APP); - caught_clients.append(client); + if (active_plugin) { + RK_ASSERT(plugin_resources.contains(active_plugin)); + plugin_resources[active_plugin].clients.append(client); + } else { + RK_DEBUG(APP, DL_DEBUG, "XML client created by unknown kate plugin"); + } } // TODO: Don't forget to make sure to emit all the signals! // TODO: Apply plugin specific hacks as needed (e.g. moving "Tool" menu, removing broken actions) /// END KTextEditor::MainWindow interface #include "katepluginintegration.moc" diff --git a/rkward/windows/katepluginintegration.h b/rkward/windows/katepluginintegration.h index e2e6ba8e..b366a925 100644 --- a/rkward/windows/katepluginintegration.h +++ b/rkward/windows/katepluginintegration.h @@ -1,114 +1,121 @@ /*************************************************************************** katepluginintegration - description ------------------- begin : Mon Jun 12 2017 copyright : (C) 2017-2020 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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) any later version. * * * ***************************************************************************/ #ifndef KATEPLUGININTEGRATION_H #define KATEPLUGININTEGRATION_H #include #include #include #include #include class KatePluginIntegrationWindow; +class RKMDIWindow; /** This class provides implementations for the KTextEditor::Application interface. * Note that there is a separate interface KatePluginIntegrationWindow / KTextEditor::MainWindow that serves * as an abstraction of main windows. Even though - for now - we will be creating (detachable) plugins only in * one "main" window, we follow this separation to be on the safe side for future extensions (and also there is * a name-clash in on of the slots (openUrl()), otherwise. */ class KatePluginIntegrationApp : public QObject { Q_OBJECT public: KatePluginIntegrationApp(QObject *parent); ~KatePluginIntegrationApp(); QObject* loadPlugin(const QString& identifier); KatePluginIntegrationWindow *mainWindow() const { return window; }; private slots: friend class KatePluginIntegrationWindow; void saveConfigAndUnload(); // These are the implementations of the KTextEditor::Application interface. // NOTE that they are not technically overrides, but get invoked via QMetaObject::invokeMethod() QList mainWindows(); KTextEditor::MainWindow *activeMainWindow(); QList documents(); KTextEditor::Document *findUrl(const QUrl &url); KTextEditor::Document *openUrl(const QUrl &url, const QString &encoding = QString()); bool closeDocument(KTextEditor::Document *document); bool closeDocuments(const QList &documents); KTextEditor::Plugin *plugin(const QString &name); private: KatePluginIntegrationWindow *window; // For now, only one main window KTextEditor::Application *app; /** Provides a hidden dummy view (created on the fly as needed), for plugins that assume there is always at least one view/document around. */ KTextEditor::View *dummyView(); KTextEditor::View *dummy_view; struct PluginInfo { KPluginMetaData data; KTextEditor::Plugin *plugin; }; QMap known_plugins; + QString idForPlugin(const KTextEditor::Plugin *plugin) const; QString idForPlugin(const KPluginMetaData &plugin) const; }; class KatePluginIntegrationWindow : public QObject, public KXMLGUIClient { Q_OBJECT public: KatePluginIntegrationWindow(KatePluginIntegrationApp *parent); KTextEditor::MainWindow *mainWindow() const { return main; }; private slots: // These are the implementations of the KTextEditor::MainWindow interface. // NOTE that they are not technically overrides, but get invoked via QMetaObject::invokeMethod() QWidget *createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text); KXMLGUIFactory *guiFactory(); QWidget *window(); QList views(); KTextEditor::View *activeView(); KTextEditor::View *activateView(KTextEditor::Document *document); KTextEditor::View *openUrl(const QUrl &url, const QString &encoding = QString()); bool closeView(KTextEditor::View *view); void splitView(Qt::Orientation orientation); bool closeSplitView(KTextEditor::View *view); bool viewsInSameSplitView(KTextEditor::View *view1, KTextEditor::View *view2); bool moveToolView(QWidget *widget, KTextEditor::MainWindow::ToolViewPosition pos); bool showToolView(QWidget *widget); bool hideToolView(QWidget *widget); QObject *pluginView(const QString &name); /* Apparently, these are truely optional, so let's disable them for the time being 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 *bar); */ private: friend class KatePluginIntegrationApp; KTextEditor::MainWindow *main; QObject* createPluginView(KTextEditor::Plugin* plugin); - QHash plugin_views; + struct PluginResources { + PluginResources() : view(0) {}; + QObject *view; + QList clients; + QList windows; + }; + QHash plugin_resources; KatePluginIntegrationApp *app; private slots: void catchXMLGUIClientsHack(KXMLGUIClient* client); private: - QList caught_clients; - QList created_tool_views; + KTextEditor::Plugin* active_plugin; }; #endif diff --git a/rkward/windows/rkmdiwindow.cpp b/rkward/windows/rkmdiwindow.cpp index 9a3803ae..e777e5f6 100644 --- a/rkward/windows/rkmdiwindow.cpp +++ b/rkward/windows/rkmdiwindow.cpp @@ -1,430 +1,431 @@ /*************************************************************************** rkmdiwindow - description ------------------- begin : Tue Sep 26 2006 - copyright : (C) 2006 - 2017 by Thomas Friedrichsmeier + copyright : (C) 2006 - 2020 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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) any later version. * * * ***************************************************************************/ #include "rkmdiwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "rkworkplace.h" #include "rkworkplaceview.h" #include "rktoolwindowbar.h" #include "rktoolwindowlist.h" #include "../settings/rksettingsmodulegeneral.h" #include "../misc/rkstandardicons.h" #include "../misc/rkxmlguisyncer.h" #include "../rbackend/rcommand.h" #include "../debug.h" RKMDIStandardActionClient::RKMDIStandardActionClient () : KXMLGUIClient () { RK_TRACE (APP); setComponentName (QCoreApplication::applicationName (), QGuiApplication::applicationDisplayName ()); setXMLFile ("rkstandardactions.rc", true); } RKMDIStandardActionClient::~RKMDIStandardActionClient () { RK_TRACE (APP); } // TODO: remove name parameter RKMDIWindow::RKMDIWindow (QWidget *parent, int type, bool tool_window, const char *) : QFrame (parent) { RK_TRACE (APP); if (tool_window) { type |= ToolWindow; } else { type |= DocumentWindow; } RKMDIWindow::type = type; state = Attached; tool_window_bar = 0; part = 0; active = false; no_border_when_active = false; standard_client = 0; status_popup = 0; status_popup_container = 0; - setWindowIcon (RKStandardIcons::iconForWindow (this)); + if (!(type & KatePluginWindow)) setWindowIcon (RKStandardIcons::iconForWindow (this)); } RKMDIWindow::~RKMDIWindow () { RK_TRACE (APP); if (isToolWindow ()) RKToolWindowList::unregisterToolWindow (this); delete standard_client; } KActionCollection *RKMDIWindow::standardActionCollection () { if (!standard_client) { RK_TRACE (APP); standard_client = new RKMDIStandardActionClient (); RK_ASSERT (part); // call setPart () first! part->insertChildClient (standard_client); } return standard_client->actionCollection (); } //virtual QString RKMDIWindow::fullCaption () { RK_TRACE (APP); return shortCaption (); } //virtual QString RKMDIWindow::shortCaption () { RK_TRACE (APP); return windowTitle (); } void RKMDIWindow::setCaption (const QString &caption) { RK_TRACE (APP); QWidget::setWindowTitle (caption); emit (captionChanged (this)); + if (tool_window_bar) tool_window_bar->captionChanged(this); } bool RKMDIWindow::isActive () { // don't trace, called pretty often if (!topLevelWidget ()->isActiveWindow ()) return false; return isActiveInsideToplevelWindow (); } bool RKMDIWindow::isActiveInsideToplevelWindow () { // don't trace, called pretty often return (active || (!isAttached ())); } void RKMDIWindow::activate (bool with_focus) { RK_TRACE (APP); QWidget *old_focus = qApp->focusWidget (); if (isToolWindow ()) { if (tool_window_bar) tool_window_bar->showWidget (this); else if (!isVisible ()) RKWorkplace::mainWorkplace ()->detachWindow (this, true); else { topLevelWidget ()->show (); topLevelWidget ()->raise (); } } else { if (isAttached ()) RKWorkplace::mainWorkplace ()->view ()->showWindow (this); else { topLevelWidget ()->show (); topLevelWidget ()->raise (); } } emit (windowActivated (this)); if (with_focus) { if (old_focus) old_focus->clearFocus (); topLevelWidget ()->activateWindow (); setFocus(); } else { if (old_focus) { old_focus->setFocus (); active = false; } } } bool RKMDIWindow::close (bool also_delete) { RK_TRACE (APP); if (isToolWindow ()) { if (!isAttached ()) { topLevelWidget ()->deleteLater (); // flee the dying DetachedWindowContainer if (tool_window_bar) RKWorkplace::mainWorkplace ()->attachWindow (this); else { state = Attached; hide (); setParent (0); } } if (tool_window_bar) tool_window_bar->hideWidget (this); else hide (); return true; } if (also_delete) { bool closed = QWidget::close (); if (closed) { // WORKAROUND for https://bugs.kde.org/show_bug.cgi?id=170806 // NOTE: can't move this to the d'tor, since the part is already partially deleted, then // TODO: use version check / remove once fixed in kdelibs if (part && part->factory ()) { part->factory ()->removeClient (part); } // WORKAROUND end delete this; // Note: using deleteLater(), here does not work well while restoring workplaces (window is not fully removed from workplace before restoring) } return closed; } else { RK_ASSERT (!testAttribute (Qt::WA_DeleteOnClose)); return QWidget::close (); } } void RKMDIWindow::prepareToBeAttached () { RK_TRACE (APP); } void RKMDIWindow::prepareToBeDetached () { RK_TRACE (APP); if (isToolWindow ()) { if (tool_window_bar) tool_window_bar->hideWidget (this); } } bool RKMDIWindow::eventFilter (QObject *watched, QEvent *e) { // WARNING: The derived object and the part may both the destroyed at this point of time! // Make sure not to call any virtual function on this object! RK_ASSERT (acceptsEventsFor (watched)); if (watched == getPart ()) { if (KParts::PartActivateEvent::test (e)) { RK_TRACE (APP); // trace only the "interesting" calls to this function KParts::PartActivateEvent *ev = static_cast (e); if (ev->activated ()) { emit (windowActivated (this)); setFocus (); // focus doesn't always work correctly for the kate part active = true; } else { active = false; } if (layout()->margin () < 1) { layout()->setMargin (1); } update (); } } return false; } bool RKMDIWindow::acceptsEventsFor (QObject *object) { // called very often. Don't trace if (object == getPart ()) return true; return false; } void RKMDIWindow::initializeActivationSignals () { RK_TRACE (APP); RK_ASSERT (getPart ()); getPart ()->installEventFilter (this); RKXMLGUISyncer::self ()->watchXMLGUIClientUIrc (getPart ()); } void RKMDIWindow::paintEvent (QPaintEvent *e) { // RK_TRACE (APP); Do not trace! QFrame::paintEvent (e); if (isActive () && !no_border_when_active) { QPainter paint (this); paint.setPen (QApplication::palette ().color(QPalette::Highlight)); paint.drawRect (0, 0, width ()-1, height ()-1); } } void RKMDIWindow::changeEvent (QEvent *event) { RK_TRACE (APP); if (event->type () == QEvent::ActivationChange) { // NOTE: active is NOT the same as isActive(). Active just means that this window *would* be active, if its toplevel window is active. if (active || (!isAttached ())) update (); } QFrame::changeEvent (event); } void RKMDIWindow::slotActivateForFocusFollowsMouse () { RK_TRACE (APP); if (!underMouse ()) return; // we can't do without activateWindow(), below. Unfortunately, this has the side effect of raising the window (in some cases). This is not always what we want, e.g. if a // plot window is stacked above this window. (And since this is activation by mouse hover, this window is already visible, by definition!) // So we try a heuristic (imperfect) to find, if there are any other windows stacked above this one, in order to re-raise them above this. QWidgetList toplevels = qApp->topLevelWidgets (); QWidgetList overlappers; QWidget *window = topLevelWidget (); QRect rect = window->frameGeometry (); for (int i = toplevels.size () - 1; i >= 0; --i) { QWidget *tl = toplevels[i]; if (!tl->isWindow ()) continue; if (tl == window) continue; if (tl->isHidden ()) continue; QRect tlrect = tl->geometry (); QRect intersected = tlrect.intersected (rect); if (!intersected.isEmpty ()) { QWidget *above = qApp->topLevelAt ((intersected.left () +intersected.right ()) / 2, (intersected.top () +intersected.bottom ()) / 2); if (above && (above != window) && (above->isWindow ()) && (!above->isHidden ()) && (overlappers.indexOf (above) < 0)) overlappers.append (above); } } activate (true); for (int i = 0; i < overlappers.size (); ++i) { overlappers[i]->raise (); } } void RKMDIWindow::enterEvent (QEvent *event) { RK_TRACE (APP); if (!isActive ()) { if (RKSettingsModuleGeneral::mdiFocusPolicy () == RKSettingsModuleGeneral::RKMDIFocusFollowsMouse) { if (!QApplication::activePopupWidget ()) { // see http://sourceforge.net/p/rkward/bugs/90/ // enter events may be delivered while a popup-menu (in a different window) is executing. If we activate in this case, the popup-menu might get deleted // while still handling events. // // Similar problems seem to occur, when the popup menu has just finished (by the user selecting an action) and this results // in the mouse entering this widget. To prevent crashes in this second case, we delay the activation until the next iteration of the event loop. // // Finally, in some cases (such as when a new script window was created), we need a short delay, as we may be catching an enter event on a window that is in the same place, // where the newly created window goes. This would cause activation to switch back, immediately. QTimer::singleShot (50, this, SLOT (slotActivateForFocusFollowsMouse())); } } } QFrame::enterEvent (event); } void RKMDIWindow::setStatusMessage (const QString& message, RCommand *command) { RK_TRACE (MISC); if (!status_popup) { // NOTE: Yes, this clearly goes against the explicit recommendation, but we do want the status message as an overlay to the main widget. // This is especially important for plots, where changing the plot area geometry will trigger redraws of the plot. // Note that these messages are mostly used on previews, so far, where they will either be a) transient ("preview updating"), // or b) in case of errors, the place of interest will be outside the preview widget _and_ the preview will generally be invalid. status_popup_container = new QWidget (this); QVBoxLayout *layout = new QVBoxLayout (status_popup_container); layout->setContentsMargins (10, 10, 10, 10); status_popup = new KMessageWidget (status_popup_container); status_popup->setCloseButtonVisible (true); status_popup->setMessageType (KMessageWidget::Warning); layout->addWidget (status_popup); layout->addStretch (); // when animation is finished, squeeze the popup-container, so as not to interfere with mouse events in the main window connect (status_popup, &KMessageWidget::showAnimationFinished, [this]() { status_popup_container->resize (QSize(width(), status_popup->height () + 20)); }); connect (status_popup, &KMessageWidget::hideAnimationFinished, status_popup_container, &QWidget::hide); } if (command) connect (command->notifier (), &RCommandNotifier::commandFinished, this, &RKMDIWindow::clearStatusMessage); if (!message.isEmpty ()) { status_popup_container->resize (size ()); status_popup_container->show (); if (status_popup->text () == message) { if (!status_popup->isVisible ()) status_popup->animatedShow (); // it might have been closed by user. And no, simply show() is _not_ good enough. KF5 (5.15.0) } if (status_popup->text () != message) { if (status_popup->isVisible ()) status_popup->hide (); // otherwise, the KMessageWidget does not update geometry (KF5, 5.15.0) status_popup->setText (message); status_popup->animatedShow (); } } else { status_popup_container->hide (); status_popup->hide (); status_popup->setText (QString ()); } } void RKMDIWindow::clearStatusMessage () { RK_TRACE (APP); setStatusMessage (QString ()); } void RKMDIWindow::resizeEvent (QResizeEvent*) { if (status_popup_container && status_popup_container->isVisible ()) status_popup_container->resize (QSize(width(), status_popup->height () + 20)); } void RKMDIWindow::setWindowStyleHint (const QString& hint) { RK_TRACE (APP); if (hint == "preview") { if (standard_client) { QAction *act = standardActionCollection ()->action ("window_help"); if (act) act->setVisible (false); act = standardActionCollection ()->action ("window_configure"); if (act) act->setVisible (false); } no_border_when_active = true; } } void RKMDIWindow::setMetaInfo (const QString& _generic_window_name, const QUrl& _help_url, RKSettings::SettingsPage _settings_page) { RK_TRACE (APP); // only meant to be called once RK_ASSERT (generic_window_name.isEmpty() && help_url.isEmpty ()); generic_window_name = _generic_window_name; help_url = _help_url; settings_page = _settings_page; if (!help_url.isEmpty ()) { QAction *action = standardActionCollection ()->addAction ("window_help", this, SLOT (showWindowHelp())); action->setText (i18n ("Help on %1", generic_window_name)); } if (settings_page != RKSettings::NoPage) { QAction *action = standardActionCollection ()->addAction ("window_configure", this, SLOT (showWindowSettings())); action->setText (i18n ("Configure %1", generic_window_name)); } } void RKMDIWindow::showWindowHelp () { RK_TRACE (APP); RK_ASSERT (!help_url.isEmpty ()); RKWorkplace::mainWorkplace()->openHelpWindow (help_url, true); } void RKMDIWindow::showWindowSettings () { RK_TRACE (APP); RK_ASSERT (settings_page != RKSettings::NoPage); RKSettings::configureSettings (settings_page, this); } diff --git a/rkward/windows/rkmdiwindow.h b/rkward/windows/rkmdiwindow.h index d5632a43..051472e1 100644 --- a/rkward/windows/rkmdiwindow.h +++ b/rkward/windows/rkmdiwindow.h @@ -1,171 +1,172 @@ /*************************************************************************** rkmdiwindow - description ------------------- begin : Tue Sep 26 2006 - copyright : (C) 2006 - 2017 by Thomas Friedrichsmeier + copyright : (C) 2006 - 2020 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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) any later version. * * * ***************************************************************************/ #ifndef RKMDIWINDOW_H #define RKMDIWINDOW_H #include #include #include #include #include "../settings/rksettings.h" class QEvent; class QPaintEvent; class RKWorkplace; class RKToolWindowBar; class KMessageWidget; class RCommand; class RKMDIStandardActionClient : public KXMLGUIClient { public: RKMDIStandardActionClient (); ~RKMDIStandardActionClient (); }; /** Base class for rkward document mdi windows */ class RKMDIWindow : public QFrame { Q_OBJECT public: enum Type { DataEditorWindow=1 << 0, CommandEditorWindow=1 << 1, OutputWindow=1 << 2, HelpWindow=1 << 3, X11Window=1 << 4, ObjectWindow=1 << 5, ConsoleWindow=1 << 10, CommandLogWindow=1 << 11, WorkspaceBrowserWindow=1 << 12, SearchHelpWindow=1 << 13, PendingJobsWindow=1 << 14, FileBrowserWindow=1 << 15, DebugConsoleWindow=1 << 16, CallstackViewerWindow=1 << 17, DebugMessageWindow=1 << 18, + KatePluginWindow=1 << 19, DocumentWindow=1 << 29, ToolWindow=1 << 30, AnyType=DocumentWindow | ToolWindow }; enum State { Attached=1, Detached=2, AnyWindowState=Attached | Detached }; protected: /** constructor @param parent parent widget @param type Type of window (see RKMDIWindow::Type).*/ RKMDIWindow (QWidget *parent, int type, bool tool_window=false, const char *name=0); virtual ~RKMDIWindow (); public slots: /** Reimplemented from QWidget::setCaption () to emit the signal captionChanged () when the caption is changed. */ void setCaption (const QString &caption); public: /** @returns true, if the window's document was modified (and would need to be saved) */ virtual bool isModified () { return false; }; /** @returns A long / complete caption. Default implementation simply calls shortCaption () */ virtual QString fullCaption (); /** @returns A short caption (e.g. only the filename without the path). Default implementation simply calls QWidget::caption () */ virtual QString shortCaption (); /** @returns The corresponding KPart for this window */ KParts::Part *getPart () { return part; }; /** Is this window attached (or detached)? @returns true if attached, false if detached */ bool isAttached () const { return (state == Attached); }; /** Is this a tool window? */ bool isToolWindow () const { return (type & ToolWindow); }; /** Returns the type of this window */ bool isType (Type t) const { return (type & t); }; /** Activate (raise) this window, regardless of whether it is attached or detached @param with_focus Should the window also get keyboard focus? */ virtual void activate (bool with_focus=true); /** If your mdi window should perform any adjustments before being attached, reimplement this function. Default implementation does nothing, but raises an assert, if this is a tool window */ virtual void prepareToBeAttached (); /** If your mdi window should perform any adjustments before being detached, reimplement this function. Default implementation does nothing, but raises an assert, if this is a tool window */ virtual void prepareToBeDetached (); /** Tool windows will only hide themselves, and ignore the also_delete flag */ virtual bool close (bool also_delete); /** Set a status message to be shown in a popup inside the window. The message persists until the given R command has finished, or until this function is called with an empty string. This should be used, when the information shown is currently out-of-date (e.g. when refreshing a preview / loading a plot from history), _not_ when the window is simply busy (e.g. when saving the current plot to history). */ void setStatusMessage (const QString& message, RCommand* command=0); /** Set a style hint for the window. So far the only interpreted style hint is "preview", and not all windows implement it. Base implements hiding of "active" indicator border for "preview"s. */ virtual void setWindowStyleHint (const QString& hint); bool eventFilter (QObject *watched, QEvent *e) override; bool acceptsEventsFor (QObject *object); /** Whether the window is active. This seems to be more reliable than hasFocus () */ bool isActive (); /** Like isActive (), but also returns true, if this window _would_ be the active one, if the parent topLevelWindow() _was_ the active Window. */ bool isActiveInsideToplevelWindow (); /** Returns a pointer to an action collection suitable to place RKStandardAction in. This collection (and the corresponding KXMLGUIClient) is created on the fly. */ KActionCollection *standardActionCollection (); /** plugin-accessible properties of this object in the global context. Currently used only by RKEditorDataFrame to give information on the currently active data.frame. NOTE: ATM, you cannot set arbitrary properties. Only those supported in RKStandardComponent will have an effect. */ QString globalContextProperty (const QString& property) { return global_context_properties.value (property); }; signals: /** This signal is emitted, whenever the window caption was changed. @param RKMDIWindow* a pointer to this window */ void captionChanged (RKMDIWindow *); /** This signal is emitted, when the window was activated *with* focus */ void windowActivated (RKMDIWindow *); protected slots: void showWindowHelp (); void showWindowSettings (); void clearStatusMessage (); protected: void setPart (KParts::Part *p) { part = p; }; void setMetaInfo (const QString& generic_window_name, const QUrl& help_url, RKSettings::SettingsPage settings_page=RKSettings::NoPage); void initializeActivationSignals (); void paintEvent (QPaintEvent *e) override; void changeEvent (QEvent *event) override; /** reimplemented from QWidget to emulate focus-follows-mouse behavior */ void enterEvent (QEvent *event) override; /** @see globalContextProperty() */ void setGlobalContextProperty (const QString& property, const QString& value) { global_context_properties.insert (property, value); }; KMessageWidget* status_popup; QWidget* status_popup_container; void resizeEvent (QResizeEvent *ev) override; friend class RKWorkplace; /** type of this window */ int type; private slots: void slotActivateForFocusFollowsMouse (); private: friend class RKToolWindowBar; /** state of this window (attached / detached). This is usually set from the RKWorkplace */ KParts::Part *part; State state; RKToolWindowBar *tool_window_bar; bool active; bool no_border_when_active; RKMDIStandardActionClient *standard_client; /** @see globalContextProperty() */ QMap global_context_properties; QString generic_window_name; QUrl help_url; RKSettings::SettingsPage settings_page; }; #endif diff --git a/rkward/windows/rktoolwindowbar.cpp b/rkward/windows/rktoolwindowbar.cpp index 03c405e3..11e67d13 100644 --- a/rkward/windows/rktoolwindowbar.cpp +++ b/rkward/windows/rktoolwindowbar.cpp @@ -1,359 +1,366 @@ /*************************************************************************** rktoolwindowbar - description ------------------- begin : Fri Oct 12 2007 - copyright : (C) 2007-2019 by Thomas Friedrichsmeier + copyright : (C) 2007-2020 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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) any later version. * * * ***************************************************************************/ /* This code is based substantially on kate's katemdi! */ #include "rktoolwindowbar.h" #include #include #include #include #include #include #include #include #include "rkworkplace.h" #include "rkworkplaceview.h" #include "rkmdiwindow.h" #include "../rkward.h" #include "../misc/rkstandardicons.h" #include "../debug.h" #define DEFAULT_SPLITTER_SIZE 200 #define SPLITTER_MIN_SIZE 30 RKToolWindowBar::RKToolWindowBar (KMultiTabBarPosition position, QWidget *parent) : KMultiTabBar (position, parent), container (0) { RK_TRACE (APP); setStyle (KMultiTabBar::KDEV3ICON); last_known_size = SPLITTER_MIN_SIZE; } RKToolWindowBar::~RKToolWindowBar () { RK_TRACE (APP); } +void RKToolWindowBar::captionChanged (RKMDIWindow* window) { + RK_TRACE (APP); + + int id = widget_to_id.value (window); + tab (id)->setText (window->shortCaption ()); +} + void RKToolWindowBar::restoreSize (const KConfigGroup &cg) { RK_TRACE (APP); last_known_size = cg.readEntry (QString ("view_size_%1").arg (position ()), DEFAULT_SPLITTER_SIZE); } void RKToolWindowBar::saveSize (KConfigGroup &cg) const { RK_TRACE (APP); cg.writeEntry (QString ("view_size_%1").arg (position ()), last_known_size); } int RKToolWindowBar::getSplitterSize () const { RK_TRACE (APP); int pos = splitter->indexOf (container); if (pos < 0) { RK_ASSERT (false); return 0; } return (splitter->sizes ()[pos]); } void RKToolWindowBar::setSplitterSize (int new_size) { RK_TRACE (APP); // HACK / WORKAROUND: reset the collapsed state of the container (if collapsed). Else we will not be able to open it again int index = splitter->indexOf (container); QList sizes = splitter->sizes (); if (sizes[index] == 0) { sizes[index] = last_known_size; splitter->setSizes (sizes); } if (splitter->orientation () == Qt::Horizontal) { container->resize (new_size, container->height ()); } else { container->resize (container->width (), new_size); } } void RKToolWindowBar::splitterMoved (int, int) { RK_TRACE (APP); int pos = getSplitterSize (); if (pos >= SPLITTER_MIN_SIZE) last_known_size = pos; if (!pos) { // collapsed. Hide it properly. for (QMap::const_iterator it = widget_to_id.constBegin (); it != widget_to_id.constEnd (); ++it) { if (isTabRaised (it.value ())) { hideWidget (it.key ()); break; } } } } void RKToolWindowBar::setSplitter (QSplitter *splitter) { RK_TRACE (APP); RK_ASSERT (!container); RKToolWindowBar::splitter = splitter; container = new QWidget (splitter); new QHBoxLayout (container); splitter->setContentsMargins (0, 0, 0, 0); container->layout ()->setContentsMargins (0, 0, 0, 0); container->layout ()->setSpacing (0); container->layout ()->setMargin (0); container->hide (); connect (splitter, &QSplitter::splitterMoved, this, &RKToolWindowBar::splitterMoved); } void RKToolWindowBar::addWidget (RKMDIWindow *window) { RK_TRACE (APP); RK_ASSERT (window); if (window->tool_window_bar == this) return; // may happen while restoring windows RK_ASSERT (container); static int id_count = 0; int id = ++id_count; if (window->tool_window_bar) { window->tool_window_bar->removeWidget (window); } #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5,13,0) appendTab (window->windowIcon (), id, window->shortCaption ()); #else appendTab (window->windowIcon ().pixmap (QSize (16, 16)), id, window->shortCaption ()); #endif window->tool_window_bar = this; widget_to_id.insert (window, id); connect (tab (id), &KMultiTabBarTab::clicked, this, &RKToolWindowBar::tabClicked); tab (id)->installEventFilter (this); if (window->isAttached ()) { reclaimDetached (window); } show (); } void RKToolWindowBar::reclaimDetached (RKMDIWindow *window) { RK_TRACE (APP); window->hide(); window->setParent (container); container->layout ()->addWidget (window); } void RKToolWindowBar::removeWidget (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget_to_id.contains (widget)); int id = widget_to_id[widget]; bool was_active_in_bar = isTabRaised (id); removeTab (id); widget_to_id.remove (widget); widget->tool_window_bar = 0; if (widget->isAttached ()) { widget->setParent (0); widget->hide (); } if (was_active_in_bar) { RK_ASSERT (widget->isAttached ()); container->hide (); widget->active = false; } if (widget_to_id.isEmpty ()) hide (); } void RKToolWindowBar::showWidget (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget_to_id.contains (widget)); int id = widget_to_id[widget]; // close any others for (QMap::const_iterator it = widget_to_id.constBegin (); it != widget_to_id.constEnd (); ++it) { RKMDIWindow *cur = it.key (); if (cur != widget) { if (cur->isAttached ()) { cur->active = false; cur->hide (); } setTab (it.value (), false); } } widget->show (); if (widget->isAttached ()) { setTab (id, true); container->show (); setSplitterSize (last_known_size); } else { widget->topLevelWidget ()->show (); widget->topLevelWidget ()->raise (); } widget->active = true; } void RKToolWindowBar::hideWidget (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget_to_id.contains (widget)); // prevent recursion if (!widget->active) return; int id = widget_to_id[widget]; bool was_active_in_bar = ((widget->parent () == container) && widget->isVisible ()); if (was_active_in_bar) { container->hide (); } RKWardMainWindow::getMain()->partManager()->setActivePart (0); widget->active = false; widget->hide (); setTab (id, false); RKWorkplace::mainWorkplace ()->view ()->setFocus (); } void RKToolWindowBar::tabClicked (int id) { RK_TRACE (APP); RKMDIWindow *widget = idToWidget (id); RK_ASSERT (widget); if (widget->isActive ()) { if (!widget->isAttached ()) widget->close (false); else hideWidget (widget); } else { widget->activate (true); } } RKMDIWindow* RKToolWindowBar::idToWidget (int id) const { RK_TRACE (APP); for (QMap::const_iterator it = widget_to_id.constBegin (); it != widget_to_id.constEnd (); ++it) { if (it.value () == id) { return (it.key ()); } } return 0; } bool RKToolWindowBar::eventFilter (QObject *obj, QEvent *ev) { if (ev->type() == QEvent::ContextMenu) { RK_TRACE (APP); QContextMenuEvent *e = (QContextMenuEvent *) ev; KMultiTabBarTab *bt = dynamic_cast(obj); if (bt) { id_of_popup = bt->id (); RKMDIWindow *widget = idToWidget (id_of_popup); RK_ASSERT (widget); if (widget) { QMenu menu (this); QAction *a = menu.addAction (RKStandardIcons::getIcon (widget->isAttached () ? RKStandardIcons::ActionDetachWindow : RKStandardIcons::ActionAttachWindow), widget->isAttached () ? i18n("Detach") : i18n("Attach")); connect (a, &QAction::triggered, this, &RKToolWindowBar::changeAttachment); KSelectAction *sel = new KSelectAction (i18n ("Position"), &menu); sel->addAction (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveLeft), i18n ("Left Sidebar")); sel->addAction (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveRight), i18n ("Right Sidebar")); sel->addAction (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveUp), i18n ("Top Sidebar")); sel->addAction (RKStandardIcons::getIcon (RKStandardIcons::ActionMoveDown), i18n ("Bottom Sidebar")); sel->addAction (RKStandardIcons::getIcon (RKStandardIcons::ActionDelete), i18n ("Not shown in sidebar")); connect (sel, static_cast(&KSelectAction::triggered), this, &RKToolWindowBar::moveToolWindow); menu.addAction (sel); menu.exec (e->globalPos()); return true; } } } return false; } void RKToolWindowBar::contextMenuEvent (QContextMenuEvent* event) { RK_TRACE (APP); QMenu menu (this); foreach (const RKToolWindowList::ToolWindowRepresentation& rep, RKToolWindowList::registeredToolWindows ()) { QAction *a = menu.addAction (rep.window->windowIcon (), rep.window->shortCaption ()); a->setCheckable (true); a->setChecked (rep.window->tool_window_bar == this); a->setData (rep.id); } connect (&menu, &QMenu::triggered, this, &RKToolWindowBar::addRemoveToolWindow); menu.exec (event->globalPos ()); event->accept (); } void RKToolWindowBar::changeAttachment () { RK_TRACE (APP); RKMDIWindow *window = idToWidget (id_of_popup); RK_ASSERT (window); // toggle attachment if (window->isAttached ()) RKWorkplace::mainWorkplace ()->detachWindow (window); else RKWorkplace::mainWorkplace ()->attachWindow (window); } void RKToolWindowBar::moveToolWindow (int target) { RK_TRACE (APP); RK_ASSERT (target >= RKToolWindowList::Left); RK_ASSERT (target <= RKToolWindowList::Bottom); if (target == position ()) return; RKMDIWindow *window = idToWidget (id_of_popup); RK_ASSERT (window); RKWorkplace::mainWorkplace ()->placeInToolWindowBar (window, target); } void RKToolWindowBar::addRemoveToolWindow (QAction *action) { RK_TRACE (APP); RK_ASSERT (action); RKMDIWindow *win = RKToolWindowList::findToolWindowById (action->data ().toString ()); if (action->isChecked ()) { RKWorkplace::mainWorkplace ()->placeInToolWindowBar (win, position ()); } else { RK_ASSERT (win->tool_window_bar == this); removeWidget (win); } } diff --git a/rkward/windows/rktoolwindowbar.h b/rkward/windows/rktoolwindowbar.h index c10e3052..5c0b8c2b 100644 --- a/rkward/windows/rktoolwindowbar.h +++ b/rkward/windows/rktoolwindowbar.h @@ -1,81 +1,82 @@ /*************************************************************************** rktoolwindowbar - description ------------------- begin : Fri Oct 12 2007 - copyright : (C) 2007, 2011 by Thomas Friedrichsmeier + copyright : (C) 2007-2020 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * 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) any later version. * * * ***************************************************************************/ /* This code is based substantially on kate's katemdi! */ #ifndef RKTOOLWINDOWBAR_H #define RKTOOLWINDOWBAR_H #include #include #include class QSplitter; class QObject; class QEvent; class QAction; class KHBox; class RKMDIWindow; /** This class represents one of the bar which tool windows can dock into (top, left, bottom, right). It contains heavy copying from Kate's katemdi SideBar class. I wish this was available as a library, but it isn't, yet. Some more would need to be copied for full functionality (session saving / restoring), but for now, I focused on the bare essentials */ class RKToolWindowBar : public KMultiTabBar { Q_OBJECT public: RKToolWindowBar (KMultiTabBar::KMultiTabBarPosition position, QWidget *parent); ~RKToolWindowBar (); void setSplitter (QSplitter *splitter); void addWidget (RKMDIWindow *widget); void removeWidget (RKMDIWindow *widget); void showWidget (RKMDIWindow *widget); void hideWidget (RKMDIWindow *widget); void restoreSize (const KConfigGroup &cg); void saveSize (KConfigGroup &cg) const; + void captionChanged(RKMDIWindow* window); private slots: void tabClicked (int id); void changeAttachment (); void moveToolWindow (int target); void addRemoveToolWindow (QAction* action); void splitterMoved (int, int); protected: /** handle RMB clicks on individual buttons */ bool eventFilter (QObject *obj, QEvent *ev) override; /** handle RMB clicks on the bar itself */ void contextMenuEvent (QContextMenuEvent *event) override; private: friend class RKWorkplace; void reclaimDetached (RKMDIWindow *window); int getSplitterSize () const; void setSplitterSize (int new_size); QMap widget_to_id; RKMDIWindow* idToWidget (int id) const; QSplitter* splitter; QWidget* container; int last_known_size; int id_of_popup; }; #endif