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"