1 /* This file is part of the KDE project
2    Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
3    Copyright (C) 2005-2006 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301, USA.
19 */
20 
21 // Local
22 #include "Selection.h"
23 
24 #include <KoCanvasBase.h>
25 #include <KoCanvasController.h>
26 #include <KoViewConverter.h>
27 
28 #include "SheetsDebug.h"
29 #include "Cell.h"
30 #include "CellStorage.h"
31 #include "RowColumnFormat.h"
32 #include "RowFormatStorage.h"
33 #include "Sheet.h"
34 
35 #include "commands/DataManipulators.h"
36 
37 #include "ui/CellEditor.h"
38 
39 using namespace Calligra::Sheets;
40 
41 // TODO
42 // - Allow resizing of all ranges in a normal selection; not just the last one.
43 // - Get rid of anchor and marker. They are the corners of the active element.
44 
45 
46 /***************************************************************************
47   class Selection::Private
48 ****************************************************************************/
49 
50 class Q_DECL_HIDDEN Selection::Private
51 {
52 public:
Private()53     Private() {
54         activeSheet = 0;
55         originSheet = 0;
56         anchor = QPoint(1, 1);
57         cursor = QPoint(1, 1);
58         marker = QPoint(1, 1);
59 
60         colors.push_back(Qt::red);
61         colors.push_back(Qt::blue);
62         colors.push_back(Qt::magenta);
63         colors.push_back(Qt::darkRed);
64         colors.push_back(Qt::darkGreen);
65         colors.push_back(Qt::darkMagenta);
66         colors.push_back(Qt::darkCyan);
67         colors.push_back(Qt::darkYellow);
68 
69         multipleOccurences = false;
70         selectionMode = MultipleCells;
71 
72         activeElement = 1;
73         activeSubRegionStart = 0;
74         activeSubRegionLength = 1;
75 
76         canvasBase = 0;
77         referenceMode = false;
78     }
79 
80     Sheet* activeSheet;
81     Sheet* originSheet;
82     QPoint anchor;
83     QPoint cursor;
84     QPoint marker;
85     QList<QColor> colors;
86 
87     bool multipleOccurences : 1;
88     Mode selectionMode : 2;
89 
90     // For reference selections this selection represents all references in a
91     // formula. The user can place the text cursor at any reference while
92     // editing the formula. Such a reference may not just be a contiguous range,
93     // but a non-contiguous sub-region.
94     // (Even though the text delimiter that separates ranges in a sub-region,
95     // ';', is also used as delimiter for function arguments. Functions, that
96     // accept two or more adjacent references as arguments cannot cope with
97     // non-contiguous references for this reason. In this case it's up to the
98     // user to select references, that serve the function's needs.)
99     // That's what the next three variables are for.
100     // For 'normal' selections these variables are actually superfluous, but may
101     // be used in conjunction with the reference selection where appropriate.
102     int activeElement; // the active range in a referenced sub-region
103     int activeSubRegionStart; // the start of a referenced sub-region
104     int activeSubRegionLength; // the length of a referenced sub-region
105 
106     KoCanvasBase* canvasBase;
107     bool referenceMode : 1;
108     Region formerSelection; // for reference selection mode
109     Region oldSelection; // for select all
110 };
111 
112 /***************************************************************************
113   class Selection
114 ****************************************************************************/
115 
Selection(KoCanvasBase * canvasBase)116 Selection::Selection(KoCanvasBase* canvasBase)
117         : KoToolSelection(0)
118         , Region(1, 1)
119         , d(new Private())
120 {
121     d->canvasBase = canvasBase;
122 }
123 
Selection(const Selection & selection)124 Selection::Selection(const Selection& selection)
125         : KoToolSelection(selection.parent())
126         , Region()
127         , d(new Private())
128 {
129     d->activeSheet = selection.d->activeSheet;
130     d->originSheet = selection.d->originSheet;
131     d->activeElement = cells().count();
132     d->activeSubRegionStart = 0;
133     d->activeSubRegionLength = cells().count();
134     d->canvasBase = selection.d->canvasBase;
135 }
136 
~Selection()137 Selection::~Selection()
138 {
139     delete d;
140 }
141 
canvas() const142 KoCanvasBase* Selection::canvas() const
143 {
144     return d->canvasBase;
145 }
146 
initialize(const QPoint & point,Sheet * sheet)147 void Selection::initialize(const QPoint& point, Sheet* sheet)
148 {
149     if (!isValid(point))
150         return;
151 
152     if (!d->activeSheet)
153         return;
154 
155     if (!sheet) {
156         if (d->originSheet) {
157             sheet = d->originSheet;
158         } else {
159             sheet = d->activeSheet;
160         }
161     }
162 
163     Region changedRegion(*this);
164     changedRegion.add(extendToMergedAreas(QRect(d->anchor, d->marker)));
165 
166     // for the case of a merged cell
167     QPoint topLeft(point);
168     Cell cell(d->activeSheet, point);
169     if (cell.isPartOfMerged()) {
170         cell = cell.masterCell();
171         topLeft = QPoint(cell.column(), cell.row());
172     }
173 
174     d->anchor = topLeft;
175     d->cursor = point;
176     d->marker = topLeft;
177 
178     fixSubRegionDimension(); // TODO remove this sanity check
179     int index = d->activeSubRegionStart + d->activeSubRegionLength;
180     if (insert(index, topLeft, sheet/*, true*/)) {
181         // if the point was inserted
182         clearSubRegion();
183         // Sets:
184         // d->activeElement = d->activeSubRegionStart + 1;
185         // d->activeSubRegionLength = 0;
186     } else {
187         warnSheets << "Unable to insert" << topLeft << "in" << sheet->sheetName();
188     }
189     Element* element = cells()[d->activeSubRegionStart];
190     // we end up with one element in the subregion
191     d->activeSubRegionLength = 1;
192     if (element && element->type() == Element::Point) {
193         Point* point = static_cast<Point*>(element);
194         point->setColor(d->colors[cells().size() % d->colors.size()]);
195     } else if (element && element->type() == Element::Range) {
196         Range* range = static_cast<Range*>(element);
197         range->setColor(d->colors[cells().size() % d->colors.size()]);
198     }
199 
200     if (changedRegion == *this) {
201         emitChanged(Region(topLeft, sheet));
202         return;
203     }
204     changedRegion.add(topLeft, sheet);
205 
206     emitChanged(changedRegion);
207 }
208 
initialize(const QRect & range,Sheet * sheet)209 void Selection::initialize(const QRect& range, Sheet* sheet)
210 {
211     if (!isValid(range) || (range == QRect(0, 0, 1, 1)))
212         return;
213 
214     if (!d->activeSheet)
215         return;
216 
217     if (d->selectionMode == SingleCell) {
218         initialize(range.bottomRight(), sheet);
219         return;
220     }
221 
222     if (!sheet) {
223         if (d->originSheet) {
224             sheet = d->originSheet;
225         } else {
226             sheet = d->activeSheet;
227         }
228     }
229 
230     Region changedRegion(*this);
231     changedRegion.add(extendToMergedAreas(QRect(d->anchor, d->marker)));
232 
233     // for the case of a merged cell
234     QPoint topLeft(range.topLeft());
235     Cell cell(d->activeSheet, topLeft);
236     if (cell.isPartOfMerged()) {
237         cell = cell.masterCell();
238         topLeft = QPoint(cell.column(), cell.row());
239     }
240 
241     // for the case of a merged cell
242     QPoint bottomRight(range.bottomRight());
243     cell = Cell(d->activeSheet, bottomRight);
244     if (cell.isPartOfMerged()) {
245         cell = cell.masterCell();
246         bottomRight = QPoint(cell.column(), cell.row());
247     }
248 
249     d->anchor = topLeft;
250     d->cursor = bottomRight;
251     d->marker = bottomRight;
252 
253     fixSubRegionDimension(); // TODO remove this sanity check
254     int index = d->activeSubRegionStart + d->activeSubRegionLength;
255     if (insert(index, QRect(topLeft, bottomRight), sheet/*, true*/)) {
256         // if the range was inserted
257         clearSubRegion();
258         // Sets:
259         // d->activeElement = d->activeSubRegionStart + 1;
260         // d->activeSubRegionLength = 0;
261     } else {
262         warnSheets << "Unable to insert" << topLeft << "in" << sheet->sheetName();
263     }
264     Element* element = cells()[d->activeSubRegionStart];
265     // we end up with one element in the subregion
266     d->activeSubRegionLength = 1;
267     if (element && element->type() == Element::Point) {
268         Point* point = static_cast<Point*>(element);
269         point->setColor(d->colors[cells().size() % d->colors.size()]);
270     } else if (element && element->type() == Element::Range) {
271         Range* range = static_cast<Range*>(element);
272         range->setColor(d->colors[cells().size() % d->colors.size()]);
273     }
274 
275     if (changedRegion == *this) {
276         return;
277     }
278     changedRegion.add(QRect(topLeft, bottomRight), sheet);
279 
280     emitChanged(changedRegion);
281 }
282 
initialize(const Region & region,Sheet * sheet)283 void Selection::initialize(const Region& region, Sheet* sheet)
284 {
285     if (!region.isValid())
286         return;
287 
288     if (d->selectionMode == SingleCell) {
289         if (!cells().isEmpty())
290             initialize(region.firstRange().bottomRight(), sheet);
291         return;
292     }
293 
294     if (!sheet) {
295         if (d->originSheet) {
296             sheet = d->originSheet;
297         } else {
298             sheet = d->activeSheet;
299         }
300     }
301 
302     Region changedRegion(*this);
303     changedRegion.add(extendToMergedAreas(QRect(d->anchor, d->marker)));
304 
305     // TODO Stefan: handle subregion insertion
306     // TODO Stefan: handle obscured cells correctly
307     Region::clear(); // all elements; no residuum
308     Element* element = add(region);
309     if (element && element->type() == Element::Point) {
310         Point* point = static_cast<Point*>(element);
311         point->setColor(d->colors[cells().size() % d->colors.size()]);
312     } else if (element && element->type() == Element::Range) {
313         Range* range = static_cast<Range*>(element);
314         range->setColor(d->colors[cells().size() % d->colors.size()]);
315     }
316 
317     // for the case of a merged cell
318     QPoint topLeft(cells().last()->rect().topLeft());
319     Cell cell(d->activeSheet, topLeft);
320     if (cell.isPartOfMerged()) {
321         cell = cell.masterCell();
322         topLeft = QPoint(cell.column(), cell.row());
323     }
324 
325     // for the case of a merged cell
326     QPoint bottomRight(cells().last()->rect().bottomRight());
327     cell = Cell(d->activeSheet, bottomRight);
328     if (cell.isPartOfMerged()) {
329         cell = cell.masterCell();
330         bottomRight = QPoint(cell.column(), cell.row());
331     }
332 
333     d->anchor = topLeft;
334     d->cursor = topLeft;
335     d->marker = bottomRight;
336 
337     d->activeElement = cells().count();
338     d->activeSubRegionStart = 0;
339     d->activeSubRegionLength = cells().count();
340 
341     if (changedRegion == *this) {
342         return;
343     }
344     changedRegion.add(region);
345 
346     emitChanged(changedRegion);
347 }
348 
update()349 void Selection::update()
350 {
351     emitChanged(*this);
352 }
353 
update(const QPoint & point)354 void Selection::update(const QPoint& point)
355 {
356     if (d->selectionMode == SingleCell) {
357         initialize(point);
358         d->activeElement = 1;
359         d->activeSubRegionStart = 0;
360         d->activeSubRegionLength = 1;
361         return;
362     }
363 
364     // A length of 0 means inserting at the position d->activeSubRegionStart.
365     if (d->activeSubRegionLength == 0) {
366         extend(point);
367         return;
368     }
369 
370     if (cells().isEmpty()) {
371         initialize(point);
372         d->activeElement = 1;
373         d->activeSubRegionStart = 0;
374         d->activeSubRegionLength = 1;
375         return;
376     }
377 
378     // Take the last range, if pointing beyond the sub-region's end.
379     const int subRegionEnd = d->activeSubRegionStart + d->activeSubRegionLength;
380     const bool atEnd = d->activeElement >= subRegionEnd;
381     if (atEnd) {
382         // d->activeSubRegionLength == 0 is already excluded.
383         d->activeElement = subRegionEnd - 1;
384     }
385 
386     Sheet* sheet = cells()[d->activeElement]->sheet();
387     if (sheet != d->activeSheet) {
388         extend(point);
389         d->activeElement = cells().count();
390         d->activeSubRegionStart = cells().count() - 1;
391         d->activeSubRegionLength = 1;
392         return;
393     }
394 
395     // for the case of a merged cell
396     QPoint topLeft(point);
397     Cell cell(d->activeSheet, point);
398     if (cell.isPartOfMerged()) {
399         cell = cell.masterCell();
400         topLeft = QPoint(cell.column(), cell.row());
401     }
402 
403     if (topLeft == d->marker) {
404         return;
405     }
406 
407     QRect area1 = cells()[d->activeElement]->rect();
408     QRect newRange = extendToMergedAreas(QRect(d->anchor, topLeft));
409 
410     // If the updated range is bigger, it may cover already existing ranges.
411     // These get removed, if multiple occurrences are not allowed. Store the old
412     // amount of ranges, to figure out how many ranges have been removed later.
413     const int count = cells().count();
414     // The update may have shrunk the range, which would be contained in
415     // the former range. Remove the latter before inserting the new range.
416     delete cells().takeAt(d->activeElement);
417     insert(d->activeElement, newRange, sheet, d->multipleOccurences);
418     const int delta = cells().count() - count;
419     d->activeSubRegionLength += delta;
420     if (atEnd) {
421         d->activeElement = d->activeSubRegionStart + d->activeSubRegionLength;
422     } else {
423         d->activeElement += delta;
424     }
425 
426     QRect area2 = newRange;
427     Region changedRegion;
428 
429     bool newLeft   = area1.left() != area2.left();
430     bool newTop    = area1.top() != area2.top();
431     bool newRight  = area1.right() != area2.right();
432     bool newBottom = area1.bottom() != area2.bottom();
433 
434     /* first, calculate some numbers that we'll use a few times */
435     int farLeft = qMin(area1.left(), area2.left());
436     int innerLeft = qMax(area1.left(), area2.left());
437 
438     int farTop = qMin(area1.top(), area2.top());
439     int innerTop = qMax(area1.top(), area2.top());
440 
441     int farRight = qMax(area1.right(), area2.right());
442     int innerRight = qMin(area1.right(), area2.right());
443 
444     int farBottom = qMax(area1.bottom(), area2.bottom());
445     int innerBottom = qMin(area1.bottom(), area2.bottom());
446 
447     if (newLeft) {
448         changedRegion.add(QRect(QPoint(farLeft, innerTop),
449                                 QPoint(innerLeft - 1, innerBottom)));
450         if (newTop) {
451             changedRegion.add(QRect(QPoint(farLeft, farTop),
452                                     QPoint(innerLeft - 1, innerTop - 1)));
453         }
454         if (newBottom) {
455             changedRegion.add(QRect(QPoint(farLeft, innerBottom + 1),
456                                     QPoint(innerLeft - 1, farBottom)));
457         }
458     }
459 
460     if (newTop) {
461         changedRegion.add(QRect(QPoint(innerLeft, farTop),
462                                 QPoint(innerRight, innerTop - 1)));
463     }
464 
465     if (newRight) {
466         changedRegion.add(QRect(QPoint(innerRight + 1, innerTop),
467                                 QPoint(farRight, innerBottom)));
468         if (newTop) {
469             changedRegion.add(QRect(QPoint(innerRight + 1, farTop),
470                                     QPoint(farRight, innerTop - 1)));
471         }
472         if (newBottom) {
473             changedRegion.add(QRect(QPoint(innerRight + 1, innerBottom + 1),
474                                     QPoint(farRight, farBottom)));
475         }
476     }
477 
478     if (newBottom) {
479         changedRegion.add(QRect(QPoint(innerLeft, innerBottom + 1),
480                                 QPoint(innerRight, farBottom)));
481     }
482 
483     d->marker = topLeft;
484     d->cursor = point;
485 
486     emitChanged(changedRegion);
487 }
488 
extend(const QPoint & point,Sheet * sheet)489 void Selection::extend(const QPoint& point, Sheet* sheet)
490 {
491     if (!isValid(point))
492         return;
493 
494     if (isEmpty() || d->selectionMode == SingleCell) {
495         initialize(point, sheet);
496         return;
497     }
498 
499     debugSheets ;
500 
501     if (!sheet) {
502         if (d->originSheet) {
503             sheet = d->originSheet;
504         } else {
505             sheet = d->activeSheet;
506         }
507     }
508 
509     Region changedRegion = Region(extendToMergedAreas(QRect(d->marker, d->marker)));
510 
511     // for the case of a merged cell
512     QPoint topLeft(point);
513     Cell cell(d->activeSheet, point);
514     if (cell.isPartOfMerged()) {
515         cell = cell.masterCell();
516         topLeft = QPoint(cell.column(), cell.row());
517     }
518 
519     if (d->multipleOccurences) {
520         const int subRegionEnd = d->activeSubRegionStart + d->activeSubRegionLength;
521         const bool prepend = d->activeSubRegionLength == 0;
522         const bool atEnd = d->activeElement == subRegionEnd;
523         // Insert the new location after the active element, if possible.
524         const int index = d->activeElement + ((prepend || atEnd) ? 0 : 1);
525         insert(index, topLeft, sheet, true);
526         ++d->activeSubRegionLength;
527         ++d->activeElement;
528         d->anchor = topLeft;
529         d->marker = topLeft;
530     } else {
531         // TODO Replace for normal selection and resizing of any range.
532         // The new point may split an existing range. Anyway, the new
533         // location/range is appended and the last element becomes active.
534         const int count = cells().count();
535         eor(topLeft, sheet);
536         d->activeSubRegionLength += cells().count() - count;
537         d->activeElement = cells().count() - 1;
538         d->anchor = cells()[d->activeElement]->rect().topLeft();
539         d->marker = cells()[d->activeElement]->rect().bottomRight();
540     }
541     d->cursor = d->marker;
542 
543     changedRegion.add(topLeft, sheet);
544     changedRegion.add(*this);
545 
546     emitChanged(changedRegion);
547 }
548 
extend(const QRect & range,Sheet * sheet)549 void Selection::extend(const QRect& range, Sheet* sheet)
550 {
551     if (!isValid(range) || (range == QRect(0, 0, 1, 1)))
552         return;
553 
554     if (isEmpty() || d->selectionMode == SingleCell) {
555         initialize(range, sheet);
556         return;
557     }
558 
559     if (!sheet) {
560         if (d->originSheet) {
561             sheet = d->originSheet;
562         } else {
563             sheet = d->activeSheet;
564         }
565     }
566 
567     // for the case of a merged cell
568     QPoint topLeft(range.topLeft());
569     Cell cell(d->activeSheet, topLeft);
570     if (cell.isPartOfMerged()) {
571         cell = cell.masterCell();
572         topLeft = QPoint(cell.column(), cell.row());
573     }
574 
575     // for the case of a merged cell
576     QPoint bottomRight(range.bottomRight());
577     cell = Cell(d->activeSheet, bottomRight);
578     if (cell.isPartOfMerged()) {
579         cell = cell.masterCell();
580         bottomRight = QPoint(cell.column(), cell.row());
581     }
582 
583     const QRect newRange = extendToMergedAreas(QRect(topLeft, bottomRight));
584 
585     Element* element = 0;
586     if (d->multipleOccurences) {
587         const int subRegionEnd = d->activeSubRegionStart + d->activeSubRegionLength;
588         const bool prepend = d->activeSubRegionLength == 0;
589         const bool atEnd = d->activeElement == subRegionEnd;
590         // Insert the new location after the active element, if possible.
591         const int index = d->activeElement + ((prepend || atEnd) ? 0 : 1);
592         insert(index, newRange, sheet, true);
593         ++d->activeSubRegionLength;
594         ++d->activeElement;
595         d->anchor = newRange.topLeft();
596         d->marker = newRange.bottomRight();
597     } else {
598         const int count = cells().count();
599         element = add(newRange, sheet);
600         d->activeSubRegionLength += cells().count() - count;
601         d->activeElement = cells().count() - 1;
602         d->anchor = cells()[d->activeElement]->rect().topLeft();
603         d->marker = cells()[d->activeElement]->rect().bottomRight();
604     }
605     d->cursor = d->marker;
606 
607     if (element && element->type() == Element::Point) {
608         Point* point = static_cast<Point*>(element);
609         point->setColor(d->colors[cells().size() % d->colors.size()]);
610     } else if (element && element->type() == Element::Range) {
611         Range* range = static_cast<Range*>(element);
612         range->setColor(d->colors[cells().size() % d->colors.size()]);
613     }
614 
615     emitChanged(*this);
616 }
617 
extend(const Region & region)618 void Selection::extend(const Region& region)
619 {
620     if (!region.isValid())
621         return;
622 
623     uint count = cells().count();
624     ConstIterator end(region.constEnd());
625     for (ConstIterator it = region.constBegin(); it != end; ++it) {
626         Element *element = *it;
627         if (!element) continue;
628         if (element->type() == Element::Point) {
629             Point* point = static_cast<Point*>(element);
630             extend(point->pos(), element->sheet());
631         } else {
632             extend(element->rect(), element->sheet());
633         }
634     }
635 
636     d->activeSubRegionLength += cells().count() - count;
637 
638     emitChanged(*this);
639 }
640 
eor(const QPoint & point,Sheet * sheet)641 Selection::Element* Selection::eor(const QPoint& point, Sheet* sheet)
642 {
643     // The selection always has to contain one location/range at least.
644     if (isSingular()) {
645         return Region::add(point, sheet);
646     }
647     return Region::eor(point, sheet);
648 }
649 
anchor() const650 const QPoint& Selection::anchor() const
651 {
652     return d->anchor;
653 }
654 
cursor() const655 const QPoint& Selection::cursor() const
656 {
657     return d->cursor;
658 }
659 
marker() const660 const QPoint& Selection::marker() const
661 {
662     return d->marker;
663 }
664 
isSingular() const665 bool Selection::isSingular() const
666 {
667     return Region::isSingular();
668 }
669 
name(Sheet * sheet) const670 QString Selection::name(Sheet* sheet) const
671 {
672     return Region::name(sheet ? sheet : d->originSheet);
673 }
674 
setActiveSheet(Sheet * sheet)675 void Selection::setActiveSheet(Sheet* sheet)
676 {
677     if (d->activeSheet == sheet) {
678         return;
679     }
680     d->activeSheet = sheet;
681     emit activeSheetChanged(sheet);
682 }
683 
activeSheet() const684 Sheet* Selection::activeSheet() const
685 {
686     return d->activeSheet;
687 }
688 
setOriginSheet(Sheet * sheet)689 void Selection::setOriginSheet(Sheet* sheet)
690 {
691     d->originSheet = sheet;
692 }
693 
originSheet() const694 Sheet* Selection::originSheet() const
695 {
696     return d->originSheet;
697 }
698 
setActiveElement(const Cell & cell)699 int Selection::setActiveElement(const Cell &cell)
700 {
701     for (int index = 0; index < cells().count(); ++index) {
702         if (cells()[index]->sheet() != cell.sheet()) {
703             continue;
704         }
705         QRect range = cells()[index]->rect();
706         const QPoint point = cell.cellPosition();
707         if (range.topLeft() == point || range.bottomRight() == point) {
708             d->anchor = range.topLeft();
709             d->cursor = range.bottomRight();
710             d->marker = range.bottomRight();
711             d->activeElement = index;
712             // Only adjust the sub-region, if index is out of bounds.
713             if (index < d->activeSubRegionStart) {
714                 d->activeSubRegionStart = index;
715             }
716             if (index > d->activeSubRegionStart + d->activeSubRegionLength) {
717                 d->activeSubRegionStart = index;
718                 d->activeSubRegionLength = 1;
719             }
720             return index;
721         }
722     }
723     return -1;
724 }
725 
activeElement() const726 Calligra::Sheets::Region::Element* Selection::activeElement() const
727 {
728     return (d->activeElement == cells().count()) ? 0 : cells()[d->activeElement];
729 }
730 
clear()731 void Selection::clear()
732 {
733     d->activeElement = 0;
734     d->activeSubRegionStart = 0;
735     d->activeSubRegionLength = 0;
736     Region::clear();
737     // If this is the normal, not the reference mode, one element must survive.
738     if (!referenceSelection()) {
739         initialize(QPoint(1, 1), d->activeSheet);
740     }
741 }
742 
clearSubRegion()743 void Selection::clearSubRegion()
744 {
745     if (isEmpty()) {
746         return;
747     }
748     for (int index = 0; index < d->activeSubRegionLength; ++index) {
749         delete cells().takeAt(d->activeSubRegionStart);
750     }
751     d->activeSubRegionLength = 0;
752     d->activeElement = d->activeSubRegionStart + 1; // point behind the last
753 }
754 
fixSubRegionDimension()755 void Selection::fixSubRegionDimension()
756 {
757     if (d->activeSubRegionStart > cells().count()) {
758         debugSheets << "start position" << d->activeSubRegionStart << "exceeds list" << cells().count();
759         d->activeSubRegionStart = 0;
760         d->activeSubRegionLength = cells().count();
761         return;
762     }
763     if (d->activeSubRegionStart + d->activeSubRegionLength > cells().count()) {
764         debugSheets << "subregion (" << d->activeSubRegionStart << ".."
765         << d->activeSubRegionStart + d->activeSubRegionLength
766         << ") exceeds list" << cells().count();
767         d->activeSubRegionLength = cells().count() - d->activeSubRegionStart;
768         return;
769     }
770 }
771 
setActiveSubRegion(int start,int length,int active)772 void Selection::setActiveSubRegion(int start, int length, int active)
773 {
774     // Set the active sub-region.
775     d->activeSubRegionStart = qBound(0, start, cells().count());
776     d->activeSubRegionLength = qBound(0, length, cells().count() - d->activeSubRegionStart);
777 
778     // Set the active element.
779     d->activeElement = qBound(d->activeSubRegionStart, active, d->activeSubRegionStart + d->activeSubRegionLength);
780 
781     if (isEmpty()) {
782         return;
783     }
784 
785     // Set the anchor, marker and cursor according to the active element.
786     const int subRegionEnd = d->activeSubRegionStart + d->activeSubRegionLength;
787     const bool atEnd = d->activeElement == subRegionEnd;
788     const int index = qBound(0, d->activeElement - (atEnd ? 1 : 0), cells().count() - 1);
789     const QRect range = cells()[index]->rect();
790     d->anchor = range.topLeft();
791     d->marker = range.bottomRight();
792     d->cursor = d->marker;
793 }
794 
activeSubRegionName() const795 QString Selection::activeSubRegionName() const
796 {
797     QStringList names;
798     int end = d->activeSubRegionStart + d->activeSubRegionLength;
799     for (int index = d->activeSubRegionStart; index < end; ++index) {
800         names += cells()[index]->name(d->originSheet);
801     }
802     return names.isEmpty() ? "" : names.join(";");
803 }
804 
setSelectionMode(Mode mode)805 void Selection::setSelectionMode(Mode mode)
806 {
807     d->selectionMode = mode;
808 }
809 
colors() const810 const QList<QColor>& Selection::colors() const
811 {
812     return d->colors;
813 }
814 
selectAll()815 void Selection::selectAll()
816 {
817     if (!isAllSelected()) {
818         d->oldSelection = *this;
819         initialize(QRect(QPoint(KS_colMax, KS_rowMax), QPoint(1, 1)));
820     } else {
821         initialize(d->oldSelection);
822         d->oldSelection.clear();
823     }
824 }
825 
startReferenceSelection()826 void Selection::startReferenceSelection()
827 {
828     // former selection exists - we are in ref mode already, even though it's suspended
829     if (!d->formerSelection.isEmpty()) {
830         setReferenceSelectionMode(true);
831         return;
832     }
833     d->formerSelection = *this;
834     clear(); // all elements; no residuum;
835     setOriginSheet(activeSheet());
836     // It is important to enable this AFTER we set the rect!
837     d->referenceMode = true;
838     d->multipleOccurences = true;
839     // Visual cue to indicate that the user can drag-select the selection selection
840     d->canvasBase->canvasWidget()->setCursor(Qt::CrossCursor);
841 }
842 
endReferenceSelection(bool saveChanges)843 void Selection::endReferenceSelection(bool saveChanges)
844 {
845     // The reference selection may be temporarily disabled.
846     // The stored selection reliably indicates the reference selection mode.
847     if (d->formerSelection.isEmpty()) {
848         return;
849     }
850     if (originSheet() != activeSheet()) {
851         emit visibleSheetRequested(originSheet());
852     }
853     d->referenceMode = false;
854     d->multipleOccurences = false;
855     // While entering a formula the choose mode is turned on and off.
856     // Clear the choice. Otherwise, cell references will stay highlighted.
857     if (!isEmpty()) {
858         emit changed(*this);
859         clear(); // all elements; no residuum
860     }
861     if (saveChanges) {
862         initialize(d->formerSelection);
863     }
864     d->formerSelection.clear();
865     // The normal selection does not support the replacments of sub-regions.
866     // Reset the active sub-region to the whole region.
867     // TODO Why not allow that? Would make resizing of all ranges in a
868     // non-contiguous selection possible!
869     setActiveSubRegion(0, cells().count());
870     d->canvasBase->canvasWidget()->setCursor(Qt::ArrowCursor);
871 }
872 
setReferenceSelectionMode(bool enable)873 void Selection::setReferenceSelectionMode(bool enable)
874 {
875     d->referenceMode = enable;
876     d->multipleOccurences = enable;
877     d->canvasBase->canvasWidget()->setCursor(enable ? Qt::CrossCursor : Qt::ArrowCursor);
878 }
879 
referenceSelectionMode() const880 bool Selection::referenceSelectionMode() const
881 {
882     return d->referenceMode;
883 }
884 
referenceSelection() const885 bool Selection::referenceSelection() const
886 {
887     return (!d->formerSelection.isEmpty());
888 }
889 
emitAboutToModify()890 void Selection::emitAboutToModify()
891 {
892     emit aboutToModify(*this);
893 }
894 
emitModified()895 void Selection::emitModified()
896 {
897     emit modified(*this);
898 }
899 
emitRefreshSheetViews()900 void Selection::emitRefreshSheetViews()
901 {
902     emit refreshSheetViews();
903 }
904 
emitVisibleSheetRequested(Sheet * sheet)905 void Selection::emitVisibleSheetRequested(Sheet* sheet)
906 {
907     emit visibleSheetRequested(sheet);
908 }
909 
emitCloseEditor(bool saveChanges,bool expandMatrix)910 void Selection::emitCloseEditor(bool saveChanges, bool expandMatrix)
911 {
912     emit closeEditor(saveChanges, expandMatrix);
913 }
914 
emitRequestFocusEditor()915 void Selection::emitRequestFocusEditor()
916 {
917     emit requestFocusEditor();
918 }
919 
extendToMergedAreas(const QRect & _area) const920 QRect Selection::extendToMergedAreas(const QRect& _area) const
921 {
922     if (!d->activeSheet)
923         return _area;
924 
925     QRect area = normalized(_area);
926     Cell cell(d->activeSheet, area.left(), area.top());
927 
928     if (Region::Range(area).isColumn() || Region::Range(area).isRow()) {
929         return area;
930     } else if (!(cell.isPartOfMerged()) &&
931                (cell.mergedXCells() + 1) >= area.width() &&
932                (cell.mergedYCells() + 1) >= area.height()) {
933         /* if just a single cell is selected, we need to merge even when
934         the obscuring isn't forced.  But only if this is the cell that
935         is doing the obscuring -- we still want to be able to click on a cell
936         that is being obscured.
937         */
938         area.setWidth(cell.mergedXCells() + 1);
939         area.setHeight(cell.mergedYCells() + 1);
940     } else {
941         int top = area.top();
942         int left = area.left();
943         int bottom = area.bottom();
944         int right = area.right();
945         for (int x = area.left(); x <= area.right(); x++)
946             for (int y = area.top(); y <= area.bottom(); y++) {
947                 cell = Cell(d->activeSheet, x, y);
948                 if (cell.doesMergeCells()) {
949                     right = qMax(right, cell.mergedXCells() + x);
950                     bottom = qMax(bottom, cell.mergedYCells() + y);
951                 } else if (cell.isPartOfMerged()) {
952                     cell = cell.masterCell();
953                     left = qMin(left, cell.column());
954                     top = qMin(top, cell.row());
955                     bottom = qMax(bottom, cell.row() + cell.mergedYCells());
956                     right = qMax(right, cell.column() + cell.mergedXCells());
957                 }
958             }
959 
960         area.setCoords(left, top, right, bottom);
961     }
962     return area;
963 }
964 
createPoint(const QPoint & point) const965 Calligra::Sheets::Region::Point* Selection::createPoint(const QPoint& point) const
966 {
967     return new Point(point);
968 }
969 
createPoint(const QString & string) const970 Calligra::Sheets::Region::Point* Selection::createPoint(const QString& string) const
971 {
972     return new Point(string);
973 }
974 
createPoint(const Region::Point & point) const975 Calligra::Sheets::Region::Point* Selection::createPoint(const Region::Point& point) const
976 {
977     return new Point(point);
978 }
979 
createRange(const QRect & rect) const980 Calligra::Sheets::Region::Range* Selection::createRange(const QRect& rect) const
981 {
982     return new Range(rect);
983 }
984 
createRange(const Calligra::Sheets::Region::Point & tl,const Calligra::Sheets::Region::Point & br) const985 Calligra::Sheets::Region::Range* Selection::createRange(const Calligra::Sheets::Region::Point& tl, const Calligra::Sheets::Region::Point& br) const
986 {
987     return new Range(tl, br);
988 }
989 
createRange(const QString & string) const990 Calligra::Sheets::Region::Range* Selection::createRange(const QString& string) const
991 {
992     return new Range(string);
993 }
994 
createRange(const Region::Range & range) const995 Calligra::Sheets::Region::Range* Selection::createRange(const Region::Range& range) const
996 {
997     return new Range(range);
998 }
999 
emitChanged(const Region & region)1000 void Selection::emitChanged(const Region& region)
1001 {
1002     Sheet * const sheet = d->activeSheet;
1003     if(!sheet) // no sheet no update needed
1004         return;
1005     Region extendedRegion;
1006     ConstIterator end(region.constEnd());
1007     for (ConstIterator it = region.constBegin(); it != end; ++it) {
1008         Element* element = *it;
1009         QRect area = element->rect();
1010 
1011         const ColumnFormat *col;
1012         //look at if column is hiding.
1013         //if it's hiding refreshing column+1 (or column -1 )
1014         int left = area.left();
1015         int right = area.right();
1016         int top = area.top();
1017         int bottom = area.bottom();
1018 
1019         // a merged cells is selected
1020         if (element->type() == Region::Element::Point) {
1021             Cell cell(sheet, left, top);
1022             if (cell.doesMergeCells()) {
1023                 // extend to the merged region
1024                 // prevents artefacts of the selection rectangle
1025                 right += cell.mergedXCells();
1026                 bottom += cell.mergedYCells();
1027             }
1028         }
1029 
1030         if (right < KS_colMax) {
1031             do {
1032                 right++;
1033                 col = sheet->columnFormat(right);
1034             } while (col->isHiddenOrFiltered() && right != KS_colMax);
1035         }
1036         if (left > 1) {
1037             do {
1038                 left--;
1039                 col = sheet->columnFormat(left);
1040             } while (col->isHiddenOrFiltered() && left != 1);
1041         }
1042 
1043         if (bottom < KS_rowMax) {
1044             do {
1045                 bottom++;
1046                 int lastHidden;
1047                 if (sheet->rowFormats()->isHiddenOrFiltered(bottom, &lastHidden)) {
1048                     bottom = lastHidden;
1049                 } else {
1050                     break;
1051                 }
1052             } while (bottom != KS_rowMax);
1053         }
1054 
1055         if (top > 1) {
1056             do {
1057                 top--;
1058                 int firstHidden;
1059                 if (sheet->rowFormats()->isHiddenOrFiltered(top, 0, &firstHidden)) {
1060                     top = firstHidden;
1061                 } else {
1062                     break;
1063                 }
1064             } while (top != 1);
1065         }
1066 
1067         area.setLeft(left);
1068         area.setRight(right);
1069         area.setTop(top);
1070         area.setBottom(bottom);
1071 
1072         extendedRegion.add(area, element->sheet());
1073     }
1074 
1075     const QList<Cell> masterCells = sheet->cellStorage()->masterCells(extendedRegion);
1076     for (int i = 0; i < masterCells.count(); ++i)
1077         extendedRegion.add(masterCells[i].cellPosition(), sheet);
1078 
1079     emit changed(extendedRegion);
1080 }
1081 
scrollToCursor()1082 void Selection::scrollToCursor()
1083 {
1084     const QPoint location = cursor();
1085     Sheet *const sheet = activeSheet();
1086 
1087     // Adjust the maximum accessed column and row for the scrollbars.
1088     emit updateAccessedCellRange(sheet, location);
1089 
1090     // The cell geometry expanded by some pixels in each direction.
1091     const Cell cell = Cell(sheet, location).masterCell();
1092     const double xpos = sheet->columnPosition(cell.cellPosition().x());
1093     const double ypos = sheet->rowPosition(cell.cellPosition().y());
1094     const double pixelWidth = canvas()->viewConverter()->viewToDocumentX(1);
1095     const double pixelHeight = canvas()->viewConverter()->viewToDocumentY(1);
1096     QRectF rect(xpos, ypos, cell.width(), cell.height());
1097     rect.adjust(-2*pixelWidth, -2*pixelHeight, +2*pixelWidth, +2*pixelHeight);
1098     rect = rect & QRectF(QPointF(0.0, 0.0), sheet->documentSize());
1099 
1100     // Scroll to cell.
1101     canvas()->canvasController()->ensureVisible(canvas()->viewConverter()->documentToView(rect), true);
1102 }
1103 
dump() const1104 void Selection::dump() const
1105 {
1106     debugSheets << *this;
1107     debugSheets << "d->activeElement:" << d->activeElement;
1108     debugSheets << "d->activeSubRegionStart:" << d->activeSubRegionStart;
1109     debugSheets << "d->activeSubRegionLength:" << d->activeSubRegionLength;
1110 }
1111 
1112 /***************************************************************************
1113   class Point
1114 ****************************************************************************/
1115 
Point(const QPoint & point)1116 Selection::Point::Point(const QPoint& point)
1117         : Region::Point(point),
1118         m_color(Qt::black)
1119 {
1120 }
1121 
Point(const QString & string)1122 Selection::Point::Point(const QString& string)
1123         : Region::Point(string),
1124         m_color(Qt::black)
1125 {
1126 }
1127 
Point(const Region::Point & point)1128 Selection::Point::Point(const Region::Point& point)
1129         : Region::Point(point),
1130         m_color(Qt::black)
1131 {
1132 }
1133 
1134 /***************************************************************************
1135   class Range
1136 ****************************************************************************/
1137 
Range(const QRect & range)1138 Selection::Range::Range(const QRect& range)
1139         : Region::Range(range),
1140         m_color(Qt::black)
1141 {
1142 }
1143 
Range(const Calligra::Sheets::Region::Point & tl,const Calligra::Sheets::Region::Point & br)1144 Selection::Range::Range(const Calligra::Sheets::Region::Point& tl, const Calligra::Sheets::Region::Point& br)
1145         : Region::Range(tl, br),
1146         m_color(Qt::black)
1147 {
1148 }
1149 
Range(const QString & string)1150 Selection::Range::Range(const QString& string)
1151         : Region::Range(string),
1152         m_color(Qt::black)
1153 {
1154 }
1155 
Range(const Region::Range & range)1156 Selection::Range::Range(const Region::Range& range)
1157         : Region::Range(range),
1158         m_color(Qt::black)
1159 {
1160 }
1161