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

0001 /* This file is part of the KDE project
0002    Copyright (C) 2005 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include <algorithm>
0021 
0022 // Local
0023 #include "RowColumnManipulators.h"
0024 
0025 #include <float.h>
0026 
0027 #include <QFontMetricsF>
0028 #include <QWidget>
0029 #include <QPen>
0030 
0031 #include <KLocalizedString>
0032 
0033 #include "CellStorage.h"
0034 #include "Damages.h"
0035 #include "Map.h"
0036 #include "RowColumnFormat.h"
0037 #include "RowFormatStorage.h"
0038 #include "Sheet.h"
0039 #include "Value.h"
0040 
0041 using namespace Calligra::Sheets;
0042 
0043 /***************************************************************************
0044   class ResizeColumnManipulator
0045 ****************************************************************************/
0046 
0047 ResizeColumnManipulator::ResizeColumnManipulator(KUndo2Command* parent)
0048         : AbstractRegionCommand(parent)
0049 {
0050     setText(kundo2_i18n("Resize Column"));
0051 }
0052 
0053 ResizeColumnManipulator::~ResizeColumnManipulator()
0054 {
0055 }
0056 
0057 bool ResizeColumnManipulator::process(Element* element)
0058 {
0059     QRect range = element->rect();
0060     for (int col = range.right(); col >= range.left(); --col) {
0061         ColumnFormat *format = m_sheet->nonDefaultColumnFormat(col);
0062         if (m_firstrun)
0063             m_oldSizes[col] = format->width();
0064         qreal delta = format->width();
0065         format->setWidth(m_reverse ? m_oldSizes[col] : qMax(2.0, m_newSize));
0066         delta = format->width() - delta;
0067         m_sheet->adjustCellAnchoredShapesX(delta, col+1);
0068     }
0069     // Just repaint everything visible; no need to invalidate the visual cache.
0070     m_sheet->map()->addDamage(new SheetDamage(m_sheet, SheetDamage::ContentChanged));
0071     // 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)
0072     m_sheet->map()->addDamage(new CellDamage(m_sheet, Region(1, 1, KS_colMax, KS_rowMax, m_sheet), CellDamage::Appearance));
0073     return true;
0074 }
0075 
0076 
0077 
0078 /***************************************************************************
0079   class ResizeRowManipulator
0080 ****************************************************************************/
0081 
0082 ResizeRowManipulator::ResizeRowManipulator(KUndo2Command* parent)
0083         : AbstractRegionCommand(parent)
0084 {
0085     setText(kundo2_i18n("Resize Row"));
0086 }
0087 
0088 ResizeRowManipulator::~ResizeRowManipulator()
0089 {
0090 }
0091 
0092 bool ResizeRowManipulator::process(Element* element)
0093 {
0094     QRect range = element->rect();
0095     // TODO: more efficiently store old sizes
0096     if (m_firstrun) {
0097         for (int row = range.bottom(); row >= range.top(); --row) {
0098             m_oldSizes[row] = m_sheet->rowFormats()->rowHeight(row);
0099         }
0100     }
0101     if (m_reverse) {
0102         for (int row = range.bottom(); row >= range.top(); --row) {
0103             m_sheet->rowFormats()->setRowHeight(row, row, m_oldSizes[row]);
0104         }
0105     } else {
0106         m_sheet->rowFormats()->setRowHeight(range.top(), range.bottom(), m_newSize);
0107     }
0108     // TODO: more efficiently update positions of cell-anchored shapes
0109     for (int row = range.top(); row <= range.bottom(); ++row) {
0110         qreal delta = m_newSize - m_oldSizes[row];
0111         if (m_reverse) delta = -delta;
0112         m_sheet->adjustCellAnchoredShapesY(delta, row+1);
0113     }
0114     // Just repaint everything visible; no need to invalidate the visual cache.
0115     m_sheet->map()->addDamage(new SheetDamage(m_sheet, SheetDamage::ContentChanged));
0116     // 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)
0117     m_sheet->map()->addDamage(new CellDamage(m_sheet, Region(1, 1, KS_colMax, KS_rowMax, m_sheet), CellDamage::Appearance));
0118     return true;
0119 }
0120 
0121 
0122 /***************************************************************************
0123   class HideShowManipulator
0124 ****************************************************************************/
0125 
0126 HideShowManipulator::HideShowManipulator(KUndo2Command* parent)
0127         : AbstractRegionCommand(parent),
0128         m_manipulateColumns(false),
0129         m_manipulateRows(false)
0130 {
0131 }
0132 
0133 HideShowManipulator::~HideShowManipulator()
0134 {
0135 }
0136 
0137 bool HideShowManipulator::process(Element* element)
0138 {
0139     QRect range = element->rect();
0140     if (m_manipulateColumns) {
0141         for (int col = range.left(); col <= range.right(); ++col) {
0142             ColumnFormat* format = m_sheet->nonDefaultColumnFormat(col);
0143             format->setHidden(!m_reverse);
0144             m_sheet->adjustCellAnchoredShapesX(m_reverse ? format->width() : -format->width(), col);
0145         }
0146     }
0147     if (m_manipulateRows) {
0148         m_sheet->rowFormats()->setHidden(range.top(), range.bottom(), !m_reverse);
0149         qreal delta = m_sheet->rowFormats()->totalRowHeight(range.top(), range.bottom());
0150         if (!m_reverse) delta = -delta;
0151         m_sheet->adjustCellAnchoredShapesY(delta, range.top());
0152     }
0153     return true;
0154 }
0155 
0156 bool HideShowManipulator::preProcessing()
0157 {
0158     if (m_firstrun)
0159         setText(name());
0160     Region region;
0161     ConstIterator endOfList = cells().constEnd();
0162     for (ConstIterator it = cells().constBegin(); it != endOfList; ++it) {
0163         if (m_reverse) {
0164             QRect range = (*it)->rect();
0165             if (m_manipulateColumns) {
0166                 if (range.left() > 1) {
0167                     int col;
0168                     for (col = 1; col < range.left(); ++col) {
0169                         if (!m_sheet->columnFormat(col)->isHidden())
0170                             break;
0171                     }
0172                     if (col == range.left()) {
0173                         region.add(QRect(1, 1, range.left() - 1, KS_rowMax));
0174                     }
0175                 }
0176                 for (int col = range.left(); col <= range.right(); ++col) {
0177                     if (m_sheet->columnFormat(col)->isHidden()) {
0178                         region.add(QRect(col, 1, 1, KS_rowMax));
0179                     }
0180                 }
0181             }
0182             if (m_manipulateRows) {
0183                 if (range.top() > 1) {
0184                     int row;
0185                     for (row = 1; row < range.top(); ++row) {
0186                         if (!m_sheet->rowFormats()->isHidden(row)) {
0187                             break;
0188                         }
0189                     }
0190                     if (row == range.top()) {
0191                         region.add(QRect(1, 1, KS_colMax, range.top() - 1));
0192                     }
0193                 }
0194                 for (int row = range.top(); row <= range.bottom(); ++row) {
0195                     if (m_sheet->rowFormats()->isHidden(row)) {
0196                         region.add(QRect(1, row, KS_colMax, 1));
0197                     }
0198                 }
0199             }
0200         }
0201 
0202         if (((*it)->isRow() && m_manipulateColumns) ||
0203                 ((*it)->isColumn() && m_manipulateRows)) {
0204             /*      KMessageBox::error( this, i18n( "Area is too large." ) );*/
0205             return false;
0206         }
0207     }
0208 
0209     if (m_reverse) {
0210         clear();
0211         add(region);
0212     }
0213 
0214     return AbstractRegionCommand::preProcessing();
0215 }
0216 
0217 bool HideShowManipulator::postProcessing()
0218 {
0219     // Just repaint everything visible; no need to invalidate the visual cache.
0220     m_sheet->map()->addDamage(new SheetDamage(m_sheet, SheetDamage::ContentChanged));
0221     return true;
0222 }
0223 
0224 KUndo2MagicString HideShowManipulator::name() const
0225 {
0226     if (m_reverse && m_manipulateColumns && m_manipulateRows) {
0227         return kundo2_i18n("Show Rows/Columns");
0228     } else if (m_reverse && m_manipulateColumns) {
0229         return kundo2_i18n("Show Columns");
0230     } else if (m_reverse && m_manipulateRows) {
0231         return kundo2_i18n("Show Rows");
0232     } else if (!m_reverse && m_manipulateColumns && m_manipulateRows) {
0233         return kundo2_i18n("Hide Rows/Columns");
0234     } else if (!m_reverse && m_manipulateColumns) {
0235         return kundo2_i18n("Hide Columns");
0236     } else if (!m_reverse && m_manipulateRows) {
0237         return kundo2_i18n("Hide Rows");
0238     }
0239 
0240     return kundo2_noi18n("XXX: bug!");
0241 }
0242 
0243 /***************************************************************************
0244   class AdjustColumnRowManipulator
0245 ****************************************************************************/
0246 
0247 AdjustColumnRowManipulator::AdjustColumnRowManipulator(KUndo2Command* parent)
0248         : AbstractRegionCommand(parent),
0249         m_adjustColumn(false),
0250         m_adjustRow(false)
0251 {
0252 }
0253 
0254 AdjustColumnRowManipulator::~AdjustColumnRowManipulator()
0255 {
0256 }
0257 
0258 bool AdjustColumnRowManipulator::process(Element* element)
0259 {
0260     Sheet* sheet = m_sheet; // TODO Stefan: element->sheet();
0261     if (m_sheet && sheet != m_sheet) {
0262         return true;
0263     }
0264 
0265     QMap<int, double> heights;
0266     QMap<int, double> widths;
0267     if (m_reverse) {
0268         heights = m_oldHeights;
0269         widths = m_oldWidths;
0270     } else {
0271         heights = m_newHeights;
0272         widths = m_newWidths;
0273     }
0274 
0275     QRect range = element->rect();
0276     if (m_adjustColumn) {
0277         if (element->isRow()) {
0278             for (int row = range.top(); row <= range.bottom(); ++row) {
0279                 Cell cell = sheet->cellStorage()->firstInRow(row);
0280                 while (!cell.isNull()) {
0281                     int col = cell.column();
0282                     if (!cell.isEmpty() && !cell.isPartOfMerged()) {
0283                         if (widths.contains(col) && widths[col] != -1.0) {
0284                             ColumnFormat* format = sheet->nonDefaultColumnFormat(col);
0285                             if (qAbs(format->width() - widths[col]) > DBL_EPSILON) {
0286                                 format->setWidth(qMax(2.0, widths[col]));
0287                             }
0288                         }
0289                     }
0290                     cell = sheet->cellStorage()->nextInRow(col, row);
0291                 }
0292             }
0293         } else {
0294             for (int col = range.left(); col <= range.right(); ++col) {
0295                 if (widths.contains(col) && widths[col] != -1.0) {
0296                     ColumnFormat* format = sheet->nonDefaultColumnFormat(col);
0297                     if (qAbs(format->width() - widths[col]) > DBL_EPSILON) {
0298                         format->setWidth(qMax(2.0, widths[col]));
0299                     }
0300                 }
0301             }
0302         }
0303     }
0304     if (m_adjustRow) {
0305         if (element->isColumn()) {
0306             for (int col = range.left(); col <= range.right(); ++col) {
0307                 Cell cell = sheet->cellStorage()->firstInColumn(col);
0308                 while (!cell.isNull()) {
0309                     int row = cell.row();
0310                     if (!cell.isEmpty() && !cell.isPartOfMerged()) {
0311                         if (heights.contains(row) && heights[row] != -1.0) {
0312                             sheet->rowFormats()->setRowHeight(row, row, heights[row]);
0313                         }
0314                     }
0315                     cell = sheet->cellStorage()->nextInColumn(col, row);
0316                 }
0317             }
0318         } else {
0319             for (int row = range.top(); row <= range.bottom(); ++row) {
0320                 if (heights.contains(row) && heights[row] != -1.0) {
0321                     sheet->rowFormats()->setRowHeight(row, row, heights[row]);
0322                 }
0323             }
0324         }
0325     }
0326     // The cell width(s) or height(s) changed, which are cached: rebuild them.
0327     const Region region(m_adjustRow ? 1 : range.left(),
0328                         m_adjustColumn ? 1 : range.top(),
0329                         m_adjustRow ? KS_colMax : range.width(),
0330                         m_adjustColumn ? KS_rowMax : range.height());
0331     m_sheet->map()->addDamage(new CellDamage(m_sheet, region, CellDamage::Appearance));
0332     return true;
0333 }
0334 
0335 bool AdjustColumnRowManipulator::preProcessing()
0336 {
0337     if (m_firstrun)
0338         setText(name());
0339     if (m_reverse) {
0340     } else {
0341         if (!m_newHeights.isEmpty() || !m_newWidths.isEmpty()) {
0342             return AbstractRegionCommand::preProcessing();
0343         }
0344 //     createUndo();
0345 
0346         ConstIterator endOfList(cells().constEnd());
0347         for (ConstIterator it = cells().constBegin(); it != endOfList; ++it) {
0348             Element* element = *it;
0349             QRect range = element->rect();
0350             if (element->isColumn()) {
0351                 for (int col = range.left(); col <= range.right(); ++col) {
0352                     Cell cell = m_sheet->cellStorage()->firstInColumn(col);
0353                     while (!cell.isNull()) {
0354                         int row = cell.row();
0355                         if (m_adjustColumn) {
0356                             if (!m_newWidths.contains(col)) {
0357                                 m_newWidths[col] = -1.0;
0358                                 m_oldWidths[col] = m_sheet->columnFormat(col)->width();
0359                             }
0360                             if (!cell.isEmpty() && !cell.isPartOfMerged()) {
0361                                 m_newWidths[col] = qMax(adjustColumnHelper(cell),
0362                                                         m_newWidths[col]);
0363                             }
0364                         }
0365                         if (m_adjustRow) {
0366                             if (!m_newHeights.contains(row)) {
0367                                 m_newHeights[row] = -1.0;
0368                                 m_oldHeights[row] = m_sheet->rowFormats()->rowHeight(row);
0369                             }
0370                             if (!cell.isEmpty() && !cell.isPartOfMerged()) {
0371                                 m_newHeights[row] = qMax(adjustRowHelper(cell),
0372                                                          m_newHeights[row]);
0373                             }
0374                         }
0375                         cell = m_sheet->cellStorage()->nextInColumn(col, row);
0376                     }
0377                 }
0378             } else if (element->isRow()) {
0379                 for (int row = range.top(); row <= range.bottom(); ++row) {
0380                     Cell cell = m_sheet->cellStorage()->firstInRow(row);
0381                     while (!cell.isNull()) {
0382                         int col = cell.column();
0383                         if (m_adjustColumn) {
0384                             if (!m_newWidths.contains(col)) {
0385                                 m_newWidths[col] = -1.0;
0386                                 m_oldWidths[col] = m_sheet->columnFormat(col)->width();
0387                             }
0388                             if (!cell.isEmpty() && !cell.isPartOfMerged()) {
0389                                 m_newWidths[col] = qMax(adjustColumnHelper(cell),
0390                                                         m_newWidths[col]);
0391                             }
0392                         }
0393                         if (m_adjustRow) {
0394                             if (!m_newHeights.contains(row)) {
0395                                 m_newHeights[row] = -1.0;
0396                                 m_oldHeights[row] = m_sheet->rowFormats()->rowHeight(row);
0397                             }
0398                             if (!cell.isEmpty() && !cell.isPartOfMerged()) {
0399                                 m_newHeights[row] = qMax(adjustRowHelper(cell),
0400                                                          m_newHeights[row]);
0401                             }
0402                         }
0403                         cell = m_sheet->cellStorage()->nextInRow(col, row);
0404                     }
0405                 }
0406             } else {
0407                 Cell cell;
0408                 for (int col = range.left(); col <= range.right(); ++col) {
0409                     for (int row = range.top(); row <= range.bottom(); ++row) {
0410                         cell = Cell(m_sheet,  col, row);
0411                         if (m_adjustColumn) {
0412                             if (!m_newWidths.contains(col)) {
0413                                 m_newWidths[col] = -1.0;
0414                                 m_oldWidths[col] = m_sheet->columnFormat(col)->width();
0415                             }
0416                             if (!cell.isEmpty() && !cell.isPartOfMerged()) {
0417                                 m_newWidths[col] = qMax(adjustColumnHelper(cell),
0418                                                         m_newWidths[col]);
0419                             }
0420                         }
0421                         if (m_adjustRow) {
0422                             if (!m_newHeights.contains(row)) {
0423                                 m_newHeights[row] = -1.0;
0424                                 m_oldHeights[row] = m_sheet->rowFormats()->rowHeight(row);
0425                             }
0426                             if (!cell.isEmpty() && !cell.isPartOfMerged()) {
0427                                 m_newHeights[row] = qMax(adjustRowHelper(cell),
0428                                                          m_newHeights[row]);
0429                             }
0430                         }
0431                     }
0432                 }
0433             }
0434         }
0435     }
0436     return AbstractRegionCommand::preProcessing();
0437 }
0438 
0439 bool AdjustColumnRowManipulator::postProcessing()
0440 {
0441     if (!m_adjustColumn && !m_adjustRow) {
0442         return false;
0443     }
0444     // Update the column/row header, if necessary.
0445     SheetDamage::Changes changes = SheetDamage::None;
0446     if (m_adjustColumn) {
0447         changes |= SheetDamage::ColumnsChanged;
0448     }
0449     if (m_adjustRow) {
0450         changes |= SheetDamage::RowsChanged;
0451     }
0452     m_sheet->map()->addDamage(new SheetDamage(m_sheet, changes));
0453     return AbstractRegionCommand::postProcessing();
0454 }
0455 
0456 class DummyWidget : public QWidget
0457 {
0458     int metric(PaintDeviceMetric metric) const override {
0459         switch (metric) {
0460         case QPaintDevice::PdmDpiX:
0461         case QPaintDevice::PdmDpiY:
0462         case QPaintDevice::PdmPhysicalDpiX:
0463         case QPaintDevice::PdmPhysicalDpiY:
0464             return 72;
0465         default:
0466             break;
0467         }
0468         return QWidget::metric(metric);
0469     }
0470 };
0471 
0472 QSizeF AdjustColumnRowManipulator::textSize(const QString& text, const Style& style) const
0473 {
0474     QSizeF size;
0475     DummyWidget dummyWiget;
0476     const QFontMetricsF fontMetrics(style.font(), &dummyWiget);
0477 
0478     // Set size to correct values according to
0479     // if the text is horizontal, vertical or rotated.
0480     if (!style.verticalText() && !style.angle()) {
0481         // Horizontal text.
0482 
0483         size = fontMetrics.size(0, text);
0484         double offsetFont = 0.0;
0485         if ((style.valign() == Style::Bottom) && style.underline())
0486             offsetFont = fontMetrics.underlinePos() + 1;
0487 
0488         size.setHeight((fontMetrics.ascent() + fontMetrics.descent() + offsetFont)
0489                        *(text.count('\n') + 1));
0490     } else if (style.angle() != 0) {
0491         // Rotated text.
0492 
0493         const double height = fontMetrics.ascent() + fontMetrics.descent();
0494         const double width  = fontMetrics.width(text);
0495         size.setHeight(height * ::cos(style.angle() * M_PI / 180)
0496                        + qAbs(width * ::sin(style.angle() * M_PI / 180)));
0497         size.setWidth(qAbs(height * ::sin(style.angle() * M_PI / 180))
0498                       + width * ::cos(style.angle() * M_PI / 180));
0499     } else {
0500         // Vertical text.
0501 
0502         qreal width = 0.0;
0503         for (int i = 0; i < text.length(); i++)
0504             width = qMax(width, fontMetrics.width(text.at(i)));
0505 
0506         size.setWidth(width);
0507         size.setHeight((fontMetrics.ascent() + fontMetrics.descent())
0508                        * text.length());
0509     }
0510     return size;
0511 }
0512 
0513 double AdjustColumnRowManipulator::adjustColumnHelper(const Cell& cell)
0514 {
0515     double long_max = 0.0;
0516     const Style style = cell.effectiveStyle();
0517     const QSizeF size = textSize(cell.displayText(), style);
0518     if (size.width() > long_max) {
0519         double indent = 0.0;
0520         Style::HAlign alignment = style.halign();
0521         if (alignment == Style::HAlignUndefined) {
0522             if (cell.value().isNumber() || cell.isDate() || cell.isTime())
0523                 alignment = Style::Right;
0524             else
0525                 alignment = Style::Left;
0526         }
0527         if (alignment == Style::Left)
0528             indent = cell.style().indentation();
0529         long_max = indent + size.width()
0530                    + style.leftBorderPen().width() + style.rightBorderPen().width();
0531         // if this cell has others merged into it, we'll subtract the width of those columns
0532         // this is not perfect, but at least should work in 90% of the cases
0533         const int mergedXCount = cell.mergedXCells();
0534         if (mergedXCount > 0) {
0535             for (int col = 1; col <= mergedXCount; col++) {
0536                 double cw = cell.sheet()->columnFormat(cell.column() + col)->width();
0537                 long_max -= cw;
0538             }
0539         }
0540     }
0541     // add 4 because long_max is the length of the text
0542     // but column has borders
0543     if (long_max == 0.0)
0544         return -1.0;
0545     else
0546         return long_max + 4.0;
0547 }
0548 
0549 double AdjustColumnRowManipulator::adjustRowHelper(const Cell& cell)
0550 {
0551     double long_max = 0.0;
0552     const Style style = cell.effectiveStyle();
0553     const QSizeF size = textSize(cell.displayText(), style);
0554     if (size.height() > long_max)
0555         long_max = size.height() + style.topBorderPen().width() + style.bottomBorderPen().width();
0556     //  add 1 because long_max is the height of the text
0557     //  but row has borders
0558     if (long_max == 0.0)
0559         return -1.0;
0560     else
0561         return long_max + 1.0;
0562 }
0563 
0564 KUndo2MagicString AdjustColumnRowManipulator::name() const
0565 {
0566     if (m_adjustColumn && m_adjustRow) {
0567         return kundo2_i18n("Adjust Columns/Rows");
0568     } else if (m_adjustColumn) {
0569         return kundo2_i18n("Adjust Columns");
0570     } else {
0571         return kundo2_i18n("Adjust Rows");
0572     }
0573 }
0574 
0575 /***************************************************************************
0576   class InsertDeleteColumnManipulator
0577 ****************************************************************************/
0578 
0579 InsertDeleteColumnManipulator::InsertDeleteColumnManipulator(KUndo2Command *parent)
0580         : AbstractRegionCommand(parent)
0581         , m_mode(Insert)
0582         , m_template(0)
0583 {
0584     setText(kundo2_i18n("Insert Columns"));
0585 }
0586 
0587 InsertDeleteColumnManipulator::~InsertDeleteColumnManipulator()
0588 {
0589     delete m_template;
0590 }
0591 
0592 void InsertDeleteColumnManipulator::setTemplate(const ColumnFormat &columnFormat)
0593 {
0594     delete m_template;
0595     m_template = new ColumnFormat(columnFormat);
0596 }
0597 
0598 void InsertDeleteColumnManipulator::setReverse(bool reverse)
0599 {
0600     m_reverse = reverse;
0601     m_mode = reverse ? Delete : Insert;
0602     if (!m_reverse)
0603         setText(kundo2_i18n("Insert Columns"));
0604     else
0605         setText(kundo2_i18n("Remove Columns"));
0606 }
0607 
0608 bool InsertDeleteColumnManipulator::process(Element* element)
0609 {
0610     const QRect range = element->rect();
0611     const int pos = range.left();
0612     const int num = range.width();
0613     if (!m_reverse) { // insertion
0614         // insert rows
0615         m_sheet->insertColumns(pos, num);
0616         if (m_template) {
0617             m_template->setSheet(m_sheet);
0618             const int end = pos + num - 1;
0619             for (int col = pos; col <= end; ++col) {
0620                 m_template->setColumn(col);
0621                 m_sheet->insertColumnFormat(m_template);
0622             }
0623         }
0624         m_sheet->cellStorage()->insertColumns(pos, num);
0625 
0626         // undo deletion
0627         if (m_mode == Delete) {
0628             KUndo2Command::undo(); // process child commands (from CellStorage)
0629         }
0630     } else {
0631         // delete rows
0632         m_sheet->removeColumns(pos, num);
0633         m_sheet->cellStorage()->removeColumns(pos, num);
0634 
0635         // undo insertion
0636         if (m_mode == Insert) {
0637             KUndo2Command::undo(); // process child commands (from CellStorage)
0638         }
0639     }
0640     return true;
0641 }
0642 
0643 bool elementLeftColumnLessThan(const Calligra::Sheets::Region::Element *e1, const Calligra::Sheets::Region::Element *e2)
0644 {
0645     return e1->rect().left() < e2->rect().left();
0646 }
0647 
0648 bool InsertDeleteColumnManipulator::preProcessing()
0649 {
0650     if (m_firstrun) {
0651         // If we have an NCS, create a child command for each element.
0652         if (cells().count() > 1) { // non-contiguous selection
0653             // Sort the elements by their top row.
0654             std::stable_sort(cells().begin(), cells().end(), elementLeftColumnLessThan);
0655             // Create sub-commands.
0656             const Region::ConstIterator end(constEnd());
0657             for (Region::ConstIterator it = constBegin(); it != end; ++it) {
0658                 InsertDeleteColumnManipulator *const command = new InsertDeleteColumnManipulator(this);
0659                 command->setSheet(m_sheet);
0660                 command->add(Region((*it)->rect(), (*it)->sheet()));
0661                 if (m_mode == Delete) {
0662                     command->setReverse(true);
0663                 }
0664             }
0665         } else { // contiguous selection
0666             m_sheet->cellStorage()->startUndoRecording();
0667         }
0668     }
0669     return AbstractRegionCommand::preProcessing();
0670 }
0671 
0672 bool InsertDeleteColumnManipulator::mainProcessing()
0673 {
0674     if (cells().count() > 1) { // non-contiguous selection
0675         if ((m_reverse && m_mode == Insert) || (!m_reverse && m_mode == Delete)) {
0676             KUndo2Command::undo(); // process all sub-commands
0677         } else {
0678             KUndo2Command::redo(); // process all sub-commands
0679         }
0680         return true;
0681     }
0682     return AbstractRegionCommand::mainProcessing(); // calls process(Element*)
0683 }
0684 
0685 bool InsertDeleteColumnManipulator::postProcessing()
0686 {
0687     if (cells().count() > 1) { // non-contiguous selection
0688         return true;
0689     }
0690     if (m_firstrun) {
0691         m_sheet->cellStorage()->stopUndoRecording(this);
0692     }
0693     const QRect rect(QPoint(boundingRect().left(), 1), QPoint(KS_colMax, KS_rowMax));
0694     m_sheet->map()->addDamage(new CellDamage(m_sheet, Region(rect, m_sheet), CellDamage::Appearance));
0695     return true;
0696 }
0697 
0698 /***************************************************************************
0699   class InsertDeleteRowManipulator
0700 ****************************************************************************/
0701 
0702 InsertDeleteRowManipulator::InsertDeleteRowManipulator(KUndo2Command *parent)
0703         : AbstractRegionCommand(parent)
0704         , m_mode(Insert)
0705         , m_template(0)
0706 {
0707     setText(kundo2_i18n("Insert Rows"));
0708 }
0709 
0710 InsertDeleteRowManipulator::~InsertDeleteRowManipulator()
0711 {
0712     delete m_template;
0713 }
0714 
0715 void InsertDeleteRowManipulator::setTemplate(const RowFormat &rowFormat)
0716 {
0717     delete m_template;
0718     m_template = new RowFormat(rowFormat);
0719 }
0720 
0721 void InsertDeleteRowManipulator::setReverse(bool reverse)
0722 {
0723     m_reverse = reverse;
0724     m_mode = reverse ? Delete : Insert;
0725     if (!m_reverse)
0726         setText(kundo2_i18n("Insert Rows"));
0727     else
0728         setText(kundo2_i18n("Remove Rows"));
0729 }
0730 
0731 bool InsertDeleteRowManipulator::process(Element* element)
0732 {
0733     const QRect range = element->rect();
0734     const int pos = range.top();
0735     const int num = range.height();
0736     if (!m_reverse) { // insertion
0737         // insert rows
0738         m_sheet->insertRows(pos, num);
0739         if (m_template) {
0740             m_template->setSheet(m_sheet);
0741             const int end = pos + num - 1;
0742             for (int row = pos; row <= end; ++row) {
0743                 m_template->setRow(row);
0744                 m_sheet->insertRowFormat(m_template);
0745             }
0746         }
0747         m_sheet->cellStorage()->insertRows(pos, num);
0748 
0749         // undo deletion
0750         if (m_mode == Delete) {
0751             KUndo2Command::undo(); // process child commands (from CellStorage)
0752         }
0753     } else {
0754         // delete rows
0755         m_sheet->removeRows(pos, num);
0756         m_sheet->cellStorage()->removeRows(pos, num);
0757 
0758         // undo insertion
0759         if (m_mode == Insert) {
0760             KUndo2Command::undo(); // process child commands (from CellStorage)
0761         }
0762     }
0763     return true;
0764 }
0765 
0766 bool elementTopRowLessThan(const Calligra::Sheets::Region::Element *e1, const Calligra::Sheets::Region::Element *e2)
0767 {
0768     return e1->rect().top() < e2->rect().top();
0769 }
0770 
0771 bool InsertDeleteRowManipulator::preProcessing()
0772 {
0773     if (m_firstrun) {
0774         // If we have an NCS, create a child command for each element.
0775         if (cells().count() > 1) { // non-contiguous selection
0776             // Sort the elements by their top row.
0777             std::stable_sort(cells().begin(), cells().end(), elementTopRowLessThan);
0778             // Create sub-commands.
0779             const Region::ConstIterator end(constEnd());
0780             for (Region::ConstIterator it = constBegin(); it != end; ++it) {
0781                 InsertDeleteRowManipulator *const command = new InsertDeleteRowManipulator(this);
0782                 command->setSheet(m_sheet);
0783                 command->add(Region((*it)->rect(), (*it)->sheet()));
0784                 if (m_mode == Delete) {
0785                     command->setReverse(true);
0786                 }
0787             }
0788         } else { // contiguous selection
0789             m_sheet->cellStorage()->startUndoRecording();
0790         }
0791     }
0792     return AbstractRegionCommand::preProcessing();
0793 }
0794 
0795 bool InsertDeleteRowManipulator::mainProcessing()
0796 {
0797     if (cells().count() > 1) { // non-contiguous selection
0798         if ((m_reverse && m_mode == Insert) || (!m_reverse && m_mode == Delete)) {
0799             KUndo2Command::undo(); // process all sub-commands
0800         } else {
0801             KUndo2Command::redo(); // process all sub-commands
0802         }
0803         return true;
0804     }
0805     return AbstractRegionCommand::mainProcessing(); // calls process(Element*)
0806 }
0807 
0808 bool InsertDeleteRowManipulator::postProcessing()
0809 {
0810     if (cells().count() > 1) { // non-contiguous selection
0811         return true;
0812     }
0813     if (m_firstrun) {
0814         m_sheet->cellStorage()->stopUndoRecording(this);
0815     }
0816     const QRect rect(QPoint(1, boundingRect().top()), QPoint(KS_colMax, KS_rowMax));
0817     m_sheet->map()->addDamage(new CellDamage(m_sheet, Region(rect, m_sheet), CellDamage::Appearance));
0818     return true;
0819 }