1 /*
2     SPDX-FileCopyrightText: 2015 Milian Wolff <mail@milianw.de>
3 
4     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 
7 #include "ktexteditorpluginintegration.h"
8 
9 #include <QWidget>
10 #include <QVBoxLayout>
11 #include <QStackedLayout>
12 
13 #include <KParts/MainWindow>
14 #include <KTextEditor/View>
15 #include <KTextEditor/Editor>
16 #include <KTextEditor/Application>
17 
18 #include <sublime/area.h>
19 #include <sublime/view.h>
20 #include <sublime/viewbarcontainer.h>
21 
22 #include "core.h"
23 #include "uicontroller.h"
24 #include "documentcontroller.h"
25 #include "plugincontroller.h"
26 #include "mainwindow.h"
27 #include "textdocument.h"
28 
29 #include <util/objectlist.h>
30 
31 using namespace KDevelop;
32 
33 namespace {
34 
toKteWrapper(KParts::MainWindow * window)35 KTextEditor::MainWindow *toKteWrapper(KParts::MainWindow *window)
36 {
37     if (auto mainWindow = qobject_cast<KDevelop::MainWindow*>(window)) {
38         return mainWindow->kateWrapper() ? mainWindow->kateWrapper()->interface() : nullptr;
39     } else {
40         return nullptr;
41     }
42 }
43 
toKteView(Sublime::View * view)44 KTextEditor::View *toKteView(Sublime::View *view)
45 {
46     if (auto textView = qobject_cast<KDevelop::TextView*>(view)) {
47         return textView->textView();
48     } else {
49         return nullptr;
50     }
51 }
52 
53 class ToolViewFactory;
54 
55 /**
56  * This HACK is required to massage the KTextEditor plugin API into the
57  * GUI concepts we apply in KDevelop. Kate does not allow the user to
58  * delete tool views and then readd them. We do. To support our use case
59  * we prevent the widget we return to KTextEditor plugins from
60  * MainWindow::createToolView from getting destroyed. This widget class
61  * unsets the parent of the so called container in its dtor. The
62  * ToolViewFactory handles the ownership and destroys the kate widget
63  * as needed.
64  */
65 class KeepAliveWidget : public QWidget
66 {
67     Q_OBJECT
68 public:
KeepAliveWidget(ToolViewFactory * factory,QWidget * parent=nullptr)69     explicit KeepAliveWidget(ToolViewFactory *factory, QWidget *parent = nullptr)
70         : QWidget(parent)
71         , m_factory(factory)
72     {
73     }
74 
75     ~KeepAliveWidget() override;
76 
77 private:
78     ToolViewFactory* const m_factory;
79 };
80 
81 class ToolViewFactory : public QObject, public KDevelop::IToolViewFactory
82 {
83     Q_OBJECT
84 public:
ToolViewFactory(const QString & text,const QIcon & icon,const QString & identifier,KTextEditor::MainWindow::ToolViewPosition pos)85     ToolViewFactory(const QString &text, const QIcon &icon, const QString &identifier,
86                     KTextEditor::MainWindow::ToolViewPosition pos)
87         : m_text(text)
88         , m_icon(icon)
89         , m_identifier(identifier)
90         , m_container(new QWidget)
91         , m_pos(pos)
92     {
93         m_container->setLayout(new QVBoxLayout);
94     }
95 
~ToolViewFactory()96     ~ToolViewFactory() override
97     {
98         delete m_container;
99     }
100 
create(QWidget * parent=nullptr)101     QWidget *create(QWidget *parent = nullptr) override
102     {
103         auto widget = new KeepAliveWidget(this, parent);
104         widget->setWindowTitle(m_text);
105         widget->setWindowIcon(m_icon);
106         widget->setLayout(new QVBoxLayout);
107         widget->layout()->addWidget(m_container);
108         widget->addActions(m_container->actions());
109         return widget;
110     }
111 
defaultPosition() const112     Qt::DockWidgetArea defaultPosition() const override
113     {
114         switch (m_pos) {
115             case KTextEditor::MainWindow::Left:
116                 return Qt::LeftDockWidgetArea;
117             case KTextEditor::MainWindow::Right:
118                 return Qt::RightDockWidgetArea;
119             case KTextEditor::MainWindow::Top:
120                 return Qt::TopDockWidgetArea;
121             case KTextEditor::MainWindow::Bottom:
122                 return Qt::BottomDockWidgetArea;
123         }
124         Q_UNREACHABLE();
125     }
126 
id() const127     QString id() const override
128     {
129         return m_identifier;
130     }
131 
container() const132     QWidget *container() const
133     {
134         return m_container;
135     }
136 
137 private:
138     const QString m_text;
139     const QIcon m_icon;
140     const QString m_identifier;
141     QPointer<QWidget> m_container;
142     const KTextEditor::MainWindow::ToolViewPosition m_pos;
143     friend class KeepAliveWidget;
144 };
145 
~KeepAliveWidget()146 KeepAliveWidget::~KeepAliveWidget()
147 {
148     // if the container is still valid, unparent it to prevent it from getting deleted
149     // this happens when the user removes a tool view
150     // on shutdown, the container does get deleted, thus we must guard against that.
151     if (m_factory->container()) {
152         Q_ASSERT(m_factory->container()->parentWidget() == this);
153         m_factory->container()->setParent(nullptr);
154     }
155 }
156 
157 }
158 
159 namespace KTextEditorIntegration {
160 
Application(QObject * parent)161 Application::Application(QObject *parent)
162     : QObject(parent)
163 {
164 }
165 
~Application()166 Application::~Application()
167 {
168     KTextEditor::Editor::instance()->setApplication(nullptr);
169 }
170 
activeMainWindow() const171 KTextEditor::MainWindow *Application::activeMainWindow() const
172 {
173     return toKteWrapper(Core::self()->uiController()->activeMainWindow());
174 }
175 
mainWindows() const176 QList<KTextEditor::MainWindow *> Application::mainWindows() const
177 {
178     return {activeMainWindow()};
179 }
180 
closeDocument(KTextEditor::Document * document) const181 bool Application::closeDocument(KTextEditor::Document *document) const
182 {
183     const auto& openDocuments = Core::self()->documentControllerInternal()->openDocuments();
184     for (auto doc : openDocuments) {
185         if (doc->textDocument() == document) {
186             return doc->close();
187         }
188     }
189     return false;
190 }
191 
plugin(const QString & id) const192 KTextEditor::Plugin *Application::plugin(const QString &id) const
193 {
194     auto kdevPlugin = Core::self()->pluginController()->loadPlugin(id);
195     const auto plugin = dynamic_cast<Plugin*>(kdevPlugin);
196     return plugin ? plugin->interface() : nullptr;
197 }
198 
documents()199 QList<KTextEditor::Document *> Application::documents()
200 {
201     QList<KTextEditor::Document *> l;
202     const auto openDocuments = Core::self()->documentControllerInternal()->openDocuments();
203     l.reserve(openDocuments.size());
204     for (auto* d : openDocuments) {
205         l << d->textDocument();
206     }
207     return l;
208 }
209 
openUrl(const QUrl & url,const QString & encoding)210 KTextEditor::Document *Application::openUrl(const QUrl &url, const QString &encoding)
211 {
212     Q_UNUSED(encoding);
213 
214     auto documentController = Core::self()->documentControllerInternal();
215     auto doc = url.isEmpty() ? documentController->openDocumentFromText(QString()) : documentController->openDocument(url);
216     return doc->textDocument();
217 }
218 
MainWindow(KDevelop::MainWindow * mainWindow)219 MainWindow::MainWindow(KDevelop::MainWindow *mainWindow)
220     : QObject(mainWindow)
221     , m_mainWindow(mainWindow)
222     , m_interface(new KTextEditor::MainWindow(this))
223 {
224     connect(mainWindow, &Sublime::MainWindow::viewAdded, this, [this] (Sublime::View *view) {
225         if (auto kteView = toKteView(view)) {
226             emit m_interface->viewCreated(kteView);
227         }
228     });
229     connect(mainWindow, &Sublime::MainWindow::activeViewChanged, this, [this] (Sublime::View *view) {
230         auto kteView = toKteView(view);
231         emit m_interface->viewChanged(kteView);
232 
233         if (auto viewBar = m_viewBars.value(kteView)) {
234             m_mainWindow->viewBarContainer()->setCurrentViewBar(viewBar);
235         }
236     });
237 }
238 
239 MainWindow::~MainWindow() = default;
240 
createToolView(KTextEditor::Plugin * plugin,const QString & identifier,KTextEditor::MainWindow::ToolViewPosition pos,const QIcon & icon,const QString & text)241 QWidget *MainWindow::createToolView(KTextEditor::Plugin* plugin, const QString &identifier,
242                                     KTextEditor::MainWindow::ToolViewPosition pos,
243                                     const QIcon &icon, const QString &text)
244 {
245     auto factory = new ToolViewFactory(text, icon, identifier, pos);
246     Core::self()->uiController()->addToolView(text, factory);
247     connect(plugin, &QObject::destroyed, this, [=] {
248         Core::self()->uiController()->removeToolView(factory);
249     });
250     return factory->container();
251 }
252 
guiFactory() const253 KXMLGUIFactory *MainWindow::guiFactory() const
254 {
255     return m_mainWindow->guiFactory();
256 }
257 
window() const258 QWidget *MainWindow::window() const
259 {
260     return m_mainWindow;
261 }
262 
views() const263 QList<KTextEditor::View *> MainWindow::views() const
264 {
265     QList<KTextEditor::View *> kteViews;
266     const auto areas = m_mainWindow->areas();
267     for (auto* area : areas) {
268         const auto views = area->views();
269         for (auto* view : views) {
270             if (auto kteView = toKteView(view)) {
271                 kteViews << kteView;
272             }
273         }
274     }
275     return kteViews;
276 }
277 
activeView() const278 KTextEditor::View *MainWindow::activeView() const
279 {
280     return toKteView(m_mainWindow->activeView());
281 }
282 
activateView(KTextEditor::Document * doc)283 KTextEditor::View *MainWindow::activateView(KTextEditor::Document *doc)
284 {
285     const auto areas = m_mainWindow->areas();
286     for (auto* area : areas) {
287         const auto views = area->views();
288         for (auto* view : views) {
289             if (auto kteView = toKteView(view)) {
290                 if (kteView->document() == doc) {
291                     m_mainWindow->activateView(view);
292                     return kteView;
293                 }
294             }
295         }
296     }
297 
298     return activeView();
299 }
300 
pluginView(const QString & id) const301 QObject *MainWindow::pluginView(const QString &id) const
302 {
303     return m_pluginViews.value(id);
304 }
305 
createViewBar(KTextEditor::View * view)306 QWidget *MainWindow::createViewBar(KTextEditor::View *view)
307 {
308     Q_UNUSED(view);
309 
310     // we reuse the central view bar for every view
311     return m_mainWindow->viewBarContainer();
312 }
313 
deleteViewBar(KTextEditor::View * view)314 void MainWindow::deleteViewBar(KTextEditor::View *view)
315 {
316     auto viewBar = m_viewBars.take(view);
317     m_mainWindow->viewBarContainer()->removeViewBar(viewBar);
318     delete viewBar;
319 }
320 
showViewBar(KTextEditor::View * view)321 void MainWindow::showViewBar(KTextEditor::View *view)
322 {
323     auto viewBar = m_viewBars.value(view);
324     Q_ASSERT(viewBar);
325 
326     m_mainWindow->viewBarContainer()->showViewBar(viewBar);
327 }
328 
hideViewBar(KTextEditor::View * view)329 void MainWindow::hideViewBar(KTextEditor::View *view)
330 {
331     auto viewBar = m_viewBars.value(view);
332     Q_ASSERT(viewBar);
333     m_mainWindow->viewBarContainer()->hideViewBar(viewBar);
334 }
335 
addWidgetToViewBar(KTextEditor::View * view,QWidget * widget)336 void MainWindow::addWidgetToViewBar(KTextEditor::View *view, QWidget *widget)
337 {
338     Q_ASSERT(widget);
339     m_viewBars[view] = widget;
340 
341     m_mainWindow->viewBarContainer()->addViewBar(widget);
342 }
343 
openUrl(const QUrl & url,const QString & encoding)344 KTextEditor::View *MainWindow::openUrl(const QUrl &url, const QString &encoding)
345 {
346     return activateView(KTextEditor::Editor::instance()->application()->openUrl(url, encoding));
347 }
348 
showToolView(QWidget * widget)349 bool MainWindow::showToolView(QWidget *widget)
350 {
351     if (widget->parentWidget()) {
352         Core::self()->uiController()->raiseToolView(widget->parentWidget());
353         return true;
354     }
355     return false;
356 }
357 
interface() const358 KTextEditor::MainWindow *MainWindow::interface() const
359 {
360     return m_interface;
361 }
362 
addPluginView(const QString & id,QObject * view)363 void MainWindow::addPluginView(const QString &id, QObject *view)
364 {
365     m_pluginViews.insert(id, view);
366     emit m_interface->pluginViewCreated(id, view);
367 }
368 
removePluginView(const QString & id)369 void MainWindow::removePluginView(const QString &id)
370 {
371     auto view = m_pluginViews.take(id).data();
372     delete view;
373     emit m_interface->pluginViewDeleted(id, view);
374 }
375 
Plugin(KTextEditor::Plugin * plugin,QObject * parent)376 Plugin::Plugin(KTextEditor::Plugin *plugin, QObject *parent)
377     : IPlugin({}, parent)
378     , m_plugin(plugin)
379     , m_tracker(new ObjectListTracker(ObjectListTracker::CleanupWhenDone, this))
380 {
381 }
382 
383 Plugin::~Plugin() = default;
384 
unload()385 void Plugin::unload()
386 {
387     if (auto mainWindow = KTextEditor::Editor::instance()->application()->activeMainWindow()) {
388         auto integration = qobject_cast<MainWindow*>(mainWindow->parent());
389         if (integration) {
390             integration->removePluginView(pluginId());
391         }
392     }
393     m_tracker->deleteAll();
394     delete m_plugin;
395 }
396 
createGUIForMainWindow(Sublime::MainWindow * window)397 KXMLGUIClient *Plugin::createGUIForMainWindow(Sublime::MainWindow* window)
398 {
399     auto ret = IPlugin::createGUIForMainWindow(window);
400     auto mainWindow = qobject_cast<KDevelop::MainWindow*>(window);
401     Q_ASSERT(mainWindow);
402 
403     auto wrapper = mainWindow->kateWrapper();
404     auto view = m_plugin->createView(wrapper->interface());
405     wrapper->addPluginView(pluginId(), view);
406     // ensure that unloading the plugin kills all views
407     m_tracker->append(view);
408 
409     return ret;
410 }
411 
interface() const412 KTextEditor::Plugin *Plugin::interface() const
413 {
414     return m_plugin.data();
415 }
416 
pluginId() const417 QString Plugin::pluginId() const
418 {
419     return Core::self()->pluginController()->pluginInfo(this).pluginId();
420 }
421 
initialize()422 void initialize()
423 {
424     auto app = new KTextEditor::Application(new Application(Core::self()));
425     KTextEditor::Editor::instance()->setApplication(app);
426 }
427 
splitView(Qt::Orientation orientation)428 void MainWindow::splitView(Qt::Orientation orientation)
429 {
430     m_mainWindow->split(orientation);
431 }
432 
433 }
434 
435 #include "ktexteditorpluginintegration.moc"
436