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