1/* This file is part of the KDE project
2 Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
3 Copyright 2005-2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program 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
13 GNU 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 program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19
20// Local
21#include "Region.h"
22
23#include <QRegExp>
24#include <QStringList>
25
26#include <kdebug.h>
27
28#include "Cell.h"
29#include "calligra_sheets_limits.h"
30#include "Map.h"
31#include "NamedAreaManager.h"
32#include "Sheet.h"
33#include "Util.h"
34
35namespace Calligra
36{
37namespace Sheets
38{
39
40class Region::Private : public QSharedData
41{
42public:
43 Private()
44 : map(0),
45 cells(QList<Element*>()) {
46 }
47
48 const Map* map;
49 mutable QList<Element*> cells;
50};
51
52
53/***************************************************************************
54 class Region
55****************************************************************************/
56
57Region::Region()
58{
59 d = new Private();
60}
61
62Region::Region(const QString& string, const Map* map, Sheet* fallbackSheet)
63{
64 d = new Private();
65 d->map = map;
66
67 if (string.isEmpty()) {
68 return;
69 }
70 // FIXME Stefan: Does not respect quoted names!
71 QStringList substrings = string.split(';');
72 QStringList::ConstIterator end = substrings.constEnd();
73 for (QStringList::ConstIterator it = substrings.constBegin(); it != end; ++it) {
74 QString sRegion = *it;
75
76 // check for a named area first
77 const Region namedAreaRegion = map ? map->namedAreaManager()->namedArea(sRegion) : Region();
78 if (namedAreaRegion.isValid()) {
79 ConstIterator end(namedAreaRegion.d->cells.constEnd());
80 for (ConstIterator it = namedAreaRegion.d->cells.constBegin(); it != end; ++it) {
81 Element *element = *it;
82 if (element->type() == Element::Point) {
83 Point* point = static_cast<Point*>(element);
84 d->cells.append(createPoint(*point));
85 } else {
86 Range* range = static_cast<Range*>(element);
87 d->cells.append(createRange(*range));
88 }
89 }
90 continue;
91 }
92
93 // Single cell or cell range
94 int delimiterPos = sRegion.indexOf(':');
95 if (delimiterPos > -1) {
96 // range
97 QString sUL = sRegion.left(delimiterPos);
98 QString sLR = sRegion.mid(delimiterPos + 1);
99
100 Sheet* firstSheet = map ? filterSheetName(sUL) : 0;
101 Sheet* lastSheet = map ? filterSheetName(sLR) : 0;
102 // TODO: lastSheet is silently ignored if it is different from firstSheet
103
104 // Still has the sheet name separator?
105 if (sUL.contains('!') || sLR.contains('!'))
106 return;
107
108 if (!firstSheet)
109 firstSheet = fallbackSheet;
110 if (!lastSheet)
111 lastSheet = fallbackSheet;
112
113 Point ul(sUL);
114 Point lr(sLR);
115
116 if (ul.isValid() && lr.isValid()) {
117 Range* range = createRange(ul, lr);
118 if (firstSheet) range->setSheet(firstSheet);
119 d->cells.append(range);
120 } else if (ul.isValid()) {
121 Point* point = createPoint(ul);
122 if (firstSheet) point->setSheet(firstSheet);
123 d->cells.append(point);
124 } else { // lr.isValid()
125 Point* point = createPoint(lr);
126 if (firstSheet) point->setSheet(firstSheet);
127 d->cells.append(point);
128 }
129 } else {
130 // single cell
131 Sheet* sheet = map ? filterSheetName(sRegion) : 0;
132 // Still has the sheet name separator?
133 if (sRegion.contains('!'))
134 return;
135 if (!sheet)
136 sheet = fallbackSheet;
137 Point* point = createPoint(sRegion);
138 if(sheet) point->setSheet(sheet);
139 d->cells.append(point);
140 }
141 }
142}
143
144Region::Region(const QRect& rect, Sheet* sheet)
145{
146 d = new Private();
147
148 Q_ASSERT(!rect.isNull());
149 if (rect.isNull()) {
150 kError(36001) << "Region::Region(const QRect&): QRect is empty!" << endl;
151 return;
152 }
153 add(rect, sheet);
154}
155
156Region::Region(const QPoint& point, Sheet* sheet)
157{
158 d = new Private();
159
160 Q_ASSERT(!point.isNull());
161 if (point.isNull()) {
162 kError(36001) << "Region::Region(const QPoint&): QPoint is empty!" << endl;
163 return;
164 }
165 add(point, sheet);
166}
167
168Region::Region(const Region& list)
169{
170 d = new Private();
171 d->map = list.d->map;
172#if QT_VERSION >= 0x040700
173 d->cells.reserve(list.d->cells.size());
174#endif
175 ConstIterator end(list.d->cells.constEnd());
176 for (ConstIterator it = list.d->cells.constBegin(); it != end; ++it) {
177 Element *element = *it;
178 if (element->type() == Element::Point) {
179 Point* point = static_cast<Point*>(element);
180 d->cells.append(createPoint(*point));
181 } else {
182 Range* range = static_cast<Range*>(element);
183 d->cells.append(createRange(*range));
184 }
185 }
186}
187
188Region::Region(int x, int y, Sheet* sheet)
189{
190 d = new Private();
191
192 Q_ASSERT(isValid(QPoint(x, y)));
193 if (!isValid(QPoint(x, y))) {
194 kError(36001) << "Region::Region(" << x << ", " << y << "): Coordinates are invalid!" << endl;
195 return;
196 }
197 add(QPoint(x, y), sheet);
198}
199
200Region::Region(int x, int y, int width, int height, Sheet* sheet)
201{
202 d = new Private();
203
204 Q_ASSERT(isValid(QRect(x, y, width, height)));
205 if (!isValid(QRect(x, y, width, height))) {
206 kError(36001) << "Region::Region(" << x << ", " << y << ", " << width << ", " << height << "): Dimensions are invalid!" << endl;
207 return;
208 }
209 add(QRect(x, y, width, height), sheet);
210}
211
212
213Region::~Region()
214{
215 qDeleteAll(d->cells);
216}
217
218QVector<QRect> Region::rects() const
219{
220 QVector<QRect> cellRects;
221 foreach(Element *element, d->cells) {
222 cellRects.append(element->rect());
223 }
224 return cellRects;
225}
226
227const Map* Region::map() const
228{
229 Q_ASSERT(d->map);
230 return d->map;
231}
232
233void Region::setMap(const Map* map)
234{
235 d->map = map;
236}
237
238bool Region::isValid() const
239{
240 if (d->cells.isEmpty())
241 return false;
242 ConstIterator end = d->cells.constEnd();
243 for (ConstIterator it = d->cells.constBegin(); it != end; ++it) {
244 if (!(*it)->isValid())
245 return false;
246 }
247 return true;
248}
249
250bool Region::isSingular() const
251{
252 if (d->cells.isEmpty() || d->cells.count() > 1 || (*d->cells.constBegin())->type() != Element::Point) {
253 return false;
254 }
255 return true;
256}
257
258bool Region::isContiguous() const
259{
260 if (d->cells.count() != 1 || !isValid()) {
261 return false;
262 }
263 return true;
264}
265
266QString Region::name(Sheet* originSheet) const
267{
268 QStringList names;
269 ConstIterator endOfList(d->cells.constEnd());
270 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
271 Element *element = *it;
272 names += element->name(originSheet);
273 }
274 return names.isEmpty() ? "" : names.join(";");
275}
276
277Region::Element* Region::add(const QPoint& point, Sheet* sheet)
278{
279 return insert(d->cells.count(), point, sheet, false);
280}
281
282Region::Element* Region::add(const QRect& range, Sheet* sheet)
283{
284 const QRect normalizedRange = normalized(range);
285 if (normalizedRange.width() == 0 || normalizedRange.height() == 0) {
286 return 0;
287 }
288 if (normalizedRange.size() == QSize(1, 1)) {
289 return add(normalizedRange.topLeft(), sheet);
290 }
291 return insert(d->cells.count(), normalizedRange, sheet, false);
292}
293
294Region::Element* Region::add(const Region& region, Sheet* sheet)
295{
296 ConstIterator endOfList(region.d->cells.constEnd());
297 for (ConstIterator it = region.d->cells.constBegin(); it != endOfList; ++it) {
298 add((*it)->rect(), (*it)->sheet() ? (*it)->sheet() : sheet);
299 }
300 return d->cells.isEmpty() ? 0 : d->cells.last();
301}
302
303void Region::sub(const QPoint& point, Sheet* sheet)
304{
305 // TODO Stefan: Improve!
306 Iterator endOfList(d->cells.end());
307 for (Iterator it = d->cells.begin(); it != endOfList; ++it) {
308 Element *element = *it;
309 if (element->sheet() != sheet) {
310 continue;
311 }
312 if (element->rect() == QRect(point, point)) {
313 delete element;
314 d->cells.removeAll(element);
315 break;
316 }
317 }
318}
319
320void Region::sub(const QRect& range, Sheet* sheet)
321{
322 const QRect normalizedRange = normalized(range);
323 // TODO Stefan: Improve!
324 Iterator endOfList(d->cells.end());
325 for (Iterator it = d->cells.begin(); it != endOfList; ++it) {
326 Element *element = *it;
327 if (element->sheet() != sheet) {
328 continue;
329 }
330 if (element->rect() == normalizedRange) {
331 delete element;
332 d->cells.removeAll(element);
333 break;
334 }
335 }
336}
337
338void Region::sub(const Region& region)
339{
340 ConstIterator endOfList(region.constEnd());
341 for (ConstIterator it = region.constBegin(); it != endOfList; ++it) {
342 Element *element = *it;
343 if (element->type() == Element::Point) {
344 Point* point = static_cast<Point*>(element);
345 sub(Region(point->pos()));
346 } else {
347 sub(Region(element->rect()));
348 }
349 }
350}
351
352Region Region::intersected(const Region& region) const
353{
354 // Special case 1: one of the regions is empty
355 if (region.isEmpty()) return region;
356 if (isEmpty()) return Region();
357
358 // Special case 2: If the region contains more elements than this one, do this operation in reverse (optimization)
359 if (region.cells().size() > cells().size())
360 return region.intersected (*this);
361
362 // Most common case: the region contains only one rectangle
363 Region result;
364 QVector<QRect> rects = region.rects();
365 if (rects.size() == 1) {
366 QRect rect = rects[0];
367 Sheet *s = region.cells()[0]->sheet();
368 // intersect each element with the rectangle
369 foreach(Element *element, d->cells) {
370 if (element->sheet() != s) continue;
371 if (element->type() == Element::Point) {
372 Point* point = static_cast<Point*>(element);
373 if (rect.contains (point->pos()))
374 result.add (point->pos(), s);
375 } else {
376 QRect rect2 = element->rect();
377 if (rect2.intersects (rect))
378 result.add (rect2.intersected (rect), s);
379 }
380 }
381 return result;
382 }
383
384 // Generic case. TODO: optimize this better - generating a ton of single-cell regions is slow
385 ConstIterator end(region.constEnd());
386 for (ConstIterator it = region.constBegin(); it != end; ++it) {
387 Element *element = *it;
388 if (element->type() == Element::Point) {
389 Point* point = static_cast<Point*>(element);
390 if(contains(point->pos(), element->sheet()))
391 result.add(point->pos(), element->sheet());
392 } else {
393 QRect rect = element->rect();
394 for(int c = rect.top(); c <= rect.bottom(); ++c) {
395 for(int r = rect.left(); r <= rect.right(); ++r) {
396 QPoint p(r,c);
397 if(contains(p, element->sheet()))
398 result.add(p, element->sheet());
399 }
400 }
401 }
402 }
403 return result;
404}
405
406Region Region::intersectedWithRow(int row) const
407{
408 Region result;
409 ConstIterator end(constEnd());
410 for (ConstIterator it = constBegin(); it != end; ++it) {
411 Element *element = *it;
412 if (element->type() == Element::Point) {
413 Point* point = static_cast<Point*>(element);
414 if (point->pos().y() == row)
415 result.add(point->pos(), point->sheet());
416 } else {
417 QRect rect = element->rect();
418 if (rect.top() <= row && rect.bottom() >= row) {
419 result.add(QRect(rect.left(), row, rect.width(), 1), element->sheet());
420 }
421 }
422 }
423 return result;
424}
425
426Region::Element* Region::eor(const QPoint& point, Sheet* sheet)
427{
428 bool containsPoint = false;
429
430 int index = 0;
431 while (index < d->cells.count()) {
432 if (!d->cells[index]->contains(point)) {
433 ++index;
434 continue;
435 }
436 containsPoint = true;
437 int x = point.x();
438 int y = point.y();
439 QRect fullRange = d->cells[index]->rect();
440 delete d->cells.takeAt(index);
441
442 // top range
443 int left = fullRange.left();
444 int top = fullRange.top();
445 int width = fullRange.width();
446 int height = y - top;
447 if (height > 0) {
448 insert(index, QRect(left, top, width, height), sheet);
449 }
450 // left range
451 left = fullRange.left();
452 top = y;
453 width = qMax(0, x - left);
454 height = 1;
455 if (width > 0) {
456 insert(index, QRect(left, top, width, height), sheet);
457 }
458 // right range
459 left = qMin(x + 1, fullRange.right());
460 top = y;
461 width = qMax(0, fullRange.right() - x);
462 height = 1;
463 if (width > 0) {
464 insert(index, QRect(left, top, width, height), sheet);
465 }
466 // bottom range
467 left = fullRange.left();
468 top = y + 1;
469 width = fullRange.width();
470 height = qMax(0, fullRange.bottom() - y);
471 if (height > 0) {
472 insert(index, QRect(left, top, width, height), sheet);
473 }
474 return d->cells[index];
475 }
476
477 if (!containsPoint) {
478 return add(point, sheet);
479 }
480 return 0;
481}
482
483Region::Element* Region::insert(int pos, const QPoint& point, Sheet* sheet, bool multi)
484{
485 if (point.x() < 1 || point.y() < 1) {
486 return 0;
487 }
488 // Keep boundaries.
489 pos = qBound(0, pos, cells().count());
490
491 bool containsPoint = false;
492// bool adjacentPoint = false;
493// QRect neighbour;
494
495 // we don't have to check for occurrences?
496 if (multi) {
497 Point* rpoint = createPoint(point);
498 rpoint->setSheet(sheet);
499 d->cells.insert(pos, rpoint);
500 return d->cells[pos];
501 }
502
503 ConstIterator endOfList(d->cells.constEnd());
504 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
505 Element *element = *it;
506 if (sheet && sheet != element->sheet()) {
507 continue;
508 }
509 if (element->contains(point)) {
510 containsPoint = true;
511 break;
512 }
513 /* else
514 {
515 neighbour = element->rect();
516 neighbour.setTopLeft(neighbour.topLeft() - QPoint(1,1));
517 neighbour.setBottomRight(neighbour.bottomRight() + QPoint(1,1));
518 if (neighbour.contains(point))
519 {
520 adjacentPoint = true; // TODO Stefan: Implement!
521 break;
522 }
523 }*/
524 }
525 if (!containsPoint) {
526 Point* rpoint = createPoint(point);
527 rpoint->setSheet(sheet);
528 d->cells.insert(pos, rpoint);
529 return d->cells[pos];
530 }
531 return 0;
532}
533
534Region::Element* Region::insert(int pos, const QRect& range, Sheet* sheet, bool multi)
535{
536 // Keep boundaries.
537 pos = qBound(0, pos, cells().count());
538
539 const QRect normalizedRange = normalized(range);
540 if (normalizedRange.size() == QSize(1, 1)) {
541 return insert(pos, normalizedRange.topLeft(), sheet);
542 }
543
544 if (multi) {
545 Range* rrange = createRange(normalizedRange);
546 rrange->setSheet(sheet);
547 d->cells.insert(pos, rrange);
548 return d->cells[pos];
549 }
550
551 bool containsRange = false;
552
553 for (int index = 0; index < d->cells.count(); ++index) {
554 if (sheet && sheet != d->cells[index]->sheet()) {
555 continue;
556 }
557 if (d->cells[index]->contains(normalizedRange)) {
558 containsRange = true;
559 } else if (normalizedRange.contains(d->cells[index]->rect())) {
560 delete d->cells.takeAt(index--);
561 continue;
562 }
563 }
564 if (!containsRange) {
565 // Keep boundaries.
566 pos = qBound(0, pos, cells().count());
567
568 Range* rrange = createRange(normalizedRange);
569 rrange->setSheet(sheet);
570 d->cells.insert(pos, rrange);
571 return d->cells[pos];
572 }
573 return 0;
574}
575
576QSet<int> Region::columnsSelected() const
577{
578 QSet<int> result;
579 ConstIterator endOfList(d->cells.constEnd());
580 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
581 if ((*it)->isColumn()) {
582 const QRect range = (*it)->rect();
583 const int right = range.right();
584 for (int col = range.left(); col <= right; ++col) {
585 result << col;
586 }
587 }
588 }
589 return result;
590}
591
592QSet<int> Region::rowsSelected() const
593{
594 QSet<int> result;
595 ConstIterator endOfList(d->cells.constEnd());
596 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
597 if ((*it)->isRow()) {
598 const QRect range = (*it)->rect();
599 const int bottom = range.bottom();
600 for (int row = range.top(); row <= bottom; ++row) {
601 result << row;
602 }
603 }
604 }
605 return result;
606}
607
608QSet<int> Region::columnsAffected() const
609{
610 QSet<int> result;
611 ConstIterator endOfList(d->cells.constEnd());
612 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
613 const QRect range = (*it)->rect();
614 const int right = range.right();
615 for (int col = range.left(); col <= right; ++col) {
616 result << col;
617 }
618 }
619 return result;
620}
621
622QSet<int> Region::rowsAffected() const
623{
624 QSet<int> result;
625 ConstIterator endOfList(d->cells.constEnd());
626 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
627 const QRect range = (*it)->rect();
628 const int bottom = range.bottom();
629 for (int row = range.top(); row <= bottom; ++row) {
630 result << row;
631 }
632 }
633 return result;
634}
635
636bool Region::isColumnSelected(uint col) const
637{
638 ConstIterator endOfList(d->cells.constEnd());
639 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
640 Element *element = *it;
641 QRect region = element->rect();
642 if ((col == 0 || ((int)col >= region.left() && (int)col <= region.right())) &&
643 region.top() == 1 && region.bottom() == KS_rowMax) {
644 return true;
645 }
646 }
647 return false;
648}
649
650bool Region::isRowSelected(uint row) const
651{
652 ConstIterator endOfList(d->cells.constEnd());
653 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
654 Element *element = *it;
655 QRect region = element->rect();
656 if ((row == 0 || ((int)row >= region.top() && (int)row <= region.bottom())) &&
657 region.left() == 1 && region.right() == KS_colMax) {
658 return true;
659 }
660 }
661 return false;
662}
663
664bool Region::isColumnOrRowSelected() const
665{
666 ConstIterator endOfList(d->cells.constEnd());
667 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
668 Element *element = *it;
669 QRect region = element->rect();
670 if ((region.top() == 1 && region.bottom() == KS_rowMax) ||
671 (region.left() == 1 && region.right() == KS_colMax)) {
672 return true;
673 }
674 }
675 return false;
676}
677
678bool Region::isAllSelected() const
679{
680 if (d->cells.count() != 1)
681 return false;
682 Q_ASSERT(d->cells.first());
683 return d->cells.first()->isAll();
684}
685
686bool Region::contains(const QPoint& point, Sheet* sheet) const
687{
688 if (d->cells.isEmpty()) {
689 return false;
690 }
691 ConstIterator endOfList(d->cells.constEnd());
692 for (ConstIterator it = d->cells.constBegin(); it != endOfList; ++it) {
693 Element *element = *it;
694 if (element->contains(point)) {
695 if (sheet && element->sheet() != sheet) {
696 return false;
697 }
698 return true;
699 }
700 }
701 return false;
702}
703
704bool Region::isEmpty() const
705{
706 return d->cells.isEmpty();
707}
708
709void Region::clear()
710{
711 qDeleteAll(d->cells);
712 d->cells.clear();
713}
714
715QRect Region::firstRange() const
716{
717 if (!isValid())
718 return QRect();
719 return d->cells.value(0)->rect();
720}
721
722QRect Region::lastRange() const
723{
724 if (!isValid())
725 return QRect();
726 return d->cells.value(d->cells.count() - 1)->rect();
727}
728
729Sheet* Region::firstSheet() const
730{
731 if (!isValid())
732 return 0;
733 return d->cells.value(0)->sheet();
734}
735
736Sheet* Region::lastSheet() const
737{
738 if (!isValid())
739 return 0;
740 return d->cells.value(d->cells.count() - 1)->sheet();
741}
742
743QRect Region::boundingRect() const
744{
745 int left = KS_colMax;
746 int right = 1;
747 int top = KS_rowMax;
748 int bottom = 1;
749 Region::ConstIterator endOfList = cells().constEnd();
750 for (Region::ConstIterator it = cells().constBegin(); it != endOfList; ++it) {
751 QRect range = (*it)->rect();
752 if (range.left() < left) {
753 left = range.left();
754 }
755 if (range.right() > right) {
756 right = range.right();
757 }
758 if (range.top() < top) {
759 top = range.top();
760 }
761 if (range.bottom() > bottom) {
762 bottom = range.bottom();
763 }
764 }
765 return QRect(left, top, right - left + 1, bottom - top + 1);
766}
767
768QRect Region::normalized(const QRect& rect)
769{
770 QRect normalizedRect(rect);
771 if (rect.left() > rect.right()) {
772 normalizedRect.setLeft(rect.right());
773 normalizedRect.setRight(rect.left());
774 }
775 if (rect.top() > rect.bottom()) {
776 normalizedRect.setTop(rect.bottom());
777 normalizedRect.setBottom(rect.top());
778 }
779 if (rect.right() > KS_colMax) {
780 normalizedRect.setRight(KS_colMax);
781 }
782 if (rect.bottom() > KS_rowMax) {
783 normalizedRect.setBottom(KS_rowMax);
784 }
785 return normalizedRect;
786}
787
788Region::ConstIterator Region::constBegin() const
789{
790 return d->cells.constBegin();
791}
792
793Region::ConstIterator Region::constEnd() const
794{
795 return d->cells.constEnd();
796}
797
798bool Region::isValid(const QPoint& point)
799{
800 if (point.x() < 1 || point.y() < 1 ||
801 point.x() > KS_colMax || point.y() > KS_rowMax)
802 return false;
803 else
804 return true;
805}
806
807bool Region::isValid(const QRect& rect)
808{
809 if (!isValid(rect.topLeft()) || !isValid(rect.bottomRight()) ||
810 rect.width() == 0 || rect.height() == 0)
811 return false;
812 else
813 return true;
814}
815
816static void append(const QChar *from, const QChar *to, QChar **dest)
817{
818 while (from < to) {
819 **dest = *from;
820 ++from;
821 ++*dest;
822 }
823}
824
825// static
826void Region::loadOdf(const QChar *&data, const QChar *&end, QChar *&out)
827{
828 enum { Start, InQuotes } state = Start;
829
830 if (*data == QChar('$', 0)) {
831 ++data;
832 }
833
834 bool isRange = false;
835
836 const QChar *pos = data;
837 while (data < end) {
838 switch (state) {
839 case Start:
840 switch (data->unicode()) {
841 case '\'': // quoted sheet name or named area
842 state = InQuotes;
843 break;
844 case '.': // sheet name separator
845 if (pos != data && !isRange) {
846 append(pos, data, &out);
847 *out = QChar('!', 0);
848 ++out;
849 }
850 pos = data;
851 ++pos;
852 break;
853 case ':': { // cell separator
854 isRange = true;
855 append(pos, data, &out);
856 *out = *data; // append :
857 ++out;
858 const QChar * next = data + 1;
859 if (!next->isNull()) {
860 const QChar * nextnext = next + 1;
861 if (!nextnext->isNull() && *next == QChar('$', 0) && *nextnext != QChar('.', 0)) {
862 ++data;
863 }
864 }
865 pos = data + 1;
866 } break;
867 case ' ': // range separator
868 append(pos, data, &out);
869 *out = QChar(';', 0);
870 ++out;
871 pos = data;
872 break;
873 default:
874 break;
875 }
876 break;
877 case InQuotes:
878 if (data->unicode() == '\'') {
879 // an escaped apostrophe?
880 // As long as Calligra Sheets does not support fixed sheets eat the dollar sign.
881 const QChar * next = data + 1;
882 if (!next->isNull() && *next == QChar('\'', 0)) {
883 ++data;
884 }
885 else { // the end
886 state = Start;
887 }
888 }
889 break;
890 }
891 ++data;
892 }
893 append(pos, data, &out);
894}
895
896// static
897QString Region::loadOdf(const QString& expression)
898{
899 QString result;
900 QString temp;
901 bool isRange = false;
902 enum { Start, InQuotes } state = Start;
903 int i = 0;
904 // NOTE Stefan: As long as KSpread does not support fixed sheets eat the dollar sign.
905 if (expression[i] == '$')
906 ++i;
907 while (i < expression.count()) {
908 switch (state) {
909 case Start: {
910 if (expression[i] == '\'') { // quoted sheet name or named area
911 temp.append(expression[i]);
912 state = InQuotes;
913 } else if (expression[i] == '.') { // sheet name separator
914 // was there already a sheet name?
915 if (!temp.isEmpty() && !isRange) {
916 result.append(temp);
917 result.append('!');
918 }
919 temp.clear();
920 } else if (expression[i] == ':') { // cell separator
921 isRange = true;
922 result.append(temp);
923 result.append(':');
924 temp.clear();
925 // NOTE Stefan: As long as KSpread does not support fixed sheets eat the dollar sign.
926 if (i + 2 < expression.count() && expression[i+1] == '$' && expression[i+2] != '.')
927 ++i;
928 } else if (expression[i] == ' ') { // range separator
929 result.append(temp);
930 result.append(';');
931 temp.clear();
932 } else
933 temp.append(expression[i]);
934 ++i;
935 break;
936 }
937 case InQuotes: {
938 temp.append(expression[i]);
939 if (expression[i] == '\'') {
940 // an escaped apostrophe?
941 if (i + 1 < expression.count() && expression[i+1] == '\'')
942 ++i; // eat it
943 else // the end
944 state = Start;
945 }
946 ++i;
947 break;
948 }
949 }
950 }
951 return result + temp;
952}
953
954// static
955QString Region::saveOdf(const QString& expression)
956{
957 QString result;
958 QString sheetName;
959 QString temp;
960 enum { Start, InQuotes } state = Start;
961 int i = 0;
962 while (i < expression.count()) {
963 switch (state) {
964 case Start: {
965 if (expression[i] == '\'') {
966 temp.append(expression[i]);
967 state = InQuotes;
968 } else if (expression[i] == '!') { // sheet name separator
969 // There has to be a sheet name.
970 if (temp.isEmpty())
971 return expression; // error
972 if (temp.count() > 2 && (temp[0] != '\'' && temp[temp.count()-1] != '\'')) {
973 temp.replace('\'', "''");
974 if (temp.contains(' ') || temp.contains('.') ||
975 temp.contains(';') || temp.contains('!') ||
976 temp.contains('$') || temp.contains(']'))
977 temp = '\'' + temp + '\'';
978 }
979 sheetName = temp;
980 result.append(temp);
981 result.append('.');
982 temp.clear();
983 } else if (expression[i] == ':') { // cell separator
984 if (result.isEmpty())
985 result = '.';
986 result.append(temp);
987 result.append(':');
988 result.append(sheetName);
989 result.append('.');
990 temp.clear();
991 } else if (expression[i] == ';') { // range separator
992 result.append(temp);
993 result.append(' ');
994 temp.clear();
995 } else
996 temp.append(expression[i]);
997 ++i;
998 break;
999 }
1000 case InQuotes: {
1001 temp.append(expression[i]);
1002 if (expression[i] == '\'') {
1003 // an escaped apostrophe?
1004 if (i + 1 < expression.count() && expression[i+1] == '\'')
1005 ++i; // eat it
1006 else // the end
1007 state = Start;
1008 }
1009 ++i;
1010 break;
1011 }
1012 }
1013 }
1014 if (result.isEmpty())
1015 result = '.';
1016 return result + temp;
1017}
1018
1019QString Region::saveOdf() const
1020{
1021 return saveOdf(Region::name());
1022}
1023
1024QList<Region::Element*>& Region::cells() const
1025{
1026 return d->cells;
1027}
1028
1029bool Region::operator==(const Region& other) const
1030{
1031 if (d->cells.count() != other.d->cells.count())
1032 return false;
1033 ConstIterator endOfList(d->cells.constEnd());
1034 ConstIterator endOfOtherList(other.d->cells.constEnd());
1035 ConstIterator it = d->cells.constBegin();
1036 ConstIterator it2 = other.d->cells.constBegin();
1037 while (it != endOfList && it2 != endOfOtherList) {
1038 if ((*it)->sheet() != (*it2)->sheet())
1039 return false;
1040 if ((*it++)->rect() != (*it2++)->rect())
1041 return false;
1042 }
1043 return true;
1044}
1045
1046void Region::operator=(const Region& other)
1047{
1048 d->map = other.d->map;
1049 clear();
1050 ConstIterator end(other.d->cells.constEnd());
1051 for (ConstIterator it = other.d->cells.constBegin(); it != end; ++it) {
1052 Element *element = *it;
1053 if (element->type() == Element::Point) {
1054 Point* point = static_cast<Point*>(element);
1055 d->cells.append(createPoint(*point));
1056 } else {
1057 Range* range = static_cast<Range*>(element);
1058 d->cells.append(createRange(*range));
1059 }
1060 }
1061}
1062
1063Sheet* Region::filterSheetName(QString& sRegion)
1064{
1065 Sheet* sheet = 0;
1066 int delimiterPos = sRegion.lastIndexOf('!');
1067 if (delimiterPos < 0)
1068 delimiterPos = sRegion.lastIndexOf('.');
1069 if (delimiterPos > -1) {
1070 QString sheetName = sRegion.left(delimiterPos);
1071 sheet = d->map->findSheet(sheetName);
1072 // try again without apostrophes
1073 while(!sheet && sheetName.count() > 2 && sheetName[0] == '\'' && sheetName[sheetName.count()-1] == '\'') {
1074 sheetName = sheetName.mid(1, sheetName.count() - 2);
1075 sheet = d->map->findSheet(sheetName);
1076 }
1077 // remove the sheet name, incl. '!', from the string
1078 if (sheet)
1079 sRegion = sRegion.right(sRegion.length() - delimiterPos - 1);
1080 }
1081 return sheet;
1082}
1083
1084Region::Point* Region::createPoint(const QPoint& point) const
1085{
1086 return new Point(point);
1087}
1088
1089Region::Point* Region::createPoint(const QString& string) const
1090{
1091 return new Point(string);
1092}
1093
1094Region::Point* Region::createPoint(const Point& point) const
1095{
1096 return new Point(point);
1097}
1098
1099Region::Range* Region::createRange(const QRect& rect) const
1100{
1101 return new Range(rect);
1102}
1103
1104Region::Range* Region::createRange(const Point& tl, const Point& br) const
1105{
1106 return new Range(tl, br);
1107}
1108
1109Region::Range* Region::createRange(const QString& string) const
1110{
1111 return new Range(string);
1112}
1113
1114Region::Range* Region::createRange(const Range& range) const
1115{
1116 return new Range(range);
1117}
1118
1119/***************************************************************************
1120 class Element
1121****************************************************************************/
1122
1123Region::Element::Element()
1124 : m_sheet(0)
1125{
1126}
1127
1128Region::Element::~Element()
1129{
1130}
1131
1132
1133/***************************************************************************
1134 class Point
1135****************************************************************************/
1136static int firstNonCharPos(const QString& s, int pos = 0)
1137{
1138 int result = -1;
1139 const QChar *data = s.constData();
1140 int i = 0;
1141 while (!data->isNull()) {
1142 if (i >= pos) {
1143 char c = data->unicode();
1144 if (c < 'A' || c > 'z' || (c < 'a' && c > 'Z')) {
1145 result = i;
1146 break;
1147 }
1148 }
1149 ++data;
1150 ++i;
1151 }
1152 return result;
1153}
1154
1155Region::Point::Point(const QPoint& point)
1156 : Region::Element()
1157 , m_point(point)
1158 , m_fixedColumn(false)
1159 , m_fixedRow(false)
1160{
1161 if (m_point.x() > KS_colMax)
1162 m_point.setX(KS_colMax);
1163 if (m_point.y() > KS_rowMax)
1164 m_point.setY(KS_rowMax);
1165}
1166
1167Region::Point::Point(const QString& string)
1168 : Region::Element()
1169 , m_fixedColumn(false)
1170 , m_fixedRow(false)
1171{
1172 const uint length = string.length();
1173 if (length == 0)
1174 return;
1175
1176 uint p = 0;
1177
1178 // Fixed ?
1179 if (string[0] == QChar('$', 0)) {
1180 m_fixedColumn = true;
1181 p++;
1182 }
1183
1184 // Malformed ?
1185 if (p == length)
1186 return;
1187
1188 if ((string[p] < QChar('A',0) || string[p] > QChar('Z',0)) && (string[p] < QChar('a', 0) || string[p] > QChar('z', 0)))
1189 return;
1190
1191 //default is error
1192 int x = -1;
1193 //search for the first character != text
1194 int result = firstNonCharPos(string, p);
1195
1196 //get the column number for the character between actual position and the first non text character
1197 if (result != -1)
1198 x = Util::decodeColumnLabelText(string.mid(p, result - p)); // x is defined now
1199 else // If there isn't any, then this is not a point -> return
1200 return;
1201 p = result;
1202
1203 //limit the x-value
1204 //Q_ASSERT(x >= 1 && x <= KS_colMax);
1205 if (x < 1)
1206 return;
1207 if (x > KS_colMax)
1208 x = KS_colMax;
1209
1210 // Malformed ?
1211 if (p == length)
1212 return;
1213
1214 if (string[p] == QChar('$', 0)) {
1215 m_fixedRow = true;
1216 p++;
1217 }
1218
1219 // Malformed ?
1220 if (p == length)
1221 return;
1222
1223 uint p2 = p;
1224 while (p < length) {
1225 if (!string[p++].isDigit())
1226 return;
1227 }
1228
1229 bool ok;
1230 int y = string.mid(p2, p - p2).toInt(&ok);
1231
1232 //limit the y-value
1233 //Q_ASSERT(y >= 1 && y <= KS_rowMax);
1234 if (!ok || y < 1)
1235 return;
1236 if (y > KS_rowMax)
1237 y = KS_rowMax;
1238
1239 m_point = QPoint(x, y);
1240}
1241
1242Region::Point::~Point()
1243{
1244}
1245
1246QString Region::Point::name(Sheet* originSheet) const
1247{
1248 QString name;
1249 if (m_sheet && m_sheet != originSheet) {
1250 name.append(m_sheet->sheetName());
1251 name.replace('\'', "''");
1252 if (name.contains('!') || name.contains(' ') || name.contains(';') || name.contains('$'))
1253 name = '\'' + name + '\'';
1254 name.append('!');
1255 }
1256 if (m_fixedColumn)
1257 name.append('$');
1258 name.append(Cell::columnName(m_point.x()));
1259 if (m_fixedRow)
1260 name.append('$');
1261 name.append(QString::number(m_point.y()));
1262 return name;
1263}
1264
1265bool Region::Point::contains(const QPoint& point) const
1266{
1267 return (m_point == point);
1268}
1269
1270bool Region::Point::contains(const QRect& range) const
1271{
1272 return (range.width() == 1) && (range.height() == 1) && (range.topLeft() == m_point);
1273}
1274
1275Cell Region::Point::cell() const
1276{
1277 return Cell(m_sheet, m_point);
1278}
1279
1280/***************************************************************************
1281 class Range
1282****************************************************************************/
1283
1284Region::Range::Range(const QRect& rect)
1285 : Region::Element()
1286 , m_range(rect)
1287 , m_fixedTop(false)
1288 , m_fixedLeft(false)
1289 , m_fixedBottom(false)
1290 , m_fixedRight(false)
1291{
1292 if (m_range.right() > KS_colMax)
1293 m_range.setRight(KS_colMax);
1294 if (m_range.bottom() > KS_rowMax)
1295 m_range.setBottom(KS_rowMax);
1296}
1297
1298Region::Range::Range(const Calligra::Sheets::Region::Point& ul, const Calligra::Sheets::Region::Point& lr)
1299 : Region::Element()
1300 , m_fixedTop(false)
1301 , m_fixedLeft(false)
1302 , m_fixedBottom(false)
1303 , m_fixedRight(false)
1304{
1305 if (!ul.isValid() || !lr.isValid())
1306 return;
1307 m_range = QRect(ul.pos(), lr.pos());
1308 m_fixedTop = ul.isRowFixed();
1309 m_fixedLeft = ul.isColumnFixed();
1310 m_fixedBottom = lr.isRowFixed();
1311 m_fixedRight = lr.isColumnFixed();
1312}
1313
1314Region::Range::Range(const QString& sRange)
1315 : Region::Element()
1316 , m_fixedTop(false)
1317 , m_fixedLeft(false)
1318 , m_fixedBottom(false)
1319 , m_fixedRight(false)
1320{
1321 int delimiterPos = sRange.indexOf(':');
1322 if (delimiterPos == -1)
1323 return;
1324
1325 Region::Point ul(sRange.left(delimiterPos));
1326 Region::Point lr(sRange.mid(delimiterPos + 1));
1327
1328 if (!ul.isValid() || !lr.isValid())
1329 return;
1330 m_range = QRect(ul.pos(), lr.pos());
1331 m_fixedTop = ul.isRowFixed();
1332 m_fixedLeft = ul.isColumnFixed();
1333 m_fixedBottom = lr.isRowFixed();
1334 m_fixedRight = lr.isColumnFixed();
1335}
1336
1337Region::Range::~Range()
1338{
1339}
1340
1341bool Region::Range::isColumn() const
1342{
1343 return (m_range.top() == 1 && m_range.bottom() == KS_rowMax);
1344}
1345
1346bool Region::Range::isRow() const
1347{
1348 return (m_range.left() == 1 && m_range.right() == KS_colMax);
1349}
1350
1351bool Region::Range::isAll() const
1352{
1353 return (m_range == QRect(1, 1, KS_colMax, KS_rowMax));
1354}
1355
1356bool Region::Range::contains(const QPoint& point) const
1357{
1358 return m_range.contains(point);
1359}
1360
1361bool Region::Range::contains(const QRect& range) const
1362{
1363 return m_range.contains(normalized(range));
1364}
1365
1366QString Region::Range::name(Sheet* originSheet) const
1367{
1368 QString name;
1369 if (m_sheet && m_sheet != originSheet) {
1370 name.append(m_sheet->sheetName());
1371 name.replace('\'', "''");
1372 if (name.contains('!') || name.contains(' ') || name.contains(';') || name.contains('$'))
1373 name = '\'' + name + '\'';
1374 name.append('!');
1375 }
1376 if (m_fixedLeft)
1377 name.append('$');
1378 name.append(Cell::columnName(m_range.left()));
1379 if (m_fixedTop)
1380 name.append('$');
1381 name.append(QString::number(m_range.top()));
1382 name.append(':');
1383 if (m_fixedRight)
1384 name.append('$');
1385 name.append(Cell::columnName(m_range.right()));
1386 if (m_fixedBottom)
1387 name.append('$');
1388 name.append(QString::number(m_range.bottom()));
1389 return name;
1390}
1391
1392} // namespace Sheets
1393} // namespace Calligra
1394