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 }