File indexing completed on 2024-05-12 16:35:17
0001 /* This file is part of the KDE project 0002 Copyright 2010 Stefan Nikolaus <stefan.nikolaus@kdemail.net> 0003 Copyright (C) 2002-2003 Norbert Andres <nandres@web.de> 0004 (C) 2002 Ariya Hidayat <ariya@kde.org> 0005 (C) 2002 Philipp Mueller <philipp.mueller@gmx.de> 0006 (C) 2002 John Dailey <dailey@vt.edu> 0007 (C) 2000-2001 Werner Trobin <trobin@kde.org> 0008 (C) 2000-2001 Laurent Montel <montel@kde.org> 0009 (C) 1999-2002 David Faure <faure@kde.org> 0010 (C) 1999 Stephan Kulow <coolo@kde.org> 0011 (C) 1999 Reginald Stadlbauer <reggie@kde.org> 0012 (C) 1998-1999 Torben Weis <weis@kde.org> 0013 0014 This library is free software; you can redistribute it and/or 0015 modify it under the terms of the GNU Library General Public 0016 License as published by the Free Software Foundation; either 0017 version 2 of the License, or (at your option) any later version. 0018 0019 This library is distributed in the hope that it will be useful, 0020 but WITHOUT ANY WARRANTY; without even the implied warranty of 0021 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0022 Library General Public License for more details. 0023 0024 You should have received a copy of the GNU Library General Public License 0025 along with this library; see the file COPYING.LIB. If not, write to 0026 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0027 Boston, MA 02110-1301, USA. 0028 */ 0029 0030 // Local 0031 #include "ConsolidateDialog.h" 0032 0033 //Qt Includes 0034 #include <QCheckBox> 0035 #include <QGridLayout> 0036 #include <QLabel> 0037 #include <QPushButton> 0038 #include <QListWidget> 0039 0040 // KF5 0041 #include <kmessagebox.h> 0042 0043 #include <KoIcon.h> 0044 0045 // Sheets 0046 #include "SheetsDebug.h" 0047 #include <Global.h> 0048 #include "calligra_sheets_limits.h" 0049 #include <Localization.h> 0050 #include <Map.h> 0051 #include "ui/Selection.h" 0052 #include <Sheet.h> 0053 #include <Util.h> 0054 0055 #include <Formula.h> 0056 #include <ValueConverter.h> 0057 #include <Cell.h> 0058 0059 #include "commands/DataManipulators.h" 0060 0061 #include "ui_ConsolidateWidget.h" 0062 #include "ui_ConsolidateDetailsWidget.h" 0063 0064 using namespace Calligra::Sheets; 0065 0066 class ConsolidateDialog::Private 0067 { 0068 public: 0069 Selection *selection; 0070 Ui::ConsolidateWidget mainWidget; 0071 Ui::ConsolidateDetailsWidget detailsWidget; 0072 0073 public: 0074 void setContent(Sheet *sheet, int row, int column, const QString &text, KUndo2Command *parent); 0075 }; 0076 0077 0078 ConsolidateDialog::ConsolidateDialog(QWidget* parent, Selection* selection) 0079 : KoDialog(parent) 0080 , d(new Private) 0081 { 0082 d->selection = selection; 0083 0084 setCaption(i18n("Consolidate")); 0085 setModal(false); 0086 setAttribute(Qt::WA_DeleteOnClose); 0087 setButtons(Ok | Cancel | Details); 0088 enableButton(Ok, false); 0089 0090 QWidget *widget = new QWidget(this); 0091 d->mainWidget.setupUi(widget); 0092 setMainWidget(widget); 0093 0094 widget = new QWidget(this); 0095 d->detailsWidget.setupUi(widget); 0096 setDetailsWidget(widget); 0097 0098 // ui refinements Designer is not capable of 0099 d->mainWidget.m_addButton->setIcon(koIcon("list-add")); 0100 d->mainWidget.m_removeButton->setIcon(koIcon("list-remove")); 0101 0102 d->mainWidget.m_function->addItem(i18n("Sum"), "SUM"); 0103 d->mainWidget.m_function->addItem(i18n("Average"), "AVERAGE"); 0104 d->mainWidget.m_function->addItem(i18n("Count"), "COUNT"); 0105 d->mainWidget.m_function->addItem(i18n("Max"), "MAX"); 0106 d->mainWidget.m_function->addItem(i18n("Min"), "MIN"); 0107 d->mainWidget.m_function->addItem(i18n("Product"), "PRODUCT"); 0108 d->mainWidget.m_function->addItem(i18n("Standard Deviation"), "STDDEV"); 0109 d->mainWidget.m_function->addItem(i18n("Variance"), "VAR"); 0110 0111 connect(d->mainWidget.m_addButton, SIGNAL(clicked()), 0112 this, SLOT(slotAdd())); 0113 connect(d->mainWidget.m_removeButton, SIGNAL(clicked()), 0114 this, SLOT(slotRemove())); 0115 connect(d->mainWidget.m_sourceRange, SIGNAL(returnPressed()), 0116 this, SLOT(slotReturnPressed())); 0117 0118 connect(d->selection, SIGNAL(changed(Region)), 0119 this, SLOT(slotSelectionChanged())); 0120 } 0121 0122 ConsolidateDialog::~ConsolidateDialog() 0123 { 0124 delete d; 0125 } 0126 0127 void ConsolidateDialog::accept() 0128 { 0129 // Quit, if there are no source ranges. 0130 if (d->mainWidget.m_sourceRanges->count() == 0) { 0131 KMessageBox::error(this, i18n("You have to define at least one source range.")); 0132 return; 0133 } 0134 0135 Map *const map = d->selection->activeSheet()->map(); 0136 ValueConverter *const converter = map->converter(); 0137 0138 Sheet *const destinationSheet = d->selection->activeSheet(); 0139 int dx = d->selection->lastRange().left(); 0140 int dy = d->selection->lastRange().top(); 0141 0142 const int index = d->mainWidget.m_function->currentIndex(); 0143 const QString function = d->mainWidget.m_function->itemData(index).toString(); 0144 0145 QList<Region> ranges; 0146 for (int i = 0; i < d->mainWidget.m_sourceRanges->count(); ++i) { 0147 const QString address = d->mainWidget.m_sourceRanges->item(i)->text(); 0148 const Region region(address, map, destinationSheet); 0149 if (!region.isValid()) { 0150 KMessageBox::error(this, i18n("%1 is not a valid cell range." , address)); 0151 return; 0152 } 0153 ranges.append(region); 0154 } 0155 0156 enum { D_ROW, D_COL, D_NONE, D_BOTH } desc; 0157 if (d->detailsWidget.m_rowHeader->isChecked() && d->detailsWidget.m_columnHeader->isChecked()) 0158 desc = D_BOTH; 0159 else if (d->detailsWidget.m_rowHeader->isChecked()) 0160 desc = D_ROW; 0161 else if (d->detailsWidget.m_columnHeader->isChecked()) 0162 desc = D_COL; 0163 else 0164 desc = D_NONE; 0165 0166 const QRect firstRange = ranges[0].firstRange(); 0167 const int columns = firstRange.width(); 0168 const int rows = firstRange.height(); 0169 0170 // Check, if the first cell range has a sufficient size. 0171 if (columns <= ((desc == D_BOTH || desc == D_COL) ? 1 : 0) || 0172 rows <= ((desc == D_BOTH || desc == D_ROW) ? 1 : 0)) { 0173 KMessageBox::error(this, i18n("The range\n%1\nis too small", ranges[0].name())); 0174 return; 0175 } 0176 0177 // Check, if the first cell range is too large. 0178 if (ranges[0].isAllSelected() || ranges[0].isColumnOrRowSelected()) { 0179 KMessageBox::error(this, i18n("The range\n%1\nis too large" , ranges[0].name())); 0180 return; 0181 } 0182 0183 // Check the other cell ranges. 0184 for (int i = 1; i < ranges.count(); ++i) { 0185 const int columns2 = ranges[i].firstRange().width(); 0186 const int rows2 = ranges[i].firstRange().height(); 0187 0188 // Check, if the cell range is too large. 0189 if (ranges[i].isAllSelected() || ranges[i].isColumnOrRowSelected()) { 0190 KMessageBox::error(this, i18n("The range\n%1\nis too large" , ranges[i].name())); 0191 return; 0192 } 0193 0194 // Check whether all ranges... 0195 // - have the same size, if no headers are given. 0196 // - have the same amount of rows, if only column headers are given 0197 // - have the same amount of columns, if only row headers are given 0198 if ((desc == D_NONE && (columns != columns2 || rows != rows2)) || 0199 (desc == D_COL && rows != rows2) || 0200 (desc == D_ROW && columns != columns2)) { 0201 QString tmp = i18n("The ranges\n%1\nand\n%2\nhave different size", ranges[0].name() , ranges[i].name()); 0202 KMessageBox::error(this, tmp); 0203 return; 0204 } 0205 } 0206 0207 // Create the cumulating parent command. 0208 KUndo2Command *const command = new KUndo2Command(kundo2_i18n("Consolidate")); 0209 0210 // Create the destination cell range 0211 if (desc == D_NONE) { 0212 // Check whether the destination is part of the source ... 0213 const QRect destinationRange(dx, dy, columns, rows); 0214 for (int i = 0; i < ranges.count(); ++i) { 0215 Sheet *const sheet = ranges[i].firstSheet(); 0216 Q_ASSERT(sheet); 0217 const QRect range = ranges[i].firstRange(); 0218 0219 if (sheet == destinationSheet && range.intersects(destinationRange)) { 0220 QString tmp(i18n("The source range intersects the destination range.")); 0221 KMessageBox::error(this, tmp); 0222 return; 0223 } 0224 } 0225 0226 // Now create the destination cell range 0227 for (int col = 0; col < columns; ++col) { 0228 for (int row = 0; row < rows; ++row) { 0229 bool novalue = true; 0230 QString formula = '=' + function + '('; 0231 for (int i = 0; i < ranges.count(); ++i) { 0232 Sheet *const sheet = ranges[i].firstSheet(); 0233 Q_ASSERT(sheet); 0234 const QRect range = ranges[i].firstRange(); 0235 const Cell cell(sheet, col + range.left(), row + range.top()); 0236 if (!cell.value().isEmpty()) 0237 novalue = false; 0238 if (i != 0) { 0239 formula += ';'; 0240 } 0241 formula += (sheet != destinationSheet) ? cell.fullName() : cell.name(); 0242 } 0243 formula += ')'; 0244 0245 if (!novalue) { 0246 d->setContent(destinationSheet, dy + row, dx + col, formula, command); 0247 } 0248 } 0249 } 0250 } else if (desc == D_COL) { 0251 // Get list of all descriptions in the columns 0252 QHash<QString, QList<Cell> > columnHeaderCells; 0253 for (int i = 0; i < ranges.count(); ++i) { 0254 Sheet *const sheet = ranges[i].firstSheet(); 0255 Q_ASSERT(sheet); 0256 const QRect range = ranges[i].firstRange(); 0257 for (int col = range.left(); col <= range.right() ; ++col) { 0258 const Cell cell(sheet, col, range.top()); 0259 const Value value = cell.value(); 0260 const QString columnHeader = converter->asString(value).asString(); 0261 columnHeaderCells[columnHeader].append(cell); 0262 } 0263 } 0264 QStringList columnHeaders = columnHeaderCells.keys(); 0265 columnHeaders.sort(); 0266 0267 // Check whether the destination is part of the source ... 0268 const QRect destinationRange(dx, dy, columnHeaders.count(), rows); 0269 for (int i = 0; i < ranges.count(); ++i) { 0270 Sheet *const sheet = ranges[i].firstSheet(); 0271 Q_ASSERT(sheet); 0272 const QRect range = ranges[i].firstRange(); 0273 if (sheet == destinationSheet && range.intersects(destinationRange)) { 0274 QString tmp(i18n("The source range intersects the destination range.")); 0275 KMessageBox::error(this, tmp); 0276 return; 0277 } 0278 } 0279 0280 // Now create the destination cell range 0281 for (int col = 0; col < columnHeaders.count(); ++col) { 0282 // Fill in the header data. 0283 const QString columnHeader = columnHeaders[col]; 0284 d->setContent(destinationSheet, dy, dx + col, columnHeader, command); 0285 0286 const QList<Cell> cells = columnHeaderCells[columnHeader]; 0287 for (int row = 1; row < rows; ++row) { 0288 QString formula = '=' + function + '('; 0289 for (int i = 0; i < cells.count(); ++i) { 0290 if (i != 0) { 0291 formula += ';'; 0292 } 0293 Sheet *const sheet = cells[i].sheet(); 0294 const int headerColumn = cells[i].column(); 0295 const int headerRow = cells[i].row(); 0296 const Cell cell(sheet, headerColumn, headerRow + row); 0297 const bool fullName = sheet != destinationSheet; 0298 formula += fullName ? cell.fullName() : cell.name(); 0299 } 0300 formula += ')'; 0301 0302 d->setContent(destinationSheet, dy + row, dx + col, formula, command); 0303 } 0304 } 0305 } else if (desc == D_ROW) { 0306 // Get list of all descriptions in the rows 0307 QHash<QString, QList<Cell> > rowHeaderCells; 0308 for (int i = 0; i < ranges.count(); ++i) { 0309 Sheet *const sheet = ranges[i].firstSheet(); 0310 Q_ASSERT(sheet); 0311 const QRect range = ranges[i].firstRange(); 0312 for (int row = range.top(); row <= range.bottom() ; ++row) { 0313 const Cell cell(sheet, range.left(), row); 0314 const Value value = cell.value(); 0315 const QString rowHeader = converter->asString(value).asString(); 0316 rowHeaderCells[rowHeader].append(cell); 0317 } 0318 } 0319 QStringList rowHeaders = rowHeaderCells.keys(); 0320 rowHeaders.sort(); 0321 0322 // Check whether the destination is part of the source ... 0323 const QRect destinationRange(dx, dy, columns, rowHeaders.count()); 0324 for (int i = 0; i < ranges.count(); ++i) { 0325 Sheet *const sheet = ranges[i].firstSheet(); 0326 Q_ASSERT(sheet); 0327 const QRect range = ranges[i].firstRange(); 0328 if (sheet == destinationSheet && range.intersects(destinationRange)) { 0329 QString tmp(i18n("The source range intersects the destination range.")); 0330 KMessageBox::error(this, tmp); 0331 return; 0332 } 0333 } 0334 0335 // Now create the destination cell range 0336 for (int row = 0; row < rowHeaders.count(); ++row) { 0337 // Fill in the header data. 0338 const QString rowHeader = rowHeaders[row]; 0339 d->setContent(destinationSheet, dy + row, dx, rowHeader, command); 0340 0341 const QList<Cell> cells = rowHeaderCells[rowHeader]; 0342 for (int col = 1; col < columns; ++col) { 0343 QString formula = '=' + function + '('; 0344 for (int i = 0; i < cells.count(); ++i) { 0345 if (i != 0) { 0346 formula += ';'; 0347 } 0348 Sheet *const sheet = cells[i].sheet(); 0349 const int headerColumn = cells[i].column(); 0350 const int headerRow = cells[i].row(); 0351 const Cell cell(sheet, headerColumn + col, headerRow); 0352 const bool fullName = sheet != destinationSheet; 0353 formula += fullName ? cell.fullName() : cell.name(); 0354 } 0355 formula += ')'; 0356 0357 d->setContent(destinationSheet, dy + row, dx + col, formula, command); 0358 } 0359 } 0360 } else if (desc == D_BOTH) { 0361 // Get list of all descriptions in the rows 0362 QStringList rowHeaders; 0363 for (int i = 0; i < ranges.count(); ++i) { 0364 Sheet *const sheet = ranges[i].firstSheet(); 0365 Q_ASSERT(sheet); 0366 const QRect range = ranges[i].firstRange(); 0367 for (int row = range.top() + 1; row <= range.bottom() ; ++row) { 0368 const Value value = Cell(sheet, range.left(), row).value(); 0369 const QString rowHeader = converter->asString(value).asString(); 0370 if (!rowHeaders.contains(rowHeader)) { 0371 rowHeaders.append(rowHeader); 0372 } 0373 } 0374 } 0375 rowHeaders.sort(); 0376 0377 // Get list of all descriptions in the columns 0378 QStringList columnHeaders; 0379 for (int i = 0; i < ranges.count(); ++i) { 0380 Sheet *const sheet = ranges[i].firstSheet(); 0381 Q_ASSERT(sheet); 0382 const QRect range = ranges[i].firstRange(); 0383 for (int col = range.left() + 1; col <= range.right() ; ++col) { 0384 const Value value = Cell(sheet, col, range.top()).value(); 0385 const QString columnHeader = converter->asString(value).asString(); 0386 if (!columnHeaders.contains(columnHeader)) { 0387 columnHeaders.append(columnHeader); 0388 } 0389 } 0390 } 0391 columnHeaders.sort(); 0392 0393 // Check whether the destination is part of the source ... 0394 const QRect destinationRange(dx, dy, columnHeaders.count(), rowHeaders.count()); 0395 for (int i = 0; i < ranges.count(); ++i) { 0396 Sheet *const sheet = ranges[i].firstSheet(); 0397 Q_ASSERT(sheet); 0398 const QRect range = ranges[i].firstRange(); 0399 if (sheet == destinationSheet && range.intersects(destinationRange)) { 0400 QString tmp(i18n("The source range intersects the destination range.")); 0401 KMessageBox::error(this, tmp); 0402 return; 0403 } 0404 } 0405 0406 // Fill the list with all interesting cells 0407 QHash<QString /* row */, QHash<QString /* col */, QList<Cell> > > list; 0408 for (int i = 0; i < ranges.count(); ++i) { 0409 Sheet *const sheet = ranges[i].firstSheet(); 0410 Q_ASSERT(sheet); 0411 const QRect range = ranges[i].firstRange(); 0412 for (int col = range.left() + 1; col <= range.right() ; ++col) { 0413 const Value columnValue = Cell(sheet, col, range.top()).value(); 0414 const QString columnHeader = converter->asString(columnValue).asString(); 0415 for (int row = range.top() + 1; row <= range.bottom() ; ++row) { 0416 const Value rowValue = Cell(sheet, range.left(), row).value(); 0417 const QString rowHeader = converter->asString(rowValue).asString(); 0418 list[rowHeader][columnHeader].append(Cell(sheet, col, row)); 0419 } 0420 } 0421 } 0422 0423 // Draw the row description 0424 for (int i = 0; i < rowHeaders.count(); ++i) { 0425 d->setContent(destinationSheet, dy + 1 + i, dx, rowHeaders[i], command); 0426 } 0427 0428 // Draw the column description 0429 for (int i = 0; i < columnHeaders.count(); ++i) { 0430 d->setContent(destinationSheet, dy, dx + 1 + i, columnHeaders[i], command); 0431 } 0432 0433 // Draw the data 0434 for (int row = 0; row < rowHeaders.count(); ++row) { 0435 for (int col = 0; col < columnHeaders.count(); ++col) { 0436 QString formula = '=' + function + '('; 0437 const QString rowHeader = rowHeaders[row]; 0438 const QString columnHeader = columnHeaders[col]; 0439 const QList<Cell> lst = list[rowHeader][columnHeader]; 0440 for (int i = 0; i < lst.count(); ++i) { 0441 if (i != 0) { 0442 formula += ';'; 0443 } 0444 const bool fullName = lst[i].sheet() != destinationSheet; 0445 formula += fullName ? lst[i].fullName() : lst[i].name(); 0446 } 0447 formula += ')'; 0448 0449 d->setContent(destinationSheet, dy + 1 + row, dx + 1 + col, formula, command); 0450 } 0451 } 0452 } 0453 0454 // execute the cumulating parent command 0455 map->addCommand(command); 0456 0457 KoDialog::accept(); 0458 } 0459 0460 void ConsolidateDialog::slotAdd() 0461 { 0462 slotReturnPressed(); 0463 } 0464 0465 void ConsolidateDialog::slotRemove() 0466 { 0467 int i = d->mainWidget.m_sourceRanges->currentRow(); 0468 if (i < 0) 0469 return; 0470 0471 delete d->mainWidget.m_sourceRanges->takeItem(i); 0472 0473 if (d->mainWidget.m_sourceRanges->count() == 0) 0474 enableButton(Ok, false); 0475 } 0476 0477 void ConsolidateDialog::slotSelectionChanged() 0478 { 0479 if (!d->selection->isValid()) { 0480 d->mainWidget.m_sourceRange->setText(""); 0481 return; 0482 } 0483 0484 QString area = d->selection->name(); 0485 d->mainWidget.m_sourceRange->setText(area); 0486 d->mainWidget.m_sourceRange->setSelection(0, area.length()); 0487 } 0488 0489 void ConsolidateDialog::slotReturnPressed() 0490 { 0491 QString txt = d->mainWidget.m_sourceRange->text(); 0492 0493 const Region r(txt, d->selection->activeSheet()->map()); 0494 if (!r.isValid()) { 0495 KMessageBox::error(this, i18n("The range\n%1\n is malformed", txt)); 0496 return; 0497 } 0498 0499 if (!txt.isEmpty()) { 0500 d->mainWidget.m_sourceRanges->addItem(txt); 0501 enableButton(Ok, true); 0502 } 0503 } 0504 0505 void ConsolidateDialog::Private::setContent(Sheet *sheet, int row, int column, 0506 const QString &text, KUndo2Command *parent) 0507 { 0508 Value value; 0509 // Directly evaluate the formula, i.e. copy data, i.e. do not link to data? 0510 if (detailsWidget.m_copyData->isChecked()) { 0511 Formula formula(sheet); 0512 formula.setExpression(text); 0513 if (!formula.isValid()) { 0514 debugSheets << "Invalid formula:" << text; 0515 return; // Quit before creating/adding the sub-command. 0516 } 0517 value = formula.eval(); 0518 } else { 0519 value = Value(text); 0520 } 0521 0522 DataManipulator *const command = new DataManipulator(parent); 0523 command->setSheet(sheet); 0524 command->setValue(value); 0525 command->setParsing(!detailsWidget.m_copyData->isChecked()); 0526 command->add(QPoint(column, row)); 0527 // executed by the parent command 0528 }