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