| 1 | /* |
| 2 | This file is part of the KDE libraries |
| 3 | SPDX-FileCopyrightText: 2000 Reginald Stadlbauer <reggie@kde.org> |
| 4 | SPDX-FileCopyrightText: 1997 Stephan Kulow <coolo@kde.org> |
| 5 | SPDX-FileCopyrightText: 1997-2000 Sven Radej <radej@kde.org> |
| 6 | SPDX-FileCopyrightText: 1997-2000 Matthias Ettrich <ettrich@kde.org> |
| 7 | SPDX-FileCopyrightText: 1999 Chris Schlaeger <cs@kde.org> |
| 8 | SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org> |
| 9 | SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org> |
| 10 | |
| 11 | SPDX-License-Identifier: LGPL-2.0-only |
| 12 | */ |
| 13 | |
| 14 | #include "kxmlguiwindow.h" |
| 15 | #include "debug.h" |
| 16 | |
| 17 | #include "kactioncollection.h" |
| 18 | #include "kmainwindow_p.h" |
| 19 | #include <KMessageBox> |
| 20 | #include <kcommandbar.h> |
| 21 | #ifdef WITH_QTDBUS |
| 22 | #include "kmainwindowiface_p.h" |
| 23 | #endif |
| 24 | #include "kedittoolbar.h" |
| 25 | #include "khelpmenu.h" |
| 26 | #include "ktoolbar.h" |
| 27 | #include "ktoolbarhandler_p.h" |
| 28 | #include "kxmlguifactory.h" |
| 29 | |
| 30 | #ifdef WITH_QTDBUS |
| 31 | #include <QDBusConnection> |
| 32 | #endif |
| 33 | #include <QDomDocument> |
| 34 | #include <QEvent> |
| 35 | #include <QList> |
| 36 | #include <QMenuBar> |
| 37 | #include <QStatusBar> |
| 38 | #include <QWidget> |
| 39 | |
| 40 | #include <KAboutData> |
| 41 | #include <KCommandBar> |
| 42 | #include <KConfig> |
| 43 | #include <KConfigGroup> |
| 44 | #include <KLocalizedString> |
| 45 | #include <KSharedConfig> |
| 46 | #include <KStandardActions> |
| 47 | #include <KToggleAction> |
| 48 | |
| 49 | #include <cctype> |
| 50 | #include <cstdlib> |
| 51 | |
| 52 | /* |
| 53 | * A helper function that takes a list of KActionCollection* and converts it |
| 54 | * to KCommandBar::ActionGroup |
| 55 | */ |
| 56 | static QList<KCommandBar::ActionGroup> actionCollectionToActionGroup(const std::vector<KActionCollection *> &actionCollections) |
| 57 | { |
| 58 | using ActionGroup = KCommandBar::ActionGroup; |
| 59 | |
| 60 | QList<ActionGroup> actionList; |
| 61 | actionList.reserve(asize: actionCollections.size()); |
| 62 | |
| 63 | for (const auto collection : actionCollections) { |
| 64 | const QList<QAction *> collectionActions = collection->actions(); |
| 65 | const QString componentName = collection->componentDisplayName(); |
| 66 | |
| 67 | ActionGroup ag; |
| 68 | ag.name = componentName; |
| 69 | ag.actions.reserve(asize: collection->count()); |
| 70 | for (const auto action : collectionActions) { |
| 71 | /* |
| 72 | * If this action is a menu, fetch all its child actions |
| 73 | * and skip the menu action itself |
| 74 | */ |
| 75 | if (QMenu * = action->menu()) { |
| 76 | const QList<QAction *> = menu->actions(); |
| 77 | |
| 78 | ActionGroup ; |
| 79 | menuActionGroup.name = KLocalizedString::removeAcceleratorMarker(label: action->text()); |
| 80 | menuActionGroup.actions.reserve(asize: menuActions.size()); |
| 81 | for (const auto mAct : menuActions) { |
| 82 | if (mAct) { |
| 83 | menuActionGroup.actions.append(t: mAct); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /* |
| 88 | * If there were no actions in the menu, we |
| 89 | * add the menu to the list instead because it could |
| 90 | * be that the actions are created on demand i.e., aboutToShow() |
| 91 | */ |
| 92 | if (!menuActions.isEmpty()) { |
| 93 | actionList.append(t: menuActionGroup); |
| 94 | continue; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | if (action && !action->text().isEmpty()) { |
| 99 | ag.actions.append(t: action); |
| 100 | } |
| 101 | } |
| 102 | actionList.append(t: ag); |
| 103 | } |
| 104 | return actionList; |
| 105 | } |
| 106 | |
| 107 | static void getActionCollections(KXMLGUIClient *client, std::vector<KActionCollection *> &actionCollections) |
| 108 | { |
| 109 | if (!client) { |
| 110 | return; |
| 111 | } |
| 112 | |
| 113 | auto actionCollection = client->actionCollection(); |
| 114 | if (actionCollection && !actionCollection->isEmpty()) { |
| 115 | actionCollections.push_back(x: client->actionCollection()); |
| 116 | } |
| 117 | |
| 118 | const QList<KXMLGUIClient *> childClients = client->childClients(); |
| 119 | for (auto child : childClients) { |
| 120 | getActionCollections(client: child, actionCollections); |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | class KXmlGuiWindowPrivate : public KMainWindowPrivate |
| 125 | { |
| 126 | public: |
| 127 | void slotFactoryMakingChanges(bool b) |
| 128 | { |
| 129 | // While the GUI factory is adding/removing clients, |
| 130 | // don't let KMainWindow think those are changes made by the user |
| 131 | // #105525 |
| 132 | letDirtySettings = !b; |
| 133 | } |
| 134 | |
| 135 | bool commandBarEnabled = true; |
| 136 | // Last executed actions in command bar |
| 137 | QList<QString> lastExecutedActions; |
| 138 | |
| 139 | bool : 1; |
| 140 | QSize defaultSize; |
| 141 | |
| 142 | KDEPrivate::ToolBarHandler *toolBarHandler; |
| 143 | KToggleAction *showStatusBarAction; |
| 144 | QPointer<KEditToolBar> toolBarEditor; |
| 145 | KXMLGUIFactory *factory; |
| 146 | }; |
| 147 | |
| 148 | KXmlGuiWindow::KXmlGuiWindow(QWidget *parent, Qt::WindowFlags flags) |
| 149 | : KMainWindow(*new KXmlGuiWindowPrivate, parent, flags) |
| 150 | , KXMLGUIBuilder(this) |
| 151 | { |
| 152 | Q_D(KXmlGuiWindow); |
| 153 | d->showHelpMenu = true; |
| 154 | d->toolBarHandler = nullptr; |
| 155 | d->showStatusBarAction = nullptr; |
| 156 | d->factory = nullptr; |
| 157 | #ifdef WITH_QTDBUS |
| 158 | new KMainWindowInterface(this); |
| 159 | #endif |
| 160 | |
| 161 | /* |
| 162 | * Set up KCommandBar launcher action |
| 163 | */ |
| 164 | auto a = actionCollection()->addAction(QStringLiteral("open_kcommand_bar" ), receiver: this, slot: [this] { |
| 165 | /* |
| 166 | * Do nothing when command bar is disabled |
| 167 | */ |
| 168 | if (!isCommandBarEnabled()) { |
| 169 | return; |
| 170 | } |
| 171 | |
| 172 | auto ac = actionCollection(); |
| 173 | if (!ac) { |
| 174 | return; |
| 175 | } |
| 176 | |
| 177 | auto kc = new KCommandBar(this); |
| 178 | std::vector<KActionCollection *> actionCollections; |
| 179 | const auto clients = guiFactory()->clients(); |
| 180 | actionCollections.reserve(n: clients.size()); |
| 181 | |
| 182 | // Grab action collections recursively |
| 183 | for (const auto &client : clients) { |
| 184 | getActionCollections(client, actionCollections); |
| 185 | } |
| 186 | |
| 187 | kc->setActions(actionCollectionToActionGroup(actionCollections)); |
| 188 | kc->show(); |
| 189 | }); |
| 190 | a->setIcon(QIcon::fromTheme(QStringLiteral("search" ))); |
| 191 | a->setText(i18n("Find Action…" )); |
| 192 | KActionCollection::setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_I)); |
| 193 | } |
| 194 | |
| 195 | QAction *KXmlGuiWindow::() |
| 196 | { |
| 197 | Q_D(KXmlGuiWindow); |
| 198 | if (!d->toolBarHandler) { |
| 199 | return nullptr; |
| 200 | } |
| 201 | |
| 202 | return d->toolBarHandler->toolBarMenuAction(); |
| 203 | } |
| 204 | |
| 205 | void KXmlGuiWindow::() |
| 206 | { |
| 207 | Q_D(KXmlGuiWindow); |
| 208 | if (d->toolBarHandler) { |
| 209 | d->toolBarHandler->setupActions(); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | bool KXmlGuiWindow::isToolBarVisible(const QString &name) |
| 214 | { |
| 215 | KToolBar *tb = findChild<KToolBar *>(aName: name); |
| 216 | if (!tb) { |
| 217 | return false; |
| 218 | } |
| 219 | |
| 220 | return tb->isVisible(); |
| 221 | } |
| 222 | |
| 223 | void KXmlGuiWindow::setToolBarVisible(const QString &name, bool visible) |
| 224 | { |
| 225 | KToolBar *tb = findChild<KToolBar *>(aName: name); |
| 226 | if (!tb) { |
| 227 | return; |
| 228 | } |
| 229 | |
| 230 | tb->setVisible(visible); |
| 231 | } |
| 232 | |
| 233 | QStringList KXmlGuiWindow::toolBarNames() const |
| 234 | { |
| 235 | QList<KToolBar *> bars = toolBars(); |
| 236 | QStringList ret; |
| 237 | |
| 238 | for (KToolBar *bar : bars) { |
| 239 | ret.append(t: bar->objectName()); |
| 240 | } |
| 241 | |
| 242 | return ret; |
| 243 | } |
| 244 | |
| 245 | KXmlGuiWindow::~KXmlGuiWindow() |
| 246 | { |
| 247 | Q_D(KXmlGuiWindow); |
| 248 | delete d->factory; |
| 249 | } |
| 250 | |
| 251 | bool KXmlGuiWindow::event(QEvent *ev) |
| 252 | { |
| 253 | bool ret = KMainWindow::event(event: ev); |
| 254 | if (ev->type() == QEvent::Polish) { |
| 255 | #ifdef WITH_QTDBUS |
| 256 | /* clang-format off */ |
| 257 | constexpr auto opts = QDBusConnection::ExportScriptableSlots |
| 258 | | QDBusConnection::ExportScriptableProperties |
| 259 | | QDBusConnection::ExportNonScriptableSlots |
| 260 | | QDBusConnection::ExportNonScriptableProperties |
| 261 | | QDBusConnection::ExportChildObjects; |
| 262 | /* clang-format on */ |
| 263 | QDBusConnection::sessionBus().registerObject(path: dbusName() + QLatin1String("/actions" ), object: actionCollection(), options: opts); |
| 264 | #endif |
| 265 | } |
| 266 | return ret; |
| 267 | } |
| 268 | |
| 269 | void KXmlGuiWindow::(bool ) |
| 270 | { |
| 271 | Q_D(KXmlGuiWindow); |
| 272 | d->showHelpMenu = showHelpMenu; |
| 273 | } |
| 274 | |
| 275 | bool KXmlGuiWindow::() const |
| 276 | { |
| 277 | Q_D(const KXmlGuiWindow); |
| 278 | return d->showHelpMenu; |
| 279 | } |
| 280 | |
| 281 | KXMLGUIFactory *KXmlGuiWindow::guiFactory() |
| 282 | { |
| 283 | Q_D(KXmlGuiWindow); |
| 284 | if (!d->factory) { |
| 285 | d->factory = new KXMLGUIFactory(this, this); |
| 286 | connect(sender: d->factory, signal: &KXMLGUIFactory::makingChanges, context: this, slot: [d](bool state) { |
| 287 | d->slotFactoryMakingChanges(b: state); |
| 288 | }); |
| 289 | } |
| 290 | return d->factory; |
| 291 | } |
| 292 | |
| 293 | void KXmlGuiWindow::configureToolbars() |
| 294 | { |
| 295 | Q_D(KXmlGuiWindow); |
| 296 | KConfigGroup cg(KSharedConfig::openConfig(), QString()); |
| 297 | saveMainWindowSettings(config&: cg); |
| 298 | if (!d->toolBarEditor) { |
| 299 | d->toolBarEditor = new KEditToolBar(guiFactory(), this); |
| 300 | d->toolBarEditor->setAttribute(Qt::WA_DeleteOnClose); |
| 301 | connect(sender: d->toolBarEditor, signal: &KEditToolBar::newToolBarConfig, context: this, slot: &KXmlGuiWindow::saveNewToolbarConfig); |
| 302 | } |
| 303 | d->toolBarEditor->show(); |
| 304 | } |
| 305 | |
| 306 | void KXmlGuiWindow::saveNewToolbarConfig() |
| 307 | { |
| 308 | // createGUI(xmlFile()); // this loses any plugged-in guiclients, so we use remove+add instead. |
| 309 | |
| 310 | guiFactory()->removeClient(client: this); |
| 311 | guiFactory()->addClient(client: this); |
| 312 | |
| 313 | KConfigGroup cg(KSharedConfig::openConfig(), QString()); |
| 314 | applyMainWindowSettings(config: cg); |
| 315 | } |
| 316 | |
| 317 | void KXmlGuiWindow::setupGUI(StandardWindowOptions options, const QString &xmlfile) |
| 318 | { |
| 319 | setupGUI(defaultSize: QSize(), options, xmlfile); |
| 320 | } |
| 321 | |
| 322 | void KXmlGuiWindow::setupGUI(const QSize &defaultSize, StandardWindowOptions options, const QString &xmlfile) |
| 323 | { |
| 324 | Q_D(KXmlGuiWindow); |
| 325 | |
| 326 | if (options & Keys) { |
| 327 | KStandardActions::keyBindings(recvr: guiFactory(), slot: &KXMLGUIFactory::showConfigureShortcutsDialog, parent: actionCollection()); |
| 328 | } |
| 329 | |
| 330 | if ((options & StatusBar) && statusBar()) { |
| 331 | createStandardStatusBarAction(); |
| 332 | } |
| 333 | |
| 334 | if (options & ToolBar) { |
| 335 | setStandardToolBarMenuEnabled(true); |
| 336 | KStandardActions::configureToolbars(recvr: this, slot: &KXmlGuiWindow::configureToolbars, parent: actionCollection()); |
| 337 | } |
| 338 | |
| 339 | d->defaultSize = defaultSize; |
| 340 | |
| 341 | if (options & Create) { |
| 342 | createGUI(xmlfile); |
| 343 | } |
| 344 | |
| 345 | if (d->defaultSize.isValid()) { |
| 346 | resize(d->defaultSize); |
| 347 | } else if (isHidden()) { |
| 348 | adjustSize(); |
| 349 | } |
| 350 | |
| 351 | if (options & Save) { |
| 352 | const KConfigGroup cg(autoSaveConfigGroup()); |
| 353 | if (cg.isValid()) { |
| 354 | setAutoSaveSettings(group: cg); |
| 355 | } else { |
| 356 | setAutoSaveSettings(); |
| 357 | } |
| 358 | } |
| 359 | } |
| 360 | void KXmlGuiWindow::createGUI(const QString &xmlfile) |
| 361 | { |
| 362 | Q_D(KXmlGuiWindow); |
| 363 | // disabling the updates prevents unnecessary redraws |
| 364 | // setUpdatesEnabled( false ); |
| 365 | |
| 366 | // just in case we are rebuilding, let's remove our old client |
| 367 | guiFactory()->removeClient(client: this); |
| 368 | |
| 369 | // make sure to have an empty GUI |
| 370 | QMenuBar *mb = menuBar(); |
| 371 | if (mb) { |
| 372 | mb->clear(); |
| 373 | } |
| 374 | |
| 375 | qDeleteAll(c: toolBars()); // delete all toolbars |
| 376 | |
| 377 | // don't build a help menu unless the user ask for it |
| 378 | if (d->showHelpMenu) { |
| 379 | delete d->helpMenu; |
| 380 | // we always want a help menu |
| 381 | d->helpMenu = new KHelpMenu(this); |
| 382 | |
| 383 | KActionCollection *actions = actionCollection(); |
| 384 | QAction *helpContentsAction = d->helpMenu->action(id: KHelpMenu::menuHelpContents); |
| 385 | QAction *whatsThisAction = d->helpMenu->action(id: KHelpMenu::menuWhatsThis); |
| 386 | QAction *reportBugAction = d->helpMenu->action(id: KHelpMenu::menuReportBug); |
| 387 | QAction *switchLanguageAction = d->helpMenu->action(id: KHelpMenu::menuSwitchLanguage); |
| 388 | QAction *aboutAppAction = d->helpMenu->action(id: KHelpMenu::menuAboutApp); |
| 389 | QAction *aboutKdeAction = d->helpMenu->action(id: KHelpMenu::menuAboutKDE); |
| 390 | QAction *donateAction = d->helpMenu->action(id: KHelpMenu::menuDonate); |
| 391 | |
| 392 | if (helpContentsAction) { |
| 393 | actions->addAction(name: helpContentsAction->objectName(), action: helpContentsAction); |
| 394 | } |
| 395 | if (whatsThisAction) { |
| 396 | actions->addAction(name: whatsThisAction->objectName(), action: whatsThisAction); |
| 397 | } |
| 398 | if (reportBugAction) { |
| 399 | actions->addAction(name: reportBugAction->objectName(), action: reportBugAction); |
| 400 | } |
| 401 | if (switchLanguageAction) { |
| 402 | actions->addAction(name: switchLanguageAction->objectName(), action: switchLanguageAction); |
| 403 | } |
| 404 | if (aboutAppAction) { |
| 405 | actions->addAction(name: aboutAppAction->objectName(), action: aboutAppAction); |
| 406 | } |
| 407 | if (aboutKdeAction) { |
| 408 | actions->addAction(name: aboutKdeAction->objectName(), action: aboutKdeAction); |
| 409 | } |
| 410 | if (donateAction) { |
| 411 | actions->addAction(name: donateAction->objectName(), action: donateAction); |
| 412 | } |
| 413 | } |
| 414 | |
| 415 | const QString windowXmlFile = xmlfile.isNull() ? componentName() + QLatin1String("ui.rc" ) : xmlfile; |
| 416 | |
| 417 | // Help beginners who call setXMLFile and then setupGUI... |
| 418 | if (!xmlFile().isEmpty() && xmlFile() != windowXmlFile) { |
| 419 | qCWarning(DEBUG_KXMLGUI) << "You called setXMLFile(" << xmlFile() << ") and then createGUI or setupGUI," |
| 420 | << "which also calls setXMLFile and will overwrite the file you have previously set.\n" |
| 421 | << "You should call createGUI(" << xmlFile() << ") or setupGUI(<options>," << xmlFile() << ") instead." ; |
| 422 | } |
| 423 | |
| 424 | // we always want to load in our global standards file |
| 425 | loadStandardsXmlFile(); |
| 426 | |
| 427 | // now, merge in our local xml file. |
| 428 | setXMLFile(file: windowXmlFile, merge: true); |
| 429 | |
| 430 | // make sure we don't have any state saved already |
| 431 | setXMLGUIBuildDocument(QDomDocument()); |
| 432 | |
| 433 | // do the actual GUI building |
| 434 | guiFactory()->reset(); |
| 435 | guiFactory()->addClient(client: this); |
| 436 | |
| 437 | checkAmbiguousShortcuts(); |
| 438 | |
| 439 | // setUpdatesEnabled( true ); |
| 440 | } |
| 441 | |
| 442 | void KXmlGuiWindow::slotStateChanged(const QString &newstate) |
| 443 | { |
| 444 | stateChanged(newstate, reverse: KXMLGUIClient::StateNoReverse); |
| 445 | } |
| 446 | |
| 447 | void KXmlGuiWindow::slotStateChanged(const QString &newstate, bool reverse) |
| 448 | { |
| 449 | stateChanged(newstate, reverse: reverse ? KXMLGUIClient::StateReverse : KXMLGUIClient::StateNoReverse); |
| 450 | } |
| 451 | |
| 452 | void KXmlGuiWindow::setStandardToolBarMenuEnabled(bool ) |
| 453 | { |
| 454 | Q_D(KXmlGuiWindow); |
| 455 | if (showToolBarMenu) { |
| 456 | if (d->toolBarHandler) { |
| 457 | return; |
| 458 | } |
| 459 | |
| 460 | d->toolBarHandler = new KDEPrivate::ToolBarHandler(this); |
| 461 | |
| 462 | if (factory()) { |
| 463 | factory()->addClient(client: d->toolBarHandler); |
| 464 | } |
| 465 | } else { |
| 466 | if (!d->toolBarHandler) { |
| 467 | return; |
| 468 | } |
| 469 | |
| 470 | if (factory()) { |
| 471 | factory()->removeClient(client: d->toolBarHandler); |
| 472 | } |
| 473 | |
| 474 | delete d->toolBarHandler; |
| 475 | d->toolBarHandler = nullptr; |
| 476 | } |
| 477 | } |
| 478 | |
| 479 | bool KXmlGuiWindow::isStandardToolBarMenuEnabled() const |
| 480 | { |
| 481 | Q_D(const KXmlGuiWindow); |
| 482 | return (d->toolBarHandler); |
| 483 | } |
| 484 | |
| 485 | void KXmlGuiWindow::createStandardStatusBarAction() |
| 486 | { |
| 487 | Q_D(KXmlGuiWindow); |
| 488 | if (!d->showStatusBarAction) { |
| 489 | d->showStatusBarAction = KStandardAction::showStatusbar(recvr: this, slot: &KMainWindow::setSettingsDirty, parent: actionCollection()); |
| 490 | QStatusBar *sb = statusBar(); // Creates statusbar if it doesn't exist already. |
| 491 | connect(sender: d->showStatusBarAction, signal: &QAction::toggled, context: sb, slot: &QWidget::setVisible); |
| 492 | d->showStatusBarAction->setChecked(sb->isHidden()); |
| 493 | } else { |
| 494 | // If the language has changed, we'll need to grab the new text and whatsThis |
| 495 | QAction *tmpStatusBar = KStandardAction::showStatusbar(recvr: nullptr, slot: nullptr, parent: nullptr); |
| 496 | d->showStatusBarAction->setText(tmpStatusBar->text()); |
| 497 | d->showStatusBarAction->setWhatsThis(tmpStatusBar->whatsThis()); |
| 498 | delete tmpStatusBar; |
| 499 | } |
| 500 | } |
| 501 | |
| 502 | void KXmlGuiWindow::finalizeGUI(bool /*force*/) |
| 503 | { |
| 504 | // FIXME: this really needs to be removed with a code more like the one we had on KDE3. |
| 505 | // what we need to do here is to position correctly toolbars so they don't overlap. |
| 506 | // Also, take in count plugins could provide their own toolbars and those also need to |
| 507 | // be restored. |
| 508 | if (autoSaveSettings() && autoSaveConfigGroup().isValid()) { |
| 509 | applyMainWindowSettings(config: autoSaveConfigGroup()); |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | void KXmlGuiWindow::applyMainWindowSettings(const KConfigGroup &config) |
| 514 | { |
| 515 | Q_D(KXmlGuiWindow); |
| 516 | KMainWindow::applyMainWindowSettings(config); |
| 517 | QStatusBar *sb = findChild<QStatusBar *>(); |
| 518 | if (sb && d->showStatusBarAction) { |
| 519 | d->showStatusBarAction->setChecked(!sb->isHidden()); |
| 520 | } |
| 521 | } |
| 522 | |
| 523 | void KXmlGuiWindow::checkAmbiguousShortcuts() |
| 524 | { |
| 525 | QMap<QString, QAction *> shortcuts; |
| 526 | QAction *editCutAction = actionCollection()->action(QStringLiteral("edit_cut" )); |
| 527 | QAction *deleteFileAction = actionCollection()->action(QStringLiteral("deletefile" )); |
| 528 | const auto actions = actionCollection()->actions(); |
| 529 | for (QAction *action : actions) { |
| 530 | if (action->isEnabled()) { |
| 531 | const auto actionShortcuts = action->shortcuts(); |
| 532 | for (const QKeySequence &shortcut : actionShortcuts) { |
| 533 | if (shortcut.isEmpty()) { |
| 534 | continue; |
| 535 | } |
| 536 | const QString portableShortcutText = shortcut.toString(); |
| 537 | const QAction *existingShortcutAction = shortcuts.value(key: portableShortcutText); |
| 538 | if (existingShortcutAction) { |
| 539 | // If the shortcut is already in use we give a warning, so that hopefully the developer will find it |
| 540 | // There is one exception, if the conflicting shortcut is a non primary shortcut of "edit_cut" |
| 541 | // and "deleteFileAction" is the other action since Shift+Delete is used for both in our default code |
| 542 | bool showWarning = true; |
| 543 | if ((action == editCutAction && existingShortcutAction == deleteFileAction) |
| 544 | || (action == deleteFileAction && existingShortcutAction == editCutAction)) { |
| 545 | QList<QKeySequence> editCutActionShortcuts = editCutAction->shortcuts(); |
| 546 | if (editCutActionShortcuts.indexOf(t: shortcut) > 0) // alternate shortcut |
| 547 | { |
| 548 | editCutActionShortcuts.removeAll(t: shortcut); |
| 549 | editCutAction->setShortcuts(editCutActionShortcuts); |
| 550 | |
| 551 | showWarning = false; |
| 552 | } |
| 553 | } |
| 554 | |
| 555 | if (showWarning) { |
| 556 | const QString actionName = KLocalizedString::removeAcceleratorMarker(label: action->text()); |
| 557 | const QString existingShortcutActionName = KLocalizedString::removeAcceleratorMarker(label: existingShortcutAction->text()); |
| 558 | QString dontShowAgainString = existingShortcutActionName + actionName + shortcut.toString(); |
| 559 | dontShowAgainString.remove(c: QLatin1Char('\\')); |
| 560 | KMessageBox::information(parent: this, |
| 561 | i18n("There are two actions (%1, %2) that want to use the same shortcut (%3). This is most probably a bug. " |
| 562 | "Please report it in <a href='https://bugs.kde.org'>bugs.kde.org</a>" , |
| 563 | existingShortcutActionName, |
| 564 | actionName, |
| 565 | shortcut.toString(QKeySequence::NativeText)), |
| 566 | i18n("Ambiguous Shortcuts" ), |
| 567 | dontShowAgainName: dontShowAgainString, |
| 568 | options: KMessageBox::Notify | KMessageBox::AllowLink); |
| 569 | } |
| 570 | } else { |
| 571 | shortcuts.insert(key: portableShortcutText, value: action); |
| 572 | } |
| 573 | } |
| 574 | } |
| 575 | } |
| 576 | } |
| 577 | |
| 578 | void KXmlGuiWindow::setCommandBarEnabled(bool showCommandBar) |
| 579 | { |
| 580 | /* |
| 581 | * Unset the shortcut |
| 582 | */ |
| 583 | auto cmdBarAction = actionCollection()->action(QStringLiteral("open_kcommand_bar" )); |
| 584 | if (showCommandBar) { |
| 585 | KActionCollection::setDefaultShortcut(action: cmdBarAction, shortcut: Qt::CTRL | Qt::ALT | Qt::Key_I); |
| 586 | } else { |
| 587 | KActionCollection::setDefaultShortcut(action: cmdBarAction, shortcut: {}); |
| 588 | } |
| 589 | |
| 590 | Q_D(KXmlGuiWindow); |
| 591 | d->commandBarEnabled = showCommandBar; |
| 592 | } |
| 593 | |
| 594 | bool KXmlGuiWindow::isCommandBarEnabled() const |
| 595 | { |
| 596 | Q_D(const KXmlGuiWindow); |
| 597 | return d->commandBarEnabled; |
| 598 | } |
| 599 | |
| 600 | #include "moc_kxmlguiwindow.cpp" |
| 601 | |