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