/* * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs * Copyright (C) 2026 Danilo M. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "savequerydialog.h" #include #include #include #include #include #include #include #include bool SaveQueryDialog::namesAnExistingQuery(const Config &config, const QString &name) { for (const SavedQuery &saved : config.savedQueries()) { if (saved.name.compare(name.trimmed(), Qt::CaseInsensitive) == 0) return true; } return false; } SaveQueryDialog::SaveQueryDialog(const Config &config, const QString &query, const QString &accountKey, QWidget *parent) : QDialog(parent) , m_config(config) { SavedQuery initial; initial.query = query; initial.account = accountKey; initial.pinned = true; setWindowTitle(tr("Save query")); build(initial); } SaveQueryDialog::SaveQueryDialog(const Config &config, const SavedQuery &existing, QWidget *parent) : QDialog(parent) , m_config(config) , m_originalName(existing.name) , m_generated(existing.generated) , m_flat(existing.flat) { setWindowTitle(tr("Edit saved query")); build(existing); } void SaveQueryDialog::build(const SavedQuery &initial) { auto *layout = new QVBoxLayout(this); auto *form = new QFormLayout; m_name = new QLineEdit(initial.name, this); m_name->setObjectName(QStringLiteral("saveQueryName")); m_name->setPlaceholderText(tr("A name for this query")); form->addRow(tr("Name"), m_name); m_query = new QLineEdit(initial.query, this); m_query->setObjectName(QStringLiteral("saveQueryQuery")); if (initial.isGenerated()) { // A generated entry has no stored query: it is composed from the // accounts every time it runs. Shown, so the user can see what it will // do, but read-only, since editing it would change nothing. m_query->setText(m_config.resolvedQuery(initial)); m_query->setReadOnly(true); m_query->setToolTip(tr("Built from your accounts and not editable. " "It follows the sent folder each account " "configures.")); form->addRow(tr("Query"), m_query); } else { form->addRow(tr("Query"), m_query); } // The scope is stored as an account KEY, so the entries carry the key as // data exactly as the main window's dropdown does. "All accounts" is the // empty key, not a missing entry, so an unscoped query is a real choice // rather than the absence of one. m_account = new QComboBox(this); m_account->setObjectName(QStringLiteral("saveQueryAccount")); m_account->addItem(tr("All accounts"), QString()); for (const Account &account : m_config.accounts()) m_account->addItem(account.key, account.key); const int index = m_account->findData(initial.account); m_account->setCurrentIndex(index >= 0 ? index : 0); form->addRow(tr("Account"), m_account); m_pinned = new QCheckBox(tr("Show as a button"), this); m_pinned->setObjectName(QStringLiteral("saveQueryPinned")); m_pinned->setChecked(initial.pinned); form->addRow(QString(), m_pinned); layout->addLayout(form); // Says what is about to happen rather than refusing the name. Overwriting // a saved query on purpose is a normal edit, and the only thing worth // preventing is doing it without noticing. m_notice = new QLabel(this); m_notice->setObjectName(QStringLiteral("saveQueryNotice")); m_notice->setWordWrap(true); layout->addWidget(m_notice); auto *buttons = new QDialogButtonBox( QDialogButtonBox::Save | QDialogButtonBox::Cancel, this); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); layout->addWidget(buttons); m_ok = buttons->button(QDialogButtonBox::Save); m_ok->setObjectName(QStringLiteral("saveQueryOk")); connect(m_name, &QLineEdit::textChanged, this, &SaveQueryDialog::updateOkState); connect(m_query, &QLineEdit::textChanged, this, &SaveQueryDialog::updateOkState); updateOkState(); m_name->setFocus(); } void SaveQueryDialog::updateOkState() { const QString name = m_name->text().trimmed(); const bool usable = !name.isEmpty() && !m_query->text().trimmed().isEmpty(); m_ok->setEnabled(usable); // Ignores the entry being edited: warning that "Inbox" already exists // while editing Inbox is noise, and the real case worth catching is a // rename onto a name something else already holds. const bool isItsOwnName = !m_originalName.isEmpty() && name.compare(m_originalName, Qt::CaseInsensitive) == 0; if (!name.isEmpty() && !isItsOwnName && namesAnExistingQuery(m_config, name)) { m_notice->setText( tr("A saved query named '%1' already exists and will be " "replaced.").arg(name)); } else { m_notice->clear(); } } SavedQuery SaveQueryDialog::savedQuery() const { SavedQuery saved; saved.name = m_name->text().trimmed(); saved.query = m_query->text().trimmed(); saved.account = m_account->currentData().toString(); saved.pinned = m_pinned->isChecked(); // Carried through rather than re-derived: an edit must not turn a // generated entry into a plain one holding a snapshot of what it happened // to resolve to today. saved.generated = m_generated; saved.flat = m_flat; if (saved.isGenerated()) saved.query.clear(); return saved; }