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