File indexing completed on 2024-12-01 09:57:51
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 Reginald Stadlbauer <reggie@kde.org> 0004 SPDX-FileCopyrightText: 1997 Stephan Kulow <coolo@kde.org> 0005 SPDX-FileCopyrightText: 1997-2000 Sven Radej <radej@kde.org> 0006 SPDX-FileCopyrightText: 1997-2000 Matthias Ettrich <ettrich@kde.org> 0007 SPDX-FileCopyrightText: 1999 Chris Schlaeger <cs@kde.org> 0008 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org> 0009 SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org> 0010 0011 SPDX-License-Identifier: LGPL-2.0-only 0012 */ 0013 0014 #include "kxmlguiwindow.h" 0015 #include "debug.h" 0016 0017 #include "kactioncollection.h" 0018 #include "kmainwindow_p.h" 0019 #include <KMessageBox> 0020 #ifdef QT_DBUS_LIB 0021 #include "kmainwindowiface_p.h" 0022 #endif 0023 #include "kedittoolbar.h" 0024 #include "khelpmenu.h" 0025 #include "ktoolbar.h" 0026 #include "ktoolbarhandler_p.h" 0027 #include "kxmlguifactory.h" 0028 0029 #ifdef QT_DBUS_LIB 0030 #include <QDBusConnection> 0031 #endif 0032 #include <QDomDocument> 0033 #include <QEvent> 0034 #include <QList> 0035 #include <QMenuBar> 0036 #include <QStatusBar> 0037 #include <QWidget> 0038 0039 #include <KAboutData> 0040 #include <KCommandBar> 0041 #include <KConfig> 0042 #include <KConfigGroup> 0043 #include <KLocalizedString> 0044 #include <KSharedConfig> 0045 #include <KStandardAction> 0046 #include <KToggleAction> 0047 0048 #include <cctype> 0049 #include <cstdlib> 0050 0051 /** 0052 * A helper function that takes a list of KActionCollection* and converts it 0053 * to KCommandBar::ActionGroup 0054 */ 0055 static QVector<KCommandBar::ActionGroup> actionCollectionToActionGroup(const std::vector<KActionCollection *> &actionCollections) 0056 { 0057 using ActionGroup = KCommandBar::ActionGroup; 0058 0059 QVector<ActionGroup> actionList; 0060 actionList.reserve(actionCollections.size()); 0061 0062 for (const auto collection : actionCollections) { 0063 const QList<QAction *> collectionActions = collection->actions(); 0064 const QString componentName = collection->componentDisplayName(); 0065 0066 ActionGroup ag; 0067 ag.name = componentName; 0068 ag.actions.reserve(collection->count()); 0069 for (const auto action : collectionActions) { 0070 /** 0071 * If this action is a menu, fetch all its child actions 0072 * and skip the menu action itself 0073 */ 0074 if (QMenu *menu = action->menu()) { 0075 const QList<QAction *> menuActions = menu->actions(); 0076 0077 ActionGroup menuActionGroup; 0078 menuActionGroup.name = KLocalizedString::removeAcceleratorMarker(action->text()); 0079 menuActionGroup.actions.reserve(menuActions.size()); 0080 for (const auto mAct : menuActions) { 0081 if (mAct) { 0082 menuActionGroup.actions.append(mAct); 0083 } 0084 } 0085 0086 /** 0087 * If there were no actions in the menu, we 0088 * add the menu to the list instead because it could 0089 * be that the actions are created on demand i.e., aboutToShow() 0090 */ 0091 if (!menuActions.isEmpty()) { 0092 actionList.append(menuActionGroup); 0093 continue; 0094 } 0095 } 0096 0097 if (action && !action->text().isEmpty()) { 0098 ag.actions.append(action); 0099 } 0100 } 0101 actionList.append(ag); 0102 } 0103 return actionList; 0104 } 0105 0106 static void getActionCollections(KXMLGUIClient *client, std::vector<KActionCollection *> &actionCollections) 0107 { 0108 if (!client) { 0109 return; 0110 } 0111 0112 auto actionCollection = client->actionCollection(); 0113 if (actionCollection && !actionCollection->isEmpty()) { 0114 actionCollections.push_back(client->actionCollection()); 0115 } 0116 0117 const QList<KXMLGUIClient *> childClients = client->childClients(); 0118 for (auto child : childClients) { 0119 getActionCollections(child, actionCollections); 0120 } 0121 } 0122 0123 class KXmlGuiWindowPrivate : public KMainWindowPrivate 0124 { 0125 public: 0126 void slotFactoryMakingChanges(bool b) 0127 { 0128 // While the GUI factory is adding/removing clients, 0129 // don't let KMainWindow think those are changes made by the user 0130 // #105525 0131 letDirtySettings = !b; 0132 } 0133 0134 bool commandBarEnabled = true; 0135 // Last executed actions in command bar 0136 QVector<QString> lastExecutedActions; 0137 0138 bool showHelpMenu : 1; 0139 QSize defaultSize; 0140 0141 KDEPrivate::ToolBarHandler *toolBarHandler; 0142 KToggleAction *showStatusBarAction; 0143 QPointer<KEditToolBar> toolBarEditor; 0144 KXMLGUIFactory *factory; 0145 }; 0146 0147 KXmlGuiWindow::KXmlGuiWindow(QWidget *parent, Qt::WindowFlags flags) 0148 : KMainWindow(*new KXmlGuiWindowPrivate, parent, flags) 0149 , KXMLGUIBuilder(this) 0150 { 0151 Q_D(KXmlGuiWindow); 0152 d->showHelpMenu = true; 0153 d->toolBarHandler = nullptr; 0154 d->showStatusBarAction = nullptr; 0155 d->factory = nullptr; 0156 #ifdef QT_DBUS_LIB 0157 new KMainWindowInterface(this); 0158 #endif 0159 0160 /* 0161 * Set up KCommandBar launcher action 0162 */ 0163 auto a = actionCollection()->addAction(QStringLiteral("open_kcommand_bar"), this, [this] { 0164 /* 0165 * Do nothing when command bar is disabled 0166 */ 0167 if (!isCommandBarEnabled()) { 0168 return; 0169 } 0170 0171 auto ac = actionCollection(); 0172 if (!ac) { 0173 return; 0174 } 0175 0176 KCommandBar kc(this); 0177 std::vector<KActionCollection *> actionCollections; 0178 const auto clients = guiFactory()->clients(); 0179 actionCollections.reserve(clients.size()); 0180 0181 // Grab action collections recursively 0182 for (const auto &client : clients) { 0183 getActionCollections(client, actionCollections); 0184 } 0185 0186 kc.setActions(actionCollectionToActionGroup(actionCollections)); 0187 kc.exec(); 0188 }); 0189 a->setIcon(QIcon::fromTheme(QStringLiteral("search"))); 0190 a->setText(i18n("Find Action…")); 0191 actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_I)); 0192 } 0193 0194 QAction *KXmlGuiWindow::toolBarMenuAction() 0195 { 0196 Q_D(KXmlGuiWindow); 0197 if (!d->toolBarHandler) { 0198 return nullptr; 0199 } 0200 0201 return d->toolBarHandler->toolBarMenuAction(); 0202 } 0203 0204 void KXmlGuiWindow::setupToolbarMenuActions() 0205 { 0206 Q_D(KXmlGuiWindow); 0207 if (d->toolBarHandler) { 0208 d->toolBarHandler->setupActions(); 0209 } 0210 } 0211 0212 KXmlGuiWindow::~KXmlGuiWindow() 0213 { 0214 Q_D(KXmlGuiWindow); 0215 delete d->factory; 0216 } 0217 0218 bool KXmlGuiWindow::event(QEvent *ev) 0219 { 0220 bool ret = KMainWindow::event(ev); 0221 if (ev->type() == QEvent::Polish) { 0222 #ifdef QT_DBUS_LIB 0223 /* clang-format off */ 0224 constexpr auto opts = QDBusConnection::ExportScriptableSlots 0225 | QDBusConnection::ExportScriptableProperties 0226 | QDBusConnection::ExportNonScriptableSlots 0227 | QDBusConnection::ExportNonScriptableProperties 0228 | QDBusConnection::ExportChildObjects; 0229 /* clang-format on */ 0230 QDBusConnection::sessionBus().registerObject(dbusName() + QLatin1String("/actions"), actionCollection(), opts); 0231 #endif 0232 } 0233 return ret; 0234 } 0235 0236 void KXmlGuiWindow::setHelpMenuEnabled(bool showHelpMenu) 0237 { 0238 Q_D(KXmlGuiWindow); 0239 d->showHelpMenu = showHelpMenu; 0240 } 0241 0242 bool KXmlGuiWindow::isHelpMenuEnabled() const 0243 { 0244 Q_D(const KXmlGuiWindow); 0245 return d->showHelpMenu; 0246 } 0247 0248 KXMLGUIFactory *KXmlGuiWindow::guiFactory() 0249 { 0250 Q_D(KXmlGuiWindow); 0251 if (!d->factory) { 0252 d->factory = new KXMLGUIFactory(this, this); 0253 connect(d->factory, &KXMLGUIFactory::makingChanges, this, [d](bool state) { 0254 d->slotFactoryMakingChanges(state); 0255 }); 0256 } 0257 return d->factory; 0258 } 0259 0260 void KXmlGuiWindow::configureToolbars() 0261 { 0262 Q_D(KXmlGuiWindow); 0263 KConfigGroup cg(KSharedConfig::openConfig(), ""); 0264 saveMainWindowSettings(cg); 0265 if (!d->toolBarEditor) { 0266 d->toolBarEditor = new KEditToolBar(guiFactory(), this); 0267 d->toolBarEditor->setAttribute(Qt::WA_DeleteOnClose); 0268 connect(d->toolBarEditor, &KEditToolBar::newToolBarConfig, this, &KXmlGuiWindow::saveNewToolbarConfig); 0269 } 0270 d->toolBarEditor->show(); 0271 } 0272 0273 void KXmlGuiWindow::saveNewToolbarConfig() 0274 { 0275 // createGUI(xmlFile()); // this loses any plugged-in guiclients, so we use remove+add instead. 0276 0277 guiFactory()->removeClient(this); 0278 guiFactory()->addClient(this); 0279 0280 KConfigGroup cg(KSharedConfig::openConfig(), ""); 0281 applyMainWindowSettings(cg); 0282 } 0283 0284 void KXmlGuiWindow::setupGUI(StandardWindowOptions options, const QString &xmlfile) 0285 { 0286 setupGUI(QSize(), options, xmlfile); 0287 } 0288 0289 void KXmlGuiWindow::setupGUI(const QSize &defaultSize, StandardWindowOptions options, const QString &xmlfile) 0290 { 0291 Q_D(KXmlGuiWindow); 0292 0293 if (options & Keys) { 0294 KStandardAction::keyBindings(guiFactory(), &KXMLGUIFactory::showConfigureShortcutsDialog, actionCollection()); 0295 } 0296 0297 if ((options & StatusBar) && statusBar()) { 0298 createStandardStatusBarAction(); 0299 } 0300 0301 if (options & ToolBar) { 0302 setStandardToolBarMenuEnabled(true); 0303 KStandardAction::configureToolbars(this, &KXmlGuiWindow::configureToolbars, actionCollection()); 0304 } 0305 0306 d->defaultSize = defaultSize; 0307 0308 if (options & Create) { 0309 createGUI(xmlfile); 0310 } 0311 0312 if (d->defaultSize.isValid()) { 0313 resize(d->defaultSize); 0314 } else if (isHidden()) { 0315 adjustSize(); 0316 } 0317 0318 if (options & Save) { 0319 const KConfigGroup cg(autoSaveConfigGroup()); 0320 if (cg.isValid()) { 0321 setAutoSaveSettings(cg); 0322 } else { 0323 setAutoSaveSettings(); 0324 } 0325 } 0326 } 0327 void KXmlGuiWindow::createGUI(const QString &xmlfile) 0328 { 0329 Q_D(KXmlGuiWindow); 0330 // disabling the updates prevents unnecessary redraws 0331 // setUpdatesEnabled( false ); 0332 0333 // just in case we are rebuilding, let's remove our old client 0334 guiFactory()->removeClient(this); 0335 0336 // make sure to have an empty GUI 0337 QMenuBar *mb = menuBar(); 0338 if (mb) { 0339 mb->clear(); 0340 } 0341 0342 qDeleteAll(toolBars()); // delete all toolbars 0343 0344 // don't build a help menu unless the user ask for it 0345 if (d->showHelpMenu) { 0346 delete d->helpMenu; 0347 // we always want a help menu 0348 d->helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); 0349 0350 KActionCollection *actions = actionCollection(); 0351 QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); 0352 QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); 0353 QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); 0354 QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); 0355 QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); 0356 QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); 0357 QAction *donateAction = d->helpMenu->action(KHelpMenu::menuDonate); 0358 0359 if (helpContentsAction) { 0360 actions->addAction(helpContentsAction->objectName(), helpContentsAction); 0361 } 0362 if (whatsThisAction) { 0363 actions->addAction(whatsThisAction->objectName(), whatsThisAction); 0364 } 0365 if (reportBugAction) { 0366 actions->addAction(reportBugAction->objectName(), reportBugAction); 0367 } 0368 if (switchLanguageAction) { 0369 actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); 0370 } 0371 if (aboutAppAction) { 0372 actions->addAction(aboutAppAction->objectName(), aboutAppAction); 0373 } 0374 if (aboutKdeAction) { 0375 actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); 0376 } 0377 if (donateAction) { 0378 actions->addAction(donateAction->objectName(), donateAction); 0379 } 0380 } 0381 0382 const QString windowXmlFile = xmlfile.isNull() ? componentName() + QLatin1String("ui.rc") : xmlfile; 0383 0384 // Help beginners who call setXMLFile and then setupGUI... 0385 if (!xmlFile().isEmpty() && xmlFile() != windowXmlFile) { 0386 qCWarning(DEBUG_KXMLGUI) << "You called setXMLFile(" << xmlFile() << ") and then createGUI or setupGUI," 0387 << "which also calls setXMLFile and will overwrite the file you have previously set.\n" 0388 << "You should call createGUI(" << xmlFile() << ") or setupGUI(<options>," << xmlFile() << ") instead."; 0389 } 0390 0391 // we always want to load in our global standards file 0392 loadStandardsXmlFile(); 0393 0394 // now, merge in our local xml file. 0395 setXMLFile(windowXmlFile, true); 0396 0397 // make sure we don't have any state saved already 0398 setXMLGUIBuildDocument(QDomDocument()); 0399 0400 // do the actual GUI building 0401 guiFactory()->reset(); 0402 guiFactory()->addClient(this); 0403 0404 checkAmbiguousShortcuts(); 0405 0406 // setUpdatesEnabled( true ); 0407 } 0408 0409 void KXmlGuiWindow::slotStateChanged(const QString &newstate) 0410 { 0411 stateChanged(newstate, KXMLGUIClient::StateNoReverse); 0412 } 0413 0414 void KXmlGuiWindow::slotStateChanged(const QString &newstate, bool reverse) 0415 { 0416 stateChanged(newstate, reverse ? KXMLGUIClient::StateReverse : KXMLGUIClient::StateNoReverse); 0417 } 0418 0419 void KXmlGuiWindow::setStandardToolBarMenuEnabled(bool showToolBarMenu) 0420 { 0421 Q_D(KXmlGuiWindow); 0422 if (showToolBarMenu) { 0423 if (d->toolBarHandler) { 0424 return; 0425 } 0426 0427 d->toolBarHandler = new KDEPrivate::ToolBarHandler(this); 0428 0429 if (factory()) { 0430 factory()->addClient(d->toolBarHandler); 0431 } 0432 } else { 0433 if (!d->toolBarHandler) { 0434 return; 0435 } 0436 0437 if (factory()) { 0438 factory()->removeClient(d->toolBarHandler); 0439 } 0440 0441 delete d->toolBarHandler; 0442 d->toolBarHandler = nullptr; 0443 } 0444 } 0445 0446 bool KXmlGuiWindow::isStandardToolBarMenuEnabled() const 0447 { 0448 Q_D(const KXmlGuiWindow); 0449 return (d->toolBarHandler); 0450 } 0451 0452 void KXmlGuiWindow::createStandardStatusBarAction() 0453 { 0454 Q_D(KXmlGuiWindow); 0455 if (!d->showStatusBarAction) { 0456 d->showStatusBarAction = KStandardAction::showStatusbar(this, &KMainWindow::setSettingsDirty, actionCollection()); 0457 QStatusBar *sb = statusBar(); // Creates statusbar if it doesn't exist already. 0458 connect(d->showStatusBarAction, &QAction::toggled, sb, &QWidget::setVisible); 0459 d->showStatusBarAction->setChecked(sb->isHidden()); 0460 } else { 0461 // If the language has changed, we'll need to grab the new text and whatsThis 0462 QAction *tmpStatusBar = KStandardAction::showStatusbar(nullptr, nullptr, nullptr); 0463 d->showStatusBarAction->setText(tmpStatusBar->text()); 0464 d->showStatusBarAction->setWhatsThis(tmpStatusBar->whatsThis()); 0465 delete tmpStatusBar; 0466 } 0467 } 0468 0469 void KXmlGuiWindow::finalizeGUI(bool /*force*/) 0470 { 0471 // FIXME: this really needs to be removed with a code more like the one we had on KDE3. 0472 // what we need to do here is to position correctly toolbars so they don't overlap. 0473 // Also, take in count plugins could provide their own toolbars and those also need to 0474 // be restored. 0475 if (autoSaveSettings() && autoSaveConfigGroup().isValid()) { 0476 applyMainWindowSettings(autoSaveConfigGroup()); 0477 } 0478 } 0479 0480 void KXmlGuiWindow::applyMainWindowSettings(const KConfigGroup &config) 0481 { 0482 Q_D(KXmlGuiWindow); 0483 KMainWindow::applyMainWindowSettings(config); 0484 QStatusBar *sb = findChild<QStatusBar *>(); 0485 if (sb && d->showStatusBarAction) { 0486 d->showStatusBarAction->setChecked(!sb->isHidden()); 0487 } 0488 } 0489 0490 // TODO KF6: change it to "using KXMLGUIBuilder::finalizeGUI;" in the header 0491 // and remove the reimplementation 0492 void KXmlGuiWindow::finalizeGUI(KXMLGUIClient *client) 0493 { 0494 KXMLGUIBuilder::finalizeGUI(client); 0495 } 0496 0497 void KXmlGuiWindow::checkAmbiguousShortcuts() 0498 { 0499 QMap<QString, QAction *> shortcuts; 0500 QAction *editCutAction = actionCollection()->action(QStringLiteral("edit_cut")); 0501 QAction *deleteFileAction = actionCollection()->action(QStringLiteral("deletefile")); 0502 const auto actions = actionCollection()->actions(); 0503 for (QAction *action : actions) { 0504 if (action->isEnabled()) { 0505 const auto actionShortcuts = action->shortcuts(); 0506 for (const QKeySequence &shortcut : actionShortcuts) { 0507 if (shortcut.isEmpty()) { 0508 continue; 0509 } 0510 const QString portableShortcutText = shortcut.toString(); 0511 const QAction *existingShortcutAction = shortcuts.value(portableShortcutText); 0512 if (existingShortcutAction) { 0513 // If the shortcut is already in use we give a warning, so that hopefully the developer will find it 0514 // There is one exception, if the conflicting shortcut is a non primary shortcut of "edit_cut" 0515 // and "deleteFileAction" is the other action since Shift+Delete is used for both in our default code 0516 bool showWarning = true; 0517 if ((action == editCutAction && existingShortcutAction == deleteFileAction) 0518 || (action == deleteFileAction && existingShortcutAction == editCutAction)) { 0519 QList<QKeySequence> editCutActionShortcuts = editCutAction->shortcuts(); 0520 if (editCutActionShortcuts.indexOf(shortcut) > 0) // alternate shortcut 0521 { 0522 editCutActionShortcuts.removeAll(shortcut); 0523 editCutAction->setShortcuts(editCutActionShortcuts); 0524 0525 showWarning = false; 0526 } 0527 } 0528 0529 if (showWarning) { 0530 const QString actionName = KLocalizedString::removeAcceleratorMarker(action->text()); 0531 const QString existingShortcutActionName = KLocalizedString::removeAcceleratorMarker(existingShortcutAction->text()); 0532 QString dontShowAgainString = existingShortcutActionName + actionName + shortcut.toString(); 0533 dontShowAgainString.remove(QLatin1Char('\\')); 0534 KMessageBox::information(this, 0535 i18n("There are two actions (%1, %2) that want to use the same shortcut (%3). This is most probably a bug. " 0536 "Please report it in <a href='https://bugs.kde.org'>bugs.kde.org</a>", 0537 existingShortcutActionName, 0538 actionName, 0539 shortcut.toString(QKeySequence::NativeText)), 0540 i18n("Ambiguous Shortcuts"), 0541 dontShowAgainString, 0542 KMessageBox::Notify | KMessageBox::AllowLink); 0543 } 0544 } else { 0545 shortcuts.insert(portableShortcutText, action); 0546 } 0547 } 0548 } 0549 } 0550 } 0551 0552 void KXmlGuiWindow::setCommandBarEnabled(bool showCommandBar) 0553 { 0554 /** 0555 * Unset the shortcut 0556 */ 0557 auto cmdBarAction = actionCollection()->action(QStringLiteral("open_kcommand_bar")); 0558 if (showCommandBar) { 0559 actionCollection()->setDefaultShortcut(cmdBarAction, Qt::CTRL | Qt::ALT | Qt::Key_I); 0560 } else { 0561 actionCollection()->setDefaultShortcut(cmdBarAction, {}); 0562 } 0563 0564 Q_D(KXmlGuiWindow); 0565 d->commandBarEnabled = showCommandBar; 0566 } 0567 0568 bool KXmlGuiWindow::isCommandBarEnabled() const 0569 { 0570 Q_D(const KXmlGuiWindow); 0571 return d->commandBarEnabled; 0572 } 0573 0574 #include "moc_kxmlguiwindow.cpp"