1 /* This file is part of the KDE project
2    Copyright 2005,2007 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 // Local
21 #include "MergeCommand.h"
22 
23 #include <KLocalizedString>
24 #include <kmessagebox.h>
25 
26 #include "Cell.h"
27 #include "Damages.h"
28 #include "Map.h"
29 #include "ui/Selection.h" // FIXME detach from ui
30 #include "Sheet.h"
31 
32 using namespace Calligra::Sheets;
33 
MergeCommand(KUndo2Command * parent)34 MergeCommand::MergeCommand(KUndo2Command* parent)
35         : AbstractRegionCommand(parent),
36         m_merge(true),
37         m_mergeHorizontal(false),
38         m_mergeVertical(false),
39         m_unmerger(0),
40         m_selection(0)
41 {
42     m_checkLock = true;
43 }
44 
~MergeCommand()45 MergeCommand::~MergeCommand()
46 {
47     delete m_unmerger;
48 }
49 
process(Element * element)50 bool MergeCommand::process(Element* element)
51 {
52     if (element->type() != Element::Range || element->isRow() || element->isColumn()) {
53         // TODO Stefan: remove these elements?!
54         return true;
55     }
56 
57     // sanity check
58     if (m_sheet->isProtected() || m_sheet->map()->isProtected()) {
59         return false;
60     }
61 
62     QRect range = element->rect();
63     int left   = range.left();
64     int right  = range.right();
65     int top    = range.top();
66     int bottom = range.bottom();
67     int height = range.height();
68     int width  = range.width();
69 
70     bool doMerge = m_reverse ? (!m_merge) : m_merge;
71 
72     if (doMerge) {
73         if (m_mergeHorizontal) {
74             for (int row = top; row <= bottom; ++row) {
75                 int rows = 0;
76                 for (int col = left; col <= right; ++col) {
77                     Cell cell = Cell(m_sheet, col, row);
78                     if (cell.doesMergeCells()) {
79                         rows = qMax(rows, cell.mergedYCells());
80                         cell.mergeCells(col, row, 0, 0);
81                     }
82                 }
83                 Cell cell = Cell(m_sheet,  left, row);
84                 if (!cell.isPartOfMerged()) {
85                     cell.mergeCells(left, row, width - 1, rows);
86                 }
87             }
88         } else if (m_mergeVertical) {
89             for (int col = left; col <= right; ++col) {
90                 int cols = 0;
91                 for (int row = top; row <= bottom; ++row) {
92                     Cell cell = Cell(m_sheet, col, row);
93                     if (cell.doesMergeCells()) {
94                         cols = qMax(cols, cell.mergedXCells());
95                         cell.mergeCells(col, row, 0, 0);
96                     }
97                 }
98                 Cell cell = Cell(m_sheet, col, top);
99                 if (!cell.isPartOfMerged()) {
100                     cell.mergeCells(col, top, cols, height - 1);
101                 }
102             }
103         } else {
104             Cell cell = Cell(m_sheet,  left, top);
105             cell.mergeCells(left, top, width - 1, height - 1);
106         }
107     } else { // dissociate
108         for (int col = left; col <= right; ++col) {
109             for (int row = top; row <= bottom; ++row) {
110                 Cell cell = Cell(m_sheet, col, row);
111                 if (!cell.doesMergeCells()) {
112                     continue;
113                 }
114                 cell.mergeCells(col, row, 0, 0);
115             }
116         }
117     }
118 
119     // adjust selection
120     if (m_selection)
121         m_selection->isEmpty() ? m_selection->initialize(range, m_sheet) : m_selection->extend(range, m_sheet);
122 
123     return true;
124 }
125 
name() const126 KUndo2MagicString MergeCommand::name() const
127 {
128     if (m_merge) { // MergeCommand
129         if (m_mergeHorizontal) {
130             return kundo2_i18n("Merge Cells Horizontally");
131         } else if (m_mergeVertical) {
132             return kundo2_i18n("Merge Cells Vertically");
133         } else {
134             return kundo2_i18n("Merge Cells");
135         }
136     }
137     return kundo2_i18n("Dissociate Cells");
138 }
139 
preProcessing()140 bool MergeCommand::preProcessing()
141 {
142     if (isColumnOrRowSelected()) {
143         KMessageBox::information(0, i18n("Merging of columns or rows is not supported."));
144         return false;
145     }
146 
147     if (m_firstrun) {
148         setText(name());
149 
150         // reduce the region to the region occupied by merged cells
151         Region mergedCells;
152         ConstIterator endOfList = constEnd();
153         for (ConstIterator it = constBegin(); it != endOfList; ++it) {
154             Element* element = *it;
155             QRect range = element->rect();
156             int right = range.right();
157             int bottom = range.bottom();
158             for (int row = range.top(); row <= bottom; ++row) {
159                 for (int col = range.left(); col <= right; ++col) {
160                     Cell cell = Cell(m_sheet, col, row);
161                     if (cell.doesMergeCells()) {
162                         QRect rect(col, row, cell.mergedXCells() + 1, cell.mergedYCells() + 1);
163                         mergedCells.add(rect);
164                     }
165                 }
166             }
167         }
168 
169         if (m_merge) { // MergeCommand
170             // we're in the manipulator's first execution
171             // initialize the undo manipulator
172             m_unmerger = new MergeCommand();
173             if (!m_mergeHorizontal && !m_mergeVertical) {
174                 m_unmerger->setReverse(true);
175             }
176             m_unmerger->setSheet(m_sheet);
177             m_unmerger->setRegisterUndo(false);
178             m_unmerger->add(mergedCells);
179         } else { // DissociateManipulator
180             clear();
181             add(mergedCells);
182         }
183     }
184 
185     if (m_merge) { // MergeCommand
186         if (m_reverse) { // dissociate
187         } else { // merge
188             // Dissociate cells before merging the whole region.
189             // For horizontal/vertical merging the cells stay
190             // as they are. E.g. the region contains a merged cell
191             // occupying two rows. Then the horizontal merge should
192             // keep the height of two rows and extend the merging to the
193             // region's width. In this case the unmerging is done while
194             // processing each region element.
195             if (!m_mergeHorizontal && !m_mergeVertical) {
196                 m_unmerger->redo();
197             }
198         }
199     }
200     // Clear the associated selection, if any. The merge/dissociate process will restore
201     // selections. This ensures that the selection isn't broken after merging.
202     if (m_selection) m_selection->Region::clear();
203 
204     return AbstractRegionCommand::preProcessing();
205 }
206 
postProcessing()207 bool MergeCommand::postProcessing()
208 {
209     if (m_merge) { // MergeCommand
210         if (m_reverse) { // dissociate
211             // restore the old merge status
212             if (m_mergeHorizontal || m_mergeVertical) {
213                 m_unmerger->redo();
214             } else {
215                 m_unmerger->undo();
216             }
217         }
218     }
219     m_sheet->map()->addDamage(new CellDamage(m_sheet, *this, CellDamage::Appearance));
220     return true;
221 }
222