1 /* This file is part of the KDE project
2 Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
3 Copyright 2006,2007 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 "StyleStorage.h"
23
24 #include <QCache>
25 #include <QRegion>
26 #include <QTimer>
27 #include <QRunnable>
28 #ifdef CALLIGRA_SHEETS_MT
29 #include <QMutex>
30 #include <QMutexLocker>
31 #endif
32
33 #include "Global.h"
34 #include "Map.h"
35 #include "RTree.h"
36 #include "Style.h"
37 #include "StyleManager.h"
38 #include "RectStorage.h"
39
40 static const int g_maximumCachedStyles = 10000;
41
42 using namespace Calligra::Sheets;
43
44 class Q_DECL_HIDDEN StyleStorage::Private
45 {
46 public:
Private()47 Private()
48 #ifdef CALLIGRA_SHEETS_MT
49 : cacheMutex(QMutex::Recursive)
50 #endif
51 {}
52 Map* map;
53 RTree<SharedSubStyle> tree;
54 QMap<int, bool> usedColumns; // FIXME Stefan: Use QList and std::upper_bound() for insertion.
55 QMap<int, bool> usedRows;
56 QRegion usedArea;
57 QHash<Style::Key, QList<SharedSubStyle> > subStyles;
58 QMap<int, QPair<QRectF, SharedSubStyle> > possibleGarbage;
59 QCache<QPoint, Style> cache;
60 QRegion cachedArea;
61 StyleStorageLoaderJob* loader;
62 #ifdef CALLIGRA_SHEETS_MT
63 QMutex cacheMutex;
64 #endif
65
66 void ensureLoaded();
67 };
68
69 class Calligra::Sheets::StyleStorageLoaderJob : public QRunnable
70 {
71 public:
72 StyleStorageLoaderJob(StyleStorage* storage, const QList<QPair<QRegion, Style> >& styles);
73 void run() override;
74 void waitForFinished();
75 bool isFinished();
data() const76 QList<QPair<QRegion, Style> > data() const { return m_styles; }
77 private:
78 StyleStorage* m_storage;
79 QList<QPair<QRegion, Style> > m_styles;
80 };
81
StyleStorageLoaderJob(StyleStorage * storage,const QList<QPair<QRegion,Style>> & styles)82 StyleStorageLoaderJob::StyleStorageLoaderJob(StyleStorage *storage, const QList<QPair<QRegion, Style> > &styles)
83 : m_storage(storage), m_styles(styles)
84 {
85
86 }
87
waitForFinished()88 void StyleStorageLoaderJob::waitForFinished()
89 {
90 run();
91 }
92
isFinished()93 bool StyleStorageLoaderJob::isFinished()
94 {
95 return false;
96 }
97
run()98 void StyleStorageLoaderJob::run()
99 {
100 static int total = 0;
101 debugSheetsStyle << "Loading styles:" << endl << m_styles;
102 QTime t; t.start();
103 StyleStorage::Private* d = m_storage->d;
104 QList<QPair<QRegion, SharedSubStyle> > subStyles;
105
106 d->usedArea = QRegion();
107 d->usedColumns.clear();
108 d->usedRows.clear();
109 {
110 #ifdef CALLIGRA_SHEETS_MT
111 QMutexLocker(&d->cacheMutex);
112 #endif
113 d->cachedArea = QRegion();
114 d->cache.clear();
115 }
116 typedef QPair<QRegion, Style> StyleRegion;
117 foreach (const StyleRegion& styleArea, m_styles) {
118 const QRegion& reg = styleArea.first;
119 const Style& style = styleArea.second;
120 if (style.isEmpty()) continue;
121
122 // update used areas
123 QRect bound = reg.boundingRect();
124 if ((bound.top() == 1 && bound.bottom() >= KS_rowMax) || (bound.left() == 1 && bound.right() >= KS_colMax)) {
125 foreach (const QRect& rect, reg.rects()) {
126 if (rect.top() == 1 && rect.bottom() >= KS_rowMax) {
127 for (int i = rect.left(); i <= rect.right(); ++i) {
128 d->usedColumns.insert(i, true);
129 }
130 } else if (rect.left() == 1 && rect.right() >= KS_colMax) {
131 for (int i = rect.top(); i <= rect.bottom(); ++i) {
132 d->usedRows.insert(i, true);
133 }
134 } else {
135 d->usedArea += rect;
136 }
137 }
138 } else {
139 d->usedArea += reg;
140 }
141
142 // find substyles
143 foreach(const SharedSubStyle& subStyle, style.subStyles()) {
144 bool foundShared = false;
145 typedef const QList< SharedSubStyle> StoredSubStyleList;
146 StoredSubStyleList& storedSubStyles(d->subStyles.value(subStyle->type()));
147 StoredSubStyleList::ConstIterator end(storedSubStyles.end());
148 for (StoredSubStyleList::ConstIterator it(storedSubStyles.begin()); it != end; ++it) {
149 if (Style::compare(subStyle.data(), (*it).data())) {
150 // debugSheetsStyle <<"[REUSING EXISTING SUBSTYLE]";
151 subStyles.append(qMakePair(reg, *it));
152 foundShared = true;
153 break;
154 }
155 }
156 if (!foundShared) {
157 // insert substyle and add to the used substyle list
158 //if (reg.contains(QPoint(1,1))) {debugSheetsStyle<<"load:"<<reg<<':'; subStyle.data()->dump();}
159 subStyles.append(qMakePair(reg, subStyle));
160 }
161 }
162 }
163 d->tree.load(subStyles);
164 int e = t.elapsed();
165 total += e;
166 debugSheetsStyle << "Time: " << e << total;
167 }
168
ensureLoaded()169 void StyleStorage::Private::ensureLoaded()
170 {
171 if (loader) {
172 loader->waitForFinished();
173 delete loader;
174 loader = 0;
175 }
176 }
177
StyleStorage(Map * map)178 StyleStorage::StyleStorage(Map* map)
179 : QObject(map)
180 , d(new Private)
181 {
182 d->map = map;
183 d->cache.setMaxCost(g_maximumCachedStyles);
184 d->loader = 0;
185 }
186
StyleStorage(const StyleStorage & other)187 StyleStorage::StyleStorage(const StyleStorage& other)
188 : QObject(other.d->map)
189 , d(new Private)
190 {
191 d->map = other.d->map;
192 d->tree = other.d->tree;
193 d->usedColumns = other.d->usedColumns;
194 d->usedRows = other.d->usedRows;
195 d->usedArea = other.d->usedArea;
196 d->subStyles = other.d->subStyles;
197 if (other.d->loader) {
198 d->loader = new StyleStorageLoaderJob(this, other.d->loader->data());
199 } else {
200 d->loader = 0;
201 }
202 // the other member variables are temporary stuff
203 }
204
~StyleStorage()205 StyleStorage::~StyleStorage()
206 {
207 delete d->loader; // in a multi-threaded approach this needs more care
208 delete d;
209 }
210
contains(const QPoint & point) const211 Style StyleStorage::contains(const QPoint& point) const
212 {
213 d->ensureLoaded();
214 if (!d->usedArea.contains(point) && !d->usedColumns.contains(point.x()) && !d->usedRows.contains(point.y()))
215 return *styleManager()->defaultStyle();
216
217 {
218 #ifdef CALLIGRA_SHEETS_MT
219 QMutexLocker ml(&d->cacheMutex);
220 #endif
221 // first, lookup point in the cache
222 if (d->cache.contains(point)) {
223 Style st = *d->cache.object(point);
224 //if (point.x() == 1 && point.y() == 1) {debugSheetsStyle <<"StyleStorage: cached style:"<<point<<':'; st.dump();}
225 return st;
226 }
227 }
228 // not found, lookup in the tree
229 QList<SharedSubStyle> subStyles = d->tree.contains(point);
230 //if (point.x() == 1 && point.y() == 1) {debugSheetsStyle <<"StyleStorage: substyles:"<<point<<':'; for (const SharedSubStyle &s : subStyles) {debugSheetsStyle<<s.data()->debugData();}}
231 if (subStyles.isEmpty()) {
232 Style *style = styleManager()->defaultStyle();
233 // let's try caching empty styles too, the lookup is rather expensive still
234 {
235 #ifdef CALLIGRA_SHEETS_MT
236 QMutexLocker ml(&d->cacheMutex);
237 #endif
238 // insert style into the cache
239 d->cache.insert(point, style);
240 d->cachedArea += QRect(point, point);
241 }
242
243 return *style;
244 }
245 Style* style = new Style();
246 (*style) = composeStyle(subStyles);
247
248 {
249 #ifdef CALLIGRA_SHEETS_MT
250 QMutexLocker ml(&d->cacheMutex);
251 #endif
252 // insert style into the cache
253 d->cache.insert(point, style);
254 d->cachedArea += QRect(point, point);
255 }
256 //if (point.x() == 1 && point.y() == 1) {debugSheetsStyle <<"StyleStorage: style:"<<point<<':'; style->dump();}
257 return *style;
258 }
259
contains(const QRect & rect) const260 Style StyleStorage::contains(const QRect& rect) const
261 {
262 d->ensureLoaded();
263 QList<SharedSubStyle> subStyles = d->tree.contains(rect);
264 return composeStyle(subStyles);
265 }
266
intersects(const QRect & rect) const267 Style StyleStorage::intersects(const QRect& rect) const
268 {
269 d->ensureLoaded();
270 QList<SharedSubStyle> subStyles = d->tree.intersects(rect);
271 return composeStyle(subStyles);
272 }
273
undoData(const Region & region) const274 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::undoData(const Region& region) const
275 {
276 d->ensureLoaded();
277 QList< QPair<QRectF, SharedSubStyle> > result;
278 Region::ConstIterator end = region.constEnd();
279 for (Region::ConstIterator it = region.constBegin(); it != end; ++it) {
280 const QRect rect = (*it)->rect();
281 QList< QPair<QRectF, SharedSubStyle> > pairs = d->tree.intersectingPairs(rect).values();
282 for (int i = 0; i < pairs.count(); ++i) {
283 // trim the rects
284 pairs[i].first = pairs[i].first.intersected(rect);
285 }
286 // Always a default subStyle first, even if there are no pairs.
287 result << qMakePair(QRectF(rect), SharedSubStyle()) << pairs;
288 }
289 return result;
290 }
291
usedArea() const292 QRect StyleStorage::usedArea() const
293 {
294 d->ensureLoaded();
295 if (d->usedArea.isEmpty())
296 return QRect(1, 1, 0, 0);
297 return QRect(QPoint(1, 1), d->usedArea.boundingRect().bottomRight());
298 }
299
300 // craete default styles in the style tables - used in Odf saving
saveCreateDefaultStyles(int & maxCols,int & maxRows,QMap<int,Style> & columnDefaultStyles,QMap<int,Style> & rowDefaultStyles) const301 void StyleStorage::saveCreateDefaultStyles(int& maxCols, int& maxRows, QMap<int, Style> &columnDefaultStyles, QMap<int, Style> &rowDefaultStyles) const
302 {
303 d->ensureLoaded();
304 #if 0 // TODO
305 // If we have both, column and row styles, we can take the short route.
306 if (!d->usedColumns.isEmpty() && !d->usedRows.isEmpty()) {
307 for (int i = 0; i < d->usedColumns.count(); ++i) {
308 const int col = d->usedColumns[i];
309 columnDefaultStyles[col].insertSubStyle(contains(QRect(col, 1, 1, KS_rowMax)));
310 }
311 for (int i = 0; i < d->usedRow.count(); ++i) {
312 const int row = d->usedRow[i];
313 rowDefaultStyles[row].insertSubStyle(contains(QRect(1, row, KS_colMax, 1)));
314 }
315 return;
316 }
317 #endif
318 const QRect sheetRect(QPoint(1, 1), QPoint(KS_colMax, KS_rowMax));
319 if (d->usedColumns.count() != 0) {
320 maxCols = qMax(maxCols, (--d->usedColumns.constEnd()).key());
321 maxRows = KS_rowMax;
322 }
323 if (d->usedRows.count() != 0) {
324 maxCols = KS_colMax;
325 maxRows = qMax(maxRows, (--d->usedRows.constEnd()).key());
326 }
327 const QList< QPair<QRectF, SharedSubStyle> > pairs = d->tree.intersectingPairs(sheetRect).values();
328 for (int i = 0; i < pairs.count(); ++i) {
329 const QRect rect = pairs[i].first.toRect();
330 // column default cell styles
331 // Columns have no content. Prefer them over rows for the default cell styles.
332 if (rect.top() == 1 && rect.bottom() == maxRows) {
333 for (int col = rect.left(); col <= rect.right(); ++col) {
334 if (pairs[i].second.data()->type() == Style::DefaultStyleKey)
335 columnDefaultStyles.remove(col);
336 else
337 columnDefaultStyles[col].insertSubStyle(pairs[i].second);
338 }
339 }
340 // row default cell styles
341 else if (rect.left() == 1 && rect.right() == maxCols) {
342 for (int row = rect.top(); row <= rect.bottom(); ++row) {
343 if (pairs[i].second.data()->type() == Style::DefaultStyleKey)
344 rowDefaultStyles.remove(row);
345 else
346 rowDefaultStyles[row].insertSubStyle(pairs[i].second);
347 }
348 }
349 }
350 }
351
nextColumnStyleIndex(int column) const352 int StyleStorage::nextColumnStyleIndex(int column) const
353 {
354 d->ensureLoaded();
355 QMap<int, bool>::iterator it = d->usedColumns.upperBound(column + 1);
356 return (it == d->usedColumns.end()) ? 0 : it.key();
357 }
358
nextRowStyleIndex(int row) const359 int StyleStorage::nextRowStyleIndex(int row) const
360 {
361 d->ensureLoaded();
362 QMap<int, bool>::iterator it = d->usedRows.upperBound(row + 1);
363 return (it == d->usedRows.end()) ? 0 : it.key();
364 }
365
firstColumnIndexInRow(int row) const366 int StyleStorage::firstColumnIndexInRow(int row) const
367 {
368 d->ensureLoaded();
369 const QRect rect = (d->usedArea & QRect(QPoint(1, row), QPoint(KS_colMax, row))).boundingRect();
370 return rect.isNull() ? 0 : rect.left();
371 }
372
nextColumnIndexInRow(int column,int row) const373 int StyleStorage::nextColumnIndexInRow(int column, int row) const
374 {
375 d->ensureLoaded();
376 const QRect rect = (d->usedArea & QRect(QPoint(column + 1, row), QPoint(KS_colMax, row))).boundingRect();
377 return rect.isNull() ? 0 : rect.left();
378 }
379
insert(const QRect & rect,const SharedSubStyle & subStyle,bool markRegionChanged)380 void StyleStorage::insert(const QRect& rect, const SharedSubStyle& subStyle, bool markRegionChanged)
381 {
382 d->ensureLoaded();
383 // debugSheetsStyle <<"StyleStorage: inserting" << SubStyle::name(subStyle->type()) <<" into" << rect;
384 // keep track of the used area
385 const bool isDefault = subStyle->type() == Style::DefaultStyleKey;
386 if (rect.top() == 1 && rect.bottom() >= KS_rowMax) {
387 for (int i = rect.left(); i <= rect.right(); ++i) {
388 if (isDefault)
389 d->usedColumns.remove(i);
390 else
391 d->usedColumns.insert(i, true);
392 }
393 if (isDefault)
394 d->usedArea -= rect;
395 } else if (rect.left() == 1 && rect.right() >= KS_colMax) {
396 for (int i = rect.top(); i <= rect.bottom(); ++i) {
397 if (isDefault)
398 d->usedRows.remove(i);
399 else
400 d->usedRows.insert(i, true);
401 }
402 if (isDefault)
403 d->usedArea -= rect;
404 } else {
405 if (isDefault)
406 d->usedArea -= rect;
407 else
408 d->usedArea += rect;
409 }
410
411 // lookup already used substyles
412 typedef const QList< SharedSubStyle> StoredSubStyleList;
413 StoredSubStyleList& storedSubStyles(d->subStyles.value(subStyle->type()));
414 StoredSubStyleList::ConstIterator end(storedSubStyles.end());
415 for (StoredSubStyleList::ConstIterator it(storedSubStyles.begin()); it != end; ++it) {
416 if (Style::compare(subStyle.data(), (*it).data())) {
417 // debugSheetsStyle <<"[REUSING EXISTING SUBSTYLE]";
418 d->tree.insert(rect, *it);
419 if (markRegionChanged) {
420 regionChanged(rect);
421 }
422 return;
423 }
424 }
425 // insert substyle and add to the used substyle list
426 d->tree.insert(rect, subStyle);
427 d->subStyles[subStyle->type()].append(subStyle);
428 if (markRegionChanged) {
429 regionChanged(rect);
430 }
431 }
432
insert(const Region & region,const Style & style)433 void StyleStorage::insert(const Region& region, const Style& style)
434 {
435 d->ensureLoaded();
436 if (style.isEmpty())
437 return;
438 foreach(const SharedSubStyle& subStyle, style.subStyles()) {
439 Region::ConstIterator end(region.constEnd());
440 for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
441 // insert substyle
442 insert((*it)->rect(), subStyle, false);
443 }
444 }
445 for (Region::ConstIterator it(region.constBegin()), end(region.constEnd()); it != end; ++it) {
446 regionChanged((*it)->rect());
447 }
448 }
449
load(const QList<QPair<QRegion,Style>> & styles)450 void StyleStorage::load(const QList<QPair<QRegion, Style> >& styles)
451 {
452 Q_ASSERT(!d->loader);
453 d->loader = new StyleStorageLoaderJob(this, styles);
454 }
455
insertRows(int position,int number)456 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::insertRows(int position, int number)
457 {
458 d->ensureLoaded();
459 const QRect invalidRect(1, position, KS_colMax, KS_rowMax);
460 // invalidate the affected, cached styles
461 invalidateCache(invalidRect);
462 // update the used area
463 const QRegion usedArea = d->usedArea & invalidRect;
464 d->usedArea -= invalidRect;
465 d->usedArea += usedArea.translated(0, number);
466 const QVector<QRect> rects = (d->usedArea & QRect(1, position - 1, KS_colMax, 1)).rects();
467 for (int i = 0; i < rects.count(); ++i)
468 d->usedArea += rects[i].adjusted(0, 1, 0, number + 1);
469 // update the used rows
470 QMap<int, bool> map;
471 QMap<int, bool>::iterator begin = d->usedRows.lowerBound(position);
472 QMap<int, bool>::iterator end = d->usedRows.end();
473 for (QMap<int, bool>::iterator it = begin; it != end; ++it) {
474 if (it.key() + number <= KS_rowMax)
475 map.insert(it.key() + number, true);
476 }
477 for (QMap<int, bool>::iterator it = begin; it != d->usedRows.end(); )
478 it = d->usedRows.erase(it);
479 d->usedRows.unite(map);
480 // process the tree
481 QList< QPair<QRectF, SharedSubStyle> > undoData;
482 undoData << qMakePair(QRectF(1, KS_rowMax - number + 1, KS_colMax, number), SharedSubStyle());
483 undoData << d->tree.insertRows(position, number);
484 return undoData;
485 }
486
insertColumns(int position,int number)487 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::insertColumns(int position, int number)
488 {
489 d->ensureLoaded();
490 const QRect invalidRect(position, 1, KS_colMax, KS_rowMax);
491 // invalidate the affected, cached styles
492 invalidateCache(invalidRect);
493 // update the used area
494 const QRegion usedArea = d->usedArea & invalidRect;
495 d->usedArea -= invalidRect;
496 d->usedArea += usedArea.translated(number, 0);
497 const QVector<QRect> rects = (d->usedArea & QRect(position - 1, 0, 1, KS_rowMax)).rects();
498 for (int i = 0; i < rects.count(); ++i)
499 d->usedArea += rects[i].adjusted(1, 0, number + 1, 0);
500 // update the used columns
501 QMap<int, bool> map;
502 QMap<int, bool>::iterator begin = d->usedColumns.upperBound(position);
503 QMap<int, bool>::iterator end = d->usedColumns.end();
504 for (QMap<int, bool>::iterator it = begin; it != end; ++it) {
505 if (it.key() + number <= KS_colMax)
506 map.insert(it.key() + number, true);
507 }
508 for (QMap<int, bool>::iterator it = begin; it != d->usedColumns.end(); )
509 it = d->usedColumns.erase(it);
510 d->usedColumns.unite(map);
511 // process the tree
512 QList< QPair<QRectF, SharedSubStyle> > undoData;
513 undoData << qMakePair(QRectF(KS_colMax - number + 1, 1, number, KS_rowMax), SharedSubStyle());
514 undoData << d->tree.insertColumns(position, number);
515 return undoData;
516 }
517
removeRows(int position,int number)518 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::removeRows(int position, int number)
519 {
520 d->ensureLoaded();
521 const QRect invalidRect(1, position, KS_colMax, KS_rowMax);
522 // invalidate the affected, cached styles
523 invalidateCache(invalidRect);
524 // update the used area
525 const QRegion usedArea = d->usedArea & QRect(1, position + number, KS_colMax, KS_rowMax);
526 d->usedArea -= invalidRect;
527 d->usedArea += usedArea.translated(0, -number);
528 // update the used rows
529 QMap<int, bool> map;
530 QMap<int, bool>::iterator begin = d->usedRows.upperBound(position);
531 QMap<int, bool>::iterator end = d->usedRows.end();
532 for (QMap<int, bool>::iterator it = begin; it != end; ++it) {
533 if (it.key() - number >= position)
534 map.insert(it.key() - number, true);
535 }
536 for (QMap<int, bool>::iterator it = begin; it != d->usedRows.end(); )
537 it = d->usedRows.erase(it);
538 d->usedRows.unite(map);
539 // process the tree
540 QList< QPair<QRectF, SharedSubStyle> > undoData;
541 undoData << qMakePair(QRectF(1, position, KS_colMax, number), SharedSubStyle());
542 undoData << d->tree.removeRows(position, number);
543 return undoData;
544 }
545
removeColumns(int position,int number)546 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::removeColumns(int position, int number)
547 {
548 d->ensureLoaded();
549 const QRect invalidRect(position, 1, KS_colMax, KS_rowMax);
550 // invalidate the affected, cached styles
551 invalidateCache(invalidRect);
552 // update the used area
553 const QRegion usedArea = d->usedArea & QRect(position + number, 1, KS_colMax, KS_rowMax);
554 d->usedArea -= invalidRect;
555 d->usedArea += usedArea.translated(-number, 0);
556 // update the used columns
557 QMap<int, bool> map;
558 QMap<int, bool>::iterator begin = d->usedColumns.upperBound(position);
559 QMap<int, bool>::iterator end = d->usedColumns.end();
560 for (QMap<int, bool>::iterator it = begin; it != end; ++it) {
561 if (it.key() - number >= position)
562 map.insert(it.key() - number, true);
563 }
564 for (QMap<int, bool>::iterator it = begin; it != d->usedColumns.end(); )
565 it = d->usedColumns.erase(it);
566 d->usedColumns.unite(map);
567 // process the tree
568 QList< QPair<QRectF, SharedSubStyle> > undoData;
569 undoData << qMakePair(QRectF(position, 1, number, KS_rowMax), SharedSubStyle());
570 undoData << d->tree.removeColumns(position, number);
571 return undoData;
572 }
573
insertShiftRight(const QRect & rect)574 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::insertShiftRight(const QRect& rect)
575 {
576 d->ensureLoaded();
577 const QRect invalidRect(rect.topLeft(), QPoint(KS_colMax, rect.bottom()));
578 QList< QPair<QRectF, SharedSubStyle> > undoData;
579 undoData << qMakePair(QRectF(rect), SharedSubStyle());
580 undoData << d->tree.insertShiftRight(rect);
581 regionChanged(invalidRect);
582 // update the used area
583 const QRegion usedArea = d->usedArea & invalidRect;
584 d->usedArea -= invalidRect;
585 d->usedArea += usedArea.translated(rect.width(), 0);
586 const QVector<QRect> rects = (d->usedArea & QRect(rect.left() - 1, rect.top(), 1, rect.height())).rects();
587 for (int i = 0; i < rects.count(); ++i)
588 d->usedArea += rects[i].adjusted(1, 0, rect.width() + 1, 0);
589 // update the used columns
590 QMap<int, bool>::iterator begin = d->usedColumns.upperBound(rect.left());
591 QMap<int, bool>::iterator end = d->usedColumns.end();
592 for (QMap<int, bool>::iterator it = begin; it != end; ++it) {
593 if (it.key() + rect.width() <= KS_colMax)
594 d->usedArea += QRect(it.key() + rect.width(), rect.top(), rect.width(), rect.height());
595 }
596 if (d->usedColumns.contains(rect.left() - 1))
597 d->usedArea += rect;
598 return undoData;
599 }
600
insertShiftDown(const QRect & rect)601 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::insertShiftDown(const QRect& rect)
602 {
603 d->ensureLoaded();
604 const QRect invalidRect(rect.topLeft(), QPoint(rect.right(), KS_rowMax));
605 QList< QPair<QRectF, SharedSubStyle> > undoData;
606 undoData << qMakePair(QRectF(rect), SharedSubStyle());
607 undoData << d->tree.insertShiftDown(rect);
608 regionChanged(invalidRect);
609 // update the used area
610 const QRegion usedArea = d->usedArea & invalidRect;
611 d->usedArea -= invalidRect;
612 d->usedArea += usedArea.translated(0, rect.height());
613 const QVector<QRect> rects = (d->usedArea & QRect(rect.left(), rect.top() - 1, rect.width(), 1)).rects();
614 for (int i = 0; i < rects.count(); ++i)
615 d->usedArea += rects[i].adjusted(0, 1, 0, rect.height() + 1);
616 // update the used rows
617 QMap<int, bool>::iterator begin = d->usedRows.upperBound(rect.top());
618 QMap<int, bool>::iterator end = d->usedRows.end();
619 for (QMap<int, bool>::iterator it = begin; it != end; ++it) {
620 if (it.key() + rect.height() <= KS_rowMax)
621 d->usedArea += QRect(rect.left(), it.key() + rect.height(), rect.width(), rect.height());
622 }
623 if (d->usedRows.contains(rect.top() - 1))
624 d->usedArea += rect;
625 return undoData;
626 }
627
removeShiftLeft(const QRect & rect)628 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::removeShiftLeft(const QRect& rect)
629 {
630 d->ensureLoaded();
631 const QRect invalidRect(rect.topLeft(), QPoint(KS_colMax, rect.bottom()));
632 QList< QPair<QRectF, SharedSubStyle> > undoData;
633 undoData << qMakePair(QRectF(rect), SharedSubStyle());
634 undoData << d->tree.removeShiftLeft(rect);
635 regionChanged(invalidRect);
636 // update the used area
637 const QRegion usedArea = d->usedArea & QRect(rect.right() + 1, rect.top(), KS_colMax, rect.height());
638 d->usedArea -= invalidRect;
639 d->usedArea += usedArea.translated(-rect.width(), 0);
640 // update the used columns
641 QMap<int, bool>::iterator begin = d->usedColumns.upperBound(rect.right() + 1);
642 QMap<int, bool>::iterator end = d->usedColumns.end();
643 for (QMap<int, bool>::iterator it = begin; it != end; ++it) {
644 if (it.key() - rect.width() >= rect.left())
645 d->usedArea += QRect(it.key() - rect.width(), rect.top(), rect.width(), rect.height());
646 }
647 return undoData;
648 }
649
removeShiftUp(const QRect & rect)650 QList< QPair<QRectF, SharedSubStyle> > StyleStorage::removeShiftUp(const QRect& rect)
651 {
652 d->ensureLoaded();
653 const QRect invalidRect(rect.topLeft(), QPoint(rect.right(), KS_rowMax));
654 QList< QPair<QRectF, SharedSubStyle> > undoData;
655 undoData << qMakePair(QRectF(rect), SharedSubStyle());
656 undoData << d->tree.removeShiftUp(rect);
657 regionChanged(invalidRect);
658 // update the used area
659 const QRegion usedArea = d->usedArea & QRect(rect.left(), rect.bottom() + 1, rect.width(), KS_rowMax);
660 d->usedArea -= invalidRect;
661 d->usedArea += usedArea.translated(0, -rect.height());
662 // update the used rows
663 QMap<int, bool>::iterator begin = d->usedRows.upperBound(rect.bottom() + 1);
664 QMap<int, bool>::iterator end = d->usedRows.end();
665 for (QMap<int, bool>::iterator it = begin; it != end; ++it) {
666 if (it.key() - rect.height() >= rect.top())
667 d->usedArea += QRect(rect.left(), it.key() - rect.height(), rect.width(), rect.height());
668 }
669 return undoData;
670 }
671
invalidateCache()672 void StyleStorage::invalidateCache()
673 {
674 // still busy loading? no cache to invalidate
675 if (d->loader && !d->loader->isFinished())
676 return;
677
678 #ifdef CALLIGRA_SHEETS_MT
679 QMutexLocker ml(&d->cacheMutex);
680 #endif
681 d->cache.clear();
682 d->cachedArea = QRegion();
683 }
684
garbageCollection()685 void StyleStorage::garbageCollection()
686 {
687 // still busy loading? no garbage to collect
688 if (d->loader && !d->loader->isFinished())
689 return;
690
691 // any possible garbage left?
692 if (d->possibleGarbage.isEmpty())
693 return;
694
695 const int currentZIndex = d->possibleGarbage.constBegin().key();
696 const QPair<QRectF, SharedSubStyle> currentPair = d->possibleGarbage.take(currentZIndex);
697
698 // check whether the named style still exists
699 if (currentPair.second->type() == Style::NamedStyleKey &&
700 !styleManager()->style(static_cast<const NamedStyle*>(currentPair.second.data())->name)) {
701 debugSheetsStyle << "removing" << currentPair.second->debugData()
702 << "at" << Region(currentPair.first.toRect()).name()
703 << "used" << currentPair.second->ref << "times" << endl;
704 d->tree.remove(currentPair.first.toRect(), currentPair.second);
705 d->subStyles[currentPair.second->type()].removeAll(currentPair.second);
706 QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
707 return; // already done
708 }
709
710 typedef QPair<QRectF, SharedSubStyle> SharedSubStylePair;
711 QMap<int, SharedSubStylePair> pairs = d->tree.intersectingPairs(currentPair.first.toRect());
712 if (pairs.isEmpty()) // actually never true, just for sanity
713 return;
714 int zIndex = pairs.constBegin().key();
715 SharedSubStylePair pair = pairs[zIndex];
716
717 // check whether the default style is placed first
718 if (zIndex == currentZIndex &&
719 currentPair.second->type() == Style::DefaultStyleKey &&
720 pair.second->type() == Style::DefaultStyleKey &&
721 pair.first == currentPair.first) {
722 debugSheetsStyle << "removing default style"
723 << "at" << Region(currentPair.first.toRect()).name()
724 << "used" << currentPair.second->ref << "times" << endl;
725 d->tree.remove(currentPair.first.toRect(), currentPair.second);
726 QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
727 return; // already done
728 }
729
730 // special handling for indentation:
731 // check whether the default indentation is placed first
732 if (zIndex == currentZIndex &&
733 currentPair.second->type() == Style::Indentation &&
734 static_cast<const SubStyleOne<Style::Indentation, int>*>(currentPair.second.data())->value1 == 0 &&
735 pair.first == currentPair.first) {
736 debugSheetsStyle << "removing default indentation"
737 << "at" << Region(currentPair.first.toRect()).name()
738 << "used" << currentPair.second->ref << "times" << endl;
739 d->tree.remove(currentPair.first.toRect(), currentPair.second);
740 QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
741 return; // already done
742 }
743
744 // special handling for precision:
745 // check whether the storage default precision is placed first
746 if (zIndex == currentZIndex &&
747 currentPair.second->type() == Style::Precision &&
748 static_cast<const SubStyleOne<Style::Precision, int>*>(currentPair.second.data())->value1 == 0 &&
749 pair.first == currentPair.first) {
750 debugSheetsStyle << "removing default precision"
751 << "at" << Region(currentPair.first.toRect()).name()
752 << "used" << currentPair.second->ref << "times" << endl;
753 d->tree.remove(currentPair.first.toRect(), currentPair.second);
754 QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
755 return; // already done
756 }
757
758 // check, if the current substyle is covered by others added after it
759 bool found = false;
760 QMap<int, SharedSubStylePair>::ConstIterator end = pairs.constEnd();
761 for (QMap<int, SharedSubStylePair>::ConstIterator it = pairs.constFind(currentZIndex); it != end; ++it) {
762 zIndex = it.key();
763 pair = it.value();
764
765 // as long as the substyle in question was not found, skip the substyle
766 if (!found) {
767 if (pair.first == currentPair.first &&
768 Style::compare(pair.second.data(), currentPair.second.data()) &&
769 zIndex == currentZIndex) {
770 found = true;
771 }
772 continue;
773 }
774
775 // remove the current pair, if another substyle of the same type,
776 // the default style or a named style follows and the rectangle
777 // is completely covered
778 if (zIndex != currentZIndex &&
779 (pair.second->type() == currentPair.second->type() ||
780 pair.second->type() == Style::DefaultStyleKey ||
781 pair.second->type() == Style::NamedStyleKey) &&
782 pair.first.toRect().contains(currentPair.first.toRect())) {
783 // special handling for indentation
784 // only remove, if covered by default
785 if (pair.second->type() == Style::Indentation &&
786 static_cast<const SubStyleOne<Style::Indentation, int>*>(pair.second.data())->value1 != 0) {
787 continue;
788 }
789
790 // special handling for precision
791 // only remove, if covered by default
792 if (pair.second->type() == Style::Precision &&
793 static_cast<const SubStyleOne<Style::Precision, int>*>(pair.second.data())->value1 != 0) {
794 continue;
795 }
796
797 debugSheetsStyle << "removing" << currentPair.second->debugData()
798 << "at" << Region(currentPair.first.toRect()).name()
799 << "used" << currentPair.second->ref << "times" << endl;
800 d->tree.remove(currentPair.first.toRect(), currentPair.second, currentZIndex);
801 #if 0
802 debugSheetsStyle << "StyleStorage: usage of" << currentPair.second->debugData() << " is" << currentPair.second->ref;
803 // FIXME Stefan: The usage of substyles used once should be
804 // two (?) here, not more. Why is this not the case?
805 // The shared pointers are used by:
806 // a) the tree
807 // b) the reusage list (where it should be removed)
808 // c) the cached styles (!)
809 // d) the undo data of operations (!)
810 if (currentPair.second->ref == 2) {
811 debugSheetsStyle << "StyleStorage: removing" << currentPair.second << " from the used subStyles";
812 d->subStyles[currentPair.second->type()].removeAll(currentPair.second);
813 }
814 #endif
815 break;
816 }
817 }
818 QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
819 }
820
regionChanged(const QRect & rect)821 void StyleStorage::regionChanged(const QRect& rect)
822 {
823 // still busy loading? no garbage to collect
824 if (d->loader && !d->loader->isFinished())
825 return;
826 if (d->map->isLoading())
827 return;
828 // mark the possible garbage
829 // NOTE Stefan: The map may contain multiple indices. The already existing possible garbage has
830 // has to be inserted most recently, because it should be accessed first.
831 d->possibleGarbage = d->tree.intersectingPairs(rect).unite(d->possibleGarbage);
832 QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
833 // invalidate cache
834 invalidateCache(rect);
835 }
836
invalidateCache(const QRect & rect)837 void StyleStorage::invalidateCache(const QRect& rect)
838 {
839 // still busy loading? no cache to invalidate
840 if (d->loader && !d->loader->isFinished())
841 return;
842
843 #ifdef CALLIGRA_SHEETS_MT
844 QMutexLocker ml(&d->cacheMutex);
845 #endif
846 // debugSheetsStyle <<"StyleStorage: Invalidating" << rect;
847 const QRegion region = d->cachedArea.intersected(rect);
848 d->cachedArea = d->cachedArea.subtracted(rect);
849 foreach(const QRect& rect, region.rects()) {
850 for (int col = rect.left(); col <= rect.right(); ++col) {
851 for (int row = rect.top(); row <= rect.bottom(); ++row) {
852 // debugSheetsStyle <<"StyleStorage: Removing cached style for" << Cell::name( col, row );
853 d->cache.remove(QPoint(col, row)); // also deletes it
854 }
855 }
856 }
857 }
858
composeStyle(const QList<SharedSubStyle> & subStyles) const859 Style StyleStorage::composeStyle(const QList<SharedSubStyle>& subStyles) const
860 {
861 d->ensureLoaded();
862
863 if (subStyles.isEmpty()) {
864 // debugSheetsStyle <<"StyleStorage:" << "nothing to merge, return the default style";
865 return *styleManager()->defaultStyle();
866 }
867 // From OpenDocument-v1.2-os-part1 16.2<style:style>
868 //
869 // The <style:style> element represents styles.
870 //
871 // Styles defined by the <style:style> element use a hierarchical style model.
872 // The <style:style> element supports inheritance of formatting properties by a style from its parent style.
873 // A parent style is specified by the style:parent-style-name attribute on a <style:style> element.
874 //
875 // The determination of the value of a formatting property begins with any style that is specified by an element.
876 // If the formatting property is present in that style, its value is used.
877 //
878 // If that style does not specify a value for that formatting property and it has a parent style,
879 // the value of the formatting element is taken from the parent style, if present.
880 //
881 // If the parent style does not have a value for the formatting property, the search for the formatting property value continues up parent styles
882 // until either the formatting property has been found or a style is found with no parent style.
883 //
884 // If a search of the parent styles of a style does not result in a value for a formatting property,
885 // the determination of its value depends on the style family and the element to which a style is applied.
886
887 // TODO review loading of libreOffice generated files:
888 // It seems libreOffice saves parent also when parent is the Default style.
889 // Sheets do not do this, it is handled implicitly.
890 // According to the spec, both ways should be ok,
891 // but the result is that when loading lo files, it may exist multiple (two) NamedStyleKey substyles:
892 // One loaded explicitly (first in the list), and one generated by our loading code (later in the list).
893 // We use the last one in the list here, this should be our generated one.
894 CustomStyle *namedStyle = 0;
895 for (int i = subStyles.count() - 1; i >= 0; --i) {
896 if (subStyles[i]->type() == Style::NamedStyleKey) {
897 namedStyle = styleManager()->style(static_cast<const NamedStyle*>(subStyles[i].data())->name);
898 if (namedStyle) {
899 debugSheetsStyle<<"Compose found namedstyle:"<<static_cast<const NamedStyle*>(subStyles[i].data())->name<<namedStyle->parentName();namedStyle->dump();
900 break;
901 }
902 }
903 }
904
905 Style style;
906 // get attributes from parent styles
907 if (namedStyle) {
908 // first, load the attributes of the parent style(s)
909 QList<CustomStyle*> parentStyles;
910 CustomStyle *parentStyle = styleManager()->style(namedStyle->parentName());
911 // debugSheetsStyle <<"StyleStorage:" << namedStyle->name() <<"'s parent =" << namedStyle->parentName();
912 while (parentStyle) {
913 // debugSheetsStyle <<"StyleStorage:" << parentStyle->name() <<"'s parent =" << parentStyle->parentName();
914 parentStyles.prepend(parentStyle);
915 parentStyle = styleManager()->style(parentStyle->parentName());
916 }
917 Style tmpStyle;
918 for (int i = 0; i < parentStyles.count(); ++i) {
919 // debugSheetsStyle <<"StyleStorage: merging" << parentStyles[i]->name() <<" in.";
920 tmpStyle = *parentStyles[i];
921 tmpStyle.merge(style); // insert/replace substyles in tmpStyle with substyles from style
922 style = tmpStyle;
923 }
924 // second, merge the other attributes in
925 // debugSheetsStyle <<"StyleStorage: merging" << namedStyle->name() <<" in.";
926 tmpStyle = *namedStyle;
927 tmpStyle.merge(style); // insert/replace substyles in tmpStyle with substyles from style
928 style = tmpStyle;
929 // not the default anymore
930 style.clearAttribute(Style::DefaultStyleKey);
931 // reset the parent name
932 style.setParentName(namedStyle->name());
933 // debugSheetsStyle <<"StyleStorage: merging done";
934 }
935 for (int i = 0; i < subStyles.count(); ++i) {
936 if (subStyles[i]->type() == Style::DefaultStyleKey) {
937 // skip
938 } else if (subStyles[i]->type() == Style::NamedStyleKey) {
939 // treated above
940 } else if (subStyles[i]->type() == Style::Indentation) {
941 // special handling for indentation
942 const int indentation = static_cast<const SubStyleOne<Style::Indentation, int>*>(subStyles[i].data())->value1;
943 if (indentation == 0 || (style.indentation() + indentation <= 0))
944 style.clearAttribute(Style::Indentation); // reset
945 else
946 style.setIndentation(style.indentation() + indentation); // increase/decrease
947 } else if (subStyles[i]->type() == Style::Precision) {
948 // special handling for precision
949 // The Style default (-1) and the storage default (0) differ.
950 const int precision = static_cast<const SubStyleOne<Style::Precision, int>*>(subStyles[i].data())->value1;
951 if (precision == 0) // storage default
952 style.clearAttribute(Style::Precision); // reset
953 else {
954 if (style.precision() == -1) // Style default
955 style.setPrecision(qMax(0, precision)); // positive initial value
956 else if (style.precision() + precision <= 0)
957 style.setPrecision(0);
958 else if (style.precision() + precision >= 10)
959 style.setPrecision(10);
960 else
961 style.setPrecision(style.precision() + precision); // increase/decrease
962 }
963 } else {
964 // insert the substyle
965 // debugSheetsStyle <<"StyleStorage: inserting" << subStyles[i]->debugData();
966 style.insertSubStyle(subStyles[i]);
967 // not the default anymore
968 style.clearAttribute(Style::DefaultStyleKey);
969 }
970 }
971 // Implicitly merge in any missing attributes from the family (table-cell) default style
972 // It might have been merged in via parent styles above, but we cannot rely on that.
973 if (!styleManager()->defaultStyle()->isEmpty()) {
974 // debugSheetsStyle << "StyleStorage: merging family default in";
975 Style tmpStyle = *styleManager()->defaultStyle();
976 tmpStyle.clearAttribute(Style::DefaultStyleKey);
977 tmpStyle.merge(style); // insert/replace substyles in tmpStyle with substyles from style
978 style = tmpStyle;
979 }
980 return style;
981 }
982
styleManager() const983 StyleManager* StyleStorage::styleManager() const
984 {
985 return d->map->styleManager();
986 }
987