diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/keymap.cpp | 6 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 53 | ||||
| -rw-r--r-- | src/mainwindow.h | 31 |
3 files changed, 90 insertions, 0 deletions
diff --git a/src/keymap.cpp b/src/keymap.cpp index cdd26a1..71c5355 100644 --- a/src/keymap.cpp +++ b/src/keymap.cpp @@ -35,6 +35,7 @@ QStringList KeyMap::knownActions() QStringLiteral("toggle_unread"), QStringLiteral("mark_all_read"), QStringLiteral("edit_tags"), + QStringLiteral("tag_rules"), QStringLiteral("flag"), QStringLiteral("focus_query"), QStringLiteral("complete_query"), @@ -89,6 +90,11 @@ QList<QPair<QString, QString>> KeyMap::defaultBindings() { QStringLiteral("Ctrl+Shift+U"), QStringLiteral("mark_all_read") }, { QStringLiteral("Ctrl+I"), QStringLiteral("flag") }, { QStringLiteral("Ctrl+T"), QStringLiteral("edit_tags") }, + // Shifted against Ctrl+T for the same reason Ctrl+Shift+U is shifted + // against Ctrl+U: this is the standing version of tagging, applied to + // every message that arrives rather than to the selection, so it takes + // the harder chord. + { QStringLiteral("Ctrl+Shift+T"), QStringLiteral("tag_rules") }, { QStringLiteral("Ctrl+L"), QStringLiteral("focus_query") }, // Ctrl+Space is the completion idiom users already carry over from // shells and editors, and it is a named key rather than a symbol, so diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f731511..331354f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -56,6 +56,7 @@ #include "cardlayout.h" #include "tagchip.h" #include "tagdialog.h" +#include "tagrulesdialog.h" #include "threadlistmodel.h" #include "threadlistview.h" #include "version.h" @@ -831,6 +832,10 @@ void MainWindow::registerActions() tr("Add or remove any tag on the selected threads"), [this]() { editTagsOnSelection(); }); + addAction(QStringLiteral("tag_rules"), tr("Tagging &rules..."), + tr("Edit the rules that tag mail as it arrives"), [this]() { + showTagRulesDialog(); + }); addAction(QStringLiteral("toggle_html"), tr("Toggle &HTML"), tr("Switch the thread between HTML and plain text"), [this]() { m_messageView->toggleHtml(); @@ -989,6 +994,10 @@ void MainWindow::buildMenus() messageMenu->addAction(m_actions.value(QStringLiteral("mark_all_read"))); messageMenu->addAction(m_actions.value(QStringLiteral("edit_tags"))); messageMenu->addAction(m_actions.value(QStringLiteral("flag"))); + // Separated from the entries above: those act on the selection, this edits + // a rule store shared with mailctl and changes nothing that is on screen. + messageMenu->addSeparator(); + messageMenu->addAction(m_actions.value(QStringLiteral("tag_rules"))); auto *viewMenu = menuBar()->addMenu(tr("&View")); viewMenu->addAction(m_actions.value(QStringLiteral("prev_thread"))); @@ -1046,6 +1055,10 @@ void MainWindow::buildMenus() { QStringLiteral("toggle_unread"), QStringLiteral("mail-mark-unread") }, { QStringLiteral("mark_all_read"), QStringLiteral("mail-mark-read") }, { QStringLiteral("edit_tags"), QStringLiteral("tag") }, + // NOT "tag", which edit_tags uses: with the toolbar icon-only the icon + // is the whole control, and editing the standing rules is not editing + // the selection's tags. + { QStringLiteral("tag_rules"), QStringLiteral("configure") }, { QStringLiteral("complete_query"), QStringLiteral("edit-find-replace") }, { QStringLiteral("select_all"), QStringLiteral("edit-select-all") }, { QStringLiteral("clear_pane"), QStringLiteral("edit-clear") }, @@ -1287,6 +1300,44 @@ void MainWindow::onDatabaseStatsReady(const DatabaseStats &stats, number(stats.tags))); } +void MainWindow::showTagRulesDialog() +{ + // One dialog. A second would edit a stale copy and the last Save would + // silently win, which is the lost-edit case the atomic write cannot help + // with because both writers are this process. + if (m_tagRulesDialog) { + m_tagRulesDialog->raise(); + m_tagRulesDialog->activateWindow(); + return; + } + + auto *dialog = new TagRulesDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + m_tagRulesDialog = dialog; + + connect(dialog, &TagRulesDialog::countsRequested, this, [this, dialog]() { + QMetaObject::invokeMethod( + m_worker, "requestMessageCounts", Qt::QueuedConnection, + Q_ARG(QStringList, dialog->countQueries()), + Q_ARG(quint64, ++m_ruleCountGeneration)); + }); + + dialog->show(); +} + +void MainWindow::onRuleCountsReady(const QVector<int> &counts, + quint64 generation) +{ + // Stale reply, or the dialog closed while the count was in flight. Both + // are ordinary rather than rare: counting every rule against a cold index + // takes seconds, which is long enough for the user to close the dialog or + // press the button again. + if (generation != m_ruleCountGeneration || !m_tagRulesDialog) + return; + + m_tagRulesDialog->setCounts(counts); +} + void MainWindow::showAbout() { QDialog dialog(this); @@ -1360,6 +1411,8 @@ void MainWindow::wireWorker() this, &MainWindow::onCountsReady); connect(m_worker, &NotmuchWorker::databaseStatsReady, this, &MainWindow::onDatabaseStatsReady); + connect(m_worker, &NotmuchWorker::messageCountsReady, + this, &MainWindow::onRuleCountsReady); // A confirmed write clears the pending revert: without this, a later // unrelated error would roll back a change that actually succeeded. diff --git a/src/mainwindow.h b/src/mainwindow.h index 19635dd..adf63b0 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -55,6 +55,7 @@ class MessageView; class MailSync; class NotmuchWorker; class QueryCompleter; +class TagRulesDialog; class MainWindow : public QMainWindow { @@ -292,6 +293,14 @@ private slots: /// Runs a query the user clicked on the placeholder pane. void onPlaceholderQueryRequested(const QString &query); + /// Opens the auto-tagging rules editor, or raises the one already open. + void showTagRulesDialog(); + + /// Message counts for the rules dialog's queries, in the order it asked + /// for them. Does nothing if the dialog has since closed, or if a newer + /// request has superseded this one. + void onRuleCountsReady(const QVector<int> &counts, quint64 generation); + private: void buildUi(); @@ -632,6 +641,28 @@ private: /// and reopened, so an old answer cannot fill in a newer dialog. quint64 m_statsGeneration = 0; + /// The open rules dialog, or null. Held so a counts reply can reach it, + /// and cleared when it closes: a reply that arrives after the dialog is + /// gone must find nothing rather than a dangling pointer. A QPointer for + /// the same reason m_overviewCounts is one, and the window is open wider + /// here, since counting every rule takes seconds. + QPointer<TagRulesDialog> m_tagRulesDialog; + + /// Generation of the rules dialog's counts request. + /// + /// Its OWN counter, deliberately not m_generation. That one is the QUERY + /// generation, and onThreadsReady, onQueryFinished, onThreadLoaded, + /// onThreadTreeLoaded and onMessageLoaded all compare against it directly: + /// bumping it here would discard whatever thread load was in flight when + /// the user pressed Count matches, blanking the message pane for a reason + /// that has nothing to do with the query. Not m_countsGeneration either, + /// though that one is closer: it belongs to the placeholder pane's THREAD + /// counts, and sharing it would let each cancel the other's reply. The two + /// arrive on different signals (countsReady against messageCountsReady), + /// so they can never be confused for one another and need not share a + /// counter. + quint64 m_ruleCountGeneration = 0; + /// The generation of a REFRESH query, run after a sync to bring the list /// up to date without disturbing it. /// |
