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