File indexing completed on 2024-04-28 04:37:20

0001 /*
0002     SPDX-FileCopyrightText: 2002 Falk Brettschneider <falkbr@kdevelop.org>
0003     SPDX-FileCopyrightText: 2003 John Firebaugh <jfirebaugh@kde.org>
0004     SPDX-FileCopyrightText: 2006 Adam Treat <treat@kde.org>
0005     SPDX-FileCopyrightText: 2006, 2007 Alexander Dymo <adymo@kdevelop.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "mainwindow_p.h"
0011 
0012 #include <QApplication>
0013 #include <QLayout>
0014 #include <QMenu>
0015 
0016 #include <KActionCollection>
0017 #include <KStandardAction>
0018 #include <KXMLGUIClient>
0019 #include <KXMLGUIFactory>
0020 
0021 #include <sublime/area.h>
0022 #include <sublime/view.h>
0023 #include <sublime/document.h>
0024 #include <sublime/tooldocument.h>
0025 
0026 #include <util/pushvalue.h>
0027 
0028 #include <interfaces/iplugin.h>
0029 
0030 #include "core.h"
0031 #include "partdocument.h"
0032 #include "partcontroller.h"
0033 #include "uicontroller.h"
0034 #include "statusbar.h"
0035 #include "mainwindow.h"
0036 #include "textdocument.h"
0037 #include "sessioncontroller.h"
0038 #include "sourceformattercontroller.h"
0039 #include "debug.h"
0040 #include "ktexteditorpluginintegration.h"
0041 #include "colorschemechooser.h"
0042 
0043 #include <language/duchain/duchainlock.h>
0044 #include <language/duchain/duchainutils.h>
0045 #include <language/duchain/topducontext.h>
0046 #include <sublime/container.h>
0047 #include <sublime/holdupdates.h>
0048 
0049 
0050 namespace KDevelop {
0051 
0052 MainWindowPrivate::MainWindowPrivate(MainWindow *mainWindow)
0053     : QObject(mainWindow)
0054     , m_mainWindow(mainWindow)
0055     , m_statusBar(nullptr)
0056     , lastXMLGUIClientView(nullptr)
0057     , m_changingActiveView(false)
0058     , m_kateWrapper(new KTextEditorIntegration::MainWindow(mainWindow))
0059 {
0060 }
0061 
0062 void MainWindowPrivate::setupGui()
0063 {
0064     m_statusBar = new KDevelop::StatusBar(m_mainWindow);
0065     setupStatusBar();
0066 }
0067 
0068 void MainWindowPrivate::setupStatusBar()
0069 {
0070     QWidget *location = m_mainWindow->statusBarLocation();
0071     if (m_statusBar)
0072         location->layout()->addWidget(m_statusBar);
0073 }
0074 
0075 void MainWindowPrivate::addPlugin( IPlugin *plugin )
0076 {
0077     qCDebug(SHELL) << "add plugin" << plugin << plugin->componentName();
0078     Q_ASSERT( plugin );
0079 
0080     //The direct plugin client can only be added to the first mainwindow
0081     if(m_mainWindow == Core::self()->uiControllerInternal()->mainWindows()[0])
0082         m_mainWindow->guiFactory()->addClient( plugin );
0083 
0084     Q_ASSERT(!m_pluginCustomClients.contains(plugin));
0085 
0086     KXMLGUIClient* ownClient = plugin->createGUIForMainWindow(m_mainWindow);
0087     if(ownClient) {
0088         m_pluginCustomClients[plugin] = ownClient;
0089         connect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed);
0090         m_mainWindow->guiFactory()->addClient(ownClient);
0091     }
0092 }
0093 
0094 void MainWindowPrivate::pluginDestroyed(QObject* pluginObj)
0095 {
0096     auto* plugin = static_cast<IPlugin*>(pluginObj);
0097     KXMLGUIClient* p = m_pluginCustomClients.take(plugin);
0098     m_mainWindow->guiFactory()->removeClient( p );
0099     delete p;
0100 }
0101 
0102 MainWindowPrivate::~MainWindowPrivate()
0103 {
0104     qDeleteAll(m_pluginCustomClients);
0105 }
0106 
0107 void MainWindowPrivate::removePlugin( IPlugin *plugin )
0108 {
0109     Q_ASSERT( plugin );
0110 
0111     pluginDestroyed(plugin);
0112     disconnect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed);
0113 
0114     m_mainWindow->guiFactory()->removeClient( plugin );
0115 }
0116 
0117 void MainWindowPrivate::updateSourceFormatterGuiClient(bool hasFormatters)
0118 {
0119     auto sourceFormatterController = Core::self()->sourceFormatterControllerInternal();
0120     auto guiFactory = m_mainWindow->guiFactory();
0121     if (hasFormatters) {
0122         guiFactory->addClient(sourceFormatterController);
0123     } else {
0124         guiFactory->removeClient(sourceFormatterController);
0125     }
0126 }
0127 
0128 void MainWindowPrivate::activePartChanged(KParts::Part *part)
0129 {
0130     if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow)
0131         m_mainWindow->createGUI(part);
0132 }
0133 
0134 void MainWindowPrivate::changeActiveView(Sublime::View *view)
0135 {
0136     //disable updates on a window to avoid toolbar flickering on xmlgui client change
0137     Sublime::HoldUpdates s(m_mainWindow);
0138     mergeView(view);
0139 
0140     if(!view)
0141         return;
0142 
0143     auto* doc = qobject_cast<KDevelop::IDocument*>(view->document());
0144     if (doc)
0145     {
0146         doc->activate(view, m_mainWindow);
0147     }
0148     else
0149     {
0150         //activated view is not a part document so we need to remove active part gui
0151         ///@todo adymo: only this window needs to remove GUI
0152 //         KParts::Part *activePart = Core::self()->partController()->activePart();
0153 //         if (activePart)
0154 //             guiFactory()->removeClient(activePart);
0155     }
0156 }
0157 
0158 void MainWindowPrivate::mergeView(Sublime::View* view)
0159 {
0160     PushPositiveValue<bool> block(m_changingActiveView, true);
0161 
0162     // If the previous view was KXMLGUIClient, remove its actions
0163     // In the case that that view was removed, lastActiveView
0164     // will auto-reset, and xmlguifactory will disconnect that
0165     // client, I think.
0166     if (lastXMLGUIClientView)
0167     {
0168         qCDebug(SHELL) << "clearing last XML GUI client" << lastXMLGUIClientView;
0169 
0170         m_mainWindow->guiFactory()->removeClient(dynamic_cast<KXMLGUIClient*>(lastXMLGUIClientView));
0171 
0172         disconnect (lastXMLGUIClientView, &QWidget::destroyed, this, nullptr);
0173 
0174         lastXMLGUIClientView = nullptr;
0175     }
0176 
0177     if (!view)
0178         return;
0179 
0180     QWidget* viewWidget = view->widget();
0181     Q_ASSERT(viewWidget);
0182 
0183     qCDebug(SHELL) << "changing active view to" << view << "doc" << view->document() << "mw" << m_mainWindow;
0184 
0185     // If the new view is KXMLGUIClient, add it.
0186     if (auto* c = dynamic_cast<KXMLGUIClient*>(viewWidget))
0187     {
0188         qCDebug(SHELL) << "setting new XMLGUI client" << viewWidget;
0189         lastXMLGUIClientView = viewWidget;
0190         m_mainWindow->guiFactory()->addClient(c);
0191         connect(viewWidget, &QWidget::destroyed,
0192                 this, &MainWindowPrivate::xmlguiclientDestroyed);
0193     }
0194 }
0195 
0196 void MainWindowPrivate::xmlguiclientDestroyed(QObject* obj)
0197 {
0198     /* We're informed the QWidget for the active view that is also
0199        KXMLGUIclient is dying.  KXMLGUIFactory will not like deleted
0200        clients, really.  Unfortunately, there's nothing we can do
0201        at this point. For example, KateView derives from QWidget and
0202        KXMLGUIClient.  The destroyed() signal is emitted by ~QWidget.
0203        At this point, event attempt to cross-cast to KXMLGUIClient
0204        is undefined behaviour.  We hope to catch view deletion a bit
0205        later, but if we fail, we better report it now, rather than
0206        get a weird crash a bit later.  */
0207        Q_ASSERT(obj == lastXMLGUIClientView);
0208        Q_ASSERT(false && "xmlgui clients management is messed up");
0209        Q_UNUSED(obj);
0210 }
0211 
0212 void MainWindowPrivate::setupActions()
0213 {
0214     connect(Core::self()->sessionController(), &SessionController::quitSession, this, &MainWindowPrivate::quitAll);
0215 
0216     QAction* action;
0217 
0218     const QString app = qApp->applicationName();
0219     action = KStandardAction::preferences( this, SLOT(settingsDialog()),
0220                                       actionCollection());
0221     action->setToolTip( i18nc( "@info:tooltip %1 = application name", "Configure %1", app ) );
0222     action->setWhatsThis( i18nc("@info:whatsthis", "Lets you customize %1.", app ) );
0223 
0224     action =  KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection());
0225     action->setText( i18nc("@action", "Configure Notifications...") );
0226     action->setToolTip( i18nc("@info:tooltip", "Configure notifications") );
0227     action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog that lets you configure notifications." ) );
0228 
0229     action = actionCollection()->addAction( QStringLiteral("loaded_plugins"), this, SLOT(showLoadedPlugins()) );
0230     action->setIcon(QIcon::fromTheme(QStringLiteral("plugins")));
0231     action->setText( i18nc("@action", "Loaded Plugins") );
0232     action->setToolTip( i18nc("@info:tooltip", "Show a list of all loaded plugins") );
0233     action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about all loaded plugins." ) );
0234 
0235     action = actionCollection()->addAction( QStringLiteral("view_next_window") );
0236     action->setText( i18nc("@action", "&Next Window" ) );
0237     connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextWindow );
0238     actionCollection()->setDefaultShortcut(action, Qt::ALT | Qt::SHIFT | Qt::Key_Right);
0239     action->setToolTip( i18nc( "@info:tooltip", "Next window" ) );
0240     action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next window." ) );
0241     action->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
0242 
0243     action = actionCollection()->addAction( QStringLiteral("view_previous_window") );
0244     action->setText( i18nc("@action", "&Previous Window" ) );
0245     connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousWindow );
0246     actionCollection()->setDefaultShortcut(action, Qt::ALT | Qt::SHIFT | Qt::Key_Left);
0247     action->setToolTip( i18nc( "@info:tooltip", "Previous window" ) );
0248     action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous window." ) );
0249     action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
0250 
0251     action = actionCollection()->addAction(QStringLiteral("next_error"));
0252     action->setText(i18nc("@action", "Jump to Next Outputmark"));
0253     actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::Key_F4) );
0254     action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
0255     connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextItem);
0256 
0257     action = actionCollection()->addAction(QStringLiteral("prev_error"));
0258     action->setText(i18nc("@action", "Jump to Previous Outputmark"));
0259     actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::SHIFT | Qt::Key_F4) );
0260     action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
0261     connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPrevItem);
0262 
0263     action = actionCollection()->addAction( QStringLiteral("split_horizontal") );
0264     action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-top-bottom") ));
0265     action->setText( i18nc("@action", "Split View &Top/Bottom" ) );
0266     actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_T);
0267     connect( action, &QAction::triggered, this, &MainWindowPrivate::splitHorizontal );
0268     action->setToolTip( i18nc( "@info:tooltip", "Split horizontal" ) );
0269     action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view horizontally." ) );
0270 
0271     action = actionCollection()->addAction( QStringLiteral("split_vertical") );
0272     action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-left-right") ));
0273     action->setText( i18nc("@action", "Split View &Left/Right" ) );
0274     actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_L);
0275     connect( action, &QAction::triggered, this, &MainWindowPrivate::splitVertical );
0276     action->setToolTip( i18nc( "@info:tooltip", "Split vertical" ) );
0277     action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view vertically." ) );
0278 
0279     action = actionCollection()->addAction( QStringLiteral("view_next_split") );
0280     action->setText( i18nc("@action", "&Next Split View" ) );
0281     connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextSplit );
0282     actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_N);
0283     action->setToolTip( i18nc( "@info:tooltip", "Next split view" ) );
0284     action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next split view." ) );
0285     action->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
0286 
0287     action = actionCollection()->addAction( QStringLiteral("view_previous_split") );
0288     action->setText( i18nc("@action", "&Previous Split View" ) );
0289     connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousSplit );
0290     actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_P);
0291     action->setToolTip( i18nc( "@info:tooltip", "Previous split view" ) );
0292     action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous split view." ) );
0293     action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
0294 
0295     KStandardAction::fullScreen( this, SLOT(toggleFullScreen(bool)), m_mainWindow, actionCollection() );
0296 
0297     action = actionCollection()->addAction( QStringLiteral("file_new") );
0298     action->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
0299     actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::Key_N);
0300     action->setText( i18nc("@action new file", "&New" ) );
0301     action->setIconText( i18nc( "@action Shorter Text for 'New File' shown in the toolbar", "New") );
0302     connect( action, &QAction::triggered, this, &MainWindowPrivate::fileNew );
0303     action->setToolTip( i18nc( "@info:tooltip", "New file" ) );
0304     action->setWhatsThis( i18nc( "@info:whatsthis", "Creates an empty file." ) );
0305 
0306     action = actionCollection()->addAction( QStringLiteral("add_toolview") );
0307     action->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
0308     actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_V);
0309     action->setText( i18nc("@action", "&Add Tool View..." ) );
0310     connect( action, &QAction::triggered,  this, &MainWindowPrivate::viewAddNewToolView );
0311     action->setToolTip( i18nc( "@info:tooltip", "Add tool view" ) );
0312     action->setWhatsThis( i18nc( "@info:whatsthis", "Adds a new tool view to this window." ) );
0313 
0314     //Load themes
0315     actionCollection()->addAction(QStringLiteral("colorscheme_menu"), new ColorSchemeChooser(actionCollection()));
0316 }
0317 
0318 void MainWindowPrivate::toggleArea(bool b)
0319 {
0320     if (!b) return;
0321     auto* action = qobject_cast<QAction*>(sender());
0322     if (!action) return;
0323     m_mainWindow->controller()->showArea(action->data().toString(), m_mainWindow);
0324 }
0325 
0326 KActionCollection * MainWindowPrivate::actionCollection()
0327 {
0328     return m_mainWindow->actionCollection();
0329 }
0330 
0331 void MainWindowPrivate::registerStatus(QObject* status)
0332 {
0333     m_statusBar->registerStatus(status);
0334 }
0335 
0336 
0337 void MainWindowPrivate::showErrorMessage(const QString& message, int timeout)
0338 {
0339     m_statusBar->showErrorMessage(message, timeout);
0340 }
0341 
0342 void MainWindowPrivate::tabContextMenuRequested(Sublime::View* view, QMenu* menu)
0343 {
0344     m_tabView = view;
0345 
0346     QAction* action;
0347 
0348     action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18nc("@action:inmenu", "Split View Top/Bottom"));
0349     connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitHorizontal);
0350 
0351     action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18nc("@action:inmenu", "Split View Left/Right"));
0352     connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitVertical);
0353     menu->addSeparator();
0354 
0355     action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@action:inmenu", "New File"));
0356 
0357     connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuFileNew);
0358 
0359     if (view) {
0360         if (auto* doc = qobject_cast<TextDocument*>(view->document())) {
0361             action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reload"));
0362             connect(action, &QAction::triggered, doc, &TextDocument::reload);
0363 
0364             action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reload All"));
0365             connect(action, &QAction::triggered, this, &MainWindowPrivate::reloadAll);
0366         }
0367     }
0368 }
0369 
0370 void MainWindowPrivate::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab)
0371 {
0372     if (m_tabTooltip.second) {
0373         if (m_tabTooltip.first == view) {
0374             // tooltip already shown, don't do anything. prevents flicker when moving mouse over same tab
0375             return;
0376         } else {
0377             m_tabTooltip.second.data()->close();
0378         }
0379     }
0380 
0381     auto* urlDoc = qobject_cast<Sublime::UrlDocument*>(view->document());
0382 
0383     if (urlDoc) {
0384         DUChainReadLocker lock;
0385         TopDUContext* top = DUChainUtils::standardContextForUrl(urlDoc->url());
0386 
0387         if (top) {
0388             if (auto* navigationWidget = top->createNavigationWidget()) {
0389                 auto* tooltip = new KDevelop::NavigationToolTip(m_mainWindow, QCursor::pos() + QPoint(20, 20), navigationWidget);
0390                 tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10));
0391                 tooltip->setHandleRect(container->tabRect(tab));
0392 
0393                 m_tabTooltip.first = view;
0394                 m_tabTooltip.second = tooltip;
0395                 ActiveToolTip::showToolTip(m_tabTooltip.second.data());
0396             }
0397         }
0398     }
0399 }
0400 
0401 void MainWindowPrivate::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position)
0402 {
0403     QMenu menu(m_mainWindow);
0404     menu.addSection(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@action:inmenu", "Add Tool View"));
0405     QHash<IToolViewFactory*, Sublime::ToolDocument*> factories =
0406         Core::self()->uiControllerInternal()->factoryDocuments();
0407     QHash<QAction*, IToolViewFactory*> actionToFactory;
0408     if ( !factories.isEmpty() ) {
0409         // sorted actions
0410         QMap<QString, QAction*> actionMap;
0411         for (QHash<IToolViewFactory*, Sublime::ToolDocument*>::const_iterator it = factories.constBegin();
0412                 it != factories.constEnd(); ++it)
0413         {
0414             auto* action = new QAction(it.value()->statusIcon(), it.value()->title(), &menu);
0415             action->setIcon(it.value()->statusIcon());
0416             if (!it.key()->allowMultiple() && Core::self()->uiControllerInternal()->toolViewPresent(it.value(), m_mainWindow->area())) {
0417                 action->setDisabled(true);
0418             }
0419             actionToFactory.insert(action, it.key());
0420             actionMap[action->text()] = action;
0421         }
0422         menu.addActions(actionMap.values());
0423     }
0424 
0425     auto* lockAction = new QAction(this);
0426     lockAction->setCheckable(true);
0427     lockAction->setText(i18nc("@action:inmenu", "Lock the Panel from Hiding"));
0428 
0429     KConfigGroup config = KSharedConfig::openConfig()->group("UI");
0430     lockAction->setChecked(config.readEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), false));
0431 
0432     menu.addSeparator();
0433     menu.addAction(lockAction);
0434 
0435     QAction* triggered = menu.exec(position);
0436     if ( !triggered ) {
0437         return;
0438     }
0439 
0440     if (triggered == lockAction) {
0441         KConfigGroup config = KSharedConfig::openConfig()->group("UI");
0442         config.writeEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), lockAction->isChecked());
0443         return;
0444     }
0445 
0446     Core::self()->uiControllerInternal()->addToolViewToDockArea(
0447         actionToFactory[triggered],
0448         area
0449     );
0450 }
0451 
0452 bool MainWindowPrivate::changingActiveView() const
0453 {
0454     return m_changingActiveView;
0455 }
0456 
0457 KTextEditorIntegration::MainWindow *MainWindowPrivate::kateWrapper() const
0458 {
0459     return m_kateWrapper;
0460 }
0461 
0462 }
0463 
0464 #include "mainwindow_actions.cpp"
0465 #include "moc_mainwindow_p.cpp"