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 }