diff --git a/rkward/misc/rkcommonfunctions.cpp b/rkward/misc/rkcommonfunctions.cpp index d65c9241..5df8ec5f 100644 --- a/rkward/misc/rkcommonfunctions.cpp +++ b/rkward/misc/rkcommonfunctions.cpp @@ -1,263 +1,276 @@ /*************************************************************************** rkcommonfunctions - description ------------------- begin : Mon Oct 17 2005 copyright : (C) 2005-2018 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 "rkcommonfunctions.h" #include #include #include #include #include #include #include #include "../settings/rksettingsmodulegeneral.h" #include "../version.h" #include "../debug.h" namespace RKCommonFunctions { void removeNamedElementsRecursive (const QStringList &names, QDomNode &parent) { QDomNode nchild; for (QDomNode child = parent.firstChild (); !child.isNull (); child = nchild) { removeNamedElementsRecursive (names, child); nchild = child.nextSibling (); // need to fetch next sibling here, as we might remove the child below if (child.isElement ()) { QDomElement e = child.toElement (); if (names.contains (e.attribute ("name"))) { parent.removeChild (child); } } } } void removeContainers (KXMLGUIClient *from, const QStringList &names, bool recursive) { QDomDocument doc = from->xmlguiBuildDocument (); if (doc.documentElement ().isNull ()) doc = from->domDocument (); QDomElement e = doc.documentElement (); removeNamedElementsRecursive (names, e); from->setXMLGUIBuildDocument (doc); if (recursive) { QList children = from->childClients (); QList::const_iterator it; for (it = children.constBegin (); it != children.constEnd (); ++it) { removeContainers ((*it), names, true); } } } - void moveContainer (KXMLGUIClient *client, const QString &tagname, const QString &name, const QString &to_name, bool recursive) { + void moveContainer (KXMLGUIClient *client, const QString &tagname, const QString &name, const QString &to_name, bool recursive, bool flatten) { + // recurse first + if (recursive) { + QList children = client->childClients (); + QList::const_iterator it; + for (it = children.constBegin (); it != children.constEnd (); ++it) { + moveContainer (*it, tagname, name, to_name, true); + } + } + QDomDocument doc = client->xmlguiBuildDocument (); if (doc.documentElement ().isNull ()) doc = client->domDocument (); // find the given elements QDomElement e = doc.documentElement (); QDomElement from_elem; QDomElement to_elem; - QDomNodeList list = e.elementsByTagName (tagname); + const QString name_attr ("name"); + const QDomNodeList list = e.elementsByTagName (tagname); int count = list.count (); for (int i = 0; i < count; ++i) { - QDomElement elem = list.item (i).toElement (); + const QDomElement elem = list.item (i).toElement (); if (elem.isNull ()) continue; - if (elem.attribute ("name") == name) { + if (elem.attribute (name_attr) == name) { from_elem = elem; - } else if (elem.attribute ("name") == to_name) { + } else if (elem.attribute (name_attr) == to_name) { to_elem = elem; } } + if (from_elem.isNull ()) return; + if (to_elem.isNull ()) { // if no place to move to, just rename (Note: children will be moved, below) + to_elem = from_elem.cloneNode (false).toElement (); + to_elem.setAttribute (name_attr, to_name); + from_elem.parentNode().appendChild(to_elem); + } // move from_elem.parentNode ().removeChild (from_elem); - to_elem.appendChild (from_elem); + if (flatten) { + while (from_elem.hasChildNodes()) { + to_elem.appendChild (from_elem.firstChild()); + } + } else { + to_elem.appendChild (from_elem); + } // set result client->setXMLGUIBuildDocument (doc); - - // recurse - if (recursive) { - QList children = client->childClients (); - QList::const_iterator it; - for (it = children.constBegin (); it != children.constEnd (); ++it) { - moveContainer (*it, tagname, name, to_name, true); - } - } } QString getCurrentSymbol (const QString &context_line, int cursor_pos, bool strict) { if (context_line.isEmpty ()) return (QString ()); int current_word_start; int current_word_end; getCurrentSymbolOffset (context_line, cursor_pos, strict, ¤t_word_start, ¤t_word_end); // if both return the same position, we're on a non-word. if (current_word_start == current_word_end) return (QString ()); return (context_line.mid (current_word_start, current_word_end - current_word_start)); } int quoteEndPosition (const QChar& quote_char, const QString& haystack, int from) { int line_end = haystack.length () - 1; for (int i=from; i <= line_end; ++i) { QChar c = haystack.at (i); if (c == quote_char) return i; if (c == '\\') { ++i; continue; } } return -1; // quote end not found } void getCurrentSymbolOffset (const QString &context_line, int cursor_pos, bool strict, int *start, int *end) { *start = 0; int line_end = context_line.length () - 1; *end = line_end + 1; if (cursor_pos > line_end) cursor_pos = line_end; for (int i=0; i <= line_end; ++i) { QChar c = context_line.at (i); if (c == '\'' || c == '\"' || c == '`') { i = quoteEndPosition (c, context_line, i+1); if (i < 0) break; continue; } else if (c.isLetterOrNumber () || c == '.' || c == '_') { continue; } else if (!strict) { if (c == '$' || c == ':' || c == '[' || c == ']' || c == '@') continue; } // if we did not hit a continue, yet, that means we are on a potential symbol boundary if (i <= cursor_pos) *start = i+1; else { *end = i; break; } } } QString getRKWardDataDir () { static QString rkward_data_dir; if (rkward_data_dir.isNull ()) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) QStringList candidates = QStandardPaths::locateAll (QStandardPaths::AppDataLocation, "resource.ver"); candidates += QStandardPaths::locateAll (QStandardPaths::AppDataLocation, "rkward/resource.ver"); // Well, isn't this just silly? AppDataLocation may or may not contain the application name (on Mac) #else QStringList candidates = QStandardPaths::locateAll (QStandardPaths::GenericDataLocation, "resource.ver"); // Fails on Mac with unpatched Qt 5.10 (and before). See https://mail.kde.org/pipermail/kde-frameworks-devel/2018-May/063151.html #endif for (int i = 0; i < candidates.size (); ++i) { QFile resource_ver (candidates[i]); if (resource_ver.open (QIODevice::ReadOnly) && (resource_ver.read (100).trimmed () == RKWARD_VERSION)) { rkward_data_dir = candidates[i].replace ("resource.ver", QString ()); return rkward_data_dir; } } rkward_data_dir = ""; // prevents checking again RK_DEBUG(APP, DL_WARNING, "resource.ver not found. Data path(s): %s", qPrintable (QStandardPaths::standardLocations (QStandardPaths::AppDataLocation).join (':'))); } return rkward_data_dir; } QString getUseableRKWardSavefileName (const QString &prefix, const QString &postfix) { QDir dir (RKSettingsModuleGeneral::filesPath ()); int i=0; while (true) { QString candidate = prefix + QString::number (i) + postfix; if (!dir.exists (candidate)) { return dir.filePath (candidate); } i++; } } QString escape (const QString &in) { QString out; for (int i = 0; i < in.size (); ++i) { QChar c = in[i]; if (c == '\\') out.append ("\\\\"); else if (c == '\n') out.append ("\\n"); else if (c == '\t') out.append ("\\t"); else if (c == '"') out.append ("\\\""); else out.append (c); } return out; } QString unescape (const QString &in) { QString out; out.reserve (in.size ()); for (int i = 0; i < in.size (); ++i) { QChar c = in[i]; if (c == '\\') { ++i; if (i >= in.size ()) break; c = in[i]; if (c == 'n') c = '\n'; else if (c == 't') c = '\t'; // NOTE: Quote (") and backslash (\) are escaped by the same symbol, i.e. c = in[i] is good enough } out.append (c); } return out; } void setTips (const QString tip, QWidget *first, QWidget *second, QWidget *third) { for (int i=0; i < 3; ++i) { QWidget *w = first; if (i == 1) w = second; if (i == 2) w = third; if (!w) return; w->setToolTip (tip); w->setWhatsThis (tip); } } QString noteSettingsTakesEffectAfterRestart () { return (i18n ("

Note: This setting does not take effect until you restart RKWard.

")); } #ifdef Q_OS_WIN # include # include #endif QString windowsShellScriptSafeCommand (const QString &orig) { #ifdef Q_OS_WIN // credits to http://erasmusjam.wordpress.com/2012/10/01/get-8-3-windows-short-path-names-in-a-qt-application/ QByteArray input (sizeof (wchar_t) * (orig.size()+1), '\0'); // wchar_t input[orig.size()+1]; -- No: MSVC (2013) does not support variable length arrays. Oh dear... orig.toWCharArray ((wchar_t*) input.data ()); long length = GetShortPathName ((wchar_t*) input.data (), NULL, 0); QByteArray output (sizeof (wchar_t) * (length), '\0'); GetShortPathName ((wchar_t*) input.data (), (wchar_t*) output.data (), length); return QString::fromWCharArray ((wchar_t*) output.data (), length-1); #else return orig; #endif } } // namespace diff --git a/rkward/misc/rkcommonfunctions.h b/rkward/misc/rkcommonfunctions.h index 34683fc9..650cd16c 100644 --- a/rkward/misc/rkcommonfunctions.h +++ b/rkward/misc/rkcommonfunctions.h @@ -1,66 +1,66 @@ /*************************************************************************** rkcommonfunctions - description ------------------- begin : Mon Oct 17 2005 - copyright : (C) 2005, 2006, 2007, 2009, 2010, 2011 by Thomas Friedrichsmeier + copyright : (C) 2005-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 RKCOMMONFUNCTIONS_H #define RKCOMMONFUNCTIONS_H #include class QStringList; class QString; class QDomNode; class KXMLGUIClient; class QWidget; /** Some common static helper functions that don't really belong to any class in particular. If ever we have more than a dozen or so functions in here, we should probably split this file up. Until then, there's no real need. @author Thomas Friedrichsmeier */ namespace RKCommonFunctions { /** remove QDomElements with attribute 'name="..."' from QDomNode parent, where "..." is any of the strings in names */ void removeNamedElementsRecursive (const QStringList &names, QDomNode &parent); /** remove containers (actions, menus, etc.) with attribute 'name="..."' from KXMLGUIClient from s XML gui, where "..." is any of the strings in names. If recursive, also removes those containers from child clients. */ void removeContainers (KXMLGUIClient *from, const QStringList &names, bool recursive); /** move container (action, menu, etc.) with tagname "tagname" and attribute 'name="..."' to be a child node of the tag with tagname=tagname and attribute name=to_name. Can be used to make a top-level menu a sub-menu of another menu instead */ - void moveContainer (KXMLGUIClient *client, const QString &tagname, const QString &name, const QString &to_name, bool recursive); + void moveContainer (KXMLGUIClient *client, const QString &tagname, const QString &name, const QString &to_name, bool recursive, bool flatten=false); /** Get the base directory where RKWard data files are stored */ QString getRKWardDataDir (); /** Get a suitable file name in the RKWard data directory */ QString getUseableRKWardSavefileName (const QString &prefix, const QString &postfix); /** given a context line, find the end of a quote started by quote_char. @returns -1 if not end of quote was found. */ int quoteEndPosition (const QChar& quote_char, const QString& haystack, int from = 0); /** given the context line, find what looks like an R symbol */ QString getCurrentSymbol (const QString &context_line, int cursor_pos, bool strict=true); /** like get current symbol, but merely returns the start and end position of the current symbol */ void getCurrentSymbolOffset (const QString &context_line, int cursor_pos, bool strict, int *start, int *end); /** escape special chars in a QString */ QString escape (const QString &in); /** reverse of escape () */ QString unescape (const QString &in); /** simultaneously sets tool tips and what's this tips on up to three QWidgets */ void setTips (const QString tip, QWidget *first, QWidget *second=0, QWidget *third=0); QString noteSettingsTakesEffectAfterRestart (); /** Passing commands as part of arguments to windows shell scripts will fail miserably for paths with spaces or special characters. Transform to short path names for safety. No-op on sane platforms.*/ QString windowsShellScriptSafeCommand (const QString &orig); }; #endif diff --git a/rkward/windows/katepluginintegration.cpp b/rkward/windows/katepluginintegration.cpp index 79f73179..a8fdf0a9 100644 --- a/rkward/windows/katepluginintegration.cpp +++ b/rkward/windows/katepluginintegration.cpp @@ -1,525 +1,526 @@ /*************************************************************************** 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 "../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; } 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); 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); 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 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 + RKCommonFunctions::removeContainers(client, QStringList() << "search_in_files", true); } } + RKCommonFunctions::moveContainer(client, "Menu", "tools", "edit", true, true); } 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); KXMLGUIClient* hacked_parent = this; 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(client); fixupPluginUI(id, i, client, window); hacked_parent->insertChildClient(client); } // TODO: If child clients were added to the window, itself, we need to tell the main window to rebuild. 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: Don't forget to make sure to emit all the signals! // - MainWindow signals -// - pluginDeleted // TODO: Apply plugin specific hacks as needed (e.g. moving "Tool" menu, removing broken actions) // 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"