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