File indexing completed on 2024-04-28 04:37:17
0001 /* 0002 SPDX-FileCopyrightText: 2015 Milian Wolff <mail@milianw.de> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "ktexteditorpluginintegration.h" 0008 0009 #include <QWidget> 0010 #include <QVBoxLayout> 0011 #include <QStackedLayout> 0012 0013 #include <KParts/MainWindow> 0014 #include <KTextEditor/View> 0015 #include <KTextEditor/Editor> 0016 #include <KTextEditor/Application> 0017 0018 #include <sublime/area.h> 0019 #include <sublime/container.h> 0020 #include <sublime/view.h> 0021 #include <sublime/viewbarcontainer.h> 0022 0023 #include "core.h" 0024 #include "uicontroller.h" 0025 #include "documentcontroller.h" 0026 #include "plugincontroller.h" 0027 #include "sessioncontroller.h" 0028 #include "mainwindow.h" 0029 #include "textdocument.h" 0030 0031 #include <util/objectlist.h> 0032 0033 using namespace KDevelop; 0034 0035 namespace { 0036 0037 KTextEditor::MainWindow *toKteWrapper(KParts::MainWindow *window) 0038 { 0039 if (auto mainWindow = qobject_cast<KDevelop::MainWindow*>(window)) { 0040 return mainWindow->kateWrapper() ? mainWindow->kateWrapper()->interface() : nullptr; 0041 } else { 0042 return nullptr; 0043 } 0044 } 0045 0046 KTextEditor::View *toKteView(Sublime::View *view) 0047 { 0048 if (auto textView = qobject_cast<KDevelop::TextView*>(view)) { 0049 return textView->textView(); 0050 } else { 0051 return nullptr; 0052 } 0053 } 0054 0055 class ToolViewFactory; 0056 0057 /** 0058 * This HACK is required to massage the KTextEditor plugin API into the 0059 * GUI concepts we apply in KDevelop. Kate does not allow the user to 0060 * delete tool views and then readd them. We do. To support our use case 0061 * we prevent the widget we return to KTextEditor plugins from 0062 * MainWindow::createToolView from getting destroyed. This widget class 0063 * unsets the parent of the so called container in its dtor. The 0064 * ToolViewFactory handles the ownership and destroys the kate widget 0065 * as needed. 0066 */ 0067 class KeepAliveWidget : public QWidget 0068 { 0069 Q_OBJECT 0070 public: 0071 explicit KeepAliveWidget(ToolViewFactory *factory, QWidget *parent = nullptr) 0072 : QWidget(parent) 0073 , m_factory(factory) 0074 { 0075 } 0076 0077 ~KeepAliveWidget() override; 0078 0079 private: 0080 ToolViewFactory* const m_factory; 0081 }; 0082 0083 class ToolViewFactory : public QObject, public KDevelop::IToolViewFactory 0084 { 0085 Q_OBJECT 0086 public: 0087 ToolViewFactory(const QString &text, const QIcon &icon, const QString &identifier, 0088 KTextEditor::MainWindow::ToolViewPosition pos) 0089 : m_text(text) 0090 , m_icon(icon) 0091 , m_identifier(identifier) 0092 , m_container(new QWidget) 0093 , m_pos(pos) 0094 { 0095 m_container->setLayout(new QVBoxLayout); 0096 } 0097 0098 ~ToolViewFactory() override 0099 { 0100 delete m_container; 0101 } 0102 0103 QWidget *create(QWidget *parent = nullptr) override 0104 { 0105 auto widget = new KeepAliveWidget(this, parent); 0106 widget->setWindowTitle(m_text); 0107 widget->setWindowIcon(m_icon); 0108 widget->setLayout(new QVBoxLayout); 0109 widget->layout()->addWidget(m_container); 0110 widget->addActions(m_container->actions()); 0111 return widget; 0112 } 0113 0114 Qt::DockWidgetArea defaultPosition() const override 0115 { 0116 switch (m_pos) { 0117 case KTextEditor::MainWindow::Left: 0118 return Qt::LeftDockWidgetArea; 0119 case KTextEditor::MainWindow::Right: 0120 return Qt::RightDockWidgetArea; 0121 case KTextEditor::MainWindow::Top: 0122 return Qt::TopDockWidgetArea; 0123 case KTextEditor::MainWindow::Bottom: 0124 return Qt::BottomDockWidgetArea; 0125 } 0126 Q_UNREACHABLE(); 0127 } 0128 0129 QString id() const override 0130 { 0131 return m_identifier; 0132 } 0133 0134 QWidget *container() const 0135 { 0136 return m_container; 0137 } 0138 0139 private: 0140 const QString m_text; 0141 const QIcon m_icon; 0142 const QString m_identifier; 0143 QPointer<QWidget> m_container; 0144 const KTextEditor::MainWindow::ToolViewPosition m_pos; 0145 friend class KeepAliveWidget; 0146 }; 0147 0148 KeepAliveWidget::~KeepAliveWidget() 0149 { 0150 // if the container is still valid, unparent it to prevent it from getting deleted 0151 // this happens when the user removes a tool view 0152 // on shutdown, the container does get deleted, thus we must guard against that. 0153 if (m_factory->container()) { 0154 Q_ASSERT(m_factory->container()->parentWidget() == this); 0155 m_factory->container()->setParent(nullptr); 0156 } 0157 } 0158 0159 } 0160 0161 namespace KTextEditorIntegration { 0162 0163 Application::Application(QObject *parent) 0164 : QObject(parent) 0165 { 0166 } 0167 0168 Application::~Application() 0169 { 0170 KTextEditor::Editor::instance()->setApplication(nullptr); 0171 } 0172 0173 KTextEditor::MainWindow *Application::activeMainWindow() const 0174 { 0175 return toKteWrapper(Core::self()->uiController()->activeMainWindow()); 0176 } 0177 0178 QList<KTextEditor::MainWindow *> Application::mainWindows() const 0179 { 0180 return {activeMainWindow()}; 0181 } 0182 0183 bool Application::closeDocument(KTextEditor::Document *document) const 0184 { 0185 const auto& openDocuments = Core::self()->documentControllerInternal()->openDocuments(); 0186 for (auto doc : openDocuments) { 0187 if (doc->textDocument() == document) { 0188 return doc->close(); 0189 } 0190 } 0191 return false; 0192 } 0193 0194 KTextEditor::Plugin *Application::plugin(const QString &id) const 0195 { 0196 auto kdevPlugin = Core::self()->pluginController()->loadPlugin(id); 0197 const auto plugin = dynamic_cast<Plugin*>(kdevPlugin); 0198 return plugin ? plugin->interface() : nullptr; 0199 } 0200 0201 QList<KTextEditor::Document *> Application::documents() 0202 { 0203 QList<KTextEditor::Document *> l; 0204 const auto openDocuments = Core::self()->documentControllerInternal()->openDocuments(); 0205 l.reserve(openDocuments.size()); 0206 for (auto* d : openDocuments) { 0207 l << d->textDocument(); 0208 } 0209 return l; 0210 } 0211 0212 KTextEditor::Document *Application::openUrl(const QUrl &url, const QString &encoding) 0213 { 0214 Q_UNUSED(encoding); 0215 0216 auto documentController = Core::self()->documentControllerInternal(); 0217 auto doc = url.isEmpty() ? documentController->openDocumentFromText(QString()) : documentController->openDocument(url); 0218 return doc->textDocument(); 0219 } 0220 0221 KTextEditor::Document *Application::findUrl(const QUrl &url) const 0222 { 0223 auto doc = Core::self()->documentControllerInternal()->documentForUrl(url); 0224 return doc ? doc->textDocument() : nullptr; 0225 } 0226 0227 bool Application::quit() const 0228 { 0229 Core::self()->sessionController()->emitQuitSession(); 0230 return true; 0231 } 0232 0233 MainWindow::MainWindow(KDevelop::MainWindow *mainWindow) 0234 : QObject(mainWindow) 0235 , m_mainWindow(mainWindow) 0236 , m_interface(new KTextEditor::MainWindow(this)) 0237 { 0238 connect(mainWindow, &Sublime::MainWindow::viewAdded, this, [this] (Sublime::View *view) { 0239 if (auto kteView = toKteView(view)) { 0240 emit m_interface->viewCreated(kteView); 0241 } 0242 }); 0243 connect(mainWindow, &Sublime::MainWindow::activeViewChanged, this, [this] (Sublime::View *view) { 0244 auto kteView = toKteView(view); 0245 emit m_interface->viewChanged(kteView); 0246 0247 if (auto viewBar = m_viewBars.value(kteView)) { 0248 m_mainWindow->viewBarContainer()->setCurrentViewBar(viewBar); 0249 } 0250 }); 0251 } 0252 0253 MainWindow::~MainWindow() = default; 0254 0255 QWidget *MainWindow::createToolView(KTextEditor::Plugin* plugin, const QString &identifier, 0256 KTextEditor::MainWindow::ToolViewPosition pos, 0257 const QIcon &icon, const QString &text) 0258 { 0259 auto factory = new ToolViewFactory(text, icon, identifier, pos); 0260 Core::self()->uiController()->addToolView(text, factory); 0261 connect(plugin, &QObject::destroyed, this, [=] { 0262 Core::self()->uiController()->removeToolView(factory); 0263 }); 0264 return factory->container(); 0265 } 0266 0267 KXMLGUIFactory *MainWindow::guiFactory() const 0268 { 0269 return m_mainWindow->guiFactory(); 0270 } 0271 0272 QWidget *MainWindow::window() const 0273 { 0274 return m_mainWindow; 0275 } 0276 0277 QList<KTextEditor::View *> MainWindow::views() const 0278 { 0279 QList<KTextEditor::View *> kteViews; 0280 const auto areas = m_mainWindow->areas(); 0281 for (auto* area : areas) { 0282 const auto views = area->views(); 0283 for (auto* view : views) { 0284 if (auto kteView = toKteView(view)) { 0285 kteViews << kteView; 0286 } 0287 } 0288 } 0289 return kteViews; 0290 } 0291 0292 KTextEditor::View *MainWindow::activeView() const 0293 { 0294 return toKteView(m_mainWindow->activeView()); 0295 } 0296 0297 KTextEditor::View *MainWindow::activateView(KTextEditor::Document *doc) 0298 { 0299 const auto areas = m_mainWindow->areas(); 0300 for (auto* area : areas) { 0301 const auto views = area->views(); 0302 for (auto* view : views) { 0303 if (auto kteView = toKteView(view)) { 0304 if (kteView->document() == doc) { 0305 m_mainWindow->activateView(view); 0306 return kteView; 0307 } 0308 } 0309 } 0310 } 0311 0312 return activeView(); 0313 } 0314 0315 bool MainWindow::closeView(KTextEditor::View *kteView) 0316 { 0317 if (!kteView) { 0318 return false; 0319 } 0320 0321 const auto areas = m_mainWindow->areas(); 0322 for (auto* area : areas) { 0323 const auto views = area->views(); 0324 for (auto* view : views) { 0325 if (toKteView(view) == kteView) { 0326 area->closeView(view); 0327 return true; 0328 } 0329 } 0330 } 0331 0332 return false; 0333 } 0334 0335 bool MainWindow::closeSplitView(KTextEditor::View *kteView) 0336 { 0337 return closeView(kteView); 0338 } 0339 0340 bool MainWindow::viewsInSameSplitView(KTextEditor::View *kteView1, KTextEditor::View *kteView2) const 0341 { 0342 if (!kteView1 || !kteView2) { 0343 return false; 0344 } 0345 if (kteView1 == kteView2) { 0346 return true; 0347 } 0348 0349 bool view1Found = false; 0350 bool view2Found = false; 0351 const auto containers = m_mainWindow->containers(); 0352 for (const auto* container : containers) { 0353 const auto views = container->views(); 0354 for (auto* view : views) { 0355 const KTextEditor::View *kteView = toKteView(view); 0356 if (kteView == kteView1) { 0357 view1Found = true; 0358 } else if (kteView == kteView2) { 0359 view2Found = true; 0360 } 0361 0362 if (view1Found && view2Found) { 0363 // both views found in the same container 0364 return true; 0365 } 0366 } 0367 0368 if (view1Found != view2Found) { 0369 // only one view being found implies that the other is in a different container 0370 return false; 0371 } 0372 } 0373 0374 return false; 0375 } 0376 0377 QObject *MainWindow::pluginView(const QString &id) const 0378 { 0379 return m_pluginViews.value(id); 0380 } 0381 0382 QWidget *MainWindow::createViewBar(KTextEditor::View *view) 0383 { 0384 Q_UNUSED(view); 0385 0386 // we reuse the central view bar for every view 0387 return m_mainWindow->viewBarContainer(); 0388 } 0389 0390 void MainWindow::deleteViewBar(KTextEditor::View *view) 0391 { 0392 auto viewBar = m_viewBars.take(view); 0393 m_mainWindow->viewBarContainer()->removeViewBar(viewBar); 0394 delete viewBar; 0395 } 0396 0397 void MainWindow::showViewBar(KTextEditor::View *view) 0398 { 0399 auto viewBar = m_viewBars.value(view); 0400 Q_ASSERT(viewBar); 0401 0402 m_mainWindow->viewBarContainer()->showViewBar(viewBar); 0403 } 0404 0405 void MainWindow::hideViewBar(KTextEditor::View *view) 0406 { 0407 auto viewBar = m_viewBars.value(view); 0408 Q_ASSERT(viewBar); 0409 m_mainWindow->viewBarContainer()->hideViewBar(viewBar); 0410 } 0411 0412 void MainWindow::addWidgetToViewBar(KTextEditor::View *view, QWidget *widget) 0413 { 0414 Q_ASSERT(widget); 0415 m_viewBars[view] = widget; 0416 0417 m_mainWindow->viewBarContainer()->addViewBar(widget); 0418 } 0419 0420 KTextEditor::View *MainWindow::openUrl(const QUrl &url, const QString &encoding) 0421 { 0422 return activateView(KTextEditor::Editor::instance()->application()->openUrl(url, encoding)); 0423 } 0424 0425 bool MainWindow::showToolView(QWidget *widget) 0426 { 0427 if (widget->parentWidget()) { 0428 Core::self()->uiController()->raiseToolView(widget->parentWidget()); 0429 return true; 0430 } 0431 return false; 0432 } 0433 0434 KTextEditor::MainWindow *MainWindow::interface() const 0435 { 0436 return m_interface; 0437 } 0438 0439 void MainWindow::addPluginView(const QString &id, QObject *view) 0440 { 0441 m_pluginViews.insert(id, view); 0442 emit m_interface->pluginViewCreated(id, view); 0443 } 0444 0445 void MainWindow::removePluginView(const QString &id) 0446 { 0447 auto view = m_pluginViews.take(id).data(); 0448 delete view; 0449 emit m_interface->pluginViewDeleted(id, view); 0450 } 0451 0452 Plugin::Plugin(KTextEditor::Plugin *plugin, QObject *parent) 0453 : IPlugin({}, parent) 0454 , m_plugin(plugin) 0455 , m_tracker(new ObjectListTracker(ObjectListTracker::CleanupWhenDone, this)) 0456 { 0457 } 0458 0459 Plugin::~Plugin() = default; 0460 0461 void Plugin::unload() 0462 { 0463 if (auto mainWindow = KTextEditor::Editor::instance()->application()->activeMainWindow()) { 0464 auto integration = qobject_cast<MainWindow*>(mainWindow->parent()); 0465 if (integration) { 0466 integration->removePluginView(pluginId()); 0467 } 0468 } 0469 m_tracker->deleteAll(); 0470 delete m_plugin; 0471 } 0472 0473 KXMLGUIClient *Plugin::createGUIForMainWindow(Sublime::MainWindow* window) 0474 { 0475 auto ret = IPlugin::createGUIForMainWindow(window); 0476 auto mainWindow = qobject_cast<KDevelop::MainWindow*>(window); 0477 Q_ASSERT(mainWindow); 0478 0479 auto wrapper = mainWindow->kateWrapper(); 0480 auto view = m_plugin->createView(wrapper->interface()); 0481 wrapper->addPluginView(pluginId(), view); 0482 // ensure that unloading the plugin kills all views 0483 m_tracker->append(view); 0484 0485 return ret; 0486 } 0487 0488 KTextEditor::Plugin *Plugin::interface() const 0489 { 0490 return m_plugin.data(); 0491 } 0492 0493 QString Plugin::pluginId() const 0494 { 0495 return Core::self()->pluginController()->pluginInfo(this).pluginId(); 0496 } 0497 0498 void initialize() 0499 { 0500 auto app = new KTextEditor::Application(new Application(Core::self())); 0501 KTextEditor::Editor::instance()->setApplication(app); 0502 } 0503 0504 void MainWindow::splitView(Qt::Orientation orientation) 0505 { 0506 m_mainWindow->split(orientation); 0507 } 0508 0509 } 0510 0511 #include "ktexteditorpluginintegration.moc" 0512 #include "moc_ktexteditorpluginintegration.cpp"