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