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