1 /* This file is part of the KDE project
2    Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18 */
19 
20 #include "FilterPopup.h"
21 
22 #include <QButtonGroup>
23 #include <QCheckBox>
24 #include <QHash>
25 #include <QList>
26 #include <QScrollArea>
27 #include <QVBoxLayout>
28 
29 #include <KLocalizedString>
30 
31 #include "CellStorage.h"
32 #include "Database.h"
33 #include "Filter.h"
34 #include "Map.h"
35 #include "RowColumnFormat.h"
36 #include "Sheet.h"
37 #include "ValueConverter.h"
38 
39 #include "commands/ApplyFilterCommand.h"
40 
41 #include <algorithm>
42 
43 using namespace Calligra::Sheets;
44 
45 class FilterPopup::Private
46 {
47 public:
48     QAbstractButton* allCheckbox;
49     QAbstractButton* emptyCheckbox;
50     QAbstractButton* notEmptyCheckbox;
51     QList<QCheckBox*> checkboxes;
52     int fieldNumber;
53     Database database;
54     bool dirty;
55 
56 public:
57     void initGUI(FilterPopup* parent, const Cell& cell, const Database* database);
58 };
59 
initGUI(FilterPopup * parent,const Cell & cell,const Database * database)60 void FilterPopup::Private::initGUI(FilterPopup* parent, const Cell& cell, const Database* database)
61 {
62     QButtonGroup* buttonGroup = new QButtonGroup(parent);
63     buttonGroup->setExclusive(false);
64     connect(buttonGroup, SIGNAL(buttonClicked(QAbstractButton*)),
65             parent, SLOT(buttonClicked(QAbstractButton*)));
66 
67     QVBoxLayout* layout = new QVBoxLayout(parent);
68     layout->setMargin(3);
69     layout->setSpacing(0);
70 
71     allCheckbox = new QCheckBox(i18n("All"), parent);
72     buttonGroup->addButton(allCheckbox);
73     layout->addWidget(allCheckbox);
74     emptyCheckbox = new QCheckBox(i18n("Empty"), parent);
75     emptyCheckbox->setChecked(true);
76     buttonGroup->addButton(emptyCheckbox);
77     layout->addWidget(emptyCheckbox);
78     notEmptyCheckbox = new QCheckBox(i18n("Non-empty"), parent);
79     notEmptyCheckbox->setChecked(true);
80     buttonGroup->addButton(notEmptyCheckbox);
81     layout->addWidget(notEmptyCheckbox);
82     layout->addSpacing(3);
83 
84     const Sheet* sheet = cell.sheet();
85     const QRect range = database->range().lastRange();
86     const bool isRowFilter = database->orientation() == Qt::Vertical;
87     const int start = isRowFilter ? range.top() : range.left();
88     const int end = isRowFilter ? range.bottom() : range.right();
89     const int j = isRowFilter ? cell.column() : cell.row();
90     QSet<QString> items;
91     for (int i = start + (database->containsHeader() ? 1 : 0); i <= end; ++i) {
92         const Value value = isRowFilter ? sheet->cellStorage()->value(j, i)
93                             : sheet->cellStorage()->value(i, j);
94         const QString string = sheet->map()->converter()->asString(value).asString();
95         if (!string.isEmpty())
96             items.insert(string);
97     }
98 
99     QWidget* scrollWidget = new QWidget(parent);
100     QVBoxLayout* scrollLayout = new QVBoxLayout(scrollWidget);
101     scrollLayout->setMargin(0);
102     scrollLayout->setSpacing(0);
103 
104     const int fieldNumber = j - (isRowFilter ? range.left() : range.top());
105     const QHash<QString, Filter::Comparison> conditions = database->filter().conditions(fieldNumber);
106     const bool defaultCheckState = conditions.isEmpty() ? true
107                                    : !(conditions[conditions.keys()[0]] == Filter::Match ||
108                                        conditions[conditions.keys()[0]] == Filter::Empty);
109     QList<QString> sortedItems = items.toList();
110     std::sort(sortedItems.begin(), sortedItems.end());
111     bool isAll = true;
112     QCheckBox* item;
113     for (int i = 0; i < sortedItems.count(); ++i) {
114         const QString value = sortedItems[i];
115         item = new QCheckBox(value, scrollWidget);
116         item->setChecked(conditions.contains(value) ? !defaultCheckState : defaultCheckState);
117         buttonGroup->addButton(item);
118         scrollLayout->addWidget(item);
119         checkboxes.append(item);
120         isAll = isAll && item->isChecked();
121     }
122     emptyCheckbox->setChecked(conditions.contains("") ? !defaultCheckState : defaultCheckState);
123     allCheckbox->setChecked(isAll && emptyCheckbox->isChecked());
124     notEmptyCheckbox->setChecked(isAll);
125 
126     QScrollArea* scrollArea = new QScrollArea(parent);
127     layout->addWidget(scrollArea);
128     scrollArea->setWidget(scrollWidget);
129 }
130 
131 
FilterPopup(QWidget * parent,const Cell & cell,Database * database)132 FilterPopup::FilterPopup(QWidget* parent, const Cell& cell, Database* database)
133         : QFrame(parent, Qt::Popup)
134         , d(new Private)
135 {
136     setAttribute(Qt::WA_DeleteOnClose);
137     setBackgroundRole(QPalette::Base);
138     setFrameStyle(QFrame::Panel | QFrame::Raised);
139 
140     d->database = *database;
141     d->dirty = false;
142 
143     d->initGUI(this, cell, database);
144 
145     if (database->orientation() == Qt::Vertical)
146         d->fieldNumber = cell.column() - database->range().lastRange().left();
147     else // Qt::Horizontal
148         d->fieldNumber = cell.row() - database->range().lastRange().top();
149     debugSheets << "FilterPopup for fieldNumber" << d->fieldNumber;
150 }
151 
~FilterPopup()152 FilterPopup::~FilterPopup()
153 {
154     delete d;
155 }
156 
updateFilter(Filter * filter) const157 void FilterPopup::updateFilter(Filter* filter) const
158 {
159     if (d->allCheckbox->isChecked())
160         filter->removeConditions(d->fieldNumber); // remove all conditions for this field
161     else if (d->notEmptyCheckbox->isChecked()) {
162         // emptyCheckbox is not checked, because allCheckbox is not.
163         filter->removeConditions(d->fieldNumber);
164         filter->addCondition(Filter::AndComposition, d->fieldNumber, Filter::NotMatch, "");
165     } else {
166         filter->removeConditions(d->fieldNumber);
167         QList<QString> matchList;
168         QList<QString> notMatchList;
169         if (d->emptyCheckbox->isChecked())
170             matchList.append("");
171         else
172             notMatchList.append("");
173         foreach(QCheckBox* checkbox, d->checkboxes) {
174             if (checkbox->isChecked())
175                 matchList.append(checkbox->text());
176             else
177                 notMatchList.append(checkbox->text());
178         }
179         // be lazy; choose the comparison causing least effort
180         const Filter::Comparison comparison = (matchList.count() < notMatchList.count())
181                                               ? Filter::Match : Filter::NotMatch;
182         const Filter::Composition composition = (comparison == Filter::Match)
183                                                 ? Filter::OrComposition : Filter::AndComposition;
184         const QList<QString> values = (comparison == Filter::Match) ? matchList : notMatchList;
185         debugSheets << "adding conditions for fieldNumber" << d->fieldNumber;
186         Filter subFilter;
187         for (int i = 0; i < values.count(); ++i)
188             subFilter.addCondition(composition, d->fieldNumber, comparison, values[i]);
189         filter->addSubFilter(Filter::AndComposition, subFilter);
190     }
191 }
192 
closeEvent(QCloseEvent * event)193 void FilterPopup::closeEvent(QCloseEvent* event)
194 {
195     if (d->dirty) {
196         Filter filter = d->database.filter();
197         updateFilter(&filter);
198         // any real change?
199         if (d->database.filter() != filter) {
200             ApplyFilterCommand* command = new ApplyFilterCommand();
201             command->setSheet(d->database.range().lastSheet());
202             command->add(d->database.range());
203             command->setOldFilter(d->database.filter());
204             d->database.setFilter(filter);
205             d->database.dump();
206             command->setDatabase(d->database);
207             command->execute();
208         }
209     }
210     QFrame::closeEvent(event);
211 }
212 
buttonClicked(QAbstractButton * button)213 void FilterPopup::buttonClicked(QAbstractButton* button)
214 {
215     d->dirty = true;
216     if (button == d->allCheckbox) {
217         foreach(QCheckBox* checkbox, d->checkboxes)
218         checkbox->setChecked(button->isChecked());
219         d->emptyCheckbox->setChecked(button->isChecked());
220         d->notEmptyCheckbox->setChecked(button->isChecked());
221     } else if (button == d->emptyCheckbox) {
222         bool isAll = button->isChecked();
223         if (isAll) {
224             foreach(QCheckBox* checkbox, d->checkboxes) {
225                 if (!checkbox->isChecked()) {
226                     isAll = false;
227                     break;
228                 }
229             }
230         }
231         d->allCheckbox->setChecked(isAll);
232     } else if (button == d->notEmptyCheckbox) {
233         foreach(QCheckBox* checkbox, d->checkboxes)
234         checkbox->setChecked(button->isChecked());
235         d->allCheckbox->setChecked(button->isChecked() && d->emptyCheckbox->isChecked());
236     } else {
237         bool isAll = d->emptyCheckbox->isChecked();
238         if (isAll) {
239             foreach(QCheckBox* checkbox, d->checkboxes) {
240                 if (!checkbox->isChecked()) {
241                     isAll = false;
242                     break;
243                 }
244             }
245         }
246         d->allCheckbox->setChecked(isAll);
247         d->notEmptyCheckbox->setChecked(isAll);
248     }
249 }
250 
showPopup(QWidget * parent,const Cell & cell,const QRect & cellRect,Database * database)251 void FilterPopup::showPopup(QWidget *parent, const Cell &cell, const QRect &cellRect, Database *database)
252 {
253     FilterPopup* popup = new FilterPopup(parent, cell, database);
254     const QPoint position(database->orientation() == Qt::Vertical ? cellRect.bottomLeft() : cellRect.topRight());
255     popup->move(parent->mapToGlobal(position));
256     popup->show();
257 }
258