File indexing completed on 2024-05-19 05:21:24

0001 /*
0002   This file is part of KDE Kontact.
0003 
0004   SPDX-FileCopyrightText: 2001 Matthias Hoelzer-Kluepfel <mhk@kde.org>
0005   SPDX-FileCopyrightText: 2002-2005 Daniel Molkentin <molkentin@kde.org>
0006   SPDX-FileCopyrightText: 2003-2005 Cornelius Schumacher <schumacher@kde.org>
0007   SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
0008 
0009   SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "mainwindow.h"
0013 #include "iconsidepane.h"
0014 #include "prefs.h"
0015 #include "webengine/introductionwebenginepage.h"
0016 #include "webengine/introductionwebengineview.h"
0017 
0018 #include "kontactconfiguredialog.h"
0019 using namespace Kontact;
0020 #ifdef WIN32
0021 #include <windows.h>
0022 #else
0023 #include <unistd.h>
0024 #endif
0025 #include <Libkdepim/ProgressStatusBarWidget>
0026 #include <Libkdepim/StatusbarProgressWidget>
0027 #include <PimCommon/BroadcastStatus>
0028 
0029 #include "kontact_debug.h"
0030 #include <KAboutData>
0031 #include <KActionCollection>
0032 #include <KActionMenu>
0033 #include <KColorSchemeManager>
0034 #include <KColorSchemeMenu>
0035 #include <KConfigGroup>
0036 #include <KDialogJobUiDelegate>
0037 #include <KEditToolBar>
0038 #include <KHelpClient>
0039 #include <KIO/CommandLauncherJob>
0040 #include <KIO/OpenUrlJob>
0041 #include <KLocalizedString>
0042 #include <KMessageBox>
0043 #include <KParts/PartManager>
0044 #include <KPluginMetaData>
0045 #include <KSharedConfig>
0046 #include <KShortcutsDialog>
0047 #include <KSqueezedTextLabel>
0048 #include <KStandardAction>
0049 #include <KSycoca>
0050 #include <KToggleAction>
0051 #include <KToolBar>
0052 #include <KWindowConfig>
0053 #include <KXMLGUIFactory>
0054 #include <QAction>
0055 #include <QApplication>
0056 #include <QDBusConnection>
0057 #include <QFontDatabase>
0058 #include <QHBoxLayout>
0059 #include <QIcon>
0060 #include <QMenuBar>
0061 #include <QPointer>
0062 #include <QShortcut>
0063 #include <QSplitter>
0064 #include <QStackedWidget>
0065 #include <QStandardPaths>
0066 #include <QStatusBar>
0067 #include <QVBoxLayout>
0068 #include <QWebEngineUrlScheme>
0069 
0070 #include <GrantleeTheme/GrantleeTheme>
0071 #include <GrantleeTheme/GrantleeThemeManager>
0072 
0073 #include <PimCommon/NeedUpdateVersionUtils>
0074 #include <PimCommon/NeedUpdateVersionWidget>
0075 
0076 // signal handler for SIGINT & SIGTERM
0077 #ifdef Q_OS_UNIX
0078 #include <KSignalHandler>
0079 #include <signal.h>
0080 #include <unistd.h>
0081 #endif
0082 
0083 MainWindow::MainWindow()
0084     : KontactInterface::Core()
0085 {
0086 #ifdef Q_OS_UNIX
0087     /**
0088      * Set up signal handler for SIGINT and SIGTERM
0089      */
0090     KSignalHandler::self()->watchSignal(SIGINT);
0091     KSignalHandler::self()->watchSignal(SIGTERM);
0092     connect(KSignalHandler::self(), &KSignalHandler::signalReceived, this, [this](int signal) {
0093         if (signal == SIGINT || signal == SIGTERM) {
0094             // Intercept console.
0095             printf("Shutting down...\n");
0096             slotQuit();
0097         }
0098     });
0099 #endif
0100     // Necessary for "cid" support in kmail.
0101     QWebEngineUrlScheme cidScheme("cid");
0102     cidScheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::ContentSecurityPolicyIgnored | QWebEngineUrlScheme::LocalScheme
0103                        | QWebEngineUrlScheme::LocalAccessAllowed);
0104 
0105     cidScheme.setSyntax(QWebEngineUrlScheme::Syntax::Path);
0106     QWebEngineUrlScheme::registerScheme(cidScheme);
0107 
0108     QDBusConnection::sessionBus().registerObject(QStringLiteral("/KontactInterface"), this, QDBusConnection::ExportScriptableSlots);
0109 
0110     initGUI();
0111     initObject();
0112 
0113     mSidePane->setMaximumWidth(mSidePane->sizeHint().width());
0114     mSidePane->setMinimumWidth(mSidePane->sizeHint().width());
0115 
0116     factory()->plugActionList(this, QStringLiteral("navigator_actionlist"), mActionPlugins);
0117 
0118     KConfigGroup grp(KSharedConfig::openConfig(), QStringLiteral("MainWindow"));
0119     KWindowConfig::restoreWindowSize(windowHandle(), grp);
0120     setAutoSaveSettings();
0121     mShowMenuBarAction->setChecked(Prefs::self()->showMenuBar());
0122     slotToggleMenubar(true);
0123 }
0124 
0125 void MainWindow::initGUI()
0126 {
0127     initWidgets();
0128     setupActions();
0129 
0130     KStandardAction::keyBindings(this, &MainWindow::configureShortcuts, actionCollection());
0131     KStandardAction::configureToolbars(this, &MainWindow::configureToolbars, actionCollection());
0132     setXMLFile(QStringLiteral("kontactui.rc"));
0133 
0134     setStandardToolBarMenuEnabled(true);
0135 
0136     createGUI(nullptr);
0137 
0138     KToolBar *navigatorToolBar = findToolBar("navigatorToolBar");
0139     if (navigatorToolBar) {
0140         if (layoutDirection() == Qt::LeftToRight) {
0141             navigatorToolBar->setLayoutDirection(Qt::RightToLeft);
0142         } else {
0143             navigatorToolBar->setLayoutDirection(Qt::LeftToRight);
0144         }
0145         Q_ASSERT(navigatorToolBar->sizeHint().isValid());
0146         navigatorToolBar->setMinimumWidth(navigatorToolBar->sizeHint().width());
0147     } else {
0148         qCCritical(KONTACT_LOG) << "Unable to find navigatorToolBar, probably kontactui.rc is missing";
0149     }
0150 }
0151 
0152 void MainWindow::initObject()
0153 {
0154     if (!KSycoca::isAvailable()) {
0155         qDebug() << "Trying to create ksycoca...";
0156         KSycoca::self()->ensureCacheValid();
0157         if (!KSycoca::isAvailable()) {
0158             // This should only happen if the distribution is broken, or the disk full
0159             qFatal("KSycoca unavailable. Kontact will be unable to find plugins.");
0160         }
0161     }
0162     mPluginMetaData = KPluginMetaData::findPlugins(QStringLiteral("pim6/kontact"), [](const KPluginMetaData &data) {
0163         return data.rawData().value(QStringLiteral("X-KDE-KontactPluginVersion")).toInt() == KONTACT_PLUGIN_VERSION;
0164     });
0165 
0166     // prepare the part manager
0167     mPartManager = new KParts::PartManager(this);
0168     connect(mPartManager, &KParts::PartManager::activePartChanged, this, &MainWindow::slotActivePartChanged);
0169 
0170     loadPlugins();
0171 
0172     if (mSidePane) {
0173         mSidePane->updatePlugins();
0174     }
0175 
0176     loadSettings();
0177 
0178     statusBar()->show();
0179 
0180     // done initializing
0181     slotShowStatusMsg(QString());
0182 
0183     connect(PimCommon::BroadcastStatus::instance(), &PimCommon::BroadcastStatus::statusMsg, this, &MainWindow::slotShowStatusMsg);
0184 
0185     // launch commandline specified module if any
0186     activateInitialPluginModule();
0187 
0188     if (Prefs::lastVersionSeen() == KAboutData::applicationData().version()) {
0189         selectPlugin(mCurrentPlugin);
0190     }
0191 
0192     paintAboutScreen(QStringLiteral("introduction_kontact.html"), introductionData());
0193     Prefs::setLastVersionSeen(KAboutData::applicationData().version());
0194 }
0195 
0196 MainWindow::~MainWindow()
0197 {
0198     if (mCurrentPlugin) {
0199         KConfigGroup grp(KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(mCurrentPlugin->identifier())));
0200         saveMainWindowSettings(grp);
0201     }
0202 
0203     createGUI(nullptr);
0204     saveSettings();
0205 
0206     Prefs::self()->save();
0207 
0208     // During deletion of plugins, we should not access the plugin list (bug #182176)
0209     delete mSidePane;
0210 
0211     qDeleteAll(mPlugins);
0212 }
0213 
0214 // Called by main().
0215 void MainWindow::setInitialActivePluginModule(const QString &module)
0216 {
0217     if (mInitialActiveModule != module) {
0218         mInitialActiveModule = module;
0219         activateInitialPluginModule();
0220     }
0221 }
0222 
0223 bool MainWindow::pluginActionWeightLessThan(const QAction *left, const QAction *right)
0224 {
0225     // Since this lessThan method is used only for the toolbar (which is on
0226     // the inverse layout direction than the rest of the system), we add the
0227     // elements on the exactly inverse order. (ereslibre)
0228     return !pluginWeightLessThan(left->data().value<KontactInterface::Plugin *>(), right->data().value<KontactInterface::Plugin *>());
0229 }
0230 
0231 bool MainWindow::pluginWeightLessThan(const KontactInterface::Plugin *left, const KontactInterface::Plugin *right)
0232 {
0233     return left->weight() < right->weight();
0234 }
0235 
0236 void MainWindow::activateInitialPluginModule()
0237 {
0238     if (!mInitialActiveModule.isEmpty() && !mPlugins.isEmpty()) {
0239         for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
0240             if (!plugin->identifier().isEmpty() && plugin->identifier().contains(mInitialActiveModule)) {
0241                 selectPlugin(plugin);
0242                 return;
0243             }
0244         }
0245     }
0246 }
0247 
0248 void MainWindow::initWidgets()
0249 {
0250     auto mTopWidget = new QWidget(this);
0251     auto layout = new QVBoxLayout;
0252     layout->setContentsMargins({});
0253     layout->setSpacing(0);
0254     mTopWidget->setLayout(layout);
0255     setCentralWidget(mTopWidget);
0256 
0257     if (PimCommon::NeedUpdateVersionUtils::checkVersion()) {
0258         const auto status = PimCommon::NeedUpdateVersionUtils::obsoleteVersionStatus(KAboutData::applicationData().version(), QDate::currentDate());
0259         if (status != PimCommon::NeedUpdateVersionUtils::ObsoleteVersion::NotObsoleteYet) {
0260             auto needUpdateVersionWidget = new PimCommon::NeedUpdateVersionWidget(this);
0261             layout->addWidget(needUpdateVersionWidget);
0262             needUpdateVersionWidget->setObsoleteVersion(status);
0263         }
0264     }
0265 
0266     mSplitter = new QSplitter(mTopWidget);
0267     connect(mSplitter, &QSplitter::splitterMoved, this, &MainWindow::slotSplitterMoved);
0268     layout->addWidget(mSplitter);
0269     mSidePane = new IconSidePane(this, mSplitter);
0270 
0271     connect(mSidePane, SIGNAL(pluginSelected(KontactInterface::Plugin *)), SLOT(selectPlugin(KontactInterface::Plugin *)));
0272 
0273     mPartsStack = new QStackedWidget(mSplitter);
0274     mPartsStack->layout()->setSpacing(0);
0275 
0276     initAboutScreen();
0277 
0278     paintAboutScreen(QStringLiteral("loading_kontact.html"), QVariantHash());
0279 
0280     auto progressStatusBarWidget = new KPIM::ProgressStatusBarWidget(statusBar(), this);
0281 
0282     mStatusMsgLabel = new KSqueezedTextLabel(i18nc("@info:status", " Initializing..."), statusBar());
0283     mStatusMsgLabel->setTextElideMode(Qt::ElideRight);
0284     mStatusMsgLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0285 
0286     statusBar()->addWidget(mStatusMsgLabel, 10);
0287     statusBar()->addPermanentWidget(progressStatusBarWidget->littleProgress(), 0);
0288 
0289     mSplitter->setCollapsible(1, false);
0290 }
0291 
0292 void MainWindow::paintAboutScreen(const QString &templateName, const QVariantHash &data)
0293 {
0294     GrantleeTheme::ThemeManager manager(QStringLiteral("splashPage"), QStringLiteral("splash.theme"), nullptr, QStringLiteral("messageviewer/about/"));
0295     GrantleeTheme::Theme theme = manager.theme(QStringLiteral("default"));
0296     if (!theme.isValid()) {
0297         qCDebug(KONTACT_LOG) << "Theme error: failed to find splash theme";
0298     } else {
0299         mIntroPart->setHtml(theme.render(templateName, data, QByteArrayLiteral("kontact")), QUrl::fromLocalFile(theme.absolutePath() + QLatin1Char('/')));
0300     }
0301 }
0302 
0303 void MainWindow::initAboutScreen()
0304 {
0305     auto introbox = new QWidget(mPartsStack);
0306     auto introboxHBoxLayout = new QHBoxLayout(introbox);
0307     introboxHBoxLayout->setContentsMargins({});
0308     mPartsStack->addWidget(introbox);
0309     mPartsStack->setCurrentWidget(introbox);
0310     mIntroPart = new IntroductionWebEngineView(introbox);
0311     connect(mIntroPart, &IntroductionWebEngineView::openUrl, this, &MainWindow::slotOpenUrl, Qt::QueuedConnection);
0312     introboxHBoxLayout->addWidget(mIntroPart);
0313 }
0314 
0315 void MainWindow::setupActions()
0316 {
0317     KStandardAction::quit(this, &MainWindow::slotQuit, actionCollection());
0318 
0319     mNewActions = new KActionMenu(i18nc("@title:menu create new pim items (message,calendar,to-do,etc.)", "New"), this);
0320     actionCollection()->addAction(QStringLiteral("action_new"), mNewActions);
0321     actionCollection()->setDefaultShortcuts(mNewActions, KStandardShortcut::openNew());
0322     connect(mNewActions, &KActionMenu::triggered, this, &MainWindow::slotNewClicked);
0323 
0324     auto action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Kontact..."), this);
0325     setHelpText(action, i18nc("@info:status", "Configure Kontact"));
0326     action->setWhatsThis(i18nc("@info:whatsthis", "You will be presented with a dialog where you can configure Kontact."));
0327     actionCollection()->addAction(QStringLiteral("settings_configure_kontact"), action);
0328     connect(action, &QAction::triggered, this, &MainWindow::slotPreferences);
0329 
0330     action = new QAction(QIcon::fromTheme(QStringLiteral("kontact")), i18nc("@action:inmenu", "&Kontact Introduction"), this);
0331     setHelpText(action, i18nc("@info:status", "Show the Kontact Introduction page"));
0332     action->setWhatsThis(i18nc("@info:whatsthis", "Choose this option to see the Kontact Introduction page."));
0333     actionCollection()->addAction(QStringLiteral("help_introduction"), action);
0334     connect(action, &QAction::triggered, this, &MainWindow::slotShowIntroduction);
0335 
0336     mShowHideAction = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-width")), i18nc("@action:inmenu", "Hide/Show the component sidebar"), this);
0337     setHelpText(mShowHideAction, i18nc("@info:status", "Hide/Show the component sidebar"));
0338     mShowHideAction->setCheckable(true);
0339     mShowHideAction->setChecked(Prefs::self()->sideBarOpen());
0340     mShowHideAction->setWhatsThis(i18nc("@info:whatsthis", "Allows you to show or hide the component sidebar as desired."));
0341     actionCollection()->addAction(QStringLiteral("hide_show_sidebar"), mShowHideAction);
0342     actionCollection()->setDefaultShortcut(mShowHideAction, QKeySequence(Qt::Key_F9));
0343     connect(mShowHideAction, &QAction::triggered, this, &MainWindow::slotShowHideSideBar);
0344 
0345     mShowFullScreenAction = KStandardAction::fullScreen(nullptr, nullptr, this, actionCollection());
0346     actionCollection()->setDefaultShortcut(mShowFullScreenAction, Qt::Key_F11);
0347     connect(mShowFullScreenAction, &QAction::toggled, this, &MainWindow::slotFullScreen);
0348 
0349     auto manager = new KColorSchemeManager(this);
0350     actionCollection()->addAction(QStringLiteral("colorscheme_menu"), KColorSchemeMenu::createMenu(manager, this));
0351 
0352     mShowMenuBarAction = KStandardAction::showMenubar(this, &MainWindow::slotToggleMenubar, actionCollection());
0353 }
0354 
0355 void MainWindow::slotToggleMenubar(bool dontShowWarning)
0356 {
0357     if (menuBar()) {
0358         if (mShowMenuBarAction->isChecked()) {
0359             menuBar()->show();
0360         } else {
0361             if (!dontShowWarning && (!toolBar()->isVisible() /* || !toolBar()->actions().contains(mHamburgerMenu)*/)) {
0362                 const QString accel = mShowMenuBarAction->shortcut().toString();
0363                 KMessageBox::information(this,
0364                                          i18n("<qt>This will hide the menu bar completely."
0365                                               " You can show it again by typing %1.</qt>",
0366                                               accel),
0367                                          i18n("Hide menu bar"),
0368                                          QStringLiteral("HideMenuBarWarning"));
0369             }
0370             menuBar()->hide();
0371         }
0372         Prefs::self()->setShowMenuBar(mShowMenuBarAction->isChecked());
0373     }
0374 }
0375 
0376 void MainWindow::slotFullScreen(bool t)
0377 {
0378     KToggleFullScreenAction::setFullScreen(this, t);
0379     QMenuBar *mb = menuBar();
0380     if (t) {
0381         auto b = new QToolButton(mb);
0382         b->setDefaultAction(mShowFullScreenAction);
0383         b->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Ignored));
0384         b->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0385         mb->setCornerWidget(b, Qt::TopRightCorner);
0386         b->setVisible(true);
0387         b->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0388     } else {
0389         QWidget *w = mb->cornerWidget(Qt::TopRightCorner);
0390         if (w) {
0391             w->deleteLater();
0392         }
0393     }
0394 }
0395 
0396 KontactInterface::Plugin *MainWindow::pluginFromName(const QString &identifier) const
0397 {
0398     auto hasIdentifier = [&](KontactInterface::Plugin *plugin) {
0399         return plugin->identifier() == identifier;
0400     };
0401     const auto it = std::find_if(mPlugins.constBegin(), mPlugins.constEnd(), hasIdentifier);
0402     return it == mPlugins.constEnd() ? nullptr : *it;
0403 }
0404 
0405 void MainWindow::loadPlugins()
0406 {
0407     QList<KontactInterface::Plugin *> plugins;
0408     KConfigGroup configGroup(Prefs::self()->config(), QStringLiteral("Plugins"));
0409     for (const KPluginMetaData &pluginMetaData : std::as_const(mPluginMetaData)) {
0410         if (!configGroup.readEntry(pluginMetaData.pluginId() + QLatin1StringView("Enabled"), pluginMetaData.isEnabledByDefault())) {
0411             continue;
0412         }
0413 
0414         KontactInterface::Plugin *plugin = pluginFromName(pluginMetaData.pluginId());
0415         if (plugin) { // already loaded
0416             plugin->configUpdated();
0417             continue;
0418         }
0419 
0420         qCDebug(KONTACT_LOG) << "Loading Plugin:" << pluginMetaData.name();
0421         const auto loadResult = KPluginFactory::instantiatePlugin<KontactInterface::Plugin>(pluginMetaData, this);
0422         if (!loadResult) {
0423             qCWarning(KONTACT_LOG) << "Error loading plugin" << pluginMetaData.fileName() << loadResult.errorString;
0424             continue;
0425         } else {
0426             plugin = loadResult.plugin;
0427             if (!plugin) {
0428                 qCWarning(KONTACT_LOG) << "Unable to create plugin for" << pluginMetaData.fileName();
0429                 continue;
0430             }
0431         }
0432         plugin->setIdentifier(pluginMetaData.pluginId());
0433         plugin->setTitle(pluginMetaData.name());
0434         plugin->setIcon(pluginMetaData.iconName());
0435 
0436         const QString libNameProp = pluginMetaData.value(QStringLiteral("X-KDE-KontactPartLibraryName"));
0437         const QString exeNameProp = pluginMetaData.value(QStringLiteral("X-KDE-KontactPartExecutableName"));
0438 
0439         if (pluginMetaData.rawData().contains(QLatin1StringView("X-KDE-KontactPartLoadOnStart"))) {
0440             const bool loadOnStart = pluginMetaData.rawData().value(QStringLiteral("X-KDE-KontactPartLoadOnStart")).toBool();
0441             if (loadOnStart) {
0442                 mDelayedPreload.append(plugin);
0443             }
0444         }
0445         if (pluginMetaData.rawData().contains(QLatin1StringView("X-KDE-KontactPluginHasPart"))) {
0446             const bool hasPartProp = pluginMetaData.rawData().value(QStringLiteral("X-KDE-KontactPluginHasPart")).toBool();
0447             plugin->setShowInSideBar(hasPartProp);
0448         } else {
0449             plugin->setShowInSideBar(true);
0450         }
0451 
0452         qCDebug(KONTACT_LOG) << "LIBNAMEPART:" << libNameProp;
0453 
0454         plugin->setPartLibraryName(libNameProp.toUtf8());
0455         plugin->setExecutableName(exeNameProp);
0456         plugins.append(plugin);
0457     }
0458     std::sort(plugins.begin(), plugins.end(), pluginWeightLessThan); // new plugins
0459 
0460     for (KontactInterface::Plugin *plugin : std::as_const(plugins)) {
0461         const QList<QAction *> actionList = plugin->newActions();
0462         for (QAction *action : actionList) {
0463             qCDebug(KONTACT_LOG) << "Plugging new action" << action->objectName();
0464             mNewActions->addAction(action);
0465         }
0466         addPlugin(plugin);
0467     }
0468 
0469     std::sort(mPlugins.begin(), mPlugins.end(), pluginWeightLessThan); // all plugins
0470 
0471     // sort the action plugins again and reset shortcuts. If we removed and then read some plugins
0472     // we need to take in count their weights for setting shortcuts again
0473     std::sort(mActionPlugins.begin(), mActionPlugins.end(), pluginActionWeightLessThan);
0474 
0475     updateShortcuts();
0476 
0477     const bool state = (!mPlugins.isEmpty());
0478     mNewActions->setEnabled(state);
0479 }
0480 
0481 void MainWindow::unloadDisabledPlugins()
0482 {
0483     // Only remove the now-disabled plugins.
0484     // Keep the other ones. loadPlugins() will skip those that are already loaded.
0485     KConfigGroup configGroup(Prefs::self()->config(), QStringLiteral("Plugins"));
0486     for (const KPluginMetaData &pluginMetaData : std::as_const(mPluginMetaData)) {
0487         if (!configGroup.readEntry(pluginMetaData.pluginId() + QLatin1StringView("Enabled"), pluginMetaData.isEnabledByDefault())) {
0488             removePlugin(pluginMetaData.pluginId());
0489         }
0490     }
0491 }
0492 
0493 void MainWindow::updateShortcuts()
0494 {
0495     for (int i = 0, total = mActionPlugins.count(); i < total; ++i) {
0496         QAction *action = mActionPlugins.at(i);
0497         const QKeySequence shortcut(QStringLiteral("Ctrl+%1").arg(mActionPlugins.count() - i));
0498         actionCollection()->setDefaultShortcut(action, shortcut);
0499         // Prevent plugActionList from restoring some old saved shortcuts
0500         action->setProperty("_k_DefaultShortcut", QVariant::fromValue(QList<QKeySequence>{shortcut}));
0501     }
0502 }
0503 
0504 bool MainWindow::removePlugin(const QString &pluginName)
0505 {
0506     auto hasIdentifier = [&](KontactInterface::Plugin *plugin) {
0507         return plugin->identifier() == pluginName;
0508     };
0509     const auto it = std::find_if(mPlugins.begin(), mPlugins.end(), hasIdentifier);
0510     if (it == mPlugins.end()) {
0511         qCDebug(KONTACT_LOG) << "Plugin not found" << pluginName;
0512         return false;
0513     }
0514     KontactInterface::Plugin *plugin = *it;
0515     const QList<QAction *> actionList = plugin->newActions();
0516     for (QAction *action : actionList) {
0517         qCDebug(KONTACT_LOG) << QStringLiteral("Unplugging New actions") << action->objectName();
0518         mNewActions->removeAction(action);
0519     }
0520 
0521     removeChildClient(plugin);
0522 
0523     if (mCurrentPlugin == plugin) {
0524         mCurrentPlugin = nullptr;
0525         createGUI(nullptr);
0526     }
0527 
0528     plugin->deleteLater(); // removes the part automatically
0529     mPlugins.erase(it);
0530     if (plugin->showInSideBar()) {
0531         QAction *q = mPluginAction[plugin]; // remove QAction, to free the shortcut for later use
0532         mActionPlugins.removeAll(q);
0533         mPluginAction.remove(plugin);
0534         actionCollection()->removeAction(q); // deletes q
0535     }
0536 
0537     if (mCurrentPlugin == nullptr) {
0538         for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
0539             if (plugin->showInSideBar()) {
0540                 selectPlugin(plugin);
0541                 return true;
0542             }
0543         }
0544     }
0545     return true;
0546 }
0547 
0548 void MainWindow::addPlugin(KontactInterface::Plugin *plugin)
0549 {
0550     qCDebug(KONTACT_LOG);
0551 
0552     mPlugins.append(plugin);
0553 
0554     if (plugin->showInSideBar()) {
0555         auto action = new QAction(QIcon::fromTheme(plugin->icon()), plugin->title(), this);
0556         // action->setHelpText(
0557         //            i18nc( "@info:status", "Plugin %1", plugin->title() ) );
0558         action->setWhatsThis(i18nc("@info:whatsthis", "Switch to plugin %1", plugin->title()));
0559         action->setCheckable(true);
0560         action->setData(QVariant::fromValue(plugin)); // used by pluginActionWeightLessThan
0561         connect(action, &QAction::triggered, this, [this, action, plugin]() {
0562             slotActionTriggered(action, plugin->identifier());
0563         });
0564         actionCollection()->addAction(plugin->identifier(), action);
0565         mActionPlugins.append(action);
0566         mPluginAction.insert(plugin, action);
0567     }
0568 
0569     // merge the plugins GUI into the main window
0570     insertChildClient(plugin);
0571 }
0572 
0573 void MainWindow::partLoaded(KontactInterface::Plugin *plugin, KParts::Part *part)
0574 {
0575     Q_UNUSED(plugin)
0576 
0577     // See if we have this part already (e.g. due to two plugins sharing it)
0578     if (mPartsStack->indexOf(part->widget()) != -1) {
0579         return;
0580     }
0581 
0582     mPartsStack->addWidget(part->widget());
0583 
0584     mPartManager->addPart(part, false);
0585     // Workaround for KParts misbehavior: addPart calls show!
0586     part->widget()->hide();
0587 }
0588 
0589 void MainWindow::slotActivePartChanged(KParts::Part *part)
0590 {
0591     if (!part) {
0592         createGUI(nullptr);
0593         return;
0594     }
0595 
0596     qCDebug(KONTACT_LOG) << QStringLiteral("Part activated:") << part << QStringLiteral("with stack id.") << mPartsStack->indexOf(part->widget());
0597 
0598     statusBar()->clearMessage();
0599 }
0600 
0601 void MainWindow::slotNewClicked()
0602 {
0603     if (!mCurrentPlugin || !mCurrentPlugin->newActions().isEmpty()) {
0604         mCurrentPlugin->newActions().at(0)->trigger();
0605     } else {
0606         for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
0607             if (!plugin->newActions().isEmpty()) {
0608                 plugin->newActions().constFirst()->trigger();
0609                 return;
0610             }
0611         }
0612     }
0613 }
0614 
0615 KToolBar *MainWindow::findToolBar(const char *name)
0616 {
0617     // like KMainWindow::toolBar, but which doesn't create the toolbar if not found
0618     return findChild<KToolBar *>(QLatin1StringView(name));
0619 }
0620 
0621 void MainWindow::selectPlugin(KontactInterface::Plugin *plugin)
0622 {
0623     if (!plugin) {
0624         return;
0625     }
0626 
0627     if (plugin->isRunningStandalone()) {
0628         statusBar()->showMessage(i18nc("@info:status", "Application is running standalone. Foregrounding..."), 1000);
0629         plugin->bringToForeground();
0630         return;
0631     }
0632 
0633     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0634 
0635     if (mCurrentPlugin) {
0636         KConfigGroup grp = KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(mCurrentPlugin->identifier()));
0637         saveMainWindowSettings(grp);
0638     }
0639 
0640     KParts::Part *part = plugin->part();
0641 
0642     if (!part) {
0643         QApplication::restoreOverrideCursor();
0644         KMessageBox::error(this, i18nc("@info", "Cannot load part for %1.", plugin->title()) + QLatin1Char('\n') + lastErrorMessage());
0645         plugin->setDisabled(true);
0646         mSidePane->updatePlugins();
0647         return;
0648     }
0649 
0650     if (mCurrentPlugin) {
0651         QAction *action = mPluginAction.value(mCurrentPlugin);
0652         if (action) {
0653             action->setChecked(false);
0654         }
0655     }
0656     QAction *selectedPluginAction = mPluginAction.value(mCurrentPlugin);
0657     if (selectedPluginAction) {
0658         selectedPluginAction->setChecked(true);
0659     }
0660 
0661     // store old focus widget
0662     QWidget *focusWidget = qApp->focusWidget();
0663     if (mCurrentPlugin && focusWidget) {
0664         // save the focus widget only when it belongs to the activated part
0665         QWidget *parent = focusWidget->parentWidget();
0666         while (parent) {
0667             if (parent == mCurrentPlugin->part()->widget()) {
0668                 mFocusWidgets.insert(mCurrentPlugin->identifier(), QPointer<QWidget>(focusWidget));
0669             }
0670             parent = parent->parentWidget();
0671         }
0672     }
0673 
0674     if (mSidePane) {
0675         mSidePane->setCurrentPlugin(plugin->identifier());
0676     }
0677 
0678     plugin->aboutToSelect();
0679 
0680     mPartManager->setActivePart(part);
0681     QWidget *view = part->widget();
0682     Q_ASSERT(view);
0683 
0684     if (view) {
0685         mPartsStack->setCurrentWidget(view);
0686         view->show();
0687 
0688         if (!mFocusWidgets.isEmpty() && mFocusWidgets.contains(plugin->identifier())) {
0689             focusWidget = mFocusWidgets.value(plugin->identifier());
0690             if (focusWidget) {
0691                 focusWidget->setFocus();
0692             }
0693         } else {
0694             view->setFocus();
0695         }
0696 
0697         mCurrentPlugin = plugin;
0698 
0699         QAction *newAction = nullptr;
0700         if (!plugin->newActions().isEmpty()) {
0701             newAction = plugin->newActions().at(0);
0702         }
0703 
0704         const QString oldWindowTitle = windowTitle();
0705 
0706         createGUI(plugin->part());
0707         plugin->shortcutChanged();
0708 
0709         // Only some parts (like kmail) set a caption in guiActivateEvent when being activated.
0710         // For others, this is the default caption:
0711         if (windowTitle() == oldWindowTitle) {
0712             setCaption(i18nc("@title:window Plugin dependent window title", "%1 - Kontact", plugin->title()));
0713         }
0714 
0715         if (newAction) {
0716             mNewActions->setIcon(newAction->icon());
0717             mNewActions->setText(newAction->text());
0718             mNewActions->setWhatsThis(newAction->whatsThis());
0719         } else { // we'll use the action of the first plugin which offers one
0720             for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
0721                 if (!plugin->newActions().isEmpty()) {
0722                     newAction = plugin->newActions().constFirst();
0723                 }
0724                 if (newAction) {
0725                     mNewActions->setIcon(newAction->icon());
0726                     mNewActions->setText(newAction->text());
0727                     mNewActions->setWhatsThis(newAction->whatsThis());
0728                     break;
0729                 }
0730             }
0731         }
0732     }
0733 
0734     KToolBar *navigatorToolBar = findToolBar("navigatorToolBar");
0735     if (navigatorToolBar && !navigatorToolBar->isHidden()
0736         && (toolBarArea(navigatorToolBar) == Qt::TopToolBarArea || toolBarArea(navigatorToolBar) == Qt::BottomToolBarArea)) {
0737         addToolBar(toolBarArea(navigatorToolBar), navigatorToolBar);
0738     }
0739 
0740     applyMainWindowSettings(KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(plugin->identifier())));
0741 
0742     QApplication::restoreOverrideCursor();
0743 }
0744 
0745 void MainWindow::slotActionTriggered(QAction *action, const QString &identifier)
0746 {
0747     action->setChecked(true);
0748     if (!identifier.isEmpty()) {
0749         mSidePane->setCurrentPlugin(identifier);
0750     }
0751 }
0752 
0753 void MainWindow::selectPlugin(const QString &pluginName)
0754 {
0755     KontactInterface::Plugin *plugin = pluginFromName(pluginName);
0756     if (plugin) {
0757         selectPlugin(plugin);
0758     }
0759 }
0760 
0761 void MainWindow::loadSettings()
0762 {
0763     if (mSplitter) {
0764         showHideSideBar(Prefs::self()->sideBarOpen());
0765     }
0766 
0767     // Preload Plugins. This _must_ happen before the default part is loaded
0768     for (KontactInterface::Plugin *plugin : std::as_const(mDelayedPreload)) {
0769         selectPlugin(plugin);
0770     }
0771     selectPlugin(Prefs::self()->mActivePlugin);
0772 }
0773 
0774 void MainWindow::saveSettings()
0775 {
0776     if (mSplitter) {
0777         Prefs::self()->mSidePaneSplitter = mSplitter->sizes();
0778     }
0779 
0780     if (mCurrentPlugin) {
0781         Prefs::self()->mActivePlugin = mCurrentPlugin->identifier();
0782     }
0783 }
0784 
0785 void MainWindow::slotShowIntroduction()
0786 {
0787     mPartsStack->setCurrentIndex(0);
0788 }
0789 
0790 void MainWindow::slotQuit()
0791 {
0792     mReallyClose = true;
0793     close();
0794 }
0795 
0796 void MainWindow::slotPreferences()
0797 {
0798     static Kontact::KontactConfigureDialog *dlg = nullptr;
0799     if (!dlg) {
0800         dlg = new Kontact::KontactConfigureDialog(this);
0801         connect(dlg, &KontactSettingsDialog::configCommitted, this, [this](const QByteArray &componentName) {
0802             if (componentName == QByteArrayLiteral("kontact")) {
0803                 MainWindow::updateConfig();
0804             }
0805         });
0806 
0807         // Add the main contact KCM which is not associated with a specific plugin
0808         dlg->addModule(KPluginMetaData(QStringLiteral("pim6/kcms/kontact/kcm_kontact")));
0809 
0810         auto sortByWeight = [](const KPluginMetaData &m1, const KPluginMetaData &m2) {
0811             return m1.rawData().value(QStringLiteral("X-KDE-Weight")).toInt() < m2.rawData().value(QStringLiteral("X-KDE-Weight")).toInt();
0812         };
0813         std::sort(mPluginMetaData.begin(), mPluginMetaData.end(), sortByWeight);
0814         for (const KPluginMetaData &metaData : std::as_const(mPluginMetaData)) {
0815             const QString pluginNamespace = metaData.value(QStringLiteral("X-KDE-ConfigModuleNamespace"));
0816             if (!pluginNamespace.isEmpty()) {
0817                 auto plugins = KPluginMetaData::findPlugins(pluginNamespace);
0818                 std::sort(plugins.begin(), plugins.end(), sortByWeight);
0819                 dlg->addPluginComponent(metaData, plugins);
0820             }
0821         }
0822     }
0823 
0824     dlg->show();
0825 }
0826 
0827 // Called when the user enables/disables plugins in the configuration dialog
0828 void MainWindow::pluginsChanged()
0829 {
0830     unplugActionList(QStringLiteral("navigator_actionlist"));
0831     unloadDisabledPlugins();
0832     loadPlugins();
0833     mSidePane->updatePlugins();
0834     factory()->plugActionList(this, QStringLiteral("navigator_actionlist"), mActionPlugins);
0835 }
0836 
0837 void MainWindow::updateConfig()
0838 {
0839     qCDebug(KONTACT_LOG);
0840 
0841     saveSettings();
0842     loadSettings();
0843 }
0844 
0845 void MainWindow::configureShortcuts()
0846 {
0847     KShortcutsDialog dialog(this);
0848     dialog.addCollection(actionCollection());
0849 
0850     if (mCurrentPlugin && mCurrentPlugin->part()) {
0851         dialog.addCollection(mCurrentPlugin->part()->actionCollection());
0852     }
0853 
0854     dialog.configure();
0855     if (mCurrentPlugin && mCurrentPlugin->part()) {
0856         mCurrentPlugin->shortcutChanged();
0857     }
0858 }
0859 
0860 void MainWindow::configureToolbars()
0861 {
0862     if (mCurrentPlugin) {
0863         KConfigGroup grp(KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(mCurrentPlugin->identifier())));
0864         saveMainWindowSettings(grp);
0865     }
0866     QPointer<KEditToolBar> edit = new KEditToolBar(factory());
0867     connect(edit.data(), &KEditToolBar::newToolBarConfig, this, &MainWindow::slotNewToolbarConfig);
0868     edit->exec();
0869     delete edit;
0870 }
0871 
0872 void MainWindow::slotNewToolbarConfig()
0873 {
0874     if (mCurrentPlugin && mCurrentPlugin->part()) {
0875         createGUI(mCurrentPlugin->part());
0876     }
0877     if (mCurrentPlugin) {
0878         applyMainWindowSettings(KSharedConfig::openConfig()->group(QStringLiteral("MainWindow%1").arg(mCurrentPlugin->identifier())));
0879     }
0880     factory()->plugActionList(this, QStringLiteral("navigator_actionlist"), mActionPlugins);
0881 }
0882 
0883 void MainWindow::slotOpenUrl(const QUrl &url)
0884 {
0885     if (url.scheme() == QLatin1StringView("exec")) {
0886         const QString path(url.path());
0887         if (path == QLatin1StringView("/switch")) {
0888             if (mCurrentPlugin) {
0889                 mPartsStack->setCurrentIndex(mPartsStack->indexOf(mCurrentPlugin->part()->widget()));
0890             }
0891         } else if (path == QLatin1StringView("/accountwizard")) {
0892             auto job = new KIO::CommandLauncherJob(QStringLiteral("accountwizard"));
0893             job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0894             job->exec();
0895             slotQuit();
0896         } else if (path.startsWith(QLatin1StringView("/help"))) {
0897             QString app(QStringLiteral("org.kde.kontact"));
0898             if (!url.query().isEmpty()) {
0899                 app = url.query();
0900             }
0901             KHelpClient::invokeHelp(QString(), app);
0902         }
0903     } else {
0904         auto job = new KIO::OpenUrlJob(url);
0905         job->start();
0906     }
0907 }
0908 
0909 void MainWindow::readProperties(const KConfigGroup &config)
0910 {
0911     Core::readProperties(config);
0912 
0913     const auto activePluginList = config.readEntry("ActivePlugins", QStringList());
0914     QSet<QString> activePlugins(activePluginList.begin(), activePluginList.end());
0915 
0916     if (!activePlugins.isEmpty()) {
0917         for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
0918             if (!plugin->isRunningStandalone() && activePlugins.contains(plugin->identifier())) {
0919                 plugin->readProperties(config);
0920             }
0921         }
0922     }
0923 }
0924 
0925 void MainWindow::saveProperties(KConfigGroup &config)
0926 {
0927     Core::saveProperties(config);
0928 
0929     QStringList activePlugins;
0930 
0931     KConfigGroup configGroup(Prefs::self()->config(), QStringLiteral("Plugins"));
0932     for (const KPluginMetaData &pluginMetaData : std::as_const(mPluginMetaData)) {
0933         if (!configGroup.readEntry(pluginMetaData.pluginId() + QLatin1StringView("Enabled"), pluginMetaData.isEnabledByDefault())) {
0934             KontactInterface::Plugin *plugin = pluginFromName(pluginMetaData.pluginId());
0935             if (plugin) {
0936                 activePlugins.append(plugin->identifier());
0937                 plugin->saveProperties(config);
0938             }
0939         }
0940     }
0941 
0942     config.writeEntry("ActivePlugins", activePlugins);
0943 }
0944 
0945 bool MainWindow::queryClose()
0946 {
0947     if (qApp->isSavingSession() || mReallyClose) {
0948         return true;
0949     }
0950 
0951     for (KontactInterface::Plugin *plugin : std::as_const(mPlugins)) {
0952         if (!plugin->isRunningStandalone()) {
0953             if (!plugin->queryClose()) {
0954                 return false;
0955             }
0956         }
0957     }
0958     return true;
0959 }
0960 
0961 void MainWindow::slotShowStatusMsg(const QString &msg)
0962 {
0963     if (!statusBar() || !mStatusMsgLabel) {
0964         return;
0965     }
0966 
0967     mStatusMsgLabel->setText(msg);
0968 }
0969 
0970 QVariantHash MainWindow::introductionData()
0971 {
0972     QVariantHash data;
0973     data[QStringLiteral("icon")] = QStringLiteral("kontact");
0974     data[QStringLiteral("name")] = i18n("Kontact");
0975     data[QStringLiteral("subtitle")] = i18n("The KDE Personal Information Management Suite.");
0976     data[QStringLiteral("version")] = KAboutData::applicationData().version();
0977 
0978     QVariantList links = {QVariantHash{{QStringLiteral("url"), QStringLiteral("exec:/help?org.kde.kontact")},
0979                                        {QStringLiteral("icon"), QStringLiteral("help-contents")},
0980                                        {QStringLiteral("title"), i18n("Read Manual")},
0981                                        {QStringLiteral("subtext"), i18n("Learn more about Kontact and its components")}},
0982                           QVariantHash{{QStringLiteral("url"), QStringLiteral("https://kontact.kde.org")},
0983                                        {QStringLiteral("icon"), QStringLiteral("kontact")},
0984                                        {QStringLiteral("title"), i18n("Visit Kontact Website")},
0985                                        {QStringLiteral("subtext"), i18n("Access online resources and tutorials")}},
0986                           QVariantHash{{QStringLiteral("url"), QStringLiteral("exec:/accountwizard")},
0987                                        {QStringLiteral("icon"), QStringLiteral("tools-wizard")},
0988                                        {QStringLiteral("title"), i18n("Setup your Accounts")},
0989                                        {QStringLiteral("subtext"), i18n("Prepare Kontact for use")}}};
0990     data[QStringLiteral("links")] = links;
0991 
0992     return data;
0993 }
0994 
0995 void MainWindow::showHideSideBar(bool show)
0996 {
0997     QList<int> sizes = mSplitter->sizes();
0998     if (!sizes.isEmpty()) {
0999         if (show) {
1000             sizes[0] = mSaveSideBarWidth;
1001         } else {
1002             mSaveSideBarWidth = qMax(sizes[0], 10);
1003             sizes[0] = 0;
1004         }
1005         mSplitter->setSizes(sizes);
1006         Prefs::self()->setSideBarOpen(show);
1007         mShowHideAction->setChecked(show);
1008     }
1009 }
1010 
1011 QString MainWindow::showHideSideBarMessage(bool hidden) const
1012 {
1013     if (hidden) {
1014         return i18nc("@info:status", "Sidebar is hidden. Show the sidebar again using the %1 key.", mShowHideAction->shortcut().toString());
1015     } else {
1016         return {};
1017     }
1018 }
1019 
1020 void MainWindow::slotShowHideSideBar()
1021 {
1022     QList<int> sizes = mSplitter->sizes();
1023     if (!sizes.isEmpty()) {
1024         bool open = (sizes.at(0) != 0);
1025         showHideSideBar(!open);
1026         if (open) {
1027             statusBar()->showMessage(showHideSideBarMessage(true));
1028         } else {
1029             statusBar()->showMessage(showHideSideBarMessage(false));
1030         }
1031     }
1032 }
1033 
1034 void MainWindow::slotSplitterMoved(int pos, int index)
1035 {
1036     if (index == 1) {
1037         if (pos == 0) {
1038             statusBar()->showMessage(showHideSideBarMessage(true));
1039         } else {
1040             statusBar()->showMessage(showHideSideBarMessage(false));
1041         }
1042     }
1043 }
1044 
1045 void MainWindow::setHelpText(QAction *action, const QString &text)
1046 {
1047     action->setStatusTip(text);
1048     action->setToolTip(text);
1049     if (action->whatsThis().isEmpty()) {
1050         action->setWhatsThis(text);
1051     }
1052 }
1053 
1054 #include "moc_mainwindow.cpp"