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