Skip to content

Commit c91d17b

Browse files
committed
#3584 shortcuts: fix regressions from #3583
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent 32ad35f commit c91d17b

3 files changed

Lines changed: 177 additions & 115 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## 26.4.23
44

5+
- Fixed shortcuts not being saved or restored in the **Shortcuts** settings;
6+
`storeShortcutSettings` now iterates the actual menu actions (mirroring how
7+
`initShortcuts` reads them) and looks up the corresponding widgets in the
8+
tree by action name, and shortcut values are stored as strings to match the
9+
format expected by `initShortcuts` (for [#3584](https://github.com/pbek/QOwnNotes/issues/3584))
10+
- Fixed a crash when assigning a shortcut in the **Shortcuts** settings;
11+
`keySequenceEvent` and `findKeySequenceWidget` now use recursive tree
12+
traversal to handle the multi-level menu hierarchy correctly
13+
(for [#3584](https://github.com/pbek/QOwnNotes/issues/3584))
514
- Fixed a possible crash that occurred when Qt's internal style machinery was still
615
processing a system color scheme change event while `applyDarkModeSettings()`
716
was called synchronously, causing a SIGSEGV in QtWidgets; the UI update is now

src/dialogs/settingsdialog.cpp

Lines changed: 167 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <QKeySequence>
3030
#include <QKeySequenceEdit>
3131
#include <QMenu>
32+
#include <QMenuBar>
3233
#include <QMessageBox>
3334
#include <QPointer>
3435
#include <QRadioButton>
@@ -422,7 +423,6 @@ void SettingsDialog::loadShortcutSettings() {
422423
QColor shortcutButtonActiveColor = darkMode ? Qt::white : palette.color(QPalette::ButtonText);
423424
QColor shortcutButtonInactiveColor = darkMode ? Qt::darkGray : palette.color(QPalette::Mid);
424425

425-
const QList<QMenu *> menus = mainWindow->menuList();
426426
ui->shortcutSearchLineEdit->clear();
427427
ui->shortcutTreeWidget->clear();
428428
ui->shortcutTreeWidget->setColumnCount(3);
@@ -438,6 +438,9 @@ void SettingsDialog::loadShortcutSettings() {
438438
QIcon::fromTheme(QStringLiteral("edit-clear"), QIcon(":/icons/breeze-qownnotes/16x16/"
439439
"edit-clear.svg"));
440440

441+
const QList<QMenu *> menus =
442+
mainWindow->menuBar()->findChildren<QMenu *>(QString(), Qt::FindDirectChildrenOnly);
443+
441444
// loop through all top-level menus and build the tree recursively
442445
for (const QMenu *menu : menus) {
443446
if (disabledMenuNames.contains(menu->objectName())) {
@@ -473,9 +476,37 @@ void SettingsDialog::buildShortcutTreeForMenu(const QMenu *menu, QTreeWidgetItem
473476
const QIcon &disableShortcutButtonIcon,
474477
const QIcon &clearButtonIcon,
475478
const QStringList &disabledMenuNames) {
476-
auto *menuItem = new QTreeWidgetItem();
479+
// Count eligible actions first to decide whether to add this menu at all
477480
int actionCount = 0;
478481

482+
foreach (QAction *action, menu->actions()) {
483+
if (action->menu() != nullptr) {
484+
if (!disabledMenuNames.contains(action->menu()->objectName())) {
485+
actionCount++;
486+
}
487+
} else if (!action->objectName().isEmpty()) {
488+
actionCount++;
489+
}
490+
}
491+
492+
if (actionCount == 0) {
493+
return;
494+
}
495+
496+
// Add the menu item to the tree before populating children so that
497+
// setItemWidget() works correctly (items must be in the tree first)
498+
auto *menuItem = new QTreeWidgetItem();
499+
menuItem->setText(0, menu->title().remove(QStringLiteral("&")));
500+
menuItem->setToolTip(0, menu->objectName());
501+
502+
if (parentItem == nullptr) {
503+
ui->shortcutTreeWidget->addTopLevelItem(menuItem);
504+
} else {
505+
parentItem->addChild(menuItem);
506+
}
507+
508+
menuItem->setExpanded(true);
509+
479510
// loop through all actions of the menu
480511
foreach (QAction *action, menu->actions()) {
481512
// Handle submenus recursively so the full menu structure is visible
@@ -489,7 +520,6 @@ void SettingsDialog::buildShortcutTreeForMenu(const QMenu *menu, QTreeWidgetItem
489520
buildShortcutTreeForMenu(subMenu, menuItem, settings, shortcutButtonActiveColor,
490521
shortcutButtonInactiveColor, disableShortcutButtonIcon,
491522
clearButtonIcon, disabledMenuNames);
492-
actionCount++;
493523
continue;
494524
}
495525

@@ -561,23 +591,6 @@ void SettingsDialog::buildShortcutTreeForMenu(const QMenu *menu, QTreeWidgetItem
561591
.toString());
562592

563593
ui->shortcutTreeWidget->setItemWidget(actionItem, 2, globalShortcutKeyWidget);
564-
565-
actionCount++;
566-
}
567-
568-
if (actionCount > 0) {
569-
menuItem->setText(0, menu->title().remove(QStringLiteral("&")));
570-
menuItem->setToolTip(0, menu->objectName());
571-
572-
if (parentItem == nullptr) {
573-
ui->shortcutTreeWidget->addTopLevelItem(menuItem);
574-
} else {
575-
parentItem->addChild(menuItem);
576-
}
577-
578-
menuItem->setExpanded(true);
579-
} else {
580-
delete menuItem;
581594
}
582595
}
583596

@@ -600,48 +613,53 @@ void SettingsDialog::keySequenceEvent(const QString &objectName) {
600613
return;
601614
}
602615

603-
// loop all top level tree widget items (menus)
604-
for (int i = 0; i < ui->shortcutTreeWidget->topLevelItemCount(); i++) {
605-
QTreeWidgetItem *menuItem = ui->shortcutTreeWidget->topLevelItem(i);
616+
// Search all action items recursively for a conflicting shortcut
617+
std::function<bool(QTreeWidgetItem *)> checkItem = [&](QTreeWidgetItem *item) -> bool {
618+
const QString itemObjectName = item->data(1, Qt::UserRole).toString();
606619

607-
// loop all tree widget items of the menu (action shortcuts)
608-
for (int j = 0; j < menuItem->childCount(); j++) {
609-
QTreeWidgetItem *shortcutItem = menuItem->child(j);
620+
if (!itemObjectName.isEmpty() && itemObjectName != objectName) {
621+
auto *frameWidget = ui->shortcutTreeWidget->itemWidget(item, 1);
610622

611-
// skip the item that threw the event
612-
if (shortcutItem->data(1, Qt::UserRole).toString() == objectName) {
613-
continue;
614-
}
623+
if (frameWidget != nullptr) {
624+
const auto keySequenceWidgets = frameWidget->findChildren<QKeySequenceWidget *>();
615625

616-
const auto keySequenceWidgets = ui->shortcutTreeWidget->itemWidget(shortcutItem, 1)
617-
->findChildren<QKeySequenceWidget *>();
626+
if (keySequenceWidgets.count() > 0) {
627+
QKeySequence keySequence = keySequenceWidgets.at(0)->keySequence();
628+
629+
// show an information if the shortcut was already used elsewhere
630+
if (keySequence == eventKeySequence) {
631+
if (Utils::Gui::information(
632+
this, tr("Shortcut already assigned"),
633+
tr("The shortcut <strong>%1</strong> is already "
634+
"assigned to <strong>%2</strong>! Do you want to "
635+
"jump to the shortcut?")
636+
.arg(eventKeySequence.toString(), item->text(0)),
637+
QStringLiteral("settings-shortcut-already-assigned"),
638+
QMessageBox::Yes | QMessageBox::Cancel,
639+
QMessageBox::Yes) == QMessageBox::Yes) {
640+
ui->shortcutTreeWidget->scrollToItem(item);
641+
ui->shortcutTreeWidget->clearSelection();
642+
item->setSelected(true);
643+
}
644+
645+
return true;
646+
}
647+
}
648+
}
649+
}
618650

619-
if (keySequenceWidgets.count() == 0) {
620-
continue;
651+
for (int i = 0; i < item->childCount(); i++) {
652+
if (checkItem(item->child(i))) {
653+
return true;
621654
}
655+
}
622656

623-
auto *keyWidget = keySequenceWidgets.at(0);
624-
QKeySequence keySequence = keyWidget->keySequence();
625-
QKeySequence defaultKeySequence = keyWidget->defaultKeySequence();
626-
627-
// show an information if the shortcut was already used elsewhere
628-
if (keySequence == eventKeySequence) {
629-
if (Utils::Gui::information(
630-
this, tr("Shortcut already assigned"),
631-
tr("The shortcut <strong>%1</strong> is already "
632-
"assigned to <strong>%2</strong>! Do you want to "
633-
"jump to the shortcut?")
634-
.arg(eventKeySequence.toString(), shortcutItem->text(0)),
635-
QStringLiteral("settings-shortcut-already-assigned"),
636-
QMessageBox::Yes | QMessageBox::Cancel,
637-
QMessageBox::Yes) == QMessageBox::Yes) {
638-
ui->shortcutTreeWidget->scrollToItem(shortcutItem);
639-
ui->shortcutTreeWidget->clearSelection();
640-
shortcutItem->setSelected(true);
641-
}
657+
return false;
658+
};
642659

643-
return;
644-
}
660+
for (int i = 0; i < ui->shortcutTreeWidget->topLevelItemCount(); i++) {
661+
if (checkItem(ui->shortcutTreeWidget->topLevelItem(i))) {
662+
return;
645663
}
646664
}
647665
}
@@ -651,92 +669,126 @@ void SettingsDialog::keySequenceEvent(const QString &objectName) {
651669
* of the assigned menu action
652670
*/
653671
QKeySequenceWidget *SettingsDialog::findKeySequenceWidget(const QString &objectName) {
654-
// loop all top level tree widget items (menus)
655-
for (int i = 0; i < ui->shortcutTreeWidget->topLevelItemCount(); i++) {
656-
QTreeWidgetItem *menuItem = ui->shortcutTreeWidget->topLevelItem(i);
672+
QKeySequenceWidget *result = nullptr;
673+
674+
std::function<void(QTreeWidgetItem *)> findItem = [&](QTreeWidgetItem *item) {
675+
if (result != nullptr) {
676+
return;
677+
}
657678

658-
// loop all tree widget items of the menu (action shortcuts)
659-
for (int j = 0; j < menuItem->childCount(); j++) {
660-
QTreeWidgetItem *shortcutItem = menuItem->child(j);
661-
QString name = shortcutItem->data(1, Qt::UserRole).toString();
679+
if (item->data(1, Qt::UserRole).toString() == objectName) {
680+
auto *frameWidget = ui->shortcutTreeWidget->itemWidget(item, 1);
662681

663-
if (name == objectName) {
664-
const auto keySequenceWidgets = ui->shortcutTreeWidget->itemWidget(shortcutItem, 1)
665-
->findChildren<QKeySequenceWidget *>();
682+
if (frameWidget != nullptr) {
683+
const auto keySequenceWidgets = frameWidget->findChildren<QKeySequenceWidget *>();
666684

667685
if (keySequenceWidgets.count() > 0) {
668-
return keySequenceWidgets.at(0);
686+
result = keySequenceWidgets.at(0);
687+
return;
669688
}
670689
}
671690
}
691+
692+
for (int i = 0; i < item->childCount(); i++) {
693+
findItem(item->child(i));
694+
}
695+
};
696+
697+
for (int i = 0; i < ui->shortcutTreeWidget->topLevelItemCount(); i++) {
698+
findItem(ui->shortcutTreeWidget->topLevelItem(i));
672699
}
673700

674-
return nullptr;
701+
return result;
675702
}
676703

677704
/**
678-
* Stores the local and global keyboard shortcut settings
705+
* Finds the global QKeySequenceWidget in the shortcutTreeWidget by the
706+
* objectName of the assigned menu action (column 2)
679707
*/
680-
void SettingsDialog::storeShortcutSettings() {
681-
SettingsService settings;
708+
QKeySequenceWidget *SettingsDialog::findGlobalKeySequenceWidget(const QString &objectName) {
709+
QKeySequenceWidget *result = nullptr;
682710

683-
// Collect all tree widget items recursively to find all action shortcut items,
684-
// regardless of nesting depth (submenus can be multiple levels deep)
685-
QList<QTreeWidgetItem *> allItems =
686-
ui->shortcutTreeWidget->findItems(QString(), Qt::MatchContains | Qt::MatchRecursive);
711+
std::function<void(QTreeWidgetItem *)> findItem = [&](QTreeWidgetItem *item) {
712+
if (result != nullptr) {
713+
return;
714+
}
687715

688-
for (QTreeWidgetItem *shortcutItem : allItems) {
689-
// Action items store their object name in column 1's UserRole;
690-
// menu items do not, so skip those
691-
const QString actionObjectName = shortcutItem->data(1, Qt::UserRole).toString();
716+
if (item->data(1, Qt::UserRole).toString() == objectName) {
717+
result =
718+
dynamic_cast<QKeySequenceWidget *>(ui->shortcutTreeWidget->itemWidget(item, 2));
692719

693-
if (actionObjectName.isEmpty()) {
694-
continue;
720+
if (result != nullptr) {
721+
return;
722+
}
695723
}
696724

697-
auto *frameWidget = ui->shortcutTreeWidget->itemWidget(shortcutItem, 1);
698-
699-
if (frameWidget == nullptr) {
700-
continue;
725+
for (int i = 0; i < item->childCount(); i++) {
726+
findItem(item->child(i));
701727
}
728+
};
702729

703-
const auto keySequenceWidgets = frameWidget->findChildren<QKeySequenceWidget *>();
730+
for (int i = 0; i < ui->shortcutTreeWidget->topLevelItemCount(); i++) {
731+
findItem(ui->shortcutTreeWidget->topLevelItem(i));
732+
}
704733

705-
if (keySequenceWidgets.count() == 0) {
706-
continue;
707-
}
734+
return result;
735+
}
708736

709-
auto *keyWidget = keySequenceWidgets.at(0);
710-
auto *globalShortcutKeyWidget =
711-
dynamic_cast<QKeySequenceWidget *>(ui->shortcutTreeWidget->itemWidget(shortcutItem, 2));
737+
/**
738+
* Stores the local and global keyboard shortcut settings
739+
*/
740+
void SettingsDialog::storeShortcutSettings() {
741+
SettingsService settings;
742+
MainWindow *mainWindow = MainWindow::instance();
712743

713-
if (keyWidget == nullptr || globalShortcutKeyWidget == nullptr) {
714-
continue;
715-
}
744+
if (mainWindow == nullptr) {
745+
return;
746+
}
716747

717-
// handle local shortcut
718-
QKeySequence keySequence = keyWidget->keySequence();
719-
QKeySequence defaultKeySequence = keyWidget->defaultKeySequence();
720-
QString settingsKey = "Shortcuts/MainWindow-" + actionObjectName;
721-
722-
// remove or store the setting for the shortcut if it's not default
723-
if (keySequence == defaultKeySequence) {
724-
settings.remove(settingsKey);
725-
} else {
726-
// set new key sequence (can also be empty if no key sequence
727-
// should be used)
728-
settings.setValue(settingsKey, keySequence);
729-
}
748+
// Store shortcuts by iterating through the actual menu actions (the same
749+
// way initShortcuts reads them) and looking up the corresponding widgets
750+
// in the tree. This avoids any issues with tree widget item traversal.
751+
const QList<QMenu *> menus = mainWindow->menuList();
752+
753+
for (QMenu *menu : menus) {
754+
for (QAction *action : menu->actions()) {
755+
const QString actionObjectName = action->objectName();
730756

731-
// handle global shortcut
732-
keySequence = globalShortcutKeyWidget->keySequence();
733-
settingsKey = "GlobalShortcuts/MainWindow-" + actionObjectName;
757+
if (actionObjectName.isEmpty()) {
758+
continue;
759+
}
760+
761+
// Find the key sequence widget for this action in the tree
762+
auto *keyWidget = findKeySequenceWidget(actionObjectName);
763+
764+
if (keyWidget != nullptr) {
765+
QKeySequence keySequence = keyWidget->keySequence();
766+
QKeySequence defaultKeySequence = keyWidget->defaultKeySequence();
767+
const QString settingsKey =
768+
QStringLiteral("Shortcuts/MainWindow-") + actionObjectName;
769+
770+
// remove or store the setting for the shortcut if it's not default
771+
if (keySequence == defaultKeySequence) {
772+
settings.remove(settingsKey);
773+
} else {
774+
settings.setValue(settingsKey, keySequence.toString());
775+
}
776+
}
777+
778+
// Find the global shortcut widget for this action in the tree
779+
auto *globalWidget = findGlobalKeySequenceWidget(actionObjectName);
734780

735-
// remove or store the setting for the shortcut if it's not empty
736-
if (keySequence.isEmpty()) {
737-
settings.remove(settingsKey);
738-
} else {
739-
settings.setValue(settingsKey, keySequence);
781+
if (globalWidget != nullptr) {
782+
QKeySequence keySequence = globalWidget->keySequence();
783+
const QString settingsKey =
784+
QStringLiteral("GlobalShortcuts/MainWindow-") + actionObjectName;
785+
786+
if (keySequence.isEmpty()) {
787+
settings.remove(settingsKey);
788+
} else {
789+
settings.setValue(settingsKey, keySequence.toString());
790+
}
791+
}
740792
}
741793
}
742794
}

src/dialogs/settingsdialog.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ class SettingsDialog : public MasterDialog {
176176
void replaceOwnCloudText() const;
177177

178178
QKeySequenceWidget *findKeySequenceWidget(const QString &objectName);
179+
QKeySequenceWidget *findGlobalKeySequenceWidget(const QString &objectName);
179180

180181
void handleDarkModeCheckBoxToggled(bool updateCheckBoxes = false, bool updateSchema = false);
181182

0 commit comments

Comments
 (0)