1 /* This file is part of the KDE project
2 Copyright (C) 2005 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
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; either
7 version 2 of the License, or (at your option) any later version.
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 // Local
23 #include "RowColumnManipulators.h"
24
25 #include <float.h>
26
27 #include <QFontMetricsF>
28 #include <QWidget>
29 #include <QPen>
30
31 #include <KLocalizedString>
32
33 #include "CellStorage.h"
34 #include "Damages.h"
35 #include "Map.h"
36 #include "RowColumnFormat.h"
37 #include "RowFormatStorage.h"
38 #include "Sheet.h"
39 #include "Value.h"
40
41 using namespace Calligra::Sheets;
42
43 /***************************************************************************
44 class ResizeColumnManipulator
45 ****************************************************************************/
46
ResizeColumnManipulator(KUndo2Command * parent)47 ResizeColumnManipulator::ResizeColumnManipulator(KUndo2Command* parent)
48 : AbstractRegionCommand(parent)
49 {
50 setText(kundo2_i18n("Resize Column"));
51 }
52
~ResizeColumnManipulator()53 ResizeColumnManipulator::~ResizeColumnManipulator()
54 {
55 }
56
process(Element * element)57 bool ResizeColumnManipulator::process(Element* element)
58 {
59 QRect range = element->rect();
60 for (int col = range.right(); col >= range.left(); --col) {
61 ColumnFormat *format = m_sheet->nonDefaultColumnFormat(col);
62 if (m_firstrun)
63 m_oldSizes[col] = format->width();
64 qreal delta = format->width();
65 format->setWidth(m_reverse ? m_oldSizes[col] : qMax(2.0, m_newSize));
66 delta = format->width() - delta;
67 m_sheet->adjustCellAnchoredShapesX(delta, col+1);
68 }
69 // Just repaint everything visible; no need to invalidate the visual cache.
70 m_sheet->map()->addDamage(new SheetDamage(m_sheet, SheetDamage::ContentChanged));
71 // TODO: only invalidate the cells that are actually effected by this resize (so everythin in this column, and everything that covers something in this column)
72 m_sheet->map()->addDamage(new CellDamage(m_sheet, Region(1, 1, KS_colMax, KS_rowMax, m_sheet), CellDamage::Appearance));
73 return true;
74 }
75
76
77
78 /***************************************************************************
79 class ResizeRowManipulator
80 ****************************************************************************/
81
ResizeRowManipulator(KUndo2Command * parent)82 ResizeRowManipulator::ResizeRowManipulator(KUndo2Command* parent)
83 : AbstractRegionCommand(parent)
84 {
85 setText(kundo2_i18n("Resize Row"));
86 }
87
~ResizeRowManipulator()88 ResizeRowManipulator::~ResizeRowManipulator()
89 {
90 }
91
process(Element * element)92 bool ResizeRowManipulator::process(Element* element)
93 {
94 QRect range = element->rect();
95 // TODO: more efficiently store old sizes
96 if (m_firstrun) {
97 for (int row = range.bottom(); row >= range.top(); --row) {
98 m_oldSizes[row] = m_sheet->rowFormats()->rowHeight(row);
99 }
100 }
101 if (m_reverse) {
102 for (int row = range.bottom(); row >= range.top(); --row) {
103 m_sheet->rowFormats()->setRowHeight(row, row, m_oldSizes[row]);
104 }
105 } else {
106 m_sheet->rowFormats()->setRowHeight(range.top(), range.bottom(), m_newSize);
107 }
108 // TODO: more efficiently update positions of cell-anchored shapes
109 for (int row = range.top(); row <= range.bottom(); ++row) {
110 qreal delta = m_newSize - m_oldSizes[row];
111 if (m_reverse) delta = -delta;
112 m_sheet->adjustCellAnchoredShapesY(delta, row+1);
113 }
114 // Just repaint everything visible; no need to invalidate the visual cache.
115 m_sheet->map()->addDamage(new SheetDamage(m_sheet, SheetDamage::ContentChanged));
116 // TODO: only invalidate the cells that are actually effected by this resize (so everythin in this row, and everything that covers something in this row)
117 m_sheet->map()->addDamage(new CellDamage(m_sheet, Region(1, 1, KS_colMax, KS_rowMax, m_sheet), CellDamage::Appearance));
118 return true;
119 }
120
121
122 /***************************************************************************
123 class HideShowManipulator
124 ****************************************************************************/
125
HideShowManipulator(KUndo2Command * parent)126 HideShowManipulator::HideShowManipulator(KUndo2Command* parent)
127 : AbstractRegionCommand(parent),
128 m_manipulateColumns(false),
129 m_manipulateRows(false)
130 {
131 }
132
~HideShowManipulator()133 HideShowManipulator::~HideShowManipulator()
134 {
135 }
136
process(Element * element)137 bool HideShowManipulator::process(Element* element)
138 {
139 QRect range = element->rect();
140 if (m_manipulateColumns) {
141 for (int col = range.left(); col <= range.right(); ++col) {
142 ColumnFormat* format = m_sheet->nonDefaultColumnFormat(col);
143 format->setHidden(!m_reverse);
144 m_sheet->adjustCellAnchoredShapesX(m_reverse ? format->width() : -format->width(), col);
145 }
146 }
147 if (m_manipulateRows) {
148 m_sheet->rowFormats()->setHidden(range.top(), range.bottom(), !m_reverse);
149 qreal delta = m_sheet->rowFormats()->totalRowHeight(range.top(), range.bottom());
150 if (!m_reverse) delta = -delta;
151 m_sheet->adjustCellAnchoredShapesY(delta, range.top());
152 }
153 return true;
154 }
155
preProcessing()156 bool HideShowManipulator::preProcessing()
157 {
158 if (m_firstrun)
159 setText(name());
160 Region region;
161 ConstIterator endOfList = cells().constEnd();
162 for (ConstIterator it = cells().constBegin(); it != endOfList; ++it) {
163 if (m_reverse) {
164 QRect range = (*it)->rect();
165 if (m_manipulateColumns) {
166 if (range.left() > 1) {
167 int col;
168 for (col = 1; col < range.left(); ++col) {
169 if (!m_sheet->columnFormat(col)->isHidden())
170 break;
171 }
172 if (col == range.left()) {
173 region.add(QRect(1, 1, range.left() - 1, KS_rowMax));
174 }
175 }
176 for (int col = range.left(); col <= range.right(); ++col) {
177 if (m_sheet->columnFormat(col)->isHidden()) {
178 region.add(QRect(col, 1, 1, KS_rowMax));
179 }
180 }
181 }
182 if (m_manipulateRows) {
183 if (range.top() > 1) {
184 int row;
185 for (row = 1; row < range.top(); ++row) {
186 if (!m_sheet->rowFormats()->isHidden(row)) {
187 break;
188 }
189 }
190 if (row == range.top()) {
191 region.add(QRect(1, 1, KS_colMax, range.top() - 1));
192 }
193 }
194 for (int row = range.top(); row <= range.bottom(); ++row) {
195 if (!m_sheet->rowFormats()->isHidden(row)) {
196 region.add(QRect(1, row, KS_colMax, 1));
197 }
198 }
199 }
200 }
201
202 if (((*it)->isRow() && m_manipulateColumns) ||
203 ((*it)->isColumn() && m_manipulateRows)) {
204 /* KMessageBox::error( this, i18n( "Area is too large." ) );*/
205 return false;
206 }
207 }
208
209 if (m_reverse) {
210 clear();
211 add(region);
212 }
213
214 return AbstractRegionCommand::preProcessing();
215 }
216
postProcessing()217 bool HideShowManipulator::postProcessing()
218 {
219 // Just repaint everything visible; no need to invalidate the visual cache.
220 m_sheet->map()->addDamage(new SheetDamage(m_sheet, SheetDamage::ContentChanged));
221 return true;
222 }
223
name() const224 KUndo2MagicString HideShowManipulator::name() const
225 {
226 if (m_reverse && m_manipulateColumns && m_manipulateRows) {
227 return kundo2_i18n("Show Rows/Columns");
228 } else if (m_reverse && m_manipulateColumns) {
229 return kundo2_i18n("Show Columns");
230 } else if (m_reverse && m_manipulateRows) {
231 return kundo2_i18n("Show Rows");
232 } else if (!m_reverse && m_manipulateColumns && m_manipulateRows) {
233 return kundo2_i18n("Hide Rows/Columns");
234 } else if (!m_reverse && m_manipulateColumns) {
235 return kundo2_i18n("Hide Columns");
236 } else if (!m_reverse && m_manipulateRows) {
237 return kundo2_i18n("Hide Rows");
238 }
239
240 return kundo2_noi18n("XXX: bug!");
241 }
242
243 /***************************************************************************
244 class AdjustColumnRowManipulator
245 ****************************************************************************/
246
AdjustColumnRowManipulator(KUndo2Command * parent)247 AdjustColumnRowManipulator::AdjustColumnRowManipulator(KUndo2Command* parent)
248 : AbstractRegionCommand(parent),
249 m_adjustColumn(false),
250 m_adjustRow(false)
251 {
252 }
253
~AdjustColumnRowManipulator()254 AdjustColumnRowManipulator::~AdjustColumnRowManipulator()
255 {
256 }
257
process(Element * element)258 bool AdjustColumnRowManipulator::process(Element* element)
259 {
260 Sheet* sheet = m_sheet; // TODO Stefan: element->sheet();
261 if (m_sheet && sheet != m_sheet) {
262 return true;
263 }
264
265 QMap<int, double> heights;
266 QMap<int, double> widths;
267 if (m_reverse) {
268 heights = m_oldHeights;
269 widths = m_oldWidths;
270 } else {
271 heights = m_newHeights;
272 widths = m_newWidths;
273 }
274
275 QRect range = element->rect();
276 if (m_adjustColumn) {
277 if (element->isRow()) {
278 for (int row = range.top(); row <= range.bottom(); ++row) {
279 Cell cell = sheet->cellStorage()->firstInRow(row);
280 while (!cell.isNull()) {
281 int col = cell.column();
282 if (!cell.isEmpty() && !cell.isPartOfMerged()) {
283 if (widths.contains(col) && widths[col] != -1.0) {
284 ColumnFormat* format = sheet->nonDefaultColumnFormat(col);
285 if (qAbs(format->width() - widths[col]) > DBL_EPSILON) {
286 format->setWidth(qMax(2.0, widths[col]));
287 }
288 }
289 }
290 cell = sheet->cellStorage()->nextInRow(col, row);
291 }
292 }
293 } else {
294 for (int col = range.left(); col <= range.right(); ++col) {
295 if (widths.contains(col) && widths[col] != -1.0) {
296 ColumnFormat* format = sheet->nonDefaultColumnFormat(col);
297 if (qAbs(format->width() - widths[col]) > DBL_EPSILON) {
298 format->setWidth(qMax(2.0, widths[col]));
299 }
300 }
301 }
302 }
303 }
304 if (m_adjustRow) {
305 if (element->isColumn()) {
306 for (int col = range.left(); col <= range.right(); ++col) {
307 Cell cell = sheet->cellStorage()->firstInColumn(col);
308 while (!cell.isNull()) {
309 int row = cell.row();
310 if (!cell.isEmpty() && !cell.isPartOfMerged()) {
311 if (heights.contains(row) && heights[row] != -1.0) {
312 sheet->rowFormats()->setRowHeight(row, row, heights[row]);
313 }
314 }
315 cell = sheet->cellStorage()->nextInColumn(col, row);
316 }
317 }
318 } else {
319 for (int row = range.top(); row <= range.bottom(); ++row) {
320 if (heights.contains(row) && heights[row] != -1.0) {
321 sheet->rowFormats()->setRowHeight(row, row, heights[row]);
322 }
323 }
324 }
325 }
326 // The cell width(s) or height(s) changed, which are cached: rebuild them.
327 const Region region(m_adjustRow ? 1 : range.left(),
328 m_adjustColumn ? 1 : range.top(),
329 m_adjustRow ? KS_colMax : range.width(),
330 m_adjustColumn ? KS_rowMax : range.height());
331 m_sheet->map()->addDamage(new CellDamage(m_sheet, region, CellDamage::Appearance));
332 return true;
333 }
334
preProcessing()335 bool AdjustColumnRowManipulator::preProcessing()
336 {
337 if (m_firstrun)
338 setText(name());
339 if (m_reverse) {
340 } else {
341 if (!m_newHeights.isEmpty() || !m_newWidths.isEmpty()) {
342 return AbstractRegionCommand::preProcessing();
343 }
344 // createUndo();
345
346 ConstIterator endOfList(cells().constEnd());
347 for (ConstIterator it = cells().constBegin(); it != endOfList; ++it) {
348 Element* element = *it;
349 QRect range = element->rect();
350 if (element->isColumn()) {
351 for (int col = range.left(); col <= range.right(); ++col) {
352 Cell cell = m_sheet->cellStorage()->firstInColumn(col);
353 while (!cell.isNull()) {
354 int row = cell.row();
355 if (m_adjustColumn) {
356 if (!m_newWidths.contains(col)) {
357 m_newWidths[col] = -1.0;
358 m_oldWidths[col] = m_sheet->columnFormat(col)->width();
359 }
360 if (!cell.isEmpty() && !cell.isPartOfMerged()) {
361 m_newWidths[col] = qMax(adjustColumnHelper(cell),
362 m_newWidths[col]);
363 }
364 }
365 if (m_adjustRow) {
366 if (!m_newHeights.contains(row)) {
367 m_newHeights[row] = -1.0;
368 m_oldHeights[row] = m_sheet->rowFormats()->rowHeight(row);
369 }
370 if (!cell.isEmpty() && !cell.isPartOfMerged()) {
371 m_newHeights[row] = qMax(adjustRowHelper(cell),
372 m_newHeights[row]);
373 }
374 }
375 cell = m_sheet->cellStorage()->nextInColumn(col, row);
376 }
377 }
378 } else if (element->isRow()) {
379 for (int row = range.top(); row <= range.bottom(); ++row) {
380 Cell cell = m_sheet->cellStorage()->firstInRow(row);
381 while (!cell.isNull()) {
382 int col = cell.column();
383 if (m_adjustColumn) {
384 if (!m_newWidths.contains(col)) {
385 m_newWidths[col] = -1.0;
386 m_oldWidths[col] = m_sheet->columnFormat(col)->width();
387 }
388 if (!cell.isEmpty() && !cell.isPartOfMerged()) {
389 m_newWidths[col] = qMax(adjustColumnHelper(cell),
390 m_newWidths[col]);
391 }
392 }
393 if (m_adjustRow) {
394 if (!m_newHeights.contains(row)) {
395 m_newHeights[row] = -1.0;
396 m_oldHeights[row] = m_sheet->rowFormats()->rowHeight(row);
397 }
398 if (!cell.isEmpty() && !cell.isPartOfMerged()) {
399 m_newHeights[row] = qMax(adjustRowHelper(cell),
400 m_newHeights[row]);
401 }
402 }
403 cell = m_sheet->cellStorage()->nextInRow(col, row);
404 }
405 }
406 } else {
407 Cell cell;
408 for (int col = range.left(); col <= range.right(); ++col) {
409 for (int row = range.top(); row <= range.bottom(); ++row) {
410 cell = Cell(m_sheet, col, row);
411 if (m_adjustColumn) {
412 if (!m_newWidths.contains(col)) {
413 m_newWidths[col] = -1.0;
414 m_oldWidths[col] = m_sheet->columnFormat(col)->width();
415 }
416 if (!cell.isEmpty() && !cell.isPartOfMerged()) {
417 m_newWidths[col] = qMax(adjustColumnHelper(cell),
418 m_newWidths[col]);
419 }
420 }
421 if (m_adjustRow) {
422 if (!m_newHeights.contains(row)) {
423 m_newHeights[row] = -1.0;
424 m_oldHeights[row] = m_sheet->rowFormats()->rowHeight(row);
425 }
426 if (!cell.isEmpty() && !cell.isPartOfMerged()) {
427 m_newHeights[row] = qMax(adjustRowHelper(cell),
428 m_newHeights[row]);
429 }
430 }
431 }
432 }
433 }
434 }
435 }
436 return AbstractRegionCommand::preProcessing();
437 }
438
postProcessing()439 bool AdjustColumnRowManipulator::postProcessing()
440 {
441 if (!m_adjustColumn && !m_adjustRow) {
442 return false;
443 }
444 // Update the column/row header, if necessary.
445 SheetDamage::Changes changes = SheetDamage::None;
446 if (m_adjustColumn) {
447 changes |= SheetDamage::ColumnsChanged;
448 }
449 if (m_adjustRow) {
450 changes |= SheetDamage::RowsChanged;
451 }
452 m_sheet->map()->addDamage(new SheetDamage(m_sheet, changes));
453 return AbstractRegionCommand::postProcessing();
454 }
455
456 class DummyWidget : public QWidget
457 {
metric(PaintDeviceMetric metric) const458 int metric(PaintDeviceMetric metric) const override {
459 switch (metric) {
460 case QPaintDevice::PdmDpiX:
461 case QPaintDevice::PdmDpiY:
462 case QPaintDevice::PdmPhysicalDpiX:
463 case QPaintDevice::PdmPhysicalDpiY:
464 return 72;
465 default:
466 break;
467 }
468 return QWidget::metric(metric);
469 }
470 };
471
textSize(const QString & text,const Style & style) const472 QSizeF AdjustColumnRowManipulator::textSize(const QString& text, const Style& style) const
473 {
474 QSizeF size;
475 DummyWidget dummyWiget;
476 const QFontMetricsF fontMetrics(style.font(), &dummyWiget);
477
478 // Set size to correct values according to
479 // if the text is horizontal, vertical or rotated.
480 if (!style.verticalText() && !style.angle()) {
481 // Horizontal text.
482
483 size = fontMetrics.size(0, text);
484 double offsetFont = 0.0;
485 if ((style.valign() == Style::Bottom) && style.underline())
486 offsetFont = fontMetrics.underlinePos() + 1;
487
488 size.setHeight((fontMetrics.ascent() + fontMetrics.descent() + offsetFont)
489 *(text.count('\n') + 1));
490 } else if (style.angle() != 0) {
491 // Rotated text.
492
493 const double height = fontMetrics.ascent() + fontMetrics.descent();
494 const double width = fontMetrics.width(text);
495 size.setHeight(height * ::cos(style.angle() * M_PI / 180)
496 + qAbs(width * ::sin(style.angle() * M_PI / 180)));
497 size.setWidth(qAbs(height * ::sin(style.angle() * M_PI / 180))
498 + width * ::cos(style.angle() * M_PI / 180));
499 } else {
500 // Vertical text.
501
502 qreal width = 0.0;
503 for (int i = 0; i < text.length(); i++)
504 width = qMax(width, fontMetrics.width(text.at(i)));
505
506 size.setWidth(width);
507 size.setHeight((fontMetrics.ascent() + fontMetrics.descent())
508 * text.length());
509 }
510 return size;
511 }
512
adjustColumnHelper(const Cell & cell)513 double AdjustColumnRowManipulator::adjustColumnHelper(const Cell& cell)
514 {
515 double long_max = 0.0;
516 const Style style = cell.effectiveStyle();
517 const QSizeF size = textSize(cell.displayText(), style);
518 if (size.width() > long_max) {
519 double indent = 0.0;
520 Style::HAlign alignment = style.halign();
521 if (alignment == Style::HAlignUndefined) {
522 if (cell.value().isNumber() || cell.isDate() || cell.isTime())
523 alignment = Style::Right;
524 else
525 alignment = Style::Left;
526 }
527 if (alignment == Style::Left)
528 indent = cell.style().indentation();
529 long_max = indent + size.width()
530 + style.leftBorderPen().width() + style.rightBorderPen().width();
531 // if this cell has others merged into it, we'll subtract the width of those columns
532 // this is not perfect, but at least should work in 90% of the cases
533 const int mergedXCount = cell.mergedXCells();
534 if (mergedXCount > 0) {
535 for (int col = 1; col <= mergedXCount; col++) {
536 double cw = cell.sheet()->columnFormat(cell.column() + col)->width();
537 long_max -= cw;
538 }
539 }
540 }
541 // add 4 because long_max is the length of the text
542 // but column has borders
543 if (long_max == 0.0)
544 return -1.0;
545 else
546 return long_max + 4.0;
547 }
548
adjustRowHelper(const Cell & cell)549 double AdjustColumnRowManipulator::adjustRowHelper(const Cell& cell)
550 {
551 double long_max = 0.0;
552 const Style style = cell.effectiveStyle();
553 const QSizeF size = textSize(cell.displayText(), style);
554 if (size.height() > long_max)
555 long_max = size.height() + style.topBorderPen().width() + style.bottomBorderPen().width();
556 // add 1 because long_max is the height of the text
557 // but row has borders
558 if (long_max == 0.0)
559 return -1.0;
560 else
561 return long_max + 1.0;
562 }
563
name() const564 KUndo2MagicString AdjustColumnRowManipulator::name() const
565 {
566 if (m_adjustColumn && m_adjustRow) {
567 return kundo2_i18n("Adjust Columns/Rows");
568 } else if (m_adjustColumn) {
569 return kundo2_i18n("Adjust Columns");
570 } else {
571 return kundo2_i18n("Adjust Rows");
572 }
573 }
574
575 /***************************************************************************
576 class InsertDeleteColumnManipulator
577 ****************************************************************************/
578
InsertDeleteColumnManipulator(KUndo2Command * parent)579 InsertDeleteColumnManipulator::InsertDeleteColumnManipulator(KUndo2Command *parent)
580 : AbstractRegionCommand(parent)
581 , m_mode(Insert)
582 , m_template(0)
583 {
584 setText(kundo2_i18n("Insert Columns"));
585 }
586
~InsertDeleteColumnManipulator()587 InsertDeleteColumnManipulator::~InsertDeleteColumnManipulator()
588 {
589 delete m_template;
590 }
591
setTemplate(const ColumnFormat & columnFormat)592 void InsertDeleteColumnManipulator::setTemplate(const ColumnFormat &columnFormat)
593 {
594 delete m_template;
595 m_template = new ColumnFormat(columnFormat);
596 }
597
setReverse(bool reverse)598 void InsertDeleteColumnManipulator::setReverse(bool reverse)
599 {
600 m_reverse = reverse;
601 m_mode = reverse ? Delete : Insert;
602 if (!m_reverse)
603 setText(kundo2_i18n("Insert Columns"));
604 else
605 setText(kundo2_i18n("Remove Columns"));
606 }
607
process(Element * element)608 bool InsertDeleteColumnManipulator::process(Element* element)
609 {
610 const QRect range = element->rect();
611 const int pos = range.left();
612 const int num = range.width();
613 if (!m_reverse) { // insertion
614 // insert rows
615 m_sheet->insertColumns(pos, num);
616 if (m_template) {
617 m_template->setSheet(m_sheet);
618 const int end = pos + num - 1;
619 for (int col = pos; col <= end; ++col) {
620 m_template->setColumn(col);
621 m_sheet->insertColumnFormat(m_template);
622 }
623 }
624 m_sheet->cellStorage()->insertColumns(pos, num);
625
626 // undo deletion
627 if (m_mode == Delete) {
628 KUndo2Command::undo(); // process child commands (from CellStorage)
629 }
630 } else {
631 // delete rows
632 m_sheet->removeColumns(pos, num);
633 m_sheet->cellStorage()->removeColumns(pos, num);
634
635 // undo insertion
636 if (m_mode == Insert) {
637 KUndo2Command::undo(); // process child commands (from CellStorage)
638 }
639 }
640 return true;
641 }
642
elementLeftColumnLessThan(const Calligra::Sheets::Region::Element * e1,const Calligra::Sheets::Region::Element * e2)643 bool elementLeftColumnLessThan(const Calligra::Sheets::Region::Element *e1, const Calligra::Sheets::Region::Element *e2)
644 {
645 return e1->rect().left() < e2->rect().left();
646 }
647
preProcessing()648 bool InsertDeleteColumnManipulator::preProcessing()
649 {
650 if (m_firstrun) {
651 // If we have an NCS, create a child command for each element.
652 if (cells().count() > 1) { // non-contiguous selection
653 // Sort the elements by their top row.
654 std::stable_sort(cells().begin(), cells().end(), elementLeftColumnLessThan);
655 // Create sub-commands.
656 const Region::ConstIterator end(constEnd());
657 for (Region::ConstIterator it = constBegin(); it != end; ++it) {
658 InsertDeleteColumnManipulator *const command = new InsertDeleteColumnManipulator(this);
659 command->setSheet(m_sheet);
660 command->add(Region((*it)->rect(), (*it)->sheet()));
661 if (m_mode == Delete) {
662 command->setReverse(true);
663 }
664 }
665 } else { // contiguous selection
666 m_sheet->cellStorage()->startUndoRecording();
667 }
668 }
669 return AbstractRegionCommand::preProcessing();
670 }
671
mainProcessing()672 bool InsertDeleteColumnManipulator::mainProcessing()
673 {
674 if (cells().count() > 1) { // non-contiguous selection
675 if ((m_reverse && m_mode == Insert) || (!m_reverse && m_mode == Delete)) {
676 KUndo2Command::undo(); // process all sub-commands
677 } else {
678 KUndo2Command::redo(); // process all sub-commands
679 }
680 return true;
681 }
682 return AbstractRegionCommand::mainProcessing(); // calls process(Element*)
683 }
684
postProcessing()685 bool InsertDeleteColumnManipulator::postProcessing()
686 {
687 if (cells().count() > 1) { // non-contiguous selection
688 return true;
689 }
690 if (m_firstrun) {
691 m_sheet->cellStorage()->stopUndoRecording(this);
692 }
693 const QRect rect(QPoint(boundingRect().left(), 1), QPoint(KS_colMax, KS_rowMax));
694 m_sheet->map()->addDamage(new CellDamage(m_sheet, Region(rect, m_sheet), CellDamage::Appearance));
695 return true;
696 }
697
698 /***************************************************************************
699 class InsertDeleteRowManipulator
700 ****************************************************************************/
701
InsertDeleteRowManipulator(KUndo2Command * parent)702 InsertDeleteRowManipulator::InsertDeleteRowManipulator(KUndo2Command *parent)
703 : AbstractRegionCommand(parent)
704 , m_mode(Insert)
705 , m_template(0)
706 {
707 setText(kundo2_i18n("Insert Rows"));
708 }
709
~InsertDeleteRowManipulator()710 InsertDeleteRowManipulator::~InsertDeleteRowManipulator()
711 {
712 delete m_template;
713 }
714
setTemplate(const RowFormat & rowFormat)715 void InsertDeleteRowManipulator::setTemplate(const RowFormat &rowFormat)
716 {
717 delete m_template;
718 m_template = new RowFormat(rowFormat);
719 }
720
setReverse(bool reverse)721 void InsertDeleteRowManipulator::setReverse(bool reverse)
722 {
723 m_reverse = reverse;
724 m_mode = reverse ? Delete : Insert;
725 if (!m_reverse)
726 setText(kundo2_i18n("Insert Rows"));
727 else
728 setText(kundo2_i18n("Remove Rows"));
729 }
730
process(Element * element)731 bool InsertDeleteRowManipulator::process(Element* element)
732 {
733 const QRect range = element->rect();
734 const int pos = range.top();
735 const int num = range.height();
736 if (!m_reverse) { // insertion
737 // insert rows
738 m_sheet->insertRows(pos, num);
739 if (m_template) {
740 m_template->setSheet(m_sheet);
741 const int end = pos + num - 1;
742 for (int row = pos; row <= end; ++row) {
743 m_template->setRow(row);
744 m_sheet->insertRowFormat(m_template);
745 }
746 }
747 m_sheet->cellStorage()->insertRows(pos, num);
748
749 // undo deletion
750 if (m_mode == Delete) {
751 KUndo2Command::undo(); // process child commands (from CellStorage)
752 }
753 } else {
754 // delete rows
755 m_sheet->removeRows(pos, num);
756 m_sheet->cellStorage()->removeRows(pos, num);
757
758 // undo insertion
759 if (m_mode == Insert) {
760 KUndo2Command::undo(); // process child commands (from CellStorage)
761 }
762 }
763 return true;
764 }
765
elementTopRowLessThan(const Calligra::Sheets::Region::Element * e1,const Calligra::Sheets::Region::Element * e2)766 bool elementTopRowLessThan(const Calligra::Sheets::Region::Element *e1, const Calligra::Sheets::Region::Element *e2)
767 {
768 return e1->rect().top() < e2->rect().top();
769 }
770
preProcessing()771 bool InsertDeleteRowManipulator::preProcessing()
772 {
773 if (m_firstrun) {
774 // If we have an NCS, create a child command for each element.
775 if (cells().count() > 1) { // non-contiguous selection
776 // Sort the elements by their top row.
777 std::stable_sort(cells().begin(), cells().end(), elementTopRowLessThan);
778 // Create sub-commands.
779 const Region::ConstIterator end(constEnd());
780 for (Region::ConstIterator it = constBegin(); it != end; ++it) {
781 InsertDeleteRowManipulator *const command = new InsertDeleteRowManipulator(this);
782 command->setSheet(m_sheet);
783 command->add(Region((*it)->rect(), (*it)->sheet()));
784 if (m_mode == Delete) {
785 command->setReverse(true);
786 }
787 }
788 } else { // contiguous selection
789 m_sheet->cellStorage()->startUndoRecording();
790 }
791 }
792 return AbstractRegionCommand::preProcessing();
793 }
794
mainProcessing()795 bool InsertDeleteRowManipulator::mainProcessing()
796 {
797 if (cells().count() > 1) { // non-contiguous selection
798 if ((m_reverse && m_mode == Insert) || (!m_reverse && m_mode == Delete)) {
799 KUndo2Command::undo(); // process all sub-commands
800 } else {
801 KUndo2Command::redo(); // process all sub-commands
802 }
803 return true;
804 }
805 return AbstractRegionCommand::mainProcessing(); // calls process(Element*)
806 }
807
postProcessing()808 bool InsertDeleteRowManipulator::postProcessing()
809 {
810 if (cells().count() > 1) { // non-contiguous selection
811 return true;
812 }
813 if (m_firstrun) {
814 m_sheet->cellStorage()->stopUndoRecording(this);
815 }
816 const QRect rect(QPoint(1, boundingRect().top()), QPoint(KS_colMax, KS_rowMax));
817 m_sheet->map()->addDamage(new CellDamage(m_sheet, Region(rect, m_sheet), CellDamage::Appearance));
818 return true;
819 }
820