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"