diff --git a/rkward/windows/detachedwindowcontainer.cpp b/rkward/windows/detachedwindowcontainer.cpp index f298287a..ce0966d4 100644 --- a/rkward/windows/detachedwindowcontainer.cpp +++ b/rkward/windows/detachedwindowcontainer.cpp @@ -1,157 +1,158 @@ /*************************************************************************** detachedwindowcontainer - description ------------------- begin : Wed Oct 21 2005 copyright : (C) 2005-2016 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 "detachedwindowcontainer.h" #include #include #include #include #include #include #include #include #include #include "rktoplevelwindowgui.h" #include "../rkward.h" #include "../misc/rkstandardicons.h" #include "../misc/rkxmlguisyncer.h" #include "rkworkplace.h" #include "../rkglobals.h" #include "../debug.h" /* Warning! Do not pass a parent widget to the KParts::MainWindow. Otherwise there will be strange crahes while removing the KXMLGUIClients! (In this case: Open a help window, and detach it. Open another help window attached. Close the detached one, then close the attached one -> crash; KDE 3.5.5) */ DetachedWindowContainer::DetachedWindowContainer (RKMDIWindow *widget_to_capture, bool copy_geometry) : KParts::MainWindow () { RK_TRACE (APP); actionCollection ()->addAction (KStandardAction::Close, "dwindow_close", this, SLOT(close())); QAction *reattach = actionCollection ()->addAction ("dwindow_attach", this, SLOT(slotReattach())); reattach->setText (i18n ("Attach to main window")); reattach->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionAttachWindow)); setHelpMenuEnabled (false); // create own GUI setXMLFile ("detachedwindowcontainer.rc"); insertChildClient (toplevel_actions = new RKTopLevelWindowGUI (this)); statusBar ()->hide (); createShellGUI (true); RKXMLGUISyncer::self ()->watchXMLGUIClientUIrc (this); // copy main window toolbar settings QMap main_window_toolbar_styles; foreach (KToolBar *bar, RKWardMainWindow::getMain ()->toolBars ()) { main_window_toolbar_styles.insert (bar->objectName (), bar->toolButtonStyle ()); } // capture widget if (copy_geometry) { setGeometry (widget_to_capture->frameGeometry ()); if (!widget_to_capture->isWindow ()) move (widget_to_capture->mapToGlobal (widget_to_capture->pos ())); #ifdef Q_OS_WIN // fix for detached tool windows positioned with the frame outside the screen ensurePolished (); QPoint adjust = pos (); if (adjust.x () < 0) adjust.setX (0); if (adjust.y () < 0) adjust.setY (0); if (adjust != pos ()) move (adjust); #endif } widget_to_capture->setParent (this); setCentralWidget (widget_to_capture); widget_to_capture->show (); createGUI (widget_to_capture->getPart ()); captured = widget_to_capture; hideEmptyMenus (); // hide empty menus now, and after any reloads connect (guiFactory (), &KXMLGUIFactory::makingChanges, this, &DetachedWindowContainer::hideEmptyMenus); // sanitize toolbars foreach (KToolBar *bar, toolBars ()) { if (main_window_toolbar_styles.contains (bar->objectName ())) { bar->setToolButtonStyle (main_window_toolbar_styles[bar->objectName ()]); } else { RK_ASSERT (false); } } // should self-destruct, when child widget is destroyed connect (widget_to_capture, &QObject::destroyed, this, &DetachedWindowContainer::viewDestroyed); connect (widget_to_capture, &RKMDIWindow::captionChanged, this, &DetachedWindowContainer::updateCaption); setCaption (widget_to_capture->fullCaption ()); // has to come after createGUI! } DetachedWindowContainer::~DetachedWindowContainer () { RK_TRACE (APP); } void DetachedWindowContainer::hideEmptyMenus (bool ignore) { if (ignore) return; RK_TRACE (APP); // remove empty menus (we had to define them in detachedwindowcontainer.rc in order to force a sane menu order) QStringList menu_names; menu_names << "file" << "device" << "history" << "edit" << "run" << "view" << "settings"; foreach (const QString& name, menu_names) { QMenu* menu = dynamic_cast(guiFactory ()->container (name, this)); if (menu) menu->menuAction ()->setVisible (!menu->isEmpty ()); } } void DetachedWindowContainer::viewDestroyed (QObject *) { RK_TRACE (APP); hide (); deleteLater (); } void DetachedWindowContainer::updateCaption (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget == captured); setCaption (widget->fullCaption ()); } void DetachedWindowContainer::slotSetStatusBarText (const QString &text) { RK_TRACE (APP); QString ntext = text.trimmed (); ntext.replace ("", QString ()); // WORKAROUND: what the ?!? is going on? The KTHMLPart seems to post such messages. statusBar ()->showMessage (ntext); statusBar ()->show (); } void DetachedWindowContainer::slotReattach () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->attachWindow (captured); + captured->activate (); } void DetachedWindowContainer::closeEvent (QCloseEvent *e) { RK_TRACE (APP); if (captured->close (true)) { e->accept (); } else { e->ignore (); } } diff --git a/rkward/windows/katepluginintegration.cpp b/rkward/windows/katepluginintegration.cpp index 4465738a..58685681 100644 --- a/rkward/windows/katepluginintegration.cpp +++ b/rkward/windows/katepluginintegration.cpp @@ -1,559 +1,559 @@ /*************************************************************************** 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 #include #include #include "../rkward.h" #include "rkworkplace.h" #include "rkworkplaceview.h" #include "rkcommandeditorwindow.h" #include "../misc/rkdummypart.h" #include "../misc/rkcommonfunctions.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; connect(RKWorkplace::getHistory(), &RKMDIWindowHistory::activeWindowChanged, this, &KatePluginIntegrationWindow::activeWindowChanged); } class KatePluginToolWindow : public RKMDIWindow { Q_OBJECT public: 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) { 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()); window->setCaption(text); window->setWindowIcon(icon); - RKWorkplace::mainWorkplace()->placeInToolWindowBar(window, pos); RKToolWindowList::registerToolWindow(window, identifier, (RKToolWindowList::Placement) pos, 0); + RKWorkplace::mainWorkplace()->placeInToolWindowBar(window, pos); 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; } void KatePluginIntegrationWindow::activeWindowChanged(RKMDIWindow* window) { RK_TRACE (APP); if (window->isType(RKMDIWindow::CommandEditorWindow)) { emit main->viewChanged(static_cast(window)->getView()); } } 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); 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_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 KatePluginIntegrationWindow::fixUpPluginUI(const QString &id, const PluginResources &resources) { RK_TRACE (APP); KXMLGUIClient* hacked_parent = this; // KF6 TODO: In KF6, plugins will probably be limited to one UI client, in the first place. 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(client); if (i == 0 && id == QStringLiteral("katesearchplugin")) { // Try to avoid confusion regarding where the plugin will search: // https://mail.kde.org/pipermail/rkward-devel/2020-January/005393.html // window->setCaption(i18nc("Tab title", "Search in Scripts")); if (!resources.windows.isEmpty()) { // I wonder how long this HACK will work... QComboBox *box = resources.windows.first()->findChild("searchPlaceCombo"); if (box && (box->count() > 1)) { box->setItemText(0, i18nc("where to search", "in Current Script")); box->setItemText(1, i18nc("where to search", "in Open Scripts")); } } RKCommonFunctions::removeContainers(client, QStringList() << "search_in_files", true); // TODO: Rename "Search more" to something sensible. These actions should still be accessible, globally. } else if (i == 0 && id == QStringLiteral("kateprojectplugin")) { RKCommonFunctions::moveContainer(client, "Menu", "projects", "view", true, false); } RKCommonFunctions::moveContainer(client, "Menu", "tools", "edit", true, true); hacked_parent->insertChildClient(client); } /* TODO: Ok, I guess we need even more specialization. kateprojectplugin: - Actions should probably be accessible, globally katesearchplugin: - should go to next / previous match be accessible, globally? katesnippetsplugin: - ok as is, I think */ // TODO: If child clients were added to the window, itself, we need to tell the main window to rebuild. // Right now, this is handled during startup, only. } 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); 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); fixUpPluginUI(app->idForPlugin(plugin), resources); connect(plugin, &QObject::destroyed, [&]() { plugin_resources.remove(plugin); }); return resources.view; } void KatePluginIntegrationWindow::catchXMLGUIClientsHack(KXMLGUIClient* client) { RK_TRACE (APP); 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: new RKToplevelWindowGUI should be called after all plugins are loaded (and have registered their tool views). However // that may be a problem, if there is no KXMLGUIFactory around, yet. So, annoyingly, we need to create the GUI, before we // have everything to populate it. /// END KTextEditor::MainWindow interface #include "katepluginintegration.moc" diff --git a/rkward/windows/rktoolwindowbar.cpp b/rkward/windows/rktoolwindowbar.cpp index e70e6348..46c4ac47 100644 --- a/rkward/windows/rktoolwindowbar.cpp +++ b/rkward/windows/rktoolwindowbar.cpp @@ -1,382 +1,383 @@ /*************************************************************************** rktoolwindowbar - description ------------------- begin : Fri Oct 12 2007 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); } - closeOthers (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 (window, &QObject::destroyed, this, &RKToolWindowBar::windowDestroyed); 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); + if (window->parent () == container) return; + window->hide(); window->setParent (container); container->layout ()->addWidget (window); } void RKToolWindowBar::windowDestroyed(QObject* window) { RK_TRACE (APP); int id = widget_to_id.take (static_cast (window)); removeTab (id); } 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); disconnect (widget, &QObject::destroyed, this, &RKToolWindowBar::windowDestroyed); 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::closeOthers (RKMDIWindow* widget) { RK_TRACE (APP); 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); } } } void RKToolWindowBar::showWidget (RKMDIWindow *widget) { RK_TRACE (APP); RK_ASSERT (widget_to_id.contains (widget)); int id = widget_to_id[widget]; // close any others closeOthers (widget); 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/rkworkplace.cpp b/rkward/windows/rkworkplace.cpp index 657d96d9..cdedd4e7 100644 --- a/rkward/windows/rkworkplace.cpp +++ b/rkward/windows/rkworkplace.cpp @@ -1,1176 +1,1175 @@ /*************************************************************************** rkworkplace - description ------------------- begin : Thu Sep 21 2006 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 "rkworkplace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "detachedwindowcontainer.h" #include "rkcommandeditorwindow.h" #include "rkhtmlwindow.h" #include "rkworkplaceview.h" #include "rktoolwindowbar.h" #include "rktoolwindowlist.h" #include "../core/robject.h" #include "../core/rcontainerobject.h" #include "../core/robjectlist.h" #include "../dataeditor/rkeditor.h" #include "../dataeditor/rkeditordataframe.h" #include "../robjectviewer.h" #include "../settings/rksettingsmodulegeneral.h" #include "../settings/rksettingsmodulecommandeditor.h" #include "../rbackend/rkrinterface.h" #include "../windows/rkwindowcatcher.h" #include "../rbackend/rcommand.h" #include "../misc/rkcommonfunctions.h" #include "../rkglobals.h" #include "../rkward.h" #include "../debug.h" // static RKWorkplace *RKWorkplace::main_workplace = 0; RKWorkplace::RKWorkplace (QWidget *parent) : QWidget (parent) { RK_TRACE (APP); RK_ASSERT (main_workplace == 0); main_workplace = this; _workspace_config = 0; window_placement_override = RKMDIWindow::AnyWindowState; // message area message_area = new QWidget (this); QVBoxLayout *message_layout = new QVBoxLayout (message_area); message_layout->setContentsMargins (0, 0, 0, 0); QVBoxLayout *vbox = new QVBoxLayout (this); vbox->setContentsMargins (0, 0, 0, 0); vbox->setSpacing (0); vbox->addWidget (message_area); tool_window_bars[RKToolWindowList::Top] = new RKToolWindowBar (KMultiTabBar::Top, this); vert_splitter = new QSplitter (Qt::Vertical, this); tool_window_bars[RKToolWindowList::Top]->setSplitter (vert_splitter); QWidget *harea = new QWidget (vert_splitter); QHBoxLayout *hbox = new QHBoxLayout (harea); hbox->setContentsMargins (0, 0, 0, 0); hbox->setSpacing (0); vert_splitter->setCollapsible (vert_splitter->indexOf (harea), false); vert_splitter->setStretchFactor (vert_splitter->indexOf (harea), 1); tool_window_bars[RKToolWindowList::Left] = new RKToolWindowBar (KMultiTabBar::Left, harea); horiz_splitter = new QSplitter (Qt::Horizontal, harea); tool_window_bars[RKToolWindowList::Left]->setSplitter (horiz_splitter); wview = new RKWorkplaceView (horiz_splitter); horiz_splitter->setCollapsible (horiz_splitter->indexOf (wview), false); horiz_splitter->setStretchFactor(horiz_splitter->indexOf (wview), 1); tool_window_bars[RKToolWindowList::Right] = new RKToolWindowBar (KMultiTabBar::Right, harea); tool_window_bars[RKToolWindowList::Right]->setSplitter (horiz_splitter); hbox->addWidget (tool_window_bars[RKToolWindowList::Left]); hbox->addWidget (horiz_splitter); hbox->addWidget (tool_window_bars[RKToolWindowList::Right]); tool_window_bars[RKToolWindowList::Bottom] = new RKToolWindowBar (KMultiTabBar::Bottom, this); tool_window_bars[RKToolWindowList::Bottom]->setSplitter (vert_splitter); vbox->addWidget (tool_window_bars[RKToolWindowList::Top]); vbox->addWidget (vert_splitter); vbox->addWidget (tool_window_bars[RKToolWindowList::Bottom]); KConfigGroup toolbar_config = KSharedConfig::openConfig ()->group ("ToolwindowBars"); for (int i = 0; i < TOOL_WINDOW_BAR_COUNT; ++i) tool_window_bars[i]->restoreSize (toolbar_config); history = new RKMDIWindowHistory (this); connect (RKWardMainWindow::getMain (), &RKWardMainWindow::aboutToQuitRKWard, this, &RKWorkplace::saveSettings); } RKWorkplace::~RKWorkplace () { RK_TRACE (APP); delete _workspace_config; // closeAll (); // not needed, as the windows will autodelete themselves using QObject mechanism. Of course, closeAll () should be called *before* quitting. for (int i = 0; i < windows.size (); ++i) { disconnect (windows[i], 0, this, 0); } } void RKWorkplace::addMessageWidget (KMessageWidget* message) { RK_TRACE (APP); message_area->layout ()->addWidget (message); topLevelWidget ()->show (); topLevelWidget ()->raise (); } QString workspaceConfigFileName (const QUrl &url) { QString base_name = QString (QCryptographicHash::hash (url.toDisplayString ().toUtf8 (), QCryptographicHash::Md5).toHex()); QDir dir (QStandardPaths::writableLocation (QStandardPaths::GenericDataLocation)); dir.mkpath ("rkward"); return (dir.absoluteFilePath ("rkward/workspace_config_" + base_name)); } KConfigBase *RKWorkplace::workspaceConfig () { if (!_workspace_config) { RK_TRACE (APP); _workspace_config = new KConfig (workspaceConfigFileName (workspaceURL ())); } return _workspace_config; } QString RKWorkplace::portableUrl (const QUrl &url) { QUrl ret = url; if (url.scheme () == workspaceURL ().scheme () && url.authority () == workspaceURL ().authority ()) { QString relative = QDir (workspaceURL ().path ()).relativeFilePath (url.path ()); ret = QUrl (relative); } // QUrl::toDisplayString () used here, for the side effect of stripping credentials return QUrl (ret).adjusted (QUrl::NormalizePathSegments).toDisplayString (); } void RKWorkplace::setWorkspaceURL (const QUrl &url, bool keep_config) { RK_TRACE (APP); if (url != current_url) { current_url = url; if (keep_config && _workspace_config) { KConfig * _new_config = _workspace_config->copyTo (workspaceConfigFileName (workspaceURL ())); delete _workspace_config; _workspace_config = _new_config; } else { delete _workspace_config; _workspace_config = 0; } emit (workspaceUrlChanged (url)); } } void RKWorkplace::saveSettings () { RK_TRACE (APP); KConfigGroup toolbar_config = KSharedConfig::openConfig ()->group ("ToolwindowBars"); for (int i = 0; i < TOOL_WINDOW_BAR_COUNT; ++i) tool_window_bars[i]->saveSize (toolbar_config); } void RKWorkplace::initActions (KActionCollection *ac) { RK_TRACE (APP); wview->initActions (ac); } void RKWorkplace::attachWindow (RKMDIWindow *window) { RK_TRACE (APP); RK_ASSERT (windows.contains (window)); // This should not happen for now. if (!window->isAttached ()) { QWidget *old_parent = window->parentWidget (); window->prepareToBeAttached (); if (old_parent && qobject_cast (old_parent)) { old_parent->deleteLater (); } } // all the rest is done, even if the window was previously "Attached", as this may also mean it was freshly created window->state = RKMDIWindow::Attached; if (window->isToolWindow ()) { if (!window->tool_window_bar) placeInToolWindowBar (window, RKToolWindowList::Bottom); else window->tool_window_bar->reclaimDetached (window); } else { view ()->addWindow (window); view ()->topLevelWidget ()->raise (); view ()->topLevelWidget ()->activateWindow (); } RK_ASSERT (window->getPart ()); - RKWardMainWindow::getMain ()->partManager ()->addPart (window->getPart ()); + RKWardMainWindow::getMain ()->partManager ()->addPart (window->getPart (), false); } void RKWorkplace::detachWindow (RKMDIWindow *window, bool was_attached) { RK_TRACE (APP); if (!window) return; RK_ASSERT (windows.contains (window)); // Can't detach a window that is not registered window->prepareToBeDetached (); window->state = RKMDIWindow::Detached; RK_ASSERT (window->getPart ()); if (was_attached) { RKWardMainWindow::getMain ()->partManager ()->removePart (window->getPart ()); if (!window->isToolWindow ()) view ()->removeWindow (window); } DetachedWindowContainer *detached = new DetachedWindowContainer (window, was_attached); detached->show (); if (!was_attached) window->activate (); } void RKWorkplace::addWindow (RKMDIWindow *window, bool attached) { RK_TRACE (APP); // first handle placement overrides if (window_placement_override == RKMDIWindow::Attached) { if (!attached) window->state = RKMDIWindow::Detached; // Ok, yeah. BAD style. But windows that would go to detached by default would not prepareToBeAttached(), without this. // TODO: Create third state: NotManaged attached = true; } else if (window_placement_override == RKMDIWindow::Detached) { attached = false; } // style override. Windows may or may not handle this if (!window_style_override.isEmpty ()) window->setWindowStyleHint (window_style_override); // next handle name overrides, if any if (!window_name_override.isEmpty ()) { int pos = -1; for (int i = 0; i < named_windows.size (); ++i) { if (named_windows[i].id == window_name_override) { pos = i; break; } } if (pos < 0) { // not yet known: implicit registration -> create corresponding named_window_spec on the fly. registerNamedWindow (window_name_override, 0, attached ? RKWardMainWindow::getMain () : 0); pos = named_windows.size () - 1; } NamedWindow &nw = named_windows[pos]; if (nw.window != window) { if (nw.window) { // kill existing window (going to be replaced) // TODO: this is not really elegant, yet, as it will change tab-placement (for attached windows), and discard / recreate container (for detached windows) disconnect (nw.window, &QObject::destroyed, this, &RKWorkplace::namedWindowDestroyed); nw.window->deleteLater (); } nw.window = window; connect (nw.window, &QObject::destroyed, this, &RKWorkplace::namedWindowDestroyed); } named_windows[pos] = nw; // add window in the correct area if (nw.parent == RKWardMainWindow::getMain ()) attached = true; else if (nw.parent == 0) attached = false; else { // custom parent window->prepareToBeAttached (); window->setParent (nw.parent); // TODO: do this is somewhat inconsistent. But such windows are not attached to the main workplace view, which makes them rather behave detached. window->state = RKMDIWindow::Detached; // NOTE: The window is _not_ added to the window list/window history in this case. return; } } windows.append (window); connect (window, &QObject::destroyed, this, &RKWorkplace::removeWindow); connect (window, &RKMDIWindow::windowActivated, history, &RKMDIWindowHistory::windowActivated); if (window->isToolWindow () && !window->tool_window_bar) return; if (attached) attachWindow (window); else detachWindow (window, false); } void RKWorkplace::placeToolWindows() { RK_TRACE (APP); foreach (const RKToolWindowList::ToolWindowRepresentation& rep, RKToolWindowList::registeredToolWindows ()) { placeInToolWindowBar (rep.window, rep.default_placement); - getHistory ()->popLastWindow (rep.window); // windows send a spurious activation signal triggered from KPartsManager::addPart(), so we pop them, again } } void RKWorkplace::placeInToolWindowBar (RKMDIWindow *window, int position) { RK_TRACE (APP); RK_ASSERT (window->isToolWindow ()); bool needs_registration = (!window->tool_window_bar && (position != RKToolWindowList::Nowhere)); if ((position < 0) || (position >= TOOL_WINDOW_BAR_COUNT)) { RK_ASSERT (position == RKToolWindowList::Nowhere); // should never happen... position = RKToolWindowList::Nowhere; // ... but let's set this explicitly, in case of a broken workplace representation } if (position == RKToolWindowList::Nowhere) { if (window->tool_window_bar) window->tool_window_bar->removeWidget (window); } else { tool_window_bars[position]->addWidget (window); } if (!windows.contains (window)) addWindow (window, true); // first time we see this window else if (needs_registration) attachWindow (window); } void RKWorkplace::setWindowPlacementOverrides(const QString& placement, const QString& name, const QString& style) { RK_TRACE (APP); if (placement == "attached") window_placement_override = RKMDIWindow::Attached; else if (placement == "detached") window_placement_override = RKMDIWindow::Detached; else { RK_ASSERT (placement.isEmpty ()); window_placement_override = RKMDIWindow::AnyWindowState; } window_name_override = name; window_style_override = style; } void RKWorkplace::registerNamedWindow (const QString& id, QObject* owner, QWidget* parent, RKMDIWindow* window) { RK_TRACE (APP); NamedWindow nw; nw.id = id; nw.owner = owner; nw.parent = parent; nw.window = window; for (int i = 0; i < named_windows.size (); ++i) { RK_ASSERT (named_windows[i].id != id); } named_windows.append (nw); if (owner) connect (owner, &QObject::destroyed, this, &RKWorkplace::namedWindowOwnerDestroyed); if (window) connect (window, &QObject::destroyed, this, &RKWorkplace::namedWindowOwnerDestroyed); } RKMDIWindow* RKWorkplace::getNamedWindow (const QString& id) { RK_TRACE (APP); for (int i = 0; i < named_windows.size (); ++i) { if (named_windows[i].id == id) { return named_windows[i].window; } } return 0; } void RKWorkplace::namedWindowDestroyed (QObject* window) { RK_TRACE (APP); for (int i = 0; i < named_windows.size (); ++i) { if (named_windows[i].window == window) { if (!named_windows[i].owner) { named_windows.removeAt (i); return; } else { named_windows[i].window = 0; } } } } void RKWorkplace::namedWindowOwnerDestroyed (QObject* owner) { RK_TRACE (APP); for (int i = 0; i < named_windows.size (); ++i) { if (named_windows[i].owner == owner) { if (named_windows[i].window) { named_windows[i].window->deleteLater (); } named_windows.removeAt (i); return; } } } bool RKWorkplace::openAnyUrl (const QUrl &url, const QString &known_mimetype, bool force_external) { RK_TRACE (APP); if (url.scheme () == "rkward") { if (RKHTMLWindow::handleRKWardURL (url)) return true; } QMimeDatabase mdb; QMimeType mimetype; if (!known_mimetype.isEmpty ()) mimetype = mdb.mimeTypeForName (known_mimetype); else mimetype = mdb.mimeTypeForUrl (url); if (!force_external) { // NOTE: Currently a known mimetype implies that the URL is local or served from the local machine. // Thus, external web pages are *not* opened, here. Which is the behavior we want, although the implementation is ugly if (mimetype.inherits ("text/html")) { openHelpWindow (url, true); return true; // TODO } if (url.fileName ().toLower ().endsWith (QLatin1String (".rdata")) || url.fileName ().toLower ().endsWith (QLatin1String (".rda"))) { RKWardMainWindow::getMain ()->askOpenWorkspace (url); return true; // TODO } if (mimetype.inherits ("text/plain")) { return (openScriptEditor (url, QString ())); } RK_DEBUG (APP, DL_INFO, "Don't know how to handle mimetype %s.", qPrintable (mimetype.name ())); } if (KMessageBox::questionYesNo (this, i18n ("The url you are trying to open ('%1') is not a local file or the filetype is not supported by RKWard. Do you want to open the url in the default application?", url.toDisplayString ()), i18n ("Open in default application?")) != KMessageBox::Yes) { return false; } KRun *runner = new KRun (url, topLevelWidget()); // according to KRun-documentation, KRun will self-destruct when done. runner->setRunExecutables (false); return false; } RKMDIWindow* RKWorkplace::openScriptEditor (const QUrl &url, const QString& encoding, int flags, const QString &force_caption) { RK_TRACE (APP); // is this url already opened? if (!url.isEmpty ()) { RKWorkplaceObjectList script_windows = getObjectList (RKMDIWindow::CommandEditorWindow, RKMDIWindow::AnyWindowState); for (int i = 0; i < script_windows.size (); ++i) { QUrl ourl = static_cast (script_windows[i])->url (); if (url == ourl) { if (view ()->windowInActivePane (script_windows[i])) { script_windows[i]->activate (); return (script_windows[i]); } } } } RKCommandEditorWindow *editor = new RKCommandEditorWindow (view (), url, encoding, flags); if (!force_caption.isEmpty ()) editor->setCaption (force_caption); addWindow (editor); return (editor); } RKMDIWindow* RKWorkplace::openHelpWindow (const QUrl &url, bool only_once) { RK_TRACE (APP); if (url.isEmpty ()) { RK_ASSERT (false); return 0; } if (only_once) { RKWorkplaceObjectList help_windows = getObjectList (RKMDIWindow::HelpWindow, RKMDIWindow::AnyWindowState); for (int i = 0; i < help_windows.size (); ++i) { if (static_cast (help_windows[i])->url ().matches (url, QUrl::StripTrailingSlash | QUrl::NormalizePathSegments)) { if (view ()->windowInActivePane (help_windows[i])) { help_windows[i]->activate (); return (help_windows[i]); } } } } // if we're working with a window hint, try to _reuse_ the existing window, even if it did not get found, above if (!window_name_override.isEmpty ()) { for (int i = 0; i < named_windows.size (); ++i) { if (named_windows[i].id == window_name_override) { RKHTMLWindow *w = dynamic_cast (named_windows[i].window); if (w) { w->openURL (url); // w->activate (); // HACK: Keep preview windows from stealing focus return w; } break; } } } RKHTMLWindow *hw = new RKHTMLWindow (view (), RKHTMLWindow::HTMLHelpWindow); hw->openURL (url); addWindow (hw); return (hw); } RKMDIWindow* RKWorkplace::openOutputWindow (const QUrl &url) { RK_TRACE (APP); QList owins = RKOutputWindowManager::self ()->existingOutputWindows (); for (int i = 0; i < owins.size (); ++i) { if (view ()->windowInActivePane (owins[i])) { owins[i]->activate (); return (owins[i]); } } RKHTMLWindow* ret = RKOutputWindowManager::self ()->newOutputWindow (); addWindow (ret); return (ret); } void RKWorkplace::newX11Window (QWindow* window_to_embed, int device_number) { RK_TRACE (APP); RKCaughtX11Window *window = new RKCaughtX11Window (window_to_embed, device_number); window->state = RKMDIWindow::Detached; addWindow (window, false); } void RKWorkplace::newRKWardGraphisWindow (RKGraphicsDevice* dev, int device_number) { RK_TRACE (APP); RKCaughtX11Window *window = new RKCaughtX11Window (dev, device_number); window->state = RKMDIWindow::Detached; addWindow (window, false); } RKMDIWindow* RKWorkplace::newObjectViewer (RObject *object) { RK_TRACE (APP); RK_ASSERT (object); RKWorkplaceObjectList object_windows = getObjectList (RKMDIWindow::ObjectWindow, RKMDIWindow::AnyWindowState); for (int i = 0; i < object_windows.size (); ++i) { RObjectViewer *viewer = static_cast (object_windows[i]); if (viewer->object () == object) { if (view ()->windowInActivePane (viewer)) { viewer->activate (); return viewer; } } } RObjectViewer *ov = new RObjectViewer (view (), object); addWindow (ov); return ov; } bool RKWorkplace::canEditObject (RObject *object) { RK_TRACE (APP); if (object->isDataFrame ()) { return true; } else if (object->isVariable () && object->parentObject ()->isDataFrame ()) { return true; } return false; } RKEditor* RKWorkplace::editNewDataFrame (const QString &name) { RK_TRACE (APP); RKEditorDataFrame* ed = new RKEditorDataFrame (name, 0); addWindow (ed); ed->activate (); return ed; } RKEditor *RKWorkplace::editObject (RObject *object) { RK_TRACE (APP); RK_ASSERT (object); RObject *iobj = object; if (!iobj->isDataFrame ()) { if (iobj->isVariable () && iobj->parentObject ()->isDataFrame ()) { iobj = iobj->parentObject (); } else { return 0; } } RKEditor *ed = 0; QList existing_editors = object->editors (); for (int i = 0; i < existing_editors.size (); ++i) { RObject *eobj = existing_editors[i]->getObject (); if (eobj == iobj) { if (view ()->windowInActivePane (existing_editors[i])) { ed = existing_editors[i]; break; } } } if (!ed) { unsigned long size = 1; foreach (int dim, iobj->getDimensions ()) { size *= dim; } if ((RKSettingsModuleGeneral::warnLargeObjectThreshold () != 0) && (size > RKSettingsModuleGeneral::warnLargeObjectThreshold ())) { if (KMessageBox::warningContinueCancel (view (), i18n ("You are about to edit object \"%1\", which is very large (%2 fields). RKWard is not optimized to handle very large objects in the built in data editor. This will use a lot of memory, and - depending on your system - might be very slow. For large objects it is generally recommended to edit using command line means or to split into smaller chunks before editing. On the other hand, if you have enough memory, or the data is simple enough (numeric data is easier to handle, than factor), editing may not be a problem at all. You can configure this warning (or turn it off entirely) under Settings->Configure RKWard->General.\nReally edit object?", iobj->getFullName (), size), i18n ("About to edit very large object")) != KMessageBox::Continue) { return 0; } } ed = new RKEditorDataFrame (static_cast (iobj), 0); addWindow (ed); } ed->activate (); return ed; } void RKWorkplace::flushAllData () { RK_TRACE (APP); for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if ((*it)->type == RKMDIWindow::DataEditorWindow) { static_cast (*it)->flushChanges (); } } } bool RKWorkplace::closeWindow (RKMDIWindow *window) { RK_TRACE (APP); RK_ASSERT (windows.contains (window)); bool tool_window = window->isToolWindow (); bool closed = window->close (true); // all the rest should happen in removeWindow () if (closed && tool_window) windowRemoved (); // for regular windows, this happens in removeWindow(), already return closed; } void RKWorkplace::closeActiveWindow () { RK_TRACE (APP); RKMDIWindow *w = activeWindow (RKMDIWindow::Attached); if (w) closeWindow (w); } RKWorkplace::RKWorkplaceObjectList RKWorkplace::getObjectList (int type, int state) { RK_TRACE (APP); RKWorkplaceObjectList ret; for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if (((*it)->type & type) && ((*it)->state & state)) { ret.append ((*it)); } } return ret; } void RKWorkplace::closeAll (int type, int state) { RK_TRACE (APP); closeWindows (getObjectList (type, state)); } bool RKWorkplace::closeWindows (QList windows) { RK_TRACE (APP); bool allclosed = true; RKWardMainWindow::getMain ()->lockGUIRebuild (true); for (int i = windows.size () - 1; i >= 0; --i) { if (!closeWindow (windows[i])) allclosed = false; } RKWardMainWindow::getMain ()->lockGUIRebuild (false); return allclosed; } void RKWorkplace::removeWindow (QObject *object) { RK_TRACE (APP); RKMDIWindow *window = static_cast (object); // remove from history first (otherwise, we might find it there, when trying to activate a new window) history->removeWindow (window); // WARNING: the window is dead. Don't call any functions on it. RK_ASSERT (windows.contains (window)); windows.removeAll (window); // do this first! view()->removeWindow will call activePage() indirectly from setCaption, causing us to iterate over all known windows! if (view ()->hasWindow (window)) view ()->removeWindow (window, true); windowRemoved (); } void RKWorkplace::windowRemoved () { RK_TRACE (APP); if (activeWindow (RKMDIWindow::AnyWindowState) != 0) return; // some RKMDIWindow is already active QWidget *appWin = QApplication::activeWindow (); if (appWin && appWin != RKWardMainWindow::getMain () && !qobject_cast (appWin)) return; // a dialog window or the like is active // try to activate an attached document window, first RKMDIWindow *window = view ()->activePage (); if (window) { window->activate (true); return; } // some document window in the history? Try that. window = history->previousDocumentWindow (); if (window) { window->activate (true); return; } // now try to activate an attached (tool) window, if one is visible for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if ((*it)->isAttached ()) { if ((*it)->isVisible ()) { (*it)->activate (true); return; } } } // nothing, yet? Try *any* visible window for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if ((*it)->isVisible ()) { (*it)->activate (true); return; } } // give up } RKMDIWindow *RKWorkplace::activeWindow (RKMDIWindow::State state) { RK_TRACE (APP); RKMDIWindow *ret = 0; for (RKWorkplaceObjectList::const_iterator it = windows.constBegin (); it != windows.constEnd (); ++it) { if (!(state & ((*it)->state))) continue; if ((*it)->isActive ()) { ret = *it; break; } } return (ret); } QUrl checkAdjustRestoredUrl (const QString &_url, const QString old_base) { QUrl url = QUrl::fromUserInput (_url, QString (), QUrl::AssumeLocalFile); if (old_base.isEmpty ()) return (url); QUrl new_base_url = RKWorkplace::mainWorkplace ()->workspaceURL ().adjusted (QUrl::RemoveFilename | QUrl::NormalizePathSegments); if (new_base_url.isEmpty ()) return (url); QUrl old_base_url (old_base); if (old_base_url == new_base_url) return (url); // TODO: Should we also care about non-local files? In theory: yes, but stat'ing remote files for existence can take a long time. if (!(old_base_url.isLocalFile () && new_base_url.isLocalFile () && url.isLocalFile ())) return (url); // if the file exists, unadjusted, return it. if (QFileInfo (url.toLocalFile ()).exists ()) return (url); // check whether a file exists for the adjusted url QString relative = QDir (new_base_url.path ()).absoluteFilePath (QDir (old_base_url.path ()).relativeFilePath (url.path ())); if (QFileInfo (relative).exists ()) return (QUrl::fromLocalFile (relative)); return (url); } QString RKWorkplace::makeItemDescription (RKMDIWindow *win) const { QString type, specification; QStringList params; if (win->isType (RKMDIWindow::DataEditorWindow)) { type = "data"; specification = static_cast (win)->getObject ()->getFullName (); } else if (win->isType (RKMDIWindow::CommandEditorWindow)) { type = "script"; specification = static_cast (win)->url ().url (); if (specification.isEmpty ()) specification = static_cast (win)->id (); } else if (win->isType (RKMDIWindow::OutputWindow)) { type = "output"; specification = static_cast (win)->url ().url (); } else if (win->isType (RKMDIWindow::HelpWindow)) { type = "help"; specification = static_cast (win)->restorableUrl ().url (); } else if (win->isToolWindow ()) { type = RKToolWindowList::idOfWindow (win); } else if (win->isType (RKMDIWindow::ObjectWindow)) { type = "object"; specification = static_cast (win)->object ()->getFullName (); } if (!type.isEmpty ()) { if (!win->isAttached ()) { params.append (QString ("detached,") + QString::number (win->x ()) + ',' + QString::number (win->y ()) + ',' + QString::number (win->width ()) + ',' + QString::number (win->height ())); } if (win->isToolWindow ()) { int sidebar = RKToolWindowList::Nowhere; for (int i = 0; i < TOOL_WINDOW_BAR_COUNT; ++i) { if (win->tool_window_bar == tool_window_bars[i]) { sidebar = i; break; } } params.append (QString ("sidebar,") + QString::number (sidebar)); } return (type + "::" + params.join (":") + "::" + specification); } return QString (); } struct ItemSpecification { QString type; QString specification; QStringList params; }; ItemSpecification parseItemDescription (const QString &description) { ItemSpecification ret; // Item format for rkward <= 0.5.4: "type:specification" // Item format for rkward <= 0.5.5: "type::[optional_params1[:optional_params2[:...]]]::specification" int typeend = description.indexOf (':'); if ((typeend < 0) || (typeend >= (description.size () - 1))) { RK_ASSERT (false); return ret; } ret.type = description.left (typeend); if (description.at (typeend + 1) == ':') { // rkward 0.5.5 or later int specstart = description.indexOf ("::", typeend + 2); if (specstart < typeend) { RK_ASSERT (false); return ret; } ret.params = description.mid (typeend + 2, specstart - typeend - 2).split (':', QString::SkipEmptyParts); ret.specification = description.mid (specstart + 2); } else { ret.specification = description.mid (typeend + 1); } return ret; } RKMDIWindow* restoreDocumentWindowInternal (RKWorkplace* wp, ItemSpecification spec, const QString &base) { RK_TRACE (APP); RKMDIWindow *win = 0; if (spec.type == "data") { RObject *object = RObjectList::getObjectList ()->findObject (spec.specification); if (object) win = wp->editObject (object); } else if (spec.type == "script") { QUrl url = checkAdjustRestoredUrl (spec.specification, base); win = wp->openScriptEditor (url, QString ()); } else if (spec.type == "output") { win = wp->openOutputWindow (checkAdjustRestoredUrl (spec.specification, base)); } else if (spec.type == "help") { win = wp->openHelpWindow (checkAdjustRestoredUrl (spec.specification, base), true); } else if (spec.type == "object") { RObject *object = RObjectList::getObjectList ()->findObject (spec.specification); if (object) win = wp->newObjectViewer (object); } return win; } bool RKWorkplace::restoreDocumentWindow (const QString &description, const QString &base) { RK_TRACE (APP); return (restoreDocumentWindowInternal (this, parseItemDescription (description), base) != 0); } QStringList RKWorkplace::makeWorkplaceDescription () { RK_TRACE (APP); QStringList workplace_description; // first, save the base directory of the workplace. This allows us to cope better with moved workspaces while restoring. QUrl base_url = workspaceURL ().adjusted (QUrl::RemoveFilename); if (base_url.isLocalFile () && !base_url.isEmpty ()) workplace_description.append ("base::::" + base_url.url ()); // window order in the workplace view may have changed with respect to our list. Thus we first generate a properly sorted list RKWorkplaceObjectList list = getObjectList (RKMDIWindow::DocumentWindow, RKMDIWindow::Detached); foreach (RKMDIWindow *win, list) { QString desc = makeItemDescription (win); if (!desc.isEmpty ()) workplace_description.append (desc); } workplace_description.append (QStringLiteral ("layout::::") + wview->listLayout ()); workplace_description.append (wview->listContents ()); list = getObjectList (RKMDIWindow::ToolWindow, RKMDIWindow::AnyWindowState); foreach (RKMDIWindow *win, list) { QString desc = makeItemDescription (win); if (!desc.isEmpty ()) workplace_description.append (desc); } return workplace_description; } void RKWorkplace::saveWorkplace (RCommandChain *chain) { RK_TRACE (APP); // TODO: This is still a mess. All workplace-related settings, including the workspaceConfig(), should be saved to a single place, and in // standard KConfig format. if (RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace) return; RKGlobals::rInterface ()->issueCommand ("rk.save.workplace(description=" + RObject::rQuote (makeWorkplaceDescription().join ("\n")) + ')', RCommand::App, i18n ("Save Workplace layout"), 0, 0, chain); } void RKWorkplace::restoreWorkplace (RCommandChain *chain, bool merge) { RK_TRACE (APP); if (RKSettingsModuleGeneral::workplaceSaveMode () != RKSettingsModuleGeneral::SaveWorkplaceWithWorkspace) return; QString no_close_windows; if (merge) no_close_windows = "close.windows = FALSE"; RKGlobals::rInterface ()->issueCommand ("rk.restore.workplace(" + no_close_windows + ')', RCommand::App, i18n ("Restore Workplace layout"), 0, 0, chain); } void RKWorkplace::restoreWorkplace (const QStringList &description) { RK_TRACE (APP); RKWardMainWindow::getMain ()->lockGUIRebuild (true); QString base; for (int i = 0; i < description.size (); ++i) { ItemSpecification spec = parseItemDescription (description[i]); RKMDIWindow *win = 0; if (spec.type == "base") { RK_ASSERT (i == 0); base = spec.specification; } else if (restoreDocumentWindowInternal (this, spec, base)) { // it was restored. nothing else to do } else if (spec.type == "layout") { view ()->restoreLayout (spec.specification); } else if (spec.type == "pane_end") { view ()->nextPane (); } else { win = RKToolWindowList::findToolWindowById (spec.type); RK_ASSERT (win); } // apply generic window parameters if (win) { for (int p = 0; p < spec.params.size (); ++p) { if (spec.params[p].startsWith (QLatin1String ("sidebar"))) { int position = spec.params[p].section (',', 1).toInt (); placeInToolWindowBar (win, position); } if (spec.params[p].startsWith (QLatin1String ("detached"))) { QStringList geom = spec.params[p].split (','); win->hide (); win->setGeometry (geom.value (1).toInt (), geom.value (2).toInt (), geom.value (3).toInt (), geom.value (4).toInt ()); detachWindow (win); } } } } view ()->purgeEmptyPanes (); RKWardMainWindow::getMain ()->lockGUIRebuild (false); } void RKWorkplace::splitAndAttachWindow (RKMDIWindow* source) { RK_TRACE (APP); RK_ASSERT (source); if (source->isType (RKMDIWindow::CommandEditorWindow)) { QUrl url = static_cast (source)->url (); openScriptEditor (url, QString ()); } else if (source->isType (RKMDIWindow::HelpWindow)) { openHelpWindow (static_cast (source)->url ()); } else if (source->isType (RKMDIWindow::OutputWindow)) { openOutputWindow (static_cast (source)->url ()); } else if (source->isType (RKMDIWindow::DataEditorWindow)) { editObject (static_cast (source)->getObject ()); } else if (source->isType (RKMDIWindow::ObjectWindow)) { newObjectViewer (static_cast (source)->object ()); } else { openHelpWindow (QUrl ("rkward://page/rkward_split_views")); } } ///////////////////////// END RKWorkplace //////////////////////////// ///////////////////// BEGIN RKMDIWindowHistory /////////////////////// #include "../misc/rkstandardicons.h" #include class RKMDIWindowHistoryWidget : public QListWidget { public: RKMDIWindowHistoryWidget () : QListWidget (0) { RK_TRACE (APP); current = 0; setFocusPolicy (Qt::StrongFocus); setWindowFlags (Qt::Popup); } ~RKMDIWindowHistoryWidget () { RK_TRACE (APP); } void update (const QList windows) { RK_TRACE (APP); clear (); _windows = windows; for (int i = windows.count () - 1; i >= 0; --i) { // most recent top RKMDIWindow *win = windows[i]; QListWidgetItem *item = new QListWidgetItem (this); item->setIcon (RKStandardIcons::iconForWindow (win)); item->setText (win->windowTitle ()); } if (current >= count ()) current = count () - 1; if (current < 0) { hide (); return; } setCurrentRow (current); } void next () { RK_TRACE (APP); if (--current < 0) current = count () - 1; setCurrentRow (current); } void prev () { RK_TRACE (APP); if (++current >= count ()) current = 0; setCurrentRow (current); } private: void focusOutEvent (QFocusEvent *) override { RK_TRACE (APP); deleteLater (); } void keyReleaseEvent (QKeyEvent *ev) override { RK_TRACE (APP); if (ev->modifiers () == Qt::NoModifier) { commit (); } } void mouseReleaseEvent (QMouseEvent *ev) override { RK_TRACE (APP); // HACK to get by without slots, and the associated moc'ing QListWidget::mouseReleaseEvent (ev); commit (); } void commit () { RK_TRACE (APP); current = currentRow (); if ((current > 0) && (current < count ())) { RKMDIWindow *win = _windows.value (count () - 1 - current); RK_ASSERT (win); win->activate (true); } deleteLater (); } int current; QList _windows; }; RKMDIWindowHistory::RKMDIWindowHistory (QObject *parent) : QObject (parent) { RK_TRACE (APP); switcher = 0; } RKMDIWindowHistory::~RKMDIWindowHistory () { RK_TRACE (APP); RK_DEBUG (APP, DL_DEBUG, "Remaining windows in history: %d", recent_windows.count ()); } void RKMDIWindowHistory::windowActivated (RKMDIWindow *window) { RK_TRACE (APP); if (!window) return; if (!recent_windows.isEmpty () && (window == recent_windows.last ())) return; // update lists recent_windows.removeAll (window); // remove dupes recent_windows.append (window); updateSwitcher (); emit activeWindowChanged (window); } void RKMDIWindowHistory::next (QAction* prev_action, QAction *next_action) { RK_TRACE (APP); if (recent_windows.isEmpty ()) return; getSwitcher (prev_action, next_action)->next (); } void RKMDIWindowHistory::prev (QAction* prev_action, QAction *next_action) { RK_TRACE (APP); if (recent_windows.isEmpty ()) return; getSwitcher (prev_action, next_action)->prev (); } RKMDIWindow* RKMDIWindowHistory::previousDocumentWindow () { RK_TRACE (APP); for (int i = recent_windows.count () - 1; i >= 0; --i) { if (!recent_windows[i]->isToolWindow ()) return (recent_windows[i]); } return 0; } void RKMDIWindowHistory::updateSwitcher () { RK_TRACE (APP); if (switcher) switcher->update (recent_windows); } void RKMDIWindowHistory::removeWindow (RKMDIWindow *window) { RK_TRACE (APP); recent_windows.removeAll (window); updateSwitcher (); } RKMDIWindowHistoryWidget* RKMDIWindowHistory::getSwitcher (QAction* prev_action, QAction *next_action) { RK_TRACE (APP); if (switcher) return switcher; switcher = new RKMDIWindowHistoryWidget (); connect (switcher, &QObject::destroyed, this, &RKMDIWindowHistory::switcherDestroyed); switcher->addAction (prev_action); switcher->addAction (next_action); switcher->update (recent_windows); switcher->show (); QWidget *act = QApplication::activeWindow (); if (act) { int center_x = act->x () + act->width () / 2; int center_y = act->y () + act->height () / 2; switcher->move (center_x - switcher->width () / 2, center_y - switcher->height () / 2); } else { RK_ASSERT (false); } switcher->setFocus (); return switcher; } void RKMDIWindowHistory::switcherDestroyed () { RK_TRACE (APP); RK_ASSERT (switcher); switcher = 0; } void RKMDIWindowHistory::popLastWindow (RKMDIWindow* match) { RK_TRACE (APP); if (recent_windows.isEmpty ()) return; else if (recent_windows.last () == match) recent_windows.removeLast (); updateSwitcher (); }