1 /*
2   This file is part of KDE Kontact.
3 
4   SPDX-FileCopyrightText: 2001 Matthias Hoelzer-Kluepfel <mhk@kde.org>
5   SPDX-FileCopyrightText: 2002-2005 Daniel Molkentin <molkentin@kde.org>
6   SPDX-FileCopyrightText: 2003-2005 Cornelius Schumacher <schumacher@kde.org>
7   SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
8 
9   SPDX-License-Identifier: GPL-2.0-or-later
10 */
11 
12 #include "mainwindow.h"
13 #include "aboutdialog.h"
14 #include "iconsidepane.h"
15 #include "prefs.h"
16 #include "webengine/introductionwebenginepage.h"
17 #include "webengine/introductionwebengineview.h"
18 
19 #include "kontactconfiguredialog.h"
20 using namespace Kontact;
21 #ifdef WIN32
22 #include <windows.h>
23 #else
24 #include <unistd.h>
25 #endif
26 #include <Libkdepim/ProgressStatusBarWidget>
27 #include <Libkdepim/StatusbarProgressWidget>
28 #include <PimCommon/BroadcastStatus>
29 
30 #include "kontact_debug.h"
31 #include <KAboutData>
32 #include <KActionCollection>
33 #include <KActionMenu>
34 #include <KConfigGroup>
35 #include <KDialogJobUiDelegate>
36 #include <KEditToolBar>
37 #include <KHelpClient>
38 #include <KHelpMenu>
39 #include <KIO/CommandLauncherJob>
40 #include <KIO/OpenUrlJob>
41 #include <KLocalizedString>
42 #include <KMessageBox>
43 #include <KParts/PartManager>
44 #include <KPluginInfo>
45 #include <KPluginMetaData>
46 #include <KSharedConfig>
47 #include <KShortcutsDialog>
48 #include <KSqueezedTextLabel>
49 #include <KStandardAction>
50 #include <KSycoca>
51 #include <KToolBar>
52 #include <KWindowConfig>
53 #include <KXMLGUIFactory>
54 #include <QAction>
55 #include <QApplication>
56 #include <QDBusConnection>
57 #include <QHBoxLayout>
58 #include <QIcon>
59 #include <QShortcut>
60 #include <QSplitter>
61 #include <QStackedWidget>
62 #include <QStandardPaths>
63 #include <QStatusBar>
64 #include <QVBoxLayout>
65 #include <QWebEngineUrlScheme>
66 
67 #include <GrantleeTheme/GrantleeTheme>
68 #include <GrantleeTheme/GrantleeThemeManager>
69 
MainWindow()70 MainWindow::MainWindow()
71     : KontactInterface::Core()
72 {
73     // Necessary for "cid" support in kmail.
74     QWebEngineUrlScheme cidScheme("cid");
75     cidScheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ContentSecurityPolicyIgnored);
76     cidScheme.setSyntax(QWebEngineUrlScheme::Syntax::Path);
77     QWebEngineUrlScheme::registerScheme(cidScheme);
78 
79     QDBusConnection::sessionBus().registerObject(QStringLiteral("/KontactInterface"), this, QDBusConnection::ExportScriptableSlots);
80 
81     // Set this to be the group leader for all subdialogs - this means
82     // modal subdialogs will only affect this dialog, not the other windows
83     setAttribute(Qt::WA_GroupLeader);
84 
85     initGUI();
86     initObject();
87 
88     mSidePane->setMaximumWidth(mSidePane->sizeHint().width());
89     mSidePane->setMinimumWidth(mSidePane->sizeHint().width());
90 
91     factory()->plugActionList(this, QStringLiteral("navigator_actionlist"), mActionPlugins);
92 
93     KConfigGroup grp(KSharedConfig::openConfig(), "MainWindow");
94     KWindowConfig::restoreWindowSize(windowHandle(), grp);
95     setAutoSaveSettings();
96 }
97 
initGUI()98 void MainWindow::initGUI()
99 {
100     initWidgets();
101     setupActions();
102 
103     KStandardAction::keyBindings(this, &MainWindow::configureShortcuts, actionCollection());
104     KStandardAction::configureToolbars(this, &MainWindow::configureToolbars, actionCollection());
105     setXMLFile(QStringLiteral("kontactui.rc"));
106 
107     setStandardToolBarMenuEnabled(true);
108 
109     createGUI(nullptr);
110 
111     KToolBar *navigatorToolBar = findToolBar("navigatorToolBar");
112     if (navigatorToolBar) {
113         if (layoutDirection() == Qt::LeftToRight) {
114             navigatorToolBar->setLayoutDirection(Qt::RightToLeft);
115         } else {
116             navigatorToolBar->setLayoutDirection(Qt::LeftToRight);
117         }
118         Q_ASSERT(navigatorToolBar->sizeHint().isValid());
119         navigatorToolBar->setMinimumWidth(navigatorToolBar->sizeHint().width());
120     } else {
121         qCritical() << "Unable to find navigatorToolBar, probably kontactui.rc is missing";
122     }
123 }
124 
initObject()125 void MainWindow::initObject()
126 {
127     if (!KSycoca::isAvailable()) {
128         qDebug() << "Trying to create ksycoca...";
129         KSycoca::self()->ensureCacheValid();
130         if (!KSycoca::isAvailable()) {
131             // This should only happen if the distribution is broken, or the disk full
132             qFatal("KSycoca unavailable. Kontact will be unable to find plugins.");
133         }
134     }
135     mPluginMetaData = KPluginMetaData::findPlugins(QStringLiteral("kontact5"), [](const KPluginMetaData &data) {
136         return data.rawData().value(QStringLiteral("X-KDE-KontactPluginVersion")).toInt() == KONTACT_PLUGIN_VERSION;
137     });
138 
139     // prepare the part manager
140     mPartManager = new KParts::PartManager(this);
141     connect(mPartManager, &KParts::PartManager::activePartChanged, this, &MainWindow::slotActivePartChanged);
142 
143     loadPlugins();
144 
145     if (mSidePane) {
146         mSidePane->updatePlugins();
147     }
148 
149     loadSettings();
150 
151     statusBar()->show();
152 
153     // done initializing
154     slotShowStatusMsg(QString());
155 
156     connect(PimCommon::BroadcastStatus::instance(), &PimCommon::BroadcastStatus::statusMsg, this, &MainWindow::slotShowStatusMsg);
157 
158     // launch commandline specified module if any
159     activateInitialPluginModule();
160 
161     if (Prefs::lastVersionSeen() == KAboutData::applicationData().version()) {
162         selectPlugin(mCurrentPlugin);
163     }
164 
165     paintAboutScreen(QStringLiteral("introduction_kontact.html"), introductionData());
166     Prefs::setLastVersionSeen(KAboutData::applicationData().version());
167 }
168 
~MainWindow()169 MainWindow::~MainWindow()
170 {
171     if (mCurrentPlugin) {
172         KConfigGroup grp(KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(mCurrentPlugin->identifier())));
173         saveMainWindowSettings(grp);
174     }
175 
176     createGUI(nullptr);
177     saveSettings();
178 
179     Prefs::self()->save();
180 
181     // During deletion of plugins, we should not access the plugin list (bug #182176)
182     delete mSidePane;
183 
184     qDeleteAll(mPlugins);
185 }
186 
187 // Called by main().
setInitialActivePluginModule(const QString & module)188 void MainWindow::setInitialActivePluginModule(const QString &module)
189 {
190     if (mInitialActiveModule != module) {
191         mInitialActiveModule = module;
192         activateInitialPluginModule();
193     }
194 }
195 
pluginActionWeightLessThan(const QAction * left,const QAction * right)196 bool MainWindow::pluginActionWeightLessThan(const QAction *left, const QAction *right)
197 {
198     // Since this lessThan method is used only for the toolbar (which is on
199     // the inverse layout direction than the rest of the system), we add the
200     // elements on the exactly inverse order. (ereslibre)
201     return !pluginWeightLessThan(left->data().value<KontactInterface::Plugin *>(), right->data().value<KontactInterface::Plugin *>());
202 }
203 
pluginWeightLessThan(const KontactInterface::Plugin * left,const KontactInterface::Plugin * right)204 bool MainWindow::pluginWeightLessThan(const KontactInterface::Plugin *left, const KontactInterface::Plugin *right)
205 {
206     return left->weight() < right->weight();
207 }
208 
activateInitialPluginModule()209 void MainWindow::activateInitialPluginModule()
210 {
211     if (!mInitialActiveModule.isEmpty() && !mPlugins.isEmpty()) {
212         for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
213             if (!plugin->identifier().isEmpty() && plugin->identifier().contains(mInitialActiveModule)) {
214                 selectPlugin(plugin);
215                 return;
216             }
217         }
218     }
219 }
220 
initWidgets()221 void MainWindow::initWidgets()
222 {
223     auto mTopWidget = new QWidget(this);
224     auto layout = new QVBoxLayout;
225     layout->setContentsMargins({});
226     layout->setSpacing(0);
227     mTopWidget->setLayout(layout);
228     setCentralWidget(mTopWidget);
229 
230     mSplitter = new QSplitter(mTopWidget);
231     connect(mSplitter, &QSplitter::splitterMoved, this, &MainWindow::slotSplitterMoved);
232     layout->addWidget(mSplitter);
233     mSidePane = new IconSidePane(this, mSplitter);
234 
235     connect(mSidePane, SIGNAL(pluginSelected(KontactInterface::Plugin *)), SLOT(selectPlugin(KontactInterface::Plugin *)));
236 
237     mPartsStack = new QStackedWidget(mSplitter);
238     mPartsStack->layout()->setSpacing(0);
239 
240     initAboutScreen();
241 
242     paintAboutScreen(QStringLiteral("loading_kontact.html"), QVariantHash());
243 
244     auto progressStatusBarWidget = new KPIM::ProgressStatusBarWidget(statusBar(), this);
245 
246     mStatusMsgLabel = new KSqueezedTextLabel(i18nc("@info:status", " Initializing..."), statusBar());
247     mStatusMsgLabel->setTextElideMode(Qt::ElideRight);
248     mStatusMsgLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
249 
250     statusBar()->addWidget(mStatusMsgLabel, 10);
251     statusBar()->addPermanentWidget(progressStatusBarWidget->littleProgress(), 0);
252 
253     mSplitter->setCollapsible(1, false);
254 }
255 
paintAboutScreen(const QString & templateName,const QVariantHash & data)256 void MainWindow::paintAboutScreen(const QString &templateName, const QVariantHash &data)
257 {
258     GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"), QStringLiteral("splash.theme"), nullptr, QStringLiteral("messageviewer/about/"));
259     GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default"));
260     if (!theme.isValid()) {
261         qCDebug(KONTACT_LOG) << "Theme error: failed to find splash theme";
262     } else {
263         mIntroPart->setHtml(theme.render(templateName, data, QByteArrayLiteral("kontact")), QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/')));
264     }
265 }
266 
initAboutScreen()267 void MainWindow::initAboutScreen()
268 {
269     auto introbox = new QWidget(mPartsStack);
270     auto introboxHBoxLayout = new QHBoxLayout(introbox);
271     introboxHBoxLayout->setContentsMargins({});
272     mPartsStack->addWidget(introbox);
273     mPartsStack->setCurrentWidget(introbox);
274     mIntroPart = new IntroductionWebEngineView(introbox);
275     connect(mIntroPart, &IntroductionWebEngineView::openUrl, this, &MainWindow::slotOpenUrl, Qt::QueuedConnection);
276     introboxHBoxLayout->addWidget(mIntroPart);
277 }
278 
setupActions()279 void MainWindow::setupActions()
280 {
281     actionCollection()->addAction(KStandardAction::Quit, this, SLOT(slotQuit()));
282 
283     mNewActions = new KActionMenu(i18nc("@title:menu create new pim items (message,calendar,to-do,etc.)", "New"), this);
284     actionCollection()->addAction(QStringLiteral("action_new"), mNewActions);
285     actionCollection()->setDefaultShortcuts(mNewActions, KStandardShortcut::openNew());
286     connect(mNewActions, &KActionMenu::triggered, this, &MainWindow::slotNewClicked);
287 
288     auto action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Kontact..."), this);
289     setHelpText(action, i18nc("@info:status", "Configure Kontact"));
290     action->setWhatsThis(i18nc("@info:whatsthis", "You will be presented with a dialog where you can configure Kontact."));
291     actionCollection()->addAction(QStringLiteral("settings_configure_kontact"), action);
292     connect(action, &QAction::triggered, this, &MainWindow::slotPreferences);
293 
294     action = new QAction(QIcon::fromTheme(QStringLiteral("kontact")), i18nc("@action:inmenu", "&Kontact Introduction"), this);
295     setHelpText(action, i18nc("@info:status", "Show the Kontact Introduction page"));
296     action->setWhatsThis(i18nc("@info:whatsthis", "Choose this option to see the Kontact Introduction page."));
297     actionCollection()->addAction(QStringLiteral("help_introduction"), action);
298     connect(action, &QAction::triggered, this, &MainWindow::slotShowIntroduction);
299 
300     mShowHideAction = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-width")), i18nc("@action:inmenu", "Hide/Show the component sidebar"), this);
301     setHelpText(mShowHideAction, i18nc("@info:status", "Hide/Show the component sidebar"));
302     mShowHideAction->setCheckable(true);
303     mShowHideAction->setChecked(Prefs::self()->sideBarOpen());
304     mShowHideAction->setWhatsThis(i18nc("@info:whatsthis", "Allows you to show or hide the component sidebar as desired."));
305     actionCollection()->addAction(QStringLiteral("hide_show_sidebar"), mShowHideAction);
306     actionCollection()->setDefaultShortcut(mShowHideAction, QKeySequence(Qt::Key_F9));
307     connect(mShowHideAction, &QAction::triggered, this, &MainWindow::slotShowHideSideBar);
308 }
309 
pluginFromName(const QString & identifier) const310 KontactInterface::Plugin *MainWindow::pluginFromName(const QString &identifier) const
311 {
312     auto hasIdentifier = [&](KontactInterface::Plugin *plugin) {
313         return plugin->identifier() == identifier;
314     };
315     const auto it = std::find_if(mPlugins.constBegin(), mPlugins.constEnd(), hasIdentifier);
316     return it == mPlugins.constEnd() ? nullptr : *it;
317 }
318 
loadPlugins()319 void MainWindow::loadPlugins()
320 {
321     QList<KontactInterface::Plugin *> plugins;
322     KConfigGroup configGroup(Prefs::self()->config(), "Plugins");
323     for (const KPluginMetaData &pluginMetaData : std::as_const(mPluginMetaData)) {
324         if (!configGroup.readEntry(pluginMetaData.pluginId() + QLatin1String("Enabled"), pluginMetaData.isEnabledByDefault())) {
325             continue;
326         }
327 
328         KontactInterface::Plugin *plugin = pluginFromName(pluginMetaData.pluginId());
329         if (plugin) { // already loaded
330             plugin->configUpdated();
331             continue;
332         }
333 
334         qCDebug(KONTACT_LOG) << "Loading Plugin:" << pluginMetaData.name();
335         const auto loadResult = KPluginFactory::instantiatePlugin<KontactInterface::Plugin>(pluginMetaData, this);
336         if (!loadResult) {
337             qCWarning(KONTACT_LOG) << "Error loading plugin" << pluginMetaData.fileName() << loadResult.errorString;
338             continue;
339         } else {
340             plugin = loadResult.plugin;
341             if (!plugin) {
342                 qCWarning(KONTACT_LOG) << "Unable to create plugin for" << pluginMetaData.fileName();
343                 continue;
344             }
345         }
346         plugin->setIdentifier(pluginMetaData.pluginId());
347         plugin->setTitle(pluginMetaData.name());
348         plugin->setIcon(pluginMetaData.iconName());
349 
350         const QString libNameProp = pluginMetaData.value(QStringLiteral("X-KDE-KontactPartLibraryName"));
351         const QString exeNameProp = pluginMetaData.value(QStringLiteral("X-KDE-KontactPartExecutableName"));
352 
353         if (pluginMetaData.rawData().contains(QLatin1String("X-KDE-KontactPartLoadOnStart"))) {
354             const bool loadOnStart = pluginMetaData.rawData().value(QStringLiteral("X-KDE-KontactPartLoadOnStart")).toBool();
355             if (loadOnStart) {
356                 mDelayedPreload.append(plugin);
357             }
358         }
359         if (pluginMetaData.rawData().contains(QLatin1String("X-KDE-KontactPluginHasPart"))) {
360             const bool hasPartProp = pluginMetaData.rawData().value(QStringLiteral("X-KDE-KontactPluginHasPart")).toBool();
361             plugin->setShowInSideBar(hasPartProp);
362         } else {
363             plugin->setShowInSideBar(true);
364         }
365 
366         qCDebug(KONTACT_LOG) << "LIBNAMEPART:" << libNameProp;
367 
368         plugin->setPartLibraryName(libNameProp.toUtf8());
369         plugin->setExecutableName(exeNameProp);
370         plugins.append(plugin);
371     }
372     std::sort(plugins.begin(), plugins.end(), pluginWeightLessThan); // new plugins
373 
374     for (KontactInterface::Plugin *plugin : std::as_const(plugins)) {
375         const QList<QAction *> actionList = plugin->newActions();
376         for (QAction *action : actionList) {
377             qCDebug(KONTACT_LOG) << "Plugging new action" << action->objectName();
378             mNewActions->addAction(action);
379         }
380         addPlugin(plugin);
381     }
382 
383     std::sort(mPlugins.begin(), mPlugins.end(), pluginWeightLessThan); // all plugins
384 
385     // sort the action plugins again and reset shortcuts. If we removed and then readded some plugins
386     // we need to take in count their weights for setting shortcuts again
387     std::sort(mActionPlugins.begin(), mActionPlugins.end(), pluginActionWeightLessThan);
388 
389     updateShortcuts();
390 
391     const bool state = (!mPlugins.isEmpty());
392     mNewActions->setEnabled(state);
393 }
394 
unloadDisabledPlugins()395 void MainWindow::unloadDisabledPlugins()
396 {
397     // Only remove the now-disabled plugins.
398     // Keep the other ones. loadPlugins() will skip those that are already loaded.
399     KConfigGroup configGroup(Prefs::self()->config(), "Plugins");
400     for (const KPluginMetaData &pluginMetaData : std::as_const(mPluginMetaData)) {
401         if (!configGroup.readEntry(pluginMetaData.pluginId() + QLatin1String("Enabled"), pluginMetaData.isEnabledByDefault())) {
402             removePlugin(pluginMetaData.pluginId());
403         }
404     }
405 }
406 
updateShortcuts()407 void MainWindow::updateShortcuts()
408 {
409     for (int i = 0, total = mActionPlugins.count(); i < total; ++i) {
410         QAction *action = mActionPlugins.at(i);
411         const QKeySequence shortcut(QStringLiteral("Ctrl+%1").arg(mActionPlugins.count() - i));
412         actionCollection()->setDefaultShortcut(action, shortcut);
413         // Prevent plugActionList from restoring some old saved shortcuts
414         action->setProperty("_k_DefaultShortcut", QVariant::fromValue(QList<QKeySequence>{shortcut}));
415     }
416 }
417 
removePlugin(const QString & pluginName)418 bool MainWindow::removePlugin(const QString &pluginName)
419 {
420     auto hasIdentifier = [&](KontactInterface::Plugin *plugin) {
421         return plugin->identifier() == pluginName;
422     };
423     const auto it = std::find_if(mPlugins.begin(), mPlugins.end(), hasIdentifier);
424     if (it == mPlugins.end()) {
425         qCDebug(KONTACT_LOG) << "Plugin not found" << pluginName;
426         return false;
427     }
428     KontactInterface::Plugin *plugin = *it;
429     const QList<QAction *> actionList = plugin->newActions();
430     for (QAction *action : actionList) {
431         qCDebug(KONTACT_LOG) << QStringLiteral("Unplugging New actions") << action->objectName();
432         mNewActions->removeAction(action);
433     }
434 
435     removeChildClient(plugin);
436 
437     if (mCurrentPlugin == plugin) {
438         mCurrentPlugin = nullptr;
439         createGUI(nullptr);
440     }
441 
442     plugin->deleteLater(); // removes the part automatically
443     mPlugins.erase(it);
444     if (plugin->showInSideBar()) {
445         QAction *q = mPluginAction[plugin]; // remove QAction, to free the shortcut for later use
446         mActionPlugins.removeAll(q);
447         mPluginAction.remove(plugin);
448         actionCollection()->removeAction(q); // deletes q
449     }
450 
451     if (mCurrentPlugin == nullptr) {
452         for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
453             if (plugin->showInSideBar()) {
454                 selectPlugin(plugin);
455                 return true;
456             }
457         }
458     }
459     return true;
460 }
461 
addPlugin(KontactInterface::Plugin * plugin)462 void MainWindow::addPlugin(KontactInterface::Plugin *plugin)
463 {
464     qCDebug(KONTACT_LOG);
465 
466     mPlugins.append(plugin);
467 
468     if (plugin->showInSideBar()) {
469         auto action = new QAction(QIcon::fromTheme(plugin->icon()), plugin->title(), this);
470         // action->setHelpText(
471         //            i18nc( "@info:status", "Plugin %1", plugin->title() ) );
472         action->setWhatsThis(i18nc("@info:whatsthis", "Switch to plugin %1", plugin->title()));
473         action->setCheckable(true);
474         action->setData(QVariant::fromValue(plugin)); // used by pluginActionWeightLessThan
475         connect(action, &QAction::triggered, this, [=]() {
476             slotActionTriggered(action, plugin->identifier());
477         });
478         actionCollection()->addAction(plugin->identifier(), action);
479         mActionPlugins.append(action);
480         mPluginAction.insert(plugin, action);
481     }
482 
483     // merge the plugins GUI into the main window
484     insertChildClient(plugin);
485 }
486 
partLoaded(KontactInterface::Plugin * plugin,KParts::Part * part)487 void MainWindow::partLoaded(KontactInterface::Plugin *plugin, KParts::Part *part)
488 {
489     Q_UNUSED(plugin)
490 
491     // See if we have this part already (e.g. due to two plugins sharing it)
492     if (mPartsStack->indexOf(part->widget()) != -1) {
493         return;
494     }
495 
496     mPartsStack->addWidget(part->widget());
497 
498     mPartManager->addPart(part, false);
499     // Workaround for KParts misbehavior: addPart calls show!
500     part->widget()->hide();
501 }
502 
slotActivePartChanged(KParts::Part * part)503 void MainWindow::slotActivePartChanged(KParts::Part *part)
504 {
505     if (!part) {
506         createGUI(nullptr);
507         return;
508     }
509 
510     qCDebug(KONTACT_LOG) << QStringLiteral("Part activated:") << part << QStringLiteral("with stack id.") << mPartsStack->indexOf(part->widget());
511 
512     statusBar()->clearMessage();
513 }
514 
slotNewClicked()515 void MainWindow::slotNewClicked()
516 {
517     if (!mCurrentPlugin || !mCurrentPlugin->newActions().isEmpty()) {
518         mCurrentPlugin->newActions().at(0)->trigger();
519     } else {
520         for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
521             if (!plugin->newActions().isEmpty()) {
522                 plugin->newActions().constFirst()->trigger();
523                 return;
524             }
525         }
526     }
527 }
528 
findToolBar(const char * name)529 KToolBar *MainWindow::findToolBar(const char *name)
530 {
531     // like KMainWindow::toolBar, but which doesn't create the toolbar if not found
532     return findChild<KToolBar *>(QLatin1String(name));
533 }
534 
selectPlugin(KontactInterface::Plugin * plugin)535 void MainWindow::selectPlugin(KontactInterface::Plugin *plugin)
536 {
537     if (!plugin) {
538         return;
539     }
540 
541     if (plugin->isRunningStandalone()) {
542         statusBar()->showMessage(i18nc("@info:status", "Application is running standalone. Foregrounding..."), 1000);
543         plugin->bringToForeground();
544         return;
545     }
546 
547     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
548 
549     if (mCurrentPlugin) {
550         KConfigGroup grp = KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(mCurrentPlugin->identifier()));
551         saveMainWindowSettings(grp);
552     }
553 
554     KParts::Part *part = plugin->part();
555 
556     if (!part) {
557         QApplication::restoreOverrideCursor();
558         KMessageBox::error(this, i18nc("@info", "Cannot load part for %1.", plugin->title()) + QLatin1Char('\n') + lastErrorMessage());
559         plugin->setDisabled(true);
560         mSidePane->updatePlugins();
561         return;
562     }
563 
564     if (mCurrentPlugin) {
565         QAction *action = mPluginAction.value(mCurrentPlugin);
566         if (action) {
567             action->setChecked(false);
568         }
569     }
570     QAction *selectedPluginAction = mPluginAction.value(mCurrentPlugin);
571     if (selectedPluginAction) {
572         selectedPluginAction->setChecked(true);
573     }
574 
575     // store old focus widget
576     QWidget *focusWidget = qApp->focusWidget();
577     if (mCurrentPlugin && focusWidget) {
578         // save the focus widget only when it belongs to the activated part
579         QWidget *parent = focusWidget->parentWidget();
580         while (parent) {
581             if (parent == mCurrentPlugin->part()->widget()) {
582                 mFocusWidgets.insert(mCurrentPlugin->identifier(), QPointer<QWidget>(focusWidget));
583             }
584             parent = parent->parentWidget();
585         }
586     }
587 
588     if (mSidePane) {
589         mSidePane->setCurrentPlugin(plugin->identifier());
590     }
591 
592     plugin->aboutToSelect();
593 
594     mPartManager->setActivePart(part);
595     QWidget *view = part->widget();
596     Q_ASSERT(view);
597 
598     if (view) {
599         mPartsStack->setCurrentWidget(view);
600         view->show();
601 
602         if (!mFocusWidgets.isEmpty() && mFocusWidgets.contains(plugin->identifier())) {
603             focusWidget = mFocusWidgets.value(plugin->identifier());
604             if (focusWidget) {
605                 focusWidget->setFocus();
606             }
607         } else {
608             view->setFocus();
609         }
610 
611         mCurrentPlugin = plugin;
612 
613         QAction *newAction = nullptr;
614         if (!plugin->newActions().isEmpty()) {
615             newAction = plugin->newActions().at(0);
616         }
617 
618         const QString oldWindowTitle = windowTitle();
619 
620         createGUI(plugin->part());
621         plugin->shortcutChanged();
622 
623         // Only some parts (like kmail) set a caption in guiActivateEvent when being activated.
624         // For others, this is the default caption:
625         if (windowTitle() == oldWindowTitle) {
626             setCaption(i18nc("@title:window Plugin dependent window title", "%1 - Kontact", plugin->title()));
627         }
628 
629         if (newAction) {
630             mNewActions->setIcon(newAction->icon());
631             mNewActions->setText(newAction->text());
632             mNewActions->setWhatsThis(newAction->whatsThis());
633         } else { // we'll use the action of the first plugin which offers one
634             for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
635                 if (!plugin->newActions().isEmpty()) {
636                     newAction = plugin->newActions().constFirst();
637                 }
638                 if (newAction) {
639                     mNewActions->setIcon(newAction->icon());
640                     mNewActions->setText(newAction->text());
641                     mNewActions->setWhatsThis(newAction->whatsThis());
642                     break;
643                 }
644             }
645         }
646     }
647 
648     KToolBar *navigatorToolBar = findToolBar("navigatorToolBar");
649     if (navigatorToolBar && !navigatorToolBar->isHidden()
650         && (toolBarArea(navigatorToolBar) == Qt::TopToolBarArea || toolBarArea(navigatorToolBar) == Qt::BottomToolBarArea)) {
651         addToolBar(toolBarArea(navigatorToolBar), navigatorToolBar);
652     }
653 
654     applyMainWindowSettings(KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(plugin->identifier())));
655 
656     QApplication::restoreOverrideCursor();
657 }
658 
slotActionTriggered(QAction * action,const QString & identifier)659 void MainWindow::slotActionTriggered(QAction *action, const QString &identifier)
660 {
661     action->setChecked(true);
662     if (!identifier.isEmpty()) {
663         mSidePane->setCurrentPlugin(identifier);
664     }
665 }
666 
selectPlugin(const QString & pluginName)667 void MainWindow::selectPlugin(const QString &pluginName)
668 {
669     KontactInterface::Plugin *plugin = pluginFromName(pluginName);
670     if (plugin) {
671         selectPlugin(plugin);
672     }
673 }
674 
loadSettings()675 void MainWindow::loadSettings()
676 {
677     if (mSplitter) {
678         showHideSideBar(Prefs::self()->sideBarOpen());
679     }
680 
681     // Preload Plugins. This _must_ happen before the default part is loaded
682     for (KontactInterface::Plugin *plugin : std::as_const(mDelayedPreload)) {
683         selectPlugin(plugin);
684     }
685     selectPlugin(Prefs::self()->mActivePlugin);
686 }
687 
saveSettings()688 void MainWindow::saveSettings()
689 {
690     if (mSplitter) {
691         Prefs::self()->mSidePaneSplitter = mSplitter->sizes();
692     }
693 
694     if (mCurrentPlugin) {
695         Prefs::self()->mActivePlugin = mCurrentPlugin->identifier();
696     }
697 }
698 
slotShowIntroduction()699 void MainWindow::slotShowIntroduction()
700 {
701     mPartsStack->setCurrentIndex(0);
702 }
703 
slotQuit()704 void MainWindow::slotQuit()
705 {
706     mReallyClose = true;
707     close();
708 }
709 
slotPreferences()710 void MainWindow::slotPreferences()
711 {
712     static Kontact::KontactConfigureDialog *dlg = nullptr;
713     if (!dlg) {
714         dlg = new Kontact::KontactConfigureDialog(this);
715         connect(dlg, &KontactSettingsDialog::configCommitted, this, [this](const QByteArray &componentName) {
716             if (componentName == QByteArrayLiteral("kontact")) {
717                 MainWindow::updateConfig();
718             }
719         });
720 
721         // Add the main contact KCM which is not associated with a specific plugin
722         dlg->addModule(KPluginMetaData(QStringLiteral("pim/kcms/kontact/kcm_kontact")));
723 
724         auto sortByWeight = [](const KPluginMetaData &m1, const KPluginMetaData &m2) {
725             return m1.rawData().value(QStringLiteral("X-KDE-Weight")).toInt() < m2.rawData().value(QStringLiteral("X-KDE-Weight")).toInt();
726         };
727         std::sort(mPluginMetaData.begin(), mPluginMetaData.end(), sortByWeight);
728         for (const KPluginMetaData &metaData : std::as_const(mPluginMetaData)) {
729             const QString pluginNamespace = metaData.value(QStringLiteral("X-KDE-ConfigModuleNamespace"));
730             if (!pluginNamespace.isEmpty()) {
731                 auto plugins = KPluginMetaData::findPlugins(pluginNamespace);
732                 std::sort(plugins.begin(), plugins.end(), sortByWeight);
733                 dlg->addPluginComponent(metaData, plugins);
734             }
735         }
736     }
737 
738     dlg->show();
739 }
740 
741 // Called when the user enables/disables plugins in the configuration dialog
pluginsChanged()742 void MainWindow::pluginsChanged()
743 {
744     unplugActionList(QStringLiteral("navigator_actionlist"));
745     unloadDisabledPlugins();
746     loadPlugins();
747     mSidePane->updatePlugins();
748     factory()->plugActionList(this, QStringLiteral("navigator_actionlist"), mActionPlugins);
749 }
750 
updateConfig()751 void MainWindow::updateConfig()
752 {
753     qCDebug(KONTACT_LOG);
754 
755     saveSettings();
756     loadSettings();
757 }
758 
showAboutDialog()759 void MainWindow::showAboutDialog()
760 {
761     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
762 
763     if (!mAboutDialog) {
764         mAboutDialog = new AboutDialog(this);
765     }
766 
767     mAboutDialog->show();
768     mAboutDialog->raise();
769     QApplication::restoreOverrideCursor();
770 }
771 
configureShortcuts()772 void MainWindow::configureShortcuts()
773 {
774     KShortcutsDialog dialog(this);
775     dialog.addCollection(actionCollection());
776 
777     if (mCurrentPlugin && mCurrentPlugin->part()) {
778         dialog.addCollection(mCurrentPlugin->part()->actionCollection());
779     }
780 
781     dialog.configure();
782     if (mCurrentPlugin && mCurrentPlugin->part()) {
783         mCurrentPlugin->shortcutChanged();
784     }
785 }
786 
configureToolbars()787 void MainWindow::configureToolbars()
788 {
789     if (mCurrentPlugin) {
790         KConfigGroup grp(KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(mCurrentPlugin->identifier())));
791         saveMainWindowSettings(grp);
792     }
793     QPointer<KEditToolBar> edit = new KEditToolBar(factory());
794     connect(edit.data(), &KEditToolBar::newToolBarConfig, this, &MainWindow::slotNewToolbarConfig);
795     edit->exec();
796     delete edit;
797 }
798 
slotNewToolbarConfig()799 void MainWindow::slotNewToolbarConfig()
800 {
801     if (mCurrentPlugin && mCurrentPlugin->part()) {
802         createGUI(mCurrentPlugin->part());
803     }
804     if (mCurrentPlugin) {
805         applyMainWindowSettings(KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(mCurrentPlugin->identifier())));
806     }
807     factory()->plugActionList(this, QStringLiteral("navigator_actionlist"), mActionPlugins);
808 }
809 
slotOpenUrl(const QUrl & url)810 void MainWindow::slotOpenUrl(const QUrl &url)
811 {
812     if (url.scheme() == QLatin1String("exec")) {
813         const QString path(url.path());
814         if (path == QLatin1String("/switch")) {
815             if (mCurrentPlugin) {
816                 mPartsStack->setCurrentIndex(mPartsStack->indexOf(mCurrentPlugin->part()->widget()));
817             }
818         } else if (path == QLatin1String("/accountwizard")) {
819             auto job = new KIO::CommandLauncherJob(QStringLiteral("accountwizard"));
820             job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
821             job->exec();
822             slotQuit();
823         } else if (path.startsWith(QLatin1String("/help"))) {
824             QString app(QStringLiteral("org.kde.kontact"));
825             if (!url.query().isEmpty()) {
826                 app = url.query();
827             }
828             KHelpClient::invokeHelp(QString(), app);
829         }
830     } else {
831         auto job = new KIO::OpenUrlJob(url);
832         job->start();
833     }
834 }
835 
readProperties(const KConfigGroup & config)836 void MainWindow::readProperties(const KConfigGroup &config)
837 {
838     Core::readProperties(config);
839 
840     const auto activePluginList = config.readEntry("ActivePlugins", QStringList());
841     QSet<QString> activePlugins(activePluginList.begin(), activePluginList.end());
842 
843     if (!activePlugins.isEmpty()) {
844         for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
845             if (!plugin->isRunningStandalone() && activePlugins.contains(plugin->identifier())) {
846                 plugin->readProperties(config);
847             }
848         }
849     }
850 }
851 
saveProperties(KConfigGroup & config)852 void MainWindow::saveProperties(KConfigGroup &config)
853 {
854     Core::saveProperties(config);
855 
856     QStringList activePlugins;
857 
858     KConfigGroup configGroup(Prefs::self()->config(), "Plugins");
859     for (const KPluginMetaData &pluginMetaData : std::as_const(mPluginMetaData)) {
860         if (!configGroup.readEntry(pluginMetaData.pluginId() + QLatin1String("Enabled"), pluginMetaData.isEnabledByDefault())) {
861             KontactInterface::Plugin *plugin = pluginFromName(pluginMetaData.pluginId());
862             if (plugin) {
863                 activePlugins.append(plugin->identifier());
864                 plugin->saveProperties(config);
865             }
866         }
867     }
868 
869     config.writeEntry("ActivePlugins", activePlugins);
870 }
871 
queryClose()872 bool MainWindow::queryClose()
873 {
874     if (qApp->isSavingSession() || mReallyClose) {
875         return true;
876     }
877 
878     for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
879         if (!plugin->isRunningStandalone()) {
880             if (!plugin->queryClose()) {
881                 return false;
882             }
883         }
884     }
885     return true;
886 }
887 
slotShowStatusMsg(const QString & msg)888 void MainWindow::slotShowStatusMsg(const QString &msg)
889 {
890     if (!statusBar() || !mStatusMsgLabel) {
891         return;
892     }
893 
894     mStatusMsgLabel->setText(msg);
895 }
896 
introductionData()897 QVariantHash MainWindow::introductionData()
898 {
899     QVariantHash data;
900     data[QStringLiteral("icon")] = QStringLiteral("kontact");
901     data[QStringLiteral("name")] = i18n("Kontact");
902     data[QStringLiteral("subtitle")] = i18n("The KDE Personal Information Management Suite.");
903     data[QStringLiteral("version")] = KAboutData::applicationData().version();
904 
905     QVariantList links = {QVariantHash{{QStringLiteral("url"), QStringLiteral("exec:/help?org.kde.kontact")},
906                                        {QStringLiteral("icon"), QStringLiteral("help-contents")},
907                                        {QStringLiteral("title"), i18n("Read Manual")},
908                                        {QStringLiteral("subtext"), i18n("Learn more about Kontact and its components")}},
909                           QVariantHash{{QStringLiteral("url"), QStringLiteral("https://kontact.kde.org")},
910                                        {QStringLiteral("icon"), QStringLiteral("kontact")},
911                                        {QStringLiteral("title"), i18n("Visit Kontact Website")},
912                                        {QStringLiteral("subtext"), i18n("Access online resources and tutorials")}},
913                           QVariantHash{{QStringLiteral("url"), QStringLiteral("exec:/accountwizard")},
914                                        {QStringLiteral("icon"), QStringLiteral("tools-wizard")},
915                                        {QStringLiteral("title"), i18n("Setup your Accounts")},
916                                        {QStringLiteral("subtext"), i18n("Prepare Kontact for use")}}};
917     data[QStringLiteral("links")] = links;
918 
919     return data;
920 }
921 
showHideSideBar(bool show)922 void MainWindow::showHideSideBar(bool show)
923 {
924     QList<int> sizes = mSplitter->sizes();
925     if (!sizes.isEmpty()) {
926         if (show) {
927             sizes[0] = mSaveSideBarWidth;
928         } else {
929             mSaveSideBarWidth = qMax(sizes[0], 10);
930             sizes[0] = 0;
931         }
932         mSplitter->setSizes(sizes);
933         Prefs::self()->setSideBarOpen(show);
934         mShowHideAction->setChecked(show);
935     }
936 }
937 
showHideSideBarMessage(bool hidden) const938 QString MainWindow::showHideSideBarMessage(bool hidden) const
939 {
940     if (hidden) {
941         return i18nc("@info:status", "Sidebar is hidden. Show the sidebar again using the %1 key.", mShowHideAction->shortcut().toString());
942     } else {
943         return QString();
944     }
945 }
946 
slotShowHideSideBar()947 void MainWindow::slotShowHideSideBar()
948 {
949     QList<int> sizes = mSplitter->sizes();
950     if (!sizes.isEmpty()) {
951         bool open = (sizes.at(0) != 0);
952         showHideSideBar(!open);
953         if (open) {
954             statusBar()->showMessage(showHideSideBarMessage(true));
955         } else {
956             statusBar()->showMessage(showHideSideBarMessage(false));
957         }
958     }
959 }
960 
slotSplitterMoved(int pos,int index)961 void MainWindow::slotSplitterMoved(int pos, int index)
962 {
963     if (index == 1) {
964         if (pos == 0) {
965             statusBar()->showMessage(showHideSideBarMessage(true));
966         } else {
967             statusBar()->showMessage(showHideSideBarMessage(false));
968         }
969     }
970 }
971 
setHelpText(QAction * action,const QString & text)972 void MainWindow::setHelpText(QAction *action, const QString &text)
973 {
974     action->setStatusTip(text);
975     action->setToolTip(text);
976     if (action->whatsThis().isEmpty()) {
977         action->setWhatsThis(text);
978     }
979 }
980