File indexing completed on 2024-04-28 16:32:00

0001 /***************************************************************************
0002     Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 // The layout borrows heavily from kmsearchpatternedit.cpp in kmail
0026 // which is authored by Marc Mutz <Marc@Mutz.com> under the GPL
0027 
0028 #include "filterdialog.h"
0029 #include "tellico_kernel.h"
0030 #include "document.h"
0031 #include "collection.h"
0032 #include "fieldcompletion.h"
0033 #include "gui/filterrulewidgetlister.h"
0034 #include "gui/filterrulewidget.h"
0035 #include "tellico_debug.h"
0036 
0037 #include <KLocalizedString>
0038 #include <KHelpClient>
0039 
0040 #include <QGroupBox>
0041 #include <QRadioButton>
0042 #include <QButtonGroup>
0043 #include <QLabel>
0044 #include <QApplication>
0045 #include <QFrame>
0046 #include <QVBoxLayout>
0047 #include <QPushButton>
0048 #include <QLineEdit>
0049 #include <QDialogButtonBox>
0050 
0051 using Tellico::FilterDialog;
0052 
0053 namespace {
0054   static const int FILTER_MIN_WIDTH = 600;
0055 }
0056 
0057 // modal dialog so I don't have to worry about updating stuff
0058 // don't show apply button if not saving, i.e. just modifying existing filter
0059 FilterDialog::FilterDialog(Mode mode_, QWidget* parent_)
0060     : QDialog(parent_), m_filter(nullptr), m_mode(mode_), m_saveFilter(nullptr) {
0061   setModal(true);
0062   setWindowTitle(mode_ == CreateFilter ? i18n("Advanced Filter") : i18n("Modify Filter"));
0063 
0064   QVBoxLayout* topLayout = new QVBoxLayout();
0065   setLayout(topLayout);
0066 
0067   QDialogButtonBox* buttonBox;
0068   if(mode_ == CreateFilter) {
0069     buttonBox = new QDialogButtonBox(QDialogButtonBox::Help|QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Apply);
0070   } else {
0071     buttonBox = new QDialogButtonBox(QDialogButtonBox::Help|QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0072   }
0073   m_okButton = buttonBox->button(QDialogButtonBox::Ok);
0074   m_applyButton = buttonBox->button(QDialogButtonBox::Apply);
0075   connect(m_okButton, &QAbstractButton::clicked, this, &FilterDialog::slotOk);
0076   if(m_applyButton) {
0077     connect(m_applyButton, &QAbstractButton::clicked, this, &FilterDialog::slotApply);
0078   }
0079   connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0080   connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0081   connect(buttonBox, &QDialogButtonBox::helpRequested, this, &FilterDialog::slotHelp);
0082 
0083   QGroupBox* m_matchGroup = new QGroupBox(i18n("Filter Criteria"), this);
0084   QVBoxLayout* vlay = new QVBoxLayout(m_matchGroup);
0085   topLayout->addWidget(m_matchGroup);
0086   m_matchGroup->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
0087 
0088   m_matchAll = new QRadioButton(i18n("Match a&ll of the following"), m_matchGroup);
0089   m_matchAny = new QRadioButton(i18n("Match an&y of the following"), m_matchGroup);
0090   m_matchAll->setChecked(true);
0091 
0092   vlay->addWidget(m_matchAll);
0093   vlay->addWidget(m_matchAny);
0094 
0095   QButtonGroup* bg = new QButtonGroup(m_matchGroup);
0096   bg->addButton(m_matchAll);
0097   bg->addButton(m_matchAny);
0098 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0099   void (QButtonGroup::* buttonClicked)(int) = &QButtonGroup::buttonClicked;
0100   connect(bg, buttonClicked, this, &FilterDialog::slotFilterChanged);
0101 #else
0102   connect(bg, &QButtonGroup::idClicked, this, &FilterDialog::slotFilterChanged);
0103 #endif
0104 
0105   m_ruleLister = new FilterRuleWidgetLister(m_matchGroup);
0106   connect(m_ruleLister, &KWidgetLister::widgetRemoved, this, &FilterDialog::slotShrink);
0107   connect(m_ruleLister, &FilterRuleWidgetLister::signalModified, this, &FilterDialog::slotFilterChanged);
0108   m_ruleLister->setFocus();
0109   vlay->addWidget(m_ruleLister);
0110 
0111   QHBoxLayout* blay = new QHBoxLayout();
0112   topLayout->addLayout(blay);
0113   QLabel* lab = new QLabel(i18n("Filter name:"), this);
0114   blay->addWidget(lab);
0115 
0116   m_filterName = new QLineEdit(this);
0117   blay->addWidget(m_filterName);
0118   connect(m_filterName, &QLineEdit::textChanged, this, &FilterDialog::slotFilterChanged);
0119 
0120   // only when creating a new filter can it be saved
0121   if(m_mode == CreateFilter) {
0122     m_saveFilter = new QPushButton(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("&Save Filter"), this);
0123     blay->addWidget(m_saveFilter);
0124     m_saveFilter->setEnabled(false);
0125     connect(m_saveFilter, &QAbstractButton::clicked, this, &FilterDialog::slotSaveFilter);
0126     m_applyButton->setEnabled(false);
0127   }
0128   m_okButton->setEnabled(false); // disable at start
0129   buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true);
0130   setMinimumWidth(qMax(minimumWidth(), FILTER_MIN_WIDTH));
0131   topLayout->addWidget(buttonBox);
0132 }
0133 
0134 Tellico::FilterPtr FilterDialog::currentFilter(bool alwaysCreateNew_) {
0135   FilterPtr newFilter(new Filter(Filter::MatchAny));
0136 
0137   if(m_matchAll->isChecked()) {
0138     newFilter->setMatch(Filter::MatchAll);
0139   } else {
0140     newFilter->setMatch(Filter::MatchAny);
0141   }
0142 
0143   foreach(QWidget* widget, m_ruleLister->widgetList()) {
0144     FilterRuleWidget* rw = static_cast<FilterRuleWidget*>(widget);
0145     FilterRule* rule = rw->rule();
0146     if(rule && !rule->isEmpty()) {
0147       newFilter->append(rule);
0148     } else {
0149       delete rule;
0150     }
0151   }
0152   newFilter->setName(m_filterName->text());
0153   if(!m_filter || !alwaysCreateNew_) {
0154     m_filter = newFilter;
0155   }
0156   return newFilter;
0157 }
0158 
0159 void FilterDialog::setFilter(Tellico::FilterPtr filter_) {
0160   if(!filter_) {
0161     slotClear();
0162     return;
0163   }
0164 
0165   if(filter_->op() == Filter::MatchAll) {
0166     m_matchAll->setChecked(true);
0167   } else {
0168     m_matchAny->setChecked(true);
0169   }
0170 
0171   m_ruleLister->setFilter(filter_);
0172   m_filterName->setText(filter_->name());
0173   m_filter = filter_;
0174 }
0175 
0176 void FilterDialog::slotOk() {
0177   slotApply();
0178   accept();
0179 }
0180 
0181 void FilterDialog::slotApply() {
0182   emit signalUpdateFilter(currentFilter());
0183 }
0184 
0185 void FilterDialog::slotHelp() {
0186   KHelpClient::invokeHelp(QStringLiteral("filter-dialog"));
0187 }
0188 
0189 void FilterDialog::slotClear() {
0190 //  myDebug();
0191   m_matchAll->setChecked(true);
0192   m_ruleLister->reset();
0193   m_filterName->clear();
0194 }
0195 
0196 void FilterDialog::slotShrink() {
0197   updateGeometry();
0198   QApplication::sendPostedEvents();
0199   resize(width(), sizeHint().height());
0200 }
0201 
0202 void FilterDialog::slotFilterChanged() {
0203   const bool hadFilter = m_filter && !m_filter->isEmpty();
0204   const bool emptyFilter = currentFilter(true)->isEmpty();
0205   // an empty filter can be ok if the filter was originally not empty
0206   const bool enableOk = !currentFilter()->isEmpty() || hadFilter;
0207   if(m_saveFilter) {
0208     m_saveFilter->setEnabled(!m_filterName->text().isEmpty() && !emptyFilter);
0209     if(m_applyButton) {
0210       m_applyButton->setEnabled(!emptyFilter);
0211     }
0212   }
0213   if(m_applyButton) {
0214     m_applyButton->setEnabled(enableOk);
0215   }
0216   m_okButton->setEnabled(enableOk);
0217   m_okButton->setDefault(enableOk);
0218 }
0219 
0220 void FilterDialog::slotSaveFilter() {
0221   // non-op if editing an existing filter
0222   if(m_mode != CreateFilter) {
0223     return;
0224   }
0225 
0226   // in this case, currentFilter() either creates a new filter or
0227   // updates the current one. If creating a new one, then I want to copy it
0228   const bool wasEmpty = !m_filter;
0229   FilterPtr filter(new Filter(*currentFilter()));
0230   if(wasEmpty) {
0231     m_filter = filter;
0232   }
0233   // this keeps the saving completely decoupled from the filter setting in the detailed view
0234   if(filter->isEmpty()) {
0235     m_filter = FilterPtr();
0236     return;
0237   }
0238   Kernel::self()->addFilter(filter);
0239 }