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"