File indexing completed on 2024-05-12 16:35:11

0001 /* This file is part of the KDE project
0002    Copyright (C) 2006 Tomas Mecir <mecirt@gmail.com>
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; only
0007    version 2 of the License.
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 <algorithm>
0021 
0022 #include "DataManipulators.h"
0023 
0024 #include <KLocalizedString>
0025 
0026 #include "Cell.h"
0027 #include "CellStorage.h"
0028 #include "Damages.h"
0029 #include "Formula.h"
0030 #include "Map.h"
0031 #include "Sheet.h"
0032 #include "ValueCalc.h"
0033 #include "ValueConverter.h"
0034 
0035 #include <float.h>
0036 #include <math.h>
0037 
0038 using namespace Calligra::Sheets;
0039 
0040 AbstractDataManipulator::AbstractDataManipulator(KUndo2Command* parent)
0041         : AbstractRegionCommand(parent)
0042 {
0043     m_checkLock = true;
0044 }
0045 
0046 AbstractDataManipulator::~AbstractDataManipulator()
0047 {
0048 }
0049 
0050 bool AbstractDataManipulator::process(Element* element)
0051 {
0052     QRect range = element->rect();
0053     for (int col = range.left(); col <= range.right(); ++col)
0054         for (int row = range.top(); row <= range.bottom(); ++row) {
0055             Value val;
0056             QString text;
0057 //       int colidx = col - range.left();
0058 //       int rowidx = row - range.top();
0059             bool parse = false;
0060             Format::Type fmtType = Format::None;
0061 
0062             // do nothing if we don't want a change here
0063             if (!wantChange(element, col, row))
0064                 continue;
0065 
0066             val = newValue(element, col, row, &parse, &fmtType);
0067 
0068             Cell cell = Cell(m_sheet, col, row);
0069             if (cell.isPartOfMerged()) cell = cell.masterCell();
0070 
0071             // we have the data - set it !
0072             if (parse) {
0073                 if (fmtType != Format::None) {
0074                     Style style;
0075                     style.setFormatType(fmtType);
0076                     cell.setStyle(style);
0077                 }
0078                 cell.parseUserInput(val.asString());
0079             } else {
0080                 cell.setValue(val); // val can be empty - that's fine
0081                 cell.setUserInput(m_sheet->map()->converter()->asString(val).asString());
0082                 if (fmtType != Format::None) {
0083                     Style style;
0084                     style.setFormatType(fmtType);
0085                     cell.setStyle(style);
0086                 }
0087             }
0088         }
0089     return true;
0090 }
0091 
0092 bool AbstractDataManipulator::preProcessing()
0093 {
0094     // not the first run - data already stored ...
0095     if (!m_firstrun)
0096         return true;
0097     m_sheet->cellStorage()->startUndoRecording();
0098     return AbstractRegionCommand::preProcessing();
0099 }
0100 
0101 bool AbstractDataManipulator::mainProcessing()
0102 {
0103     if (m_reverse) {
0104         // reverse - use the stored value
0105         KUndo2Command::undo(); // undo child commands
0106         return true;
0107     }
0108     return AbstractRegionCommand::mainProcessing();
0109 }
0110 
0111 bool AbstractDataManipulator::postProcessing()
0112 {
0113     // not the first run - data already stored ...
0114     if (!m_firstrun)
0115         return true;
0116     m_sheet->cellStorage()->stopUndoRecording(this);
0117     return true;
0118 }
0119 
0120 AbstractDFManipulator::AbstractDFManipulator(KUndo2Command *parent)
0121         : AbstractDataManipulator(parent)
0122 {
0123     m_changeformat = true;
0124 }
0125 
0126 AbstractDFManipulator::~AbstractDFManipulator()
0127 {
0128 }
0129 
0130 bool AbstractDFManipulator::process(Element* element)
0131 {
0132     // let parent class process it first
0133     AbstractDataManipulator::process(element);
0134 
0135     // don't continue if we don't have to change formatting
0136     if (!m_changeformat) return true;
0137     if (m_reverse) return true; // undo done by AbstractDataManipulator
0138 
0139     QRect range = element->rect();
0140     for (int col = range.left(); col <= range.right(); ++col) {
0141         for (int row = range.top(); row <= range.bottom(); ++row) {
0142             Cell cell(m_sheet, col, row);
0143 //       int colidx = col - range.left();
0144 //       int rowidx = row - range.top();
0145             Style style = newFormat(element, col, row);
0146             cell.setStyle(style);
0147         }
0148     }
0149     return true;
0150 }
0151 
0152 
0153 DataManipulator::DataManipulator(KUndo2Command* parent)
0154         : AbstractDataManipulator(parent)
0155         , m_format(Format::None)
0156         , m_parsing(false)
0157         , m_expandMatrix(false)
0158 {
0159     // default name for DataManipulator, can be changed using setText
0160     setText(kundo2_i18n("Change Value"));
0161 }
0162 
0163 DataManipulator::~DataManipulator()
0164 {
0165 }
0166 
0167 bool DataManipulator::preProcessing()
0168 {
0169     // extend a singular region to the matrix size, if applicable
0170     if (m_firstrun && m_parsing && m_expandMatrix && Region::isSingular()) {
0171         const QString expression = m_data.asString();
0172         if (!expression.isEmpty() && expression[0] == '=') {
0173             Formula formula(m_sheet);
0174             formula.setExpression(expression);
0175             if (formula.isValid()) {
0176                 const Value result = formula.eval();
0177                 if (result.columns() > 1 || result.rows() > 1) {
0178                     const QPoint point = cells()[0]->rect().topLeft();
0179                     Region::add(QRect(point.x(), point.y(), result.columns(), result.rows()), m_sheet);
0180                 }
0181             }
0182         } else if (!m_data.isArray()) {
0183             // not a formula; not a matrix: unset m_expandMatrix
0184             m_expandMatrix = false;
0185         }
0186     }
0187     return AbstractDataManipulator::preProcessing();
0188 }
0189 
0190 bool DataManipulator::process(Element* element)
0191 {
0192     bool success = AbstractDataManipulator::process(element);
0193     if (!success)
0194         return false;
0195     if (!m_reverse) {
0196         // Only lock cells, if expansion is desired and the value is a formula.
0197         if (m_expandMatrix && (m_data.asString().isEmpty() || m_data.asString().at(0) == '='))
0198             m_sheet->cellStorage()->lockCells(element->rect());
0199     }
0200     return true;
0201 }
0202 
0203 bool DataManipulator::wantChange(Element *element, int col, int row)
0204 {
0205   if (m_expandMatrix) {
0206     QRect range = element->rect();
0207     int colidx = col - range.left();
0208     int rowidx = row - range.top();
0209     // don't set this value, RecalcManager already did it
0210     if (colidx || rowidx) return false;
0211   }
0212   return true;
0213 }
0214 
0215 Value DataManipulator::newValue(Element *element, int col, int row,
0216                                 bool *parsing, Format::Type *formatType)
0217 {
0218     *parsing = m_parsing;
0219     if (m_format != Format::None)
0220         *formatType = m_format;
0221     QRect range = element->rect();
0222     int colidx = col - range.left();
0223     int rowidx = row - range.top();
0224     return m_data.element(colidx, rowidx);
0225 }
0226 
0227 
0228 SeriesManipulator::SeriesManipulator()
0229 {
0230     setText(kundo2_i18n("Insert Series"));
0231 
0232     m_type = Linear;
0233     m_last = -2;
0234 }
0235 
0236 SeriesManipulator::~SeriesManipulator()
0237 {
0238 }
0239 
0240 void SeriesManipulator::setupSeries(const QPoint &_marker, double start,
0241                                     double end, double step, Series mode, Series type)
0242 {
0243     m_type = type;
0244     m_start = Value(start);
0245     m_step = Value(step);
0246     // compute cell count
0247     int numberOfCells = 1;
0248     if (type == Linear)
0249         numberOfCells = (int)((end - start) / step + 1);
0250     if (type == Geometric)
0251         /* basically, A(n) = start * step ^ n
0252         * so when is end >= start * step ^ n ??
0253         * when n = ln(end/start) / ln(step)
0254         */
0255         // DBL_EPSILON is added to prevent rounding errors
0256         numberOfCells = (int)(::log(end / start) / ::log(step) + DBL_EPSILON) + 1;
0257 
0258     // with this, generate range information
0259     Region range(_marker.x(), _marker.y(), (mode == Column) ? 1 : numberOfCells,
0260                  (mode == Row) ? 1 : numberOfCells);
0261 
0262     // and add the range to the manipulator
0263     add(range);
0264 }
0265 
0266 Value SeriesManipulator::newValue(Element *element, int col, int row,
0267                                   bool *parse, Format::Type *)
0268 {
0269     *parse = false;
0270     ValueCalc *calc = m_sheet->map()->calc();
0271 
0272     // either colidx or rowidx is always zero
0273     QRect range = element->rect();
0274     int colidx = col - range.left();
0275     int rowidx = row - range.top();
0276     int which = (colidx > 0) ? colidx : rowidx;
0277     Value val;
0278     if (which == m_last + 1) {
0279         // if we are requesting next item in the series, which should almost always
0280         // be the case, we can use the pre-computed value to speed up the process
0281         if (m_type == Linear)
0282             val = calc->add(m_prev, m_step);
0283         if (m_type == Geometric)
0284             val = calc->mul(m_prev, m_step);
0285     } else {
0286         // otherwise compute from scratch
0287         val = m_start;
0288         for (int i = 0; i < which; ++i) {
0289             if (m_type == Linear)
0290                 val = calc->add(val, m_step);
0291             if (m_type == Geometric)
0292                 val = calc->mul(val, m_step);
0293         }
0294     }
0295     // store last value
0296     m_prev = val;
0297     m_last = which;
0298 
0299     // return the computed value
0300     return val;
0301 }
0302 
0303 
0304 FillManipulator::FillManipulator()
0305 {
0306     m_dir = Down;
0307     m_changeformat = true;
0308     setText(kundo2_i18n("Fill Selection"));
0309 }
0310 
0311 FillManipulator::~FillManipulator()
0312 {
0313 }
0314 
0315 Value FillManipulator::newValue(Element *element, int col, int row,
0316                                 bool *parse, Format::Type *fmtType)
0317 {
0318     Q_UNUSED(fmtType);
0319     const int targetRow = row;
0320     const int targetCol = col;
0321     switch (m_dir) {
0322     case Up:    row = element->rect().bottom(); break;
0323     case Down:  row = element->rect().top();    break;
0324     case Left:  col = element->rect().right();  break;
0325     case Right: col = element->rect().left();   break;
0326     };
0327     Cell cell(m_sheet, col, row); // the reference cell
0328     if (cell.isFormula()) {
0329         *parse = true;
0330         return Value(Cell(m_sheet, targetCol, targetRow).decodeFormula(cell.encodeFormula()));
0331     }
0332     return cell.value();
0333 }
0334 
0335 Style FillManipulator::newFormat(Element *element, int col, int row)
0336 {
0337     switch (m_dir) {
0338     case Up:    row = element->rect().bottom(); break;
0339     case Down:  row = element->rect().top();    break;
0340     case Left:  col = element->rect().right();  break;
0341     case Right: col = element->rect().left();   break;
0342     };
0343     return Cell(m_sheet, col, row).style();
0344 }
0345 
0346 CaseManipulator::CaseManipulator()
0347 {
0348     m_mode = Upper;
0349     setText(kundo2_i18n("Change Case"));
0350 }
0351 
0352 CaseManipulator::~CaseManipulator()
0353 {
0354 }
0355 
0356 Value CaseManipulator::newValue(Element *element, int col, int row,
0357                                 bool *parse, Format::Type *)
0358 {
0359     Q_UNUSED(element)
0360     // if we are here, we know that we want the change
0361     *parse = false;
0362     QString str = Cell(m_sheet, col, row).value().asString();
0363     switch (m_mode) {
0364     case Upper: str = str.toUpper();
0365         break;
0366     case Lower: str = str.toLower();
0367         break;
0368     case FirstUpper:
0369         if (str.length() > 0)
0370             str = str.at(0).toUpper() + str.right(str.length() - 1);
0371         break;
0372     };
0373     return Value(str);
0374 }
0375 
0376 bool CaseManipulator::wantChange(Element *element, int col, int row)
0377 {
0378     Q_UNUSED(element)
0379     Cell cell(m_sheet, col, row);
0380     // don't change cells with a formula
0381     if (cell.isFormula())
0382         return false;
0383     // don't change cells containing other things than strings
0384     if (!cell.value().isString())
0385         return false;
0386     // original version was dismissing text starting with '!' and '*', is this
0387     // necessary ?
0388     return true;
0389 }
0390 
0391 
0392 
0393 ShiftManipulator::ShiftManipulator(KUndo2Command *parent)
0394         : AbstractRegionCommand(parent)
0395         , m_mode(Insert)
0396 {
0397     m_checkLock = true;
0398     setText(kundo2_i18n("Insert Cells"));
0399 }
0400 
0401 ShiftManipulator::~ShiftManipulator()
0402 {
0403 }
0404 
0405 void ShiftManipulator::setReverse(bool reverse)
0406 {
0407     m_reverse = reverse;
0408     m_mode = reverse ? Delete : Insert;
0409     if (!m_reverse)
0410         setText(kundo2_i18n("Insert Cells"));
0411     else
0412         setText(kundo2_i18n("Remove Cells"));
0413 }
0414 
0415 bool ShiftManipulator::process(Element* element)
0416 {
0417     const QRect range = element->rect();
0418     if (!m_reverse) { // insertion
0419         if (m_direction == ShiftBottom) {
0420             m_sheet->insertShiftDown(range);
0421             m_sheet->cellStorage()->insertShiftDown(range);
0422         } else if (m_direction == ShiftRight) {
0423             m_sheet->insertShiftRight(range);
0424             m_sheet->cellStorage()->insertShiftRight(range);
0425         }
0426 
0427         // undo deletion
0428         if (m_mode == Delete) {
0429             KUndo2Command::undo(); // undo child commands
0430         }
0431     } else { // deletion
0432         if (m_direction == ShiftBottom) {
0433             m_sheet->removeShiftUp(range);
0434             m_sheet->cellStorage()->removeShiftUp(range);
0435         } else if (m_direction == ShiftRight) {
0436             m_sheet->removeShiftLeft(range);
0437             m_sheet->cellStorage()->removeShiftLeft(range);
0438         }
0439 
0440         // undo insertion
0441         if (m_mode == Insert) {
0442             KUndo2Command::undo(); // undo child commands
0443         }
0444     }
0445     return true;
0446 }
0447 
0448 namespace Calligra
0449 {
0450 namespace Sheets
0451 {
0452 bool topRowLessThan(const Region::Element *e1, const Region::Element *e2)
0453 {
0454     return e1->rect().top() < e2->rect().top();
0455 }
0456 
0457 bool leftColumnLessThan(const Region::Element *e1, const Region::Element *e2)
0458 {
0459     return e1->rect().top() < e2->rect().top();
0460 }
0461 } // namespace Sheets
0462 } // namespace Calligra
0463 
0464 bool ShiftManipulator::preProcessing()
0465 {
0466     if (m_firstrun) {
0467         // If we have an NCS, create a child command for each element.
0468         if (cells().count() > 1) { // non-contiguous selection
0469             // Sort the elements by their top row.
0470             if (m_direction == ShiftBottom) {
0471                 std::stable_sort(cells().begin(), cells().end(), topRowLessThan);
0472             } else { // ShiftRight
0473                 std::stable_sort(cells().begin(), cells().end(), leftColumnLessThan);
0474             }
0475             // Create sub-commands.
0476             const Region::ConstIterator end(constEnd());
0477             for (Region::ConstIterator it = constBegin(); it != end; ++it) {
0478                 ShiftManipulator *const command = new ShiftManipulator(this);
0479                 command->setSheet(m_sheet);
0480                 command->add(Region((*it)->rect(), (*it)->sheet()));
0481                 if (m_mode == Delete) {
0482                     command->setReverse(true);
0483                 }
0484                 command->setDirection(m_direction);
0485             }
0486         } else { // contiguous selection
0487             m_sheet->cellStorage()->startUndoRecording();
0488         }
0489     }
0490     return AbstractRegionCommand::preProcessing();
0491 }
0492 
0493 bool ShiftManipulator::mainProcessing()
0494 {
0495     if (cells().count() > 1) { // non-contiguous selection
0496         if ((m_reverse && m_mode == Insert) || (!m_reverse && m_mode == Delete)) {
0497             KUndo2Command::undo(); // process all sub-commands
0498         } else {
0499             KUndo2Command::redo(); // process all sub-commands
0500         }
0501         return true;
0502     }
0503     return AbstractRegionCommand::mainProcessing(); // calls process(Element*)
0504 }
0505 
0506 bool ShiftManipulator::postProcessing()
0507 {
0508     if (cells().count() > 1) { // non-contiguous selection
0509         return true;
0510     }
0511     if (m_firstrun) {
0512         m_sheet->cellStorage()->stopUndoRecording(this);
0513     }
0514     CellDamage *damage = 0;
0515     if (m_direction == ShiftBottom) {
0516         const QPoint bottomRight(lastRange().right(), KS_rowMax);
0517         const Region region(QRect(lastRange().topLeft(), bottomRight), m_sheet);
0518         damage = new CellDamage(m_sheet, region, CellDamage::Appearance);
0519     } else { // ShiftRight
0520         const QPoint bottomRight(KS_colMax, lastRange().bottom());
0521         const Region region(QRect(lastRange().topLeft(), bottomRight), m_sheet);
0522         damage = new CellDamage(m_sheet, region, CellDamage::Appearance);
0523     }
0524     m_sheet->map()->addDamage(damage);
0525     return true;
0526 }