From e4bb80b96ca1eafdfcd4b56cbf94c376ff319ba7 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sat, 22 Feb 2025 20:44:12 -0500 Subject: [PATCH] Support tearing off tags menu (#11652) * Support tearing off tags menu * Closes #11649 - tags menu can be torn off to set and unset tags without having to dive into the context menu every time. * Tags menu will hide when database is locked or view is switched away from the main database view (eg, settings) --- src/gui/MainWindow.cpp | 45 ++++++++++++++++++++++++------- src/gui/MainWindow.ui | 3 +++ src/gui/styles/base/BaseStyle.cpp | 14 ++++++++++ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 997092366..cd67a4982 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -817,26 +817,45 @@ void MainWindow::updateCopyAttributesMenu() void MainWindow::updateSetTagsMenu() { - // Remove all existing actions - m_ui->menuTags->clear(); + auto actionForTag = [](const QMenu* menu, const QString& tag) -> QAction* { + for (const auto action : menu->actions()) { + if (action->text() == tag) { + return action; + } + } + return nullptr; + }; auto dbWidget = m_ui->tabWidget->currentDatabaseWidget(); if (dbWidget) { // Enumerate tags applied to the selected entries QSet selectedTags; - for (auto entry : dbWidget->entryView()->selectedEntries()) { - for (auto tag : entry->tagList()) { + for (const auto entry : dbWidget->entryView()->selectedEntries()) { + for (const auto& tag : entry->tagList()) { selectedTags.insert(tag); } } // Add known database tags as actions and set checked if // a selected entry has that tag - for (auto tag : dbWidget->database()->tagList()) { - auto action = m_ui->menuTags->addAction(icons()->icon("tag"), tag); - action->setCheckable(true); - action->setChecked(selectedTags.contains(tag)); - m_setTagsMenuActions->addAction(action); + const auto tagList = dbWidget->database()->tagList(); + for (const auto& tag : tagList) { + auto action = actionForTag(m_ui->menuTags, tag); + if (action) { + action->setChecked(selectedTags.contains(tag)); + } else { + action = m_ui->menuTags->addAction(icons()->icon("tag"), tag); + action->setCheckable(true); + action->setChecked(selectedTags.contains(tag)); + m_setTagsMenuActions->addAction(action); + } + } + + // Remove missing tags + for (const auto action : m_ui->menuTags->actions()) { + if (!tagList.contains(action->text())) { + action->deleteLater(); + } } } @@ -942,6 +961,14 @@ void MainWindow::updateMenuActionState() m_ui->menuEntryCopyAttribute->setEnabled(singleEntryOrEditing); m_ui->menuEntryTotp->setEnabled(singleEntrySelected); m_ui->menuTags->setEnabled(multiEntrySelected); + // Handle tear-off tags menu + if (m_ui->menuTags->isTearOffMenuVisible()) { + if (!databaseUnlocked) { + m_ui->menuTags->hideTearOffMenu(); + } else { + updateSetTagsMenu(); + } + } m_ui->actionEntryAutoType->setEnabled(singleEntrySelected && dbWidget->currentEntryHasAutoTypeEnabled()); m_ui->actionEntryAutoType->menu()->setEnabled(singleEntrySelected && dbWidget->currentEntryHasAutoTypeEnabled()); m_ui->actionEntryAutoTypeSequence->setText(singleEntrySelected diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index fc4c0b9d5..283bfd999 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -314,6 +314,9 @@ + + true + Tags diff --git a/src/gui/styles/base/BaseStyle.cpp b/src/gui/styles/base/BaseStyle.cpp index 9cb844d70..8fa8f64bb 100644 --- a/src/gui/styles/base/BaseStyle.cpp +++ b/src/gui/styles/base/BaseStyle.cpp @@ -2636,7 +2636,21 @@ void BaseStyle::drawControl(ControlElement element, } break; } + case CE_MenuTearoff: { + if (option->state & State_Selected) { + painter->fillRect(option->rect, option->palette.brush(QPalette::Highlight)); + painter->setPen(QPen(option->palette.highlightedText().color(), 1, Qt::DashLine)); + } else { + painter->fillRect(option->rect, option->palette.brush(QPalette::Button)); + painter->setPen(QPen(option->palette.buttonText().color(), 1, Qt::DashLine)); + } + painter->drawLine(option->rect.x() + 2, + option->rect.y() + option->rect.height() / 2, + option->rect.x() + option->rect.width() - 4, + option->rect.y() + option->rect.height() / 2); + break; + } case CE_MenuItem: { auto menuItem = qstyleoption_cast(option); if (!menuItem)