File indexing completed on 2024-05-12 16:35:15

0001 /* This file is part of the KDE project
0002    Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "FilterPopup.h"
0021 
0022 #include <QButtonGroup>
0023 #include <QCheckBox>
0024 #include <QHash>
0025 #include <QList>
0026 #include <QScrollArea>
0027 #include <QVBoxLayout>
0028 
0029 #include <KLocalizedString>
0030 
0031 #include "CellStorage.h"
0032 #include "Database.h"
0033 #include "Filter.h"
0034 #include "Map.h"
0035 #include "RowColumnFormat.h"
0036 #include "Sheet.h"
0037 #include "ValueConverter.h"
0038 
0039 #include "commands/ApplyFilterCommand.h"
0040 
0041 #include <algorithm>
0042 
0043 using namespace Calligra::Sheets;
0044 
0045 class FilterPopup::Private
0046 {
0047 public:
0048     QAbstractButton* allCheckbox;
0049     QAbstractButton* emptyCheckbox;
0050     QAbstractButton* notEmptyCheckbox;
0051     QList<QCheckBox*> checkboxes;
0052     int fieldNumber;
0053     Database database;
0054     bool dirty;
0055 
0056 public:
0057     void initGUI(FilterPopup* parent, const Cell& cell, const Database* database);
0058 };
0059 
0060 void FilterPopup::Private::initGUI(FilterPopup* parent, const Cell& cell, const Database* database)
0061 {
0062     QButtonGroup* buttonGroup = new QButtonGroup(parent);
0063     buttonGroup->setExclusive(false);
0064     connect(buttonGroup, SIGNAL(buttonClicked(QAbstractButton*)),
0065             parent, SLOT(buttonClicked(QAbstractButton*)));
0066 
0067     QVBoxLayout* layout = new QVBoxLayout(parent);
0068     layout->setMargin(3);
0069     layout->setSpacing(0);
0070 
0071     allCheckbox = new QCheckBox(i18n("All"), parent);
0072     buttonGroup->addButton(allCheckbox);
0073     layout->addWidget(allCheckbox);
0074     emptyCheckbox = new QCheckBox(i18n("Empty"), parent);
0075     emptyCheckbox->setChecked(true);
0076     buttonGroup->addButton(emptyCheckbox);
0077     layout->addWidget(emptyCheckbox);
0078     notEmptyCheckbox = new QCheckBox(i18n("Non-empty"), parent);
0079     notEmptyCheckbox->setChecked(true);
0080     buttonGroup->addButton(notEmptyCheckbox);
0081     layout->addWidget(notEmptyCheckbox);
0082     layout->addSpacing(3);
0083 
0084     const Sheet* sheet = cell.sheet();
0085     const QRect range = database->range().lastRange();
0086     const bool isRowFilter = database->orientation() == Qt::Vertical;
0087     const int start = isRowFilter ? range.top() : range.left();
0088     const int end = isRowFilter ? range.bottom() : range.right();
0089     const int j = isRowFilter ? cell.column() : cell.row();
0090     QSet<QString> items;
0091     for (int i = start + (database->containsHeader() ? 1 : 0); i <= end; ++i) {
0092         const Value value = isRowFilter ? sheet->cellStorage()->value(j, i)
0093                             : sheet->cellStorage()->value(i, j);
0094         const QString string = sheet->map()->converter()->asString(value).asString();
0095         if (!string.isEmpty())
0096             items.insert(string);
0097     }
0098 
0099     QWidget* scrollWidget = new QWidget(parent);
0100     QVBoxLayout* scrollLayout = new QVBoxLayout(scrollWidget);
0101     scrollLayout->setMargin(0);
0102     scrollLayout->setSpacing(0);
0103 
0104     const int fieldNumber = j - (isRowFilter ? range.left() : range.top());
0105     const QHash<QString, Filter::Comparison> conditions = database->filter().conditions(fieldNumber);
0106     const bool defaultCheckState = conditions.isEmpty() ? true
0107                                    : !(conditions[conditions.keys()[0]] == Filter::Match ||
0108                                        conditions[conditions.keys()[0]] == Filter::Empty);
0109     QList<QString> sortedItems = items.toList();
0110     std::sort(sortedItems.begin(), sortedItems.end());
0111     bool isAll = true;
0112     QCheckBox* item;
0113     for (int i = 0; i < sortedItems.count(); ++i) {
0114         const QString value = sortedItems[i];
0115         item = new QCheckBox(value, scrollWidget);
0116         item->setChecked(conditions.contains(value) ? !defaultCheckState : defaultCheckState);
0117         buttonGroup->addButton(item);
0118         scrollLayout->addWidget(item);
0119         checkboxes.append(item);
0120         isAll = isAll && item->isChecked();
0121     }
0122     emptyCheckbox->setChecked(conditions.contains("") ? !defaultCheckState : defaultCheckState);
0123     allCheckbox->setChecked(isAll && emptyCheckbox->isChecked());
0124     notEmptyCheckbox->setChecked(isAll);
0125 
0126     QScrollArea* scrollArea = new QScrollArea(parent);
0127     layout->addWidget(scrollArea);
0128     scrollArea->setWidget(scrollWidget);
0129 }
0130 
0131 
0132 FilterPopup::FilterPopup(QWidget* parent, const Cell& cell, Database* database)
0133         : QFrame(parent, Qt::Popup)
0134         , d(new Private)
0135 {
0136     setAttribute(Qt::WA_DeleteOnClose);
0137     setBackgroundRole(QPalette::Base);
0138     setFrameStyle(QFrame::Panel | QFrame::Raised);
0139 
0140     d->database = *database;
0141     d->dirty = false;
0142 
0143     d->initGUI(this, cell, database);
0144 
0145     if (database->orientation() == Qt::Vertical)
0146         d->fieldNumber = cell.column() - database->range().lastRange().left();
0147     else // Qt::Horizontal
0148         d->fieldNumber = cell.row() - database->range().lastRange().top();
0149     debugSheets << "FilterPopup for fieldNumber" << d->fieldNumber;
0150 }
0151 
0152 FilterPopup::~FilterPopup()
0153 {
0154     delete d;
0155 }
0156 
0157 void FilterPopup::updateFilter(Filter* filter) const
0158 {
0159     if (d->allCheckbox->isChecked())
0160         filter->removeConditions(d->fieldNumber); // remove all conditions for this field
0161     else if (d->notEmptyCheckbox->isChecked()) {
0162         // emptyCheckbox is not checked, because allCheckbox is not.
0163         filter->removeConditions(d->fieldNumber);
0164         filter->addCondition(Filter::AndComposition, d->fieldNumber, Filter::NotMatch, "");
0165     } else {
0166         filter->removeConditions(d->fieldNumber);
0167         QList<QString> matchList;
0168         QList<QString> notMatchList;
0169         if (d->emptyCheckbox->isChecked())
0170             matchList.append("");
0171         else
0172             notMatchList.append("");
0173         foreach(QCheckBox* checkbox, d->checkboxes) {
0174             if (checkbox->isChecked())
0175                 matchList.append(checkbox->text());
0176             else
0177                 notMatchList.append(checkbox->text());
0178         }
0179         // be lazy; choose the comparison causing least effort
0180         const Filter::Comparison comparison = (matchList.count() < notMatchList.count())
0181                                               ? Filter::Match : Filter::NotMatch;
0182         const Filter::Composition composition = (comparison == Filter::Match)
0183                                                 ? Filter::OrComposition : Filter::AndComposition;
0184         const QList<QString> values = (comparison == Filter::Match) ? matchList : notMatchList;
0185         debugSheets << "adding conditions for fieldNumber" << d->fieldNumber;
0186         Filter subFilter;
0187         for (int i = 0; i < values.count(); ++i)
0188             subFilter.addCondition(composition, d->fieldNumber, comparison, values[i]);
0189         filter->addSubFilter(Filter::AndComposition, subFilter);
0190     }
0191 }
0192 
0193 void FilterPopup::closeEvent(QCloseEvent* event)
0194 {
0195     if (d->dirty) {
0196         Filter filter = d->database.filter();
0197         updateFilter(&filter);
0198         // any real change?
0199         if (d->database.filter() != filter) {
0200             ApplyFilterCommand* command = new ApplyFilterCommand();
0201             command->setSheet(d->database.range().lastSheet());
0202             command->add(d->database.range());
0203             command->setOldFilter(d->database.filter());
0204             d->database.setFilter(filter);
0205             d->database.dump();
0206             command->setDatabase(d->database);
0207             command->execute();
0208         }
0209     }
0210     QFrame::closeEvent(event);
0211 }
0212 
0213 void FilterPopup::buttonClicked(QAbstractButton* button)
0214 {
0215     d->dirty = true;
0216     if (button == d->allCheckbox) {
0217         foreach(QCheckBox* checkbox, d->checkboxes)
0218         checkbox->setChecked(button->isChecked());
0219         d->emptyCheckbox->setChecked(button->isChecked());
0220         d->notEmptyCheckbox->setChecked(button->isChecked());
0221     } else if (button == d->emptyCheckbox) {
0222         bool isAll = button->isChecked();
0223         if (isAll) {
0224             foreach(QCheckBox* checkbox, d->checkboxes) {
0225                 if (!checkbox->isChecked()) {
0226                     isAll = false;
0227                     break;
0228                 }
0229             }
0230         }
0231         d->allCheckbox->setChecked(isAll);
0232     } else if (button == d->notEmptyCheckbox) {
0233         foreach(QCheckBox* checkbox, d->checkboxes)
0234         checkbox->setChecked(button->isChecked());
0235         d->allCheckbox->setChecked(button->isChecked() && d->emptyCheckbox->isChecked());
0236     } else {
0237         bool isAll = d->emptyCheckbox->isChecked();
0238         if (isAll) {
0239             foreach(QCheckBox* checkbox, d->checkboxes) {
0240                 if (!checkbox->isChecked()) {
0241                     isAll = false;
0242                     break;
0243                 }
0244             }
0245         }
0246         d->allCheckbox->setChecked(isAll);
0247         d->notEmptyCheckbox->setChecked(isAll);
0248     }
0249 }
0250 
0251 void FilterPopup::showPopup(QWidget *parent, const Cell &cell, const QRect &cellRect, Database *database)
0252 {
0253     FilterPopup* popup = new FilterPopup(parent, cell, database);
0254     const QPoint position(database->orientation() == Qt::Vertical ? cellRect.bottomLeft() : cellRect.topRight());
0255     popup->move(parent->mapToGlobal(position));
0256     popup->show();
0257 }