File indexing completed on 2024-04-28 05:08:22

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 "gui/filterrulewidgetlister.h"
0031 #include "gui/filterrulewidget.h"
0032 
0033 #include <KLocalizedString>
0034 #include <KHelpClient>
0035 
0036 #include <QGroupBox>
0037 #include <QRadioButton>
0038 #include <QButtonGroup>
0039 #include <QLabel>
0040 #include <QApplication>
0041 #include <QFrame>
0042 #include <QVBoxLayout>
0043 #include <QPushButton>
0044 #include <QLineEdit>
0045 #include <QDialogButtonBox>
0046 
0047 using Tellico::FilterDialog;
0048 
0049 namespace {
0050   static const int FILTER_MIN_WIDTH = 600;
0051 }
0052 
0053 // modal dialog so I don't have to worry about updating stuff
0054 // don't show apply button if not saving, i.e. just modifying existing filter
0055 FilterDialog::FilterDialog(Mode mode_, QWidget* parent_)
0056     : QDialog(parent_), m_filter(nullptr), m_mode(mode_), m_saveFilter(nullptr) {
0057   setModal(true);
0058   setWindowTitle(mode_ == CreateFilter ? i18n("Advanced Filter") : i18n("Modify Filter"));
0059 
0060   QVBoxLayout* topLayout = new QVBoxLayout();
0061   setLayout(topLayout);
0062 
0063   QDialogButtonBox* buttonBox;
0064   if(mode_ == CreateFilter) {
0065     buttonBox = new QDialogButtonBox(QDialogButtonBox::Help|QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Apply);
0066   } else {
0067     buttonBox = new QDialogButtonBox(QDialogButtonBox::Help|QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0068   }
0069   m_okButton = buttonBox->button(QDialogButtonBox::Ok);
0070   m_applyButton = buttonBox->button(QDialogButtonBox::Apply);
0071   connect(m_okButton, &QAbstractButton::clicked, this, &FilterDialog::slotOk);
0072   if(m_applyButton) {
0073     connect(m_applyButton, &QAbstractButton::clicked, this, &FilterDialog::slotApply);
0074   }
0075   connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0076   connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0077   connect(buttonBox, &QDialogButtonBox::helpRequested, this, &FilterDialog::slotHelp);
0078 
0079   QGroupBox* m_matchGroup = new QGroupBox(i18n("Filter Criteria"), this);
0080   QVBoxLayout* vlay = new QVBoxLayout(m_matchGroup);
0081   topLayout->addWidget(m_matchGroup);
0082   m_matchGroup->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
0083 
0084   m_matchAll = new QRadioButton(i18n("Match a&ll of the following"), m_matchGroup);
0085   m_matchAny = new QRadioButton(i18n("Match an&y of the following"), m_matchGroup);
0086   m_matchAll->setChecked(true);
0087 
0088   vlay->addWidget(m_matchAll);
0089   vlay->addWidget(m_matchAny);
0090 
0091   QButtonGroup* bg = new QButtonGroup(m_matchGroup);
0092   bg->addButton(m_matchAll);
0093   bg->addButton(m_matchAny);
0094 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0095   void (QButtonGroup::* buttonClicked)(int) = &QButtonGroup::buttonClicked;
0096   connect(bg, buttonClicked, this, &FilterDialog::slotFilterChanged);
0097 #else
0098   connect(bg, &QButtonGroup::idClicked, this, &FilterDialog::slotFilterChanged);
0099 #endif
0100 
0101   m_ruleLister = new FilterRuleWidgetLister(m_matchGroup);
0102   connect(m_ruleLister, &KWidgetLister::widgetRemoved, this, &FilterDialog::slotShrink);
0103   connect(m_ruleLister, &FilterRuleWidgetLister::signalModified, this, &FilterDialog::slotFilterChanged);
0104   m_ruleLister->setFocus();
0105   vlay->addWidget(m_ruleLister);
0106 
0107   QHBoxLayout* blay = new QHBoxLayout();
0108   topLayout->addLayout(blay);
0109   QLabel* lab = new QLabel(i18n("Filter name:"), this);
0110   blay->addWidget(lab);
0111 
0112   m_filterName = new QLineEdit(this);
0113   blay->addWidget(m_filterName);
0114   connect(m_filterName, &QLineEdit::textChanged, this, &FilterDialog::slotFilterChanged);
0115 
0116   // only when creating a new filter can it be saved
0117   if(m_mode == CreateFilter) {
0118     m_saveFilter = new QPushButton(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("&Save Filter"), this);
0119     blay->addWidget(m_saveFilter);
0120     m_saveFilter->setEnabled(false);
0121     connect(m_saveFilter, &QAbstractButton::clicked, this, &FilterDialog::slotSaveFilter);
0122     m_applyButton->setEnabled(false);
0123   }
0124   m_okButton->setEnabled(false); // disable at start
0125   buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true);
0126   setMinimumWidth(qMax(minimumWidth(), FILTER_MIN_WIDTH));
0127   topLayout->addWidget(buttonBox);
0128 }
0129 
0130 Tellico::FilterPtr FilterDialog::currentFilter(bool alwaysCreateNew_) {
0131   FilterPtr newFilter(new Filter(Filter::MatchAny));
0132 
0133   if(m_matchAll->isChecked()) {
0134     newFilter->setMatch(Filter::MatchAll);
0135   } else {
0136     newFilter->setMatch(Filter::MatchAny);
0137   }
0138 
0139   foreach(QWidget* widget, m_ruleLister->widgetList()) {
0140     FilterRuleWidget* rw = static_cast<FilterRuleWidget*>(widget);
0141     FilterRule* rule = rw->rule();
0142     if(rule && !rule->isEmpty()) {
0143       newFilter->append(rule);
0144     } else {
0145       delete rule;
0146     }
0147   }
0148   newFilter->setName(m_filterName->text());
0149   if(!m_filter || !alwaysCreateNew_) {
0150     m_filter = newFilter;
0151   }
0152   return newFilter;
0153 }
0154 
0155 void FilterDialog::setFilter(Tellico::FilterPtr filter_) {
0156   if(!filter_) {
0157     slotClear();
0158     return;
0159   }
0160 
0161   if(filter_->op() == Filter::MatchAll) {
0162     m_matchAll->setChecked(true);
0163   } else {
0164     m_matchAny->setChecked(true);
0165   }
0166 
0167   m_ruleLister->setFilter(filter_);
0168   m_filterName->setText(filter_->name());
0169   m_filter = filter_;
0170 }
0171 
0172 void FilterDialog::slotOk() {
0173   slotApply();
0174   accept();
0175 }
0176 
0177 void FilterDialog::slotApply() {
0178   emit signalUpdateFilter(currentFilter());
0179 }
0180 
0181 void FilterDialog::slotHelp() {
0182   KHelpClient::invokeHelp(QStringLiteral("filter-dialog"));
0183 }
0184 
0185 void FilterDialog::slotClear() {
0186 //  myDebug();
0187   m_matchAll->setChecked(true);
0188   m_ruleLister->reset();
0189   m_filterName->clear();
0190 }
0191 
0192 void FilterDialog::slotShrink() {
0193   updateGeometry();
0194   QApplication::sendPostedEvents();
0195   resize(width(), sizeHint().height());
0196 }
0197 
0198 void FilterDialog::slotFilterChanged() {
0199   const bool hadFilter = m_filter && !m_filter->isEmpty();
0200   const bool emptyFilter = currentFilter(true)->isEmpty();
0201   // an empty filter can be ok if the filter was originally not empty
0202   const bool enableOk = !currentFilter()->isEmpty() || hadFilter;
0203   if(m_saveFilter) {
0204     m_saveFilter->setEnabled(!m_filterName->text().isEmpty() && !emptyFilter);
0205     if(m_applyButton) {
0206       m_applyButton->setEnabled(!emptyFilter);
0207     }
0208   }
0209   if(m_applyButton) {
0210     m_applyButton->setEnabled(enableOk);
0211   }
0212   m_okButton->setEnabled(enableOk);
0213   m_okButton->setDefault(enableOk);
0214 }
0215 
0216 void FilterDialog::slotSaveFilter() {
0217   // non-op if editing an existing filter
0218   if(m_mode != CreateFilter) {
0219     return;
0220   }
0221 
0222   // in this case, currentFilter() either creates a new filter or
0223   // updates the current one. If creating a new one, then I want to copy it
0224   const bool wasEmpty = !m_filter;
0225   FilterPtr filter(new Filter(*currentFilter()));
0226   if(wasEmpty) {
0227     m_filter = filter;
0228   }
0229   // this keeps the saving completely decoupled from the filter setting in the detailed view
0230   if(filter->isEmpty()) {
0231     m_filter = FilterPtr();
0232     return;
0233   }
0234   Kernel::self()->addFilter(filter);
0235 }