1 /* This file is part of the KDE project
2    Copyright (C) 2006 Tomas Mecir <mecirt@gmail.com>
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; only
7    version 2 of the License.
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 <algorithm>
21 
22 #include "DataManipulators.h"
23 
24 #include <KLocalizedString>
25 
26 #include "Cell.h"
27 #include "CellStorage.h"
28 #include "Damages.h"
29 #include "Formula.h"
30 #include "Map.h"
31 #include "Sheet.h"
32 #include "ValueCalc.h"
33 #include "ValueConverter.h"
34 
35 #include <float.h>
36 #include <math.h>
37 
38 using namespace Calligra::Sheets;
39 
AbstractDataManipulator(KUndo2Command * parent)40 AbstractDataManipulator::AbstractDataManipulator(KUndo2Command* parent)
41         : AbstractRegionCommand(parent)
42 {
43     m_checkLock = true;
44 }
45 
~AbstractDataManipulator()46 AbstractDataManipulator::~AbstractDataManipulator()
47 {
48 }
49 
process(Element * element)50 bool AbstractDataManipulator::process(Element* element)
51 {
52     QRect range = element->rect();
53     for (int col = range.left(); col <= range.right(); ++col)
54         for (int row = range.top(); row <= range.bottom(); ++row) {
55             Value val;
56             QString text;
57 //       int colidx = col - range.left();
58 //       int rowidx = row - range.top();
59             bool parse = false;
60             Format::Type fmtType = Format::None;
61 
62             // do nothing if we don't want a change here
63             if (!wantChange(element, col, row))
64                 continue;
65 
66             val = newValue(element, col, row, &parse, &fmtType);
67 
68             Cell cell = Cell(m_sheet, col, row);
69             if (cell.isPartOfMerged()) cell = cell.masterCell();
70 
71             // we have the data - set it !
72             if (parse) {
73                 if (fmtType != Format::None) {
74                     Style style;
75                     style.setFormatType(fmtType);
76                     cell.setStyle(style);
77                 }
78                 cell.parseUserInput(val.asString());
79             } else {
80                 cell.setValue(val); // val can be empty - that's fine
81                 cell.setUserInput(m_sheet->map()->converter()->asString(val).asString());
82                 if (fmtType != Format::None) {
83                     Style style;
84                     style.setFormatType(fmtType);
85                     cell.setStyle(style);
86                 }
87             }
88         }
89     return true;
90 }
91 
preProcessing()92 bool AbstractDataManipulator::preProcessing()
93 {
94     // not the first run - data already stored ...
95     if (!m_firstrun)
96         return true;
97     m_sheet->cellStorage()->startUndoRecording();
98     return AbstractRegionCommand::preProcessing();
99 }
100 
mainProcessing()101 bool AbstractDataManipulator::mainProcessing()
102 {
103     if (m_reverse) {
104         // reverse - use the stored value
105         KUndo2Command::undo(); // undo child commands
106         return true;
107     }
108     return AbstractRegionCommand::mainProcessing();
109 }
110 
postProcessing()111 bool AbstractDataManipulator::postProcessing()
112 {
113     // not the first run - data already stored ...
114     if (!m_firstrun)
115         return true;
116     m_sheet->cellStorage()->stopUndoRecording(this);
117     return true;
118 }
119 
AbstractDFManipulator(KUndo2Command * parent)120 AbstractDFManipulator::AbstractDFManipulator(KUndo2Command *parent)
121         : AbstractDataManipulator(parent)
122 {
123     m_changeformat = true;
124 }
125 
~AbstractDFManipulator()126 AbstractDFManipulator::~AbstractDFManipulator()
127 {
128 }
129 
process(Element * element)130 bool AbstractDFManipulator::process(Element* element)
131 {
132     // let parent class process it first
133     AbstractDataManipulator::process(element);
134 
135     // don't continue if we don't have to change formatting
136     if (!m_changeformat) return true;
137     if (m_reverse) return true; // undo done by AbstractDataManipulator
138 
139     QRect range = element->rect();
140     for (int col = range.left(); col <= range.right(); ++col) {
141         for (int row = range.top(); row <= range.bottom(); ++row) {
142             Cell cell(m_sheet, col, row);
143 //       int colidx = col - range.left();
144 //       int rowidx = row - range.top();
145             Style style = newFormat(element, col, row);
146             cell.setStyle(style);
147         }
148     }
149     return true;
150 }
151 
152 
DataManipulator(KUndo2Command * parent)153 DataManipulator::DataManipulator(KUndo2Command* parent)
154         : AbstractDataManipulator(parent)
155         , m_format(Format::None)
156         , m_parsing(false)
157         , m_expandMatrix(false)
158 {
159     // default name for DataManipulator, can be changed using setText
160     setText(kundo2_i18n("Change Value"));
161 }
162 
~DataManipulator()163 DataManipulator::~DataManipulator()
164 {
165 }
166 
preProcessing()167 bool DataManipulator::preProcessing()
168 {
169     // extend a singular region to the matrix size, if applicable
170     if (m_firstrun && m_parsing && m_expandMatrix && Region::isSingular()) {
171         const QString expression = m_data.asString();
172         if (!expression.isEmpty() && expression[0] == '=') {
173             Formula formula(m_sheet);
174             formula.setExpression(expression);
175             if (formula.isValid()) {
176                 const Value result = formula.eval();
177                 if (result.columns() > 1 || result.rows() > 1) {
178                     const QPoint point = cells()[0]->rect().topLeft();
179                     Region::add(QRect(point.x(), point.y(), result.columns(), result.rows()), m_sheet);
180                 }
181             }
182         } else if (!m_data.isArray()) {
183             // not a formula; not a matrix: unset m_expandMatrix
184             m_expandMatrix = false;
185         }
186     }
187     return AbstractDataManipulator::preProcessing();
188 }
189 
process(Element * element)190 bool DataManipulator::process(Element* element)
191 {
192     bool success = AbstractDataManipulator::process(element);
193     if (!success)
194         return false;
195     if (!m_reverse) {
196         // Only lock cells, if expansion is desired and the value is a formula.
197         if (m_expandMatrix && (m_data.asString().isEmpty() || m_data.asString().at(0) == '='))
198             m_sheet->cellStorage()->lockCells(element->rect());
199     }
200     return true;
201 }
202 
wantChange(Element * element,int col,int row)203 bool DataManipulator::wantChange(Element *element, int col, int row)
204 {
205   if (m_expandMatrix) {
206     QRect range = element->rect();
207     int colidx = col - range.left();
208     int rowidx = row - range.top();
209     // don't set this value, RecalcManager already did it
210     if (colidx || rowidx) return false;
211   }
212   return true;
213 }
214 
newValue(Element * element,int col,int row,bool * parsing,Format::Type * formatType)215 Value DataManipulator::newValue(Element *element, int col, int row,
216                                 bool *parsing, Format::Type *formatType)
217 {
218     *parsing = m_parsing;
219     if (m_format != Format::None)
220         *formatType = m_format;
221     QRect range = element->rect();
222     int colidx = col - range.left();
223     int rowidx = row - range.top();
224     return m_data.element(colidx, rowidx);
225 }
226 
227 
SeriesManipulator()228 SeriesManipulator::SeriesManipulator()
229 {
230     setText(kundo2_i18n("Insert Series"));
231 
232     m_type = Linear;
233     m_last = -2;
234 }
235 
~SeriesManipulator()236 SeriesManipulator::~SeriesManipulator()
237 {
238 }
239 
setupSeries(const QPoint & _marker,double start,double end,double step,Series mode,Series type)240 void SeriesManipulator::setupSeries(const QPoint &_marker, double start,
241                                     double end, double step, Series mode, Series type)
242 {
243     m_type = type;
244     m_start = Value(start);
245     m_step = Value(step);
246     // compute cell count
247     int numberOfCells = 1;
248     if (type == Linear)
249         numberOfCells = (int)((end - start) / step + 1);
250     if (type == Geometric)
251         /* basically, A(n) = start * step ^ n
252         * so when is end >= start * step ^ n ??
253         * when n = ln(end/start) / ln(step)
254         */
255         // DBL_EPSILON is added to prevent rounding errors
256         numberOfCells = (int)(::log(end / start) / ::log(step) + DBL_EPSILON) + 1;
257 
258     // with this, generate range information
259     Region range(_marker.x(), _marker.y(), (mode == Column) ? 1 : numberOfCells,
260                  (mode == Row) ? 1 : numberOfCells);
261 
262     // and add the range to the manipulator
263     add(range);
264 }
265 
newValue(Element * element,int col,int row,bool * parse,Format::Type *)266 Value SeriesManipulator::newValue(Element *element, int col, int row,
267                                   bool *parse, Format::Type *)
268 {
269     *parse = false;
270     ValueCalc *calc = m_sheet->map()->calc();
271 
272     // either colidx or rowidx is always zero
273     QRect range = element->rect();
274     int colidx = col - range.left();
275     int rowidx = row - range.top();
276     int which = (colidx > 0) ? colidx : rowidx;
277     Value val;
278     if (which == m_last + 1) {
279         // if we are requesting next item in the series, which should almost always
280         // be the case, we can use the pre-computed value to speed up the process
281         if (m_type == Linear)
282             val = calc->add(m_prev, m_step);
283         if (m_type == Geometric)
284             val = calc->mul(m_prev, m_step);
285     } else {
286         // otherwise compute from scratch
287         val = m_start;
288         for (int i = 0; i < which; ++i) {
289             if (m_type == Linear)
290                 val = calc->add(val, m_step);
291             if (m_type == Geometric)
292                 val = calc->mul(val, m_step);
293         }
294     }
295     // store last value
296     m_prev = val;
297     m_last = which;
298 
299     // return the computed value
300     return val;
301 }
302 
303 
FillManipulator()304 FillManipulator::FillManipulator()
305 {
306     m_dir = Down;
307     m_changeformat = true;
308     setText(kundo2_i18n("Fill Selection"));
309 }
310 
~FillManipulator()311 FillManipulator::~FillManipulator()
312 {
313 }
314 
newValue(Element * element,int col,int row,bool * parse,Format::Type * fmtType)315 Value FillManipulator::newValue(Element *element, int col, int row,
316                                 bool *parse, Format::Type *fmtType)
317 {
318     Q_UNUSED(fmtType);
319     const int targetRow = row;
320     const int targetCol = col;
321     switch (m_dir) {
322     case Up:    row = element->rect().bottom(); break;
323     case Down:  row = element->rect().top();    break;
324     case Left:  col = element->rect().right();  break;
325     case Right: col = element->rect().left();   break;
326     };
327     Cell cell(m_sheet, col, row); // the reference cell
328     if (cell.isFormula()) {
329         *parse = true;
330         return Value(Cell(m_sheet, targetCol, targetRow).decodeFormula(cell.encodeFormula()));
331     }
332     return cell.value();
333 }
334 
newFormat(Element * element,int col,int row)335 Style FillManipulator::newFormat(Element *element, int col, int row)
336 {
337     switch (m_dir) {
338     case Up:    row = element->rect().bottom(); break;
339     case Down:  row = element->rect().top();    break;
340     case Left:  col = element->rect().right();  break;
341     case Right: col = element->rect().left();   break;
342     };
343     return Cell(m_sheet, col, row).style();
344 }
345 
CaseManipulator()346 CaseManipulator::CaseManipulator()
347 {
348     m_mode = Upper;
349     setText(kundo2_i18n("Change Case"));
350 }
351 
~CaseManipulator()352 CaseManipulator::~CaseManipulator()
353 {
354 }
355 
newValue(Element * element,int col,int row,bool * parse,Format::Type *)356 Value CaseManipulator::newValue(Element *element, int col, int row,
357                                 bool *parse, Format::Type *)
358 {
359     Q_UNUSED(element)
360     // if we are here, we know that we want the change
361     *parse = false;
362     QString str = Cell(m_sheet, col, row).value().asString();
363     switch (m_mode) {
364     case Upper: str = str.toUpper();
365         break;
366     case Lower: str = str.toLower();
367         break;
368     case FirstUpper:
369         if (str.length() > 0)
370             str = str.at(0).toUpper() + str.right(str.length() - 1);
371         break;
372     };
373     return Value(str);
374 }
375 
wantChange(Element * element,int col,int row)376 bool CaseManipulator::wantChange(Element *element, int col, int row)
377 {
378     Q_UNUSED(element)
379     Cell cell(m_sheet, col, row);
380     // don't change cells with a formula
381     if (cell.isFormula())
382         return false;
383     // don't change cells containing other things than strings
384     if (!cell.value().isString())
385         return false;
386     // original version was dismissing text starting with '!' and '*', is this
387     // necessary ?
388     return true;
389 }
390 
391 
392 
ShiftManipulator(KUndo2Command * parent)393 ShiftManipulator::ShiftManipulator(KUndo2Command *parent)
394         : AbstractRegionCommand(parent)
395         , m_mode(Insert)
396 {
397     m_checkLock = true;
398     setText(kundo2_i18n("Insert Cells"));
399 }
400 
~ShiftManipulator()401 ShiftManipulator::~ShiftManipulator()
402 {
403 }
404 
setReverse(bool reverse)405 void ShiftManipulator::setReverse(bool reverse)
406 {
407     m_reverse = reverse;
408     m_mode = reverse ? Delete : Insert;
409     if (!m_reverse)
410         setText(kundo2_i18n("Insert Cells"));
411     else
412         setText(kundo2_i18n("Remove Cells"));
413 }
414 
process(Element * element)415 bool ShiftManipulator::process(Element* element)
416 {
417     const QRect range = element->rect();
418     if (!m_reverse) { // insertion
419         if (m_direction == ShiftBottom) {
420             m_sheet->insertShiftDown(range);
421             m_sheet->cellStorage()->insertShiftDown(range);
422         } else if (m_direction == ShiftRight) {
423             m_sheet->insertShiftRight(range);
424             m_sheet->cellStorage()->insertShiftRight(range);
425         }
426 
427         // undo deletion
428         if (m_mode == Delete) {
429             KUndo2Command::undo(); // undo child commands
430         }
431     } else { // deletion
432         if (m_direction == ShiftBottom) {
433             m_sheet->removeShiftUp(range);
434             m_sheet->cellStorage()->removeShiftUp(range);
435         } else if (m_direction == ShiftRight) {
436             m_sheet->removeShiftLeft(range);
437             m_sheet->cellStorage()->removeShiftLeft(range);
438         }
439 
440         // undo insertion
441         if (m_mode == Insert) {
442             KUndo2Command::undo(); // undo child commands
443         }
444     }
445     return true;
446 }
447 
448 namespace Calligra
449 {
450 namespace Sheets
451 {
topRowLessThan(const Region::Element * e1,const Region::Element * e2)452 bool topRowLessThan(const Region::Element *e1, const Region::Element *e2)
453 {
454     return e1->rect().top() < e2->rect().top();
455 }
456 
leftColumnLessThan(const Region::Element * e1,const Region::Element * e2)457 bool leftColumnLessThan(const Region::Element *e1, const Region::Element *e2)
458 {
459     return e1->rect().top() < e2->rect().top();
460 }
461 } // namespace Sheets
462 } // namespace Calligra
463 
preProcessing()464 bool ShiftManipulator::preProcessing()
465 {
466     if (m_firstrun) {
467         // If we have an NCS, create a child command for each element.
468         if (cells().count() > 1) { // non-contiguous selection
469             // Sort the elements by their top row.
470             if (m_direction == ShiftBottom) {
471                 std::stable_sort(cells().begin(), cells().end(), topRowLessThan);
472             } else { // ShiftRight
473                 std::stable_sort(cells().begin(), cells().end(), leftColumnLessThan);
474             }
475             // Create sub-commands.
476             const Region::ConstIterator end(constEnd());
477             for (Region::ConstIterator it = constBegin(); it != end; ++it) {
478                 ShiftManipulator *const command = new ShiftManipulator(this);
479                 command->setSheet(m_sheet);
480                 command->add(Region((*it)->rect(), (*it)->sheet()));
481                 if (m_mode == Delete) {
482                     command->setReverse(true);
483                 }
484                 command->setDirection(m_direction);
485             }
486         } else { // contiguous selection
487             m_sheet->cellStorage()->startUndoRecording();
488         }
489     }
490     return AbstractRegionCommand::preProcessing();
491 }
492 
mainProcessing()493 bool ShiftManipulator::mainProcessing()
494 {
495     if (cells().count() > 1) { // non-contiguous selection
496         if ((m_reverse && m_mode == Insert) || (!m_reverse && m_mode == Delete)) {
497             KUndo2Command::undo(); // process all sub-commands
498         } else {
499             KUndo2Command::redo(); // process all sub-commands
500         }
501         return true;
502     }
503     return AbstractRegionCommand::mainProcessing(); // calls process(Element*)
504 }
505 
postProcessing()506 bool ShiftManipulator::postProcessing()
507 {
508     if (cells().count() > 1) { // non-contiguous selection
509         return true;
510     }
511     if (m_firstrun) {
512         m_sheet->cellStorage()->stopUndoRecording(this);
513     }
514     CellDamage *damage = 0;
515     if (m_direction == ShiftBottom) {
516         const QPoint bottomRight(lastRange().right(), KS_rowMax);
517         const Region region(QRect(lastRange().topLeft(), bottomRight), m_sheet);
518         damage = new CellDamage(m_sheet, region, CellDamage::Appearance);
519     } else { // ShiftRight
520         const QPoint bottomRight(KS_colMax, lastRange().bottom());
521         const Region region(QRect(lastRange().topLeft(), bottomRight), m_sheet);
522         damage = new CellDamage(m_sheet, region, CellDamage::Appearance);
523     }
524     m_sheet->map()->addDamage(damage);
525     return true;
526 }
527