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 | |
35 | namespace Calligra |
36 | { |
37 | namespace Sheets |
38 | { |
39 | |
40 | class Region::Private : public QSharedData |
41 | { |
42 | public: |
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 | |
57 | Region::Region() |
58 | { |
59 | d = new Private(); |
60 | } |
61 | |
62 | Region::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 | |
144 | Region::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 | |
156 | Region::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 | |
168 | Region::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 | |
188 | Region::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 | |
200 | Region::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 | |
213 | Region::~Region() |
214 | { |
215 | qDeleteAll(d->cells); |
216 | } |
217 | |
218 | QVector<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 | |
227 | const Map* Region::map() const |
228 | { |
229 | Q_ASSERT(d->map); |
230 | return d->map; |
231 | } |
232 | |
233 | void Region::setMap(const Map* map) |
234 | { |
235 | d->map = map; |
236 | } |
237 | |
238 | bool 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 | |
250 | bool 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 | |
258 | bool Region::isContiguous() const |
259 | { |
260 | if (d->cells.count() != 1 || !isValid()) { |
261 | return false; |
262 | } |
263 | return true; |
264 | } |
265 | |
266 | QString 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 | |
277 | Region::Element* Region::add(const QPoint& point, Sheet* sheet) |
278 | { |
279 | return insert(d->cells.count(), point, sheet, false); |
280 | } |
281 | |
282 | Region::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 | |
294 | Region::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 | |
303 | void 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 | |
320 | void 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 | |
338 | void 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 | |
352 | Region 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 | |
406 | Region 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 | |
426 | Region::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 | |
483 | Region::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 | |
534 | Region::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 | |
576 | QSet<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 | |
592 | QSet<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 | |
608 | QSet<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 | |
622 | QSet<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 | |
636 | bool 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 | |
650 | bool 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 | |
664 | bool 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 | |
678 | bool 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 | |
686 | bool 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 | |
704 | bool Region::isEmpty() const |
705 | { |
706 | return d->cells.isEmpty(); |
707 | } |
708 | |
709 | void Region::clear() |
710 | { |
711 | qDeleteAll(d->cells); |
712 | d->cells.clear(); |
713 | } |
714 | |
715 | QRect Region::firstRange() const |
716 | { |
717 | if (!isValid()) |
718 | return QRect(); |
719 | return d->cells.value(0)->rect(); |
720 | } |
721 | |
722 | QRect Region::lastRange() const |
723 | { |
724 | if (!isValid()) |
725 | return QRect(); |
726 | return d->cells.value(d->cells.count() - 1)->rect(); |
727 | } |
728 | |
729 | Sheet* Region::firstSheet() const |
730 | { |
731 | if (!isValid()) |
732 | return 0; |
733 | return d->cells.value(0)->sheet(); |
734 | } |
735 | |
736 | Sheet* Region::lastSheet() const |
737 | { |
738 | if (!isValid()) |
739 | return 0; |
740 | return d->cells.value(d->cells.count() - 1)->sheet(); |
741 | } |
742 | |
743 | QRect 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 | |
768 | QRect 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 | |
788 | Region::ConstIterator Region::constBegin() const |
789 | { |
790 | return d->cells.constBegin(); |
791 | } |
792 | |
793 | Region::ConstIterator Region::constEnd() const |
794 | { |
795 | return d->cells.constEnd(); |
796 | } |
797 | |
798 | bool 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 | |
807 | bool 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 | |
816 | static 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 |
826 | void 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 |
897 | QString 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 |
955 | QString 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 | |
1019 | QString Region::saveOdf() const |
1020 | { |
1021 | return saveOdf(Region::name()); |
1022 | } |
1023 | |
1024 | QList<Region::Element*>& Region::cells() const |
1025 | { |
1026 | return d->cells; |
1027 | } |
1028 | |
1029 | bool 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 | |
1046 | void 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 | |
1063 | Sheet* 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 | |
1084 | Region::Point* Region::createPoint(const QPoint& point) const |
1085 | { |
1086 | return new Point(point); |
1087 | } |
1088 | |
1089 | Region::Point* Region::createPoint(const QString& string) const |
1090 | { |
1091 | return new Point(string); |
1092 | } |
1093 | |
1094 | Region::Point* Region::createPoint(const Point& point) const |
1095 | { |
1096 | return new Point(point); |
1097 | } |
1098 | |
1099 | Region::Range* Region::createRange(const QRect& rect) const |
1100 | { |
1101 | return new Range(rect); |
1102 | } |
1103 | |
1104 | Region::Range* Region::createRange(const Point& tl, const Point& br) const |
1105 | { |
1106 | return new Range(tl, br); |
1107 | } |
1108 | |
1109 | Region::Range* Region::createRange(const QString& string) const |
1110 | { |
1111 | return new Range(string); |
1112 | } |
1113 | |
1114 | Region::Range* Region::createRange(const Range& range) const |
1115 | { |
1116 | return new Range(range); |
1117 | } |
1118 | |
1119 | /*************************************************************************** |
1120 | class Element |
1121 | ****************************************************************************/ |
1122 | |
1123 | Region::Element::Element() |
1124 | : m_sheet(0) |
1125 | { |
1126 | } |
1127 | |
1128 | Region::Element::~Element() |
1129 | { |
1130 | } |
1131 | |
1132 | |
1133 | /*************************************************************************** |
1134 | class Point |
1135 | ****************************************************************************/ |
1136 | static 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 | |
1155 | Region::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 | |
1167 | Region::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 | |
1242 | Region::Point::~Point() |
1243 | { |
1244 | } |
1245 | |
1246 | QString 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 | |
1265 | bool Region::Point::contains(const QPoint& point) const |
1266 | { |
1267 | return (m_point == point); |
1268 | } |
1269 | |
1270 | bool Region::Point::contains(const QRect& range) const |
1271 | { |
1272 | return (range.width() == 1) && (range.height() == 1) && (range.topLeft() == m_point); |
1273 | } |
1274 | |
1275 | Cell Region::Point::cell() const |
1276 | { |
1277 | return Cell(m_sheet, m_point); |
1278 | } |
1279 | |
1280 | /*************************************************************************** |
1281 | class Range |
1282 | ****************************************************************************/ |
1283 | |
1284 | Region::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 | |
1298 | Region::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 | |
1314 | Region::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 | |
1337 | Region::Range::~Range() |
1338 | { |
1339 | } |
1340 | |
1341 | bool Region::Range::isColumn() const |
1342 | { |
1343 | return (m_range.top() == 1 && m_range.bottom() == KS_rowMax); |
1344 | } |
1345 | |
1346 | bool Region::Range::isRow() const |
1347 | { |
1348 | return (m_range.left() == 1 && m_range.right() == KS_colMax); |
1349 | } |
1350 | |
1351 | bool Region::Range::isAll() const |
1352 | { |
1353 | return (m_range == QRect(1, 1, KS_colMax, KS_rowMax)); |
1354 | } |
1355 | |
1356 | bool Region::Range::contains(const QPoint& point) const |
1357 | { |
1358 | return m_range.contains(point); |
1359 | } |
1360 | |
1361 | bool Region::Range::contains(const QRect& range) const |
1362 | { |
1363 | return m_range.contains(normalized(range)); |
1364 | } |
1365 | |
1366 | QString 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 | |