1 /*
2 SPDX-FileCopyrightText: 2002 Falk Brettschneider <falkbr@kdevelop.org>
3 SPDX-FileCopyrightText: 2003 John Firebaugh <jfirebaugh@kde.org>
4 SPDX-FileCopyrightText: 2006 Adam Treat <treat@kde.org>
5 SPDX-FileCopyrightText: 2006, 2007 Alexander Dymo <adymo@kdevelop.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9
10 #include "mainwindow_p.h"
11
12 #include <QApplication>
13 #include <QLayout>
14 #include <QMenu>
15
16 #include <KActionCollection>
17 #include <KStandardAction>
18 #include <KXMLGUIClient>
19 #include <KXMLGUIFactory>
20
21 #include <sublime/area.h>
22 #include <sublime/view.h>
23 #include <sublime/document.h>
24 #include <sublime/tooldocument.h>
25
26 #include <util/pushvalue.h>
27
28 #include <interfaces/iplugin.h>
29
30 #include "core.h"
31 #include "partdocument.h"
32 #include "partcontroller.h"
33 #include "uicontroller.h"
34 #include "statusbar.h"
35 #include "mainwindow.h"
36 #include "textdocument.h"
37 #include "sessioncontroller.h"
38 #include "sourceformattercontroller.h"
39 #include "debug.h"
40 #include "ktexteditorpluginintegration.h"
41 #include "colorschemechooser.h"
42
43 #include <language/duchain/duchainlock.h>
44 #include <language/duchain/duchainutils.h>
45 #include <language/duchain/topducontext.h>
46 #include <sublime/container.h>
47 #include <sublime/holdupdates.h>
48
49
50 namespace KDevelop {
51
MainWindowPrivate(MainWindow * mainWindow)52 MainWindowPrivate::MainWindowPrivate(MainWindow *mainWindow)
53 : QObject(mainWindow)
54 , m_mainWindow(mainWindow)
55 , m_statusBar(nullptr)
56 , lastXMLGUIClientView(nullptr)
57 , m_changingActiveView(false)
58 , m_kateWrapper(new KTextEditorIntegration::MainWindow(mainWindow))
59 {
60 }
61
setupGui()62 void MainWindowPrivate::setupGui()
63 {
64 m_statusBar = new KDevelop::StatusBar(m_mainWindow);
65 setupStatusBar();
66 }
67
setupStatusBar()68 void MainWindowPrivate::setupStatusBar()
69 {
70 QWidget *location = m_mainWindow->statusBarLocation();
71 if (m_statusBar)
72 location->layout()->addWidget(m_statusBar);
73 }
74
addPlugin(IPlugin * plugin)75 void MainWindowPrivate::addPlugin( IPlugin *plugin )
76 {
77 qCDebug(SHELL) << "add plugin" << plugin << plugin->componentName();
78 Q_ASSERT( plugin );
79
80 //The direct plugin client can only be added to the first mainwindow
81 if(m_mainWindow == Core::self()->uiControllerInternal()->mainWindows()[0])
82 m_mainWindow->guiFactory()->addClient( plugin );
83
84 Q_ASSERT(!m_pluginCustomClients.contains(plugin));
85
86 KXMLGUIClient* ownClient = plugin->createGUIForMainWindow(m_mainWindow);
87 if(ownClient) {
88 m_pluginCustomClients[plugin] = ownClient;
89 connect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed);
90 m_mainWindow->guiFactory()->addClient(ownClient);
91 }
92 }
93
pluginDestroyed(QObject * pluginObj)94 void MainWindowPrivate::pluginDestroyed(QObject* pluginObj)
95 {
96 auto* plugin = static_cast<IPlugin*>(pluginObj);
97 KXMLGUIClient* p = m_pluginCustomClients.take(plugin);
98 m_mainWindow->guiFactory()->removeClient( p );
99 delete p;
100 }
101
~MainWindowPrivate()102 MainWindowPrivate::~MainWindowPrivate()
103 {
104 qDeleteAll(m_pluginCustomClients);
105 }
106
removePlugin(IPlugin * plugin)107 void MainWindowPrivate::removePlugin( IPlugin *plugin )
108 {
109 Q_ASSERT( plugin );
110
111 pluginDestroyed(plugin);
112 disconnect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed);
113
114 m_mainWindow->guiFactory()->removeClient( plugin );
115 }
116
updateSourceFormatterGuiClient(bool hasFormatters)117 void MainWindowPrivate::updateSourceFormatterGuiClient(bool hasFormatters)
118 {
119 auto sourceFormatterController = Core::self()->sourceFormatterControllerInternal();
120 auto guiFactory = m_mainWindow->guiFactory();
121 if (hasFormatters) {
122 guiFactory->addClient(sourceFormatterController);
123 } else {
124 guiFactory->removeClient(sourceFormatterController);
125 }
126 }
127
activePartChanged(KParts::Part * part)128 void MainWindowPrivate::activePartChanged(KParts::Part *part)
129 {
130 if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow)
131 m_mainWindow->createGUI(part);
132 }
133
changeActiveView(Sublime::View * view)134 void MainWindowPrivate::changeActiveView(Sublime::View *view)
135 {
136 //disable updates on a window to avoid toolbar flickering on xmlgui client change
137 Sublime::HoldUpdates s(m_mainWindow);
138 mergeView(view);
139
140 if(!view)
141 return;
142
143 auto* doc = qobject_cast<KDevelop::IDocument*>(view->document());
144 if (doc)
145 {
146 doc->activate(view, m_mainWindow);
147 }
148 else
149 {
150 //activated view is not a part document so we need to remove active part gui
151 ///@todo adymo: only this window needs to remove GUI
152 // KParts::Part *activePart = Core::self()->partController()->activePart();
153 // if (activePart)
154 // guiFactory()->removeClient(activePart);
155 }
156 }
157
mergeView(Sublime::View * view)158 void MainWindowPrivate::mergeView(Sublime::View* view)
159 {
160 PushPositiveValue<bool> block(m_changingActiveView, true);
161
162 // If the previous view was KXMLGUIClient, remove its actions
163 // In the case that that view was removed, lastActiveView
164 // will auto-reset, and xmlguifactory will disconnect that
165 // client, I think.
166 if (lastXMLGUIClientView)
167 {
168 qCDebug(SHELL) << "clearing last XML GUI client" << lastXMLGUIClientView;
169
170 m_mainWindow->guiFactory()->removeClient(dynamic_cast<KXMLGUIClient*>(lastXMLGUIClientView));
171
172 disconnect (lastXMLGUIClientView, &QWidget::destroyed, this, nullptr);
173
174 lastXMLGUIClientView = nullptr;
175 }
176
177 if (!view)
178 return;
179
180 QWidget* viewWidget = view->widget();
181 Q_ASSERT(viewWidget);
182
183 qCDebug(SHELL) << "changing active view to" << view << "doc" << view->document() << "mw" << m_mainWindow;
184
185 // If the new view is KXMLGUIClient, add it.
186 if (auto* c = dynamic_cast<KXMLGUIClient*>(viewWidget))
187 {
188 qCDebug(SHELL) << "setting new XMLGUI client" << viewWidget;
189 lastXMLGUIClientView = viewWidget;
190 m_mainWindow->guiFactory()->addClient(c);
191 connect(viewWidget, &QWidget::destroyed,
192 this, &MainWindowPrivate::xmlguiclientDestroyed);
193 }
194 }
195
xmlguiclientDestroyed(QObject * obj)196 void MainWindowPrivate::xmlguiclientDestroyed(QObject* obj)
197 {
198 /* We're informed the QWidget for the active view that is also
199 KXMLGUIclient is dying. KXMLGUIFactory will not like deleted
200 clients, really. Unfortunately, there's nothing we can do
201 at this point. For example, KateView derives from QWidget and
202 KXMLGUIClient. The destroyed() signal is emitted by ~QWidget.
203 At this point, event attempt to cross-cast to KXMLGUIClient
204 is undefined behaviour. We hope to catch view deletion a bit
205 later, but if we fail, we better report it now, rather than
206 get a weird crash a bit later. */
207 Q_ASSERT(obj == lastXMLGUIClientView);
208 Q_ASSERT(false && "xmlgui clients management is messed up");
209 Q_UNUSED(obj);
210 }
211
setupActions()212 void MainWindowPrivate::setupActions()
213 {
214 connect(Core::self()->sessionController(), &SessionController::quitSession, this, &MainWindowPrivate::quitAll);
215
216 QAction* action;
217
218 const QString app = qApp->applicationName();
219 action = KStandardAction::preferences( this, SLOT(settingsDialog()),
220 actionCollection());
221 action->setToolTip( i18nc( "@info:tooltip %1 = application name", "Configure %1", app ) );
222 action->setWhatsThis( i18nc("@info:whatsthis", "Lets you customize %1.", app ) );
223
224 action = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection());
225 action->setText( i18nc("@action", "Configure Notifications...") );
226 action->setToolTip( i18nc("@info:tooltip", "Configure notifications") );
227 action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog that lets you configure notifications." ) );
228
229 action = actionCollection()->addAction( QStringLiteral("loaded_plugins"), this, SLOT(showLoadedPlugins()) );
230 action->setIcon(QIcon::fromTheme(QStringLiteral("plugins")));
231 action->setText( i18nc("@action", "Loaded Plugins") );
232 action->setToolTip( i18nc("@info:tooltip", "Show a list of all loaded plugins") );
233 action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about all loaded plugins." ) );
234
235 action = actionCollection()->addAction( QStringLiteral("view_next_window") );
236 action->setText( i18nc("@action", "&Next Window" ) );
237 connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextWindow );
238 actionCollection()->setDefaultShortcut(action, Qt::ALT | Qt::SHIFT | Qt::Key_Right);
239 action->setToolTip( i18nc( "@info:tooltip", "Next window" ) );
240 action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next window." ) );
241 action->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
242
243 action = actionCollection()->addAction( QStringLiteral("view_previous_window") );
244 action->setText( i18nc("@action", "&Previous Window" ) );
245 connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousWindow );
246 actionCollection()->setDefaultShortcut(action, Qt::ALT | Qt::SHIFT | Qt::Key_Left);
247 action->setToolTip( i18nc( "@info:tooltip", "Previous window" ) );
248 action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous window." ) );
249 action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
250
251 action = actionCollection()->addAction(QStringLiteral("next_error"));
252 action->setText(i18nc("@action", "Jump to Next Outputmark"));
253 actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::Key_F4) );
254 action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
255 connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextItem);
256
257 action = actionCollection()->addAction(QStringLiteral("prev_error"));
258 action->setText(i18nc("@action", "Jump to Previous Outputmark"));
259 actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::SHIFT | Qt::Key_F4) );
260 action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
261 connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPrevItem);
262
263 action = actionCollection()->addAction( QStringLiteral("split_horizontal") );
264 action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-top-bottom") ));
265 action->setText( i18nc("@action", "Split View &Top/Bottom" ) );
266 actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_T);
267 connect( action, &QAction::triggered, this, &MainWindowPrivate::splitHorizontal );
268 action->setToolTip( i18nc( "@info:tooltip", "Split horizontal" ) );
269 action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view horizontally." ) );
270
271 action = actionCollection()->addAction( QStringLiteral("split_vertical") );
272 action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-left-right") ));
273 action->setText( i18nc("@action", "Split View &Left/Right" ) );
274 actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_L);
275 connect( action, &QAction::triggered, this, &MainWindowPrivate::splitVertical );
276 action->setToolTip( i18nc( "@info:tooltip", "Split vertical" ) );
277 action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view vertically." ) );
278
279 action = actionCollection()->addAction( QStringLiteral("view_next_split") );
280 action->setText( i18nc("@action", "&Next Split View" ) );
281 connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextSplit );
282 actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_N);
283 action->setToolTip( i18nc( "@info:tooltip", "Next split view" ) );
284 action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next split view." ) );
285 action->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
286
287 action = actionCollection()->addAction( QStringLiteral("view_previous_split") );
288 action->setText( i18nc("@action", "&Previous Split View" ) );
289 connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousSplit );
290 actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_P);
291 action->setToolTip( i18nc( "@info:tooltip", "Previous split view" ) );
292 action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous split view." ) );
293 action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
294
295 KStandardAction::fullScreen( this, SLOT(toggleFullScreen(bool)), m_mainWindow, actionCollection() );
296
297 action = actionCollection()->addAction( QStringLiteral("file_new") );
298 action->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
299 actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::Key_N);
300 action->setText( i18nc("@action new file", "&New" ) );
301 action->setIconText( i18nc( "@action Shorter Text for 'New File' shown in the toolbar", "New") );
302 connect( action, &QAction::triggered, this, &MainWindowPrivate::fileNew );
303 action->setToolTip( i18nc( "@info:tooltip", "New file" ) );
304 action->setWhatsThis( i18nc( "@info:whatsthis", "Creates an empty file." ) );
305
306 action = actionCollection()->addAction( QStringLiteral("add_toolview") );
307 action->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
308 actionCollection()->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_V);
309 action->setText( i18nc("@action", "&Add Tool View..." ) );
310 connect( action, &QAction::triggered, this, &MainWindowPrivate::viewAddNewToolView );
311 action->setToolTip( i18nc( "@info:tooltip", "Add tool view" ) );
312 action->setWhatsThis( i18nc( "@info:whatsthis", "Adds a new tool view to this window." ) );
313
314 //Load themes
315 actionCollection()->addAction(QStringLiteral("colorscheme_menu"), new ColorSchemeChooser(actionCollection()));
316 }
317
toggleArea(bool b)318 void MainWindowPrivate::toggleArea(bool b)
319 {
320 if (!b) return;
321 auto* action = qobject_cast<QAction*>(sender());
322 if (!action) return;
323 m_mainWindow->controller()->showArea(action->data().toString(), m_mainWindow);
324 }
325
actionCollection()326 KActionCollection * MainWindowPrivate::actionCollection()
327 {
328 return m_mainWindow->actionCollection();
329 }
330
registerStatus(QObject * status)331 void MainWindowPrivate::registerStatus(QObject* status)
332 {
333 m_statusBar->registerStatus(status);
334 }
335
336
showErrorMessage(const QString & message,int timeout)337 void MainWindowPrivate::showErrorMessage(const QString& message, int timeout)
338 {
339 m_statusBar->showErrorMessage(message, timeout);
340 }
341
tabContextMenuRequested(Sublime::View * view,QMenu * menu)342 void MainWindowPrivate::tabContextMenuRequested(Sublime::View* view, QMenu* menu)
343 {
344 m_tabView = view;
345
346 QAction* action;
347
348 action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18nc("@action:inmenu", "Split View Top/Bottom"));
349 connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitHorizontal);
350
351 action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18nc("@action:inmenu", "Split View Left/Right"));
352 connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitVertical);
353 menu->addSeparator();
354
355 action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@action:inmenu", "New File"));
356
357 connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuFileNew);
358
359 if (view) {
360 if (auto* doc = qobject_cast<TextDocument*>(view->document())) {
361 action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reload"));
362 connect(action, &QAction::triggered, doc, &TextDocument::reload);
363
364 action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reload All"));
365 connect(action, &QAction::triggered, this, &MainWindowPrivate::reloadAll);
366 }
367 }
368 }
369
tabToolTipRequested(Sublime::View * view,Sublime::Container * container,int tab)370 void MainWindowPrivate::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab)
371 {
372 if (m_tabTooltip.second) {
373 if (m_tabTooltip.first == view) {
374 // tooltip already shown, don't do anything. prevents flicker when moving mouse over same tab
375 return;
376 } else {
377 m_tabTooltip.second.data()->close();
378 }
379 }
380
381 auto* urlDoc = qobject_cast<Sublime::UrlDocument*>(view->document());
382
383 if (urlDoc) {
384 DUChainReadLocker lock;
385 TopDUContext* top = DUChainUtils::standardContextForUrl(urlDoc->url());
386
387 if (top) {
388 if (auto* navigationWidget = top->createNavigationWidget()) {
389 auto* tooltip = new KDevelop::NavigationToolTip(m_mainWindow, QCursor::pos() + QPoint(20, 20), navigationWidget);
390 tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10));
391 tooltip->setHandleRect(container->tabRect(tab));
392
393 m_tabTooltip.first = view;
394 m_tabTooltip.second = tooltip;
395 ActiveToolTip::showToolTip(m_tabTooltip.second.data());
396 }
397 }
398 }
399 }
400
dockBarContextMenuRequested(Qt::DockWidgetArea area,const QPoint & position)401 void MainWindowPrivate::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position)
402 {
403 QMenu menu(m_mainWindow);
404 menu.addSection(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@action:inmenu", "Add Tool View"));
405 QHash<IToolViewFactory*, Sublime::ToolDocument*> factories =
406 Core::self()->uiControllerInternal()->factoryDocuments();
407 QHash<QAction*, IToolViewFactory*> actionToFactory;
408 if ( !factories.isEmpty() ) {
409 // sorted actions
410 QMap<QString, QAction*> actionMap;
411 for (QHash<IToolViewFactory*, Sublime::ToolDocument*>::const_iterator it = factories.constBegin();
412 it != factories.constEnd(); ++it)
413 {
414 auto* action = new QAction(it.value()->statusIcon(), it.value()->title(), &menu);
415 action->setIcon(it.value()->statusIcon());
416 if (!it.key()->allowMultiple() && Core::self()->uiControllerInternal()->toolViewPresent(it.value(), m_mainWindow->area())) {
417 action->setDisabled(true);
418 }
419 actionToFactory.insert(action, it.key());
420 actionMap[action->text()] = action;
421 }
422 menu.addActions(actionMap.values());
423 }
424
425 auto* lockAction = new QAction(this);
426 lockAction->setCheckable(true);
427 lockAction->setText(i18nc("@action:inmenu", "Lock the Panel from Hiding"));
428
429 KConfigGroup config = KSharedConfig::openConfig()->group("UI");
430 lockAction->setChecked(config.readEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), false));
431
432 menu.addSeparator();
433 menu.addAction(lockAction);
434
435 QAction* triggered = menu.exec(position);
436 if ( !triggered ) {
437 return;
438 }
439
440 if (triggered == lockAction) {
441 KConfigGroup config = KSharedConfig::openConfig()->group("UI");
442 config.writeEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), lockAction->isChecked());
443 return;
444 }
445
446 Core::self()->uiControllerInternal()->addToolViewToDockArea(
447 actionToFactory[triggered],
448 area
449 );
450 }
451
changingActiveView() const452 bool MainWindowPrivate::changingActiveView() const
453 {
454 return m_changingActiveView;
455 }
456
kateWrapper() const457 KTextEditorIntegration::MainWindow *MainWindowPrivate::kateWrapper() const
458 {
459 return m_kateWrapper;
460 }
461
462 }
463
464 #include "mainwindow_actions.cpp"
465
466