File indexing completed on 2024-04-28 16:21:26

0001 /* This file is part of the KDE project
0002    Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
0003    Copyright 2006,2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018    Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #ifndef CALLIGRA_SHEETS_RECT_STORAGE
0022 #define CALLIGRA_SHEETS_RECT_STORAGE
0023 
0024 #include <QCache>
0025 #include <QRegion>
0026 #include <QTimer>
0027 #include <QRunnable>
0028 #include <QTime>
0029 #ifdef CALLIGRA_SHEETS_MT
0030 #include <QMutex>
0031 #include <QMutexLocker>
0032 #endif
0033 
0034 #include "sheets_odf_export.h"
0035 
0036 #include "Map.h"
0037 #include "Region.h"
0038 #include "RTree.h"
0039 
0040 static const int g_garbageCollectionTimeOut = 100;
0041 
0042 namespace Calligra
0043 {
0044 namespace Sheets
0045 {
0046 
0047 template<typename T>
0048 class RectStorageLoader;
0049 
0050 /**
0051  * \ingroup Storage
0052  * A custom rectangular storage.
0053  * Based on an R-Tree data structure.
0054  * Usable for any kind of data attached to rectangular regions.
0055  *
0056  * Acts mainly as a wrapper around the R-Tree data structure to allow a future
0057  * replacement of this backend. Decorated with some additional features like
0058  * garbage collection, caching, used area tracking, etc.
0059  *
0060  * \author Stefan Nikolaus <stefan.nikolaus@kdemail.net>
0061  *
0062  * \note For data assigned to points use PointStorage.
0063  */
0064 template<typename T>
0065 class RectStorage
0066 {
0067 public:
0068     explicit RectStorage(Map* map);
0069     RectStorage(const RectStorage& other);
0070     virtual ~RectStorage();
0071 
0072     /**
0073      * \return the stored value at the position \p point .
0074      */
0075     T contains(const QPoint& point) const;
0076 
0077     /**
0078      * \return the stored rect/value pair at the position \p point .
0079      */
0080     QPair<QRectF, T> containedPair(const QPoint& point) const;
0081 
0082     QList< QPair<QRectF, T> > intersectingPairs(const Region& region) const;
0083 
0084     QList< QPair<QRectF, T> > undoData(const Region& region) const;
0085 
0086     /**
0087      * Returns the area, which got data attached.
0088      * \return the area using data
0089      */
0090     QRect usedArea() const;
0091 
0092     /**
0093      * Mass loading of data, removes any existing data first
0094      */
0095     void load(const QList<QPair<QRegion, T> >& data);
0096 
0097     /**
0098      * Assigns \p data to \p region .
0099      */
0100     void insert(const Region& region, const T& data);
0101 
0102     /**
0103      * Removes \p data from \p region .
0104      */
0105     void remove(const Region& region, const T& data);
0106 
0107     /**
0108      * Inserts \p number rows at the position \p position .
0109      * It extends or shifts rectangles, respectively.
0110      */
0111     QList< QPair<QRectF, T> > insertRows(int position, int number);
0112 
0113     /**
0114      * Inserts \p number columns at the position \p position .
0115      * It extends or shifts rectangles, respectively.
0116      */
0117     QList< QPair<QRectF, T> > insertColumns(int position, int number);
0118 
0119     /**
0120      * Deletes \p number rows at the position \p position .
0121      * It shrinks or shifts rectangles, respectively.
0122      */
0123     QList< QPair<QRectF, T> > removeRows(int position, int number);
0124 
0125     /**
0126      * Deletes \p number columns at the position \p position .
0127      * It shrinks or shifts rectangles, respectively.
0128      */
0129     QList< QPair<QRectF, T> > removeColumns(int position, int number);
0130 
0131     /**
0132      * Shifts the rows right of \p rect to the right by the width of \p rect .
0133      * It extends or shifts rectangles, respectively.
0134      */
0135     QList< QPair<QRectF, T> > insertShiftRight(const QRect& rect);
0136 
0137     /**
0138      * Shifts the columns at the bottom of \p rect to the bottom by the height of \p rect .
0139      * It extends or shifts rectangles, respectively.
0140      */
0141     QList< QPair<QRectF, T> > insertShiftDown(const QRect& rect);
0142 
0143     /**
0144      * Shifts the rows left of \p rect to the left by the width of \p rect .
0145      * It shrinks or shifts rectangles, respectively.
0146      * \return the former rectangle/data pairs
0147      */
0148     QList< QPair<QRectF, T> > removeShiftLeft(const QRect& rect);
0149 
0150     /**
0151      * Shifts the columns on top of \p rect to the top by the height of \p rect .
0152      * It shrinks or shifts rectangles, respectively.
0153      * \return the former rectangle/data pairs
0154      */
0155     QList< QPair<QRectF, T> > removeShiftUp(const QRect& rect);
0156 
0157 protected:
0158     virtual void triggerGarbageCollection();
0159     virtual void garbageCollection();
0160 
0161     /**
0162      * Triggers all necessary actions after a change of \p rect .
0163      * Calls invalidateCache() and adds the data in
0164      * \p rect to the list of possible garbage.
0165      */
0166     void regionChanged(const QRect& rect);
0167 
0168     /**
0169      * Invalidates all cached styles lying in \p rect .
0170      */
0171     void invalidateCache(const QRect& rect);
0172 
0173     /**
0174      * Ensures that any load() operation has completed.
0175      */
0176     void ensureLoaded() const;
0177 private:
0178     Map* m_map;
0179     RTree<T> m_tree;
0180     QRegion m_usedArea;
0181     QMap<int, QPair<QRectF, T> > m_possibleGarbage;
0182     QList<T> m_storedData;
0183     mutable QCache<QPoint, T> m_cache;
0184 #ifdef CALLIGRA_SHEETS_MT
0185     mutable QMutex m_mutex;
0186 #endif
0187     mutable QRegion m_cachedArea;
0188 
0189     RectStorageLoader<T>* m_loader;
0190     friend class RectStorageLoader<T>;
0191 };
0192 
0193 template<typename T>
0194 class RectStorageLoader : public QRunnable
0195 {
0196 public:
0197     RectStorageLoader(RectStorage<T>* storage, const QList<QPair<QRegion, T> >& data);
0198     void run() override;
0199     void waitForFinished();
0200     bool isFinished() const;
0201     QList<QPair<QRegion, T> > data() const;
0202 private:
0203     RectStorage<T>* m_storage;
0204     QList<QPair<QRegion, T> > m_data;
0205 };
0206 
0207 template<typename T>
0208 RectStorage<T>::RectStorage(Map* map)
0209         : m_map(map), m_loader(0)
0210 {
0211 }
0212 
0213 template<typename T>
0214 RectStorage<T>::RectStorage(const RectStorage& other)
0215         : m_map(other.m_map)
0216         , m_usedArea(other.m_usedArea)
0217         , m_storedData(other.m_storedData)
0218         , m_loader(0)
0219 {
0220     m_tree = other.m_tree;
0221     if (other.m_loader) {
0222         m_loader = new RectStorageLoader<T>(this, other.m_loader->data());
0223     }
0224 }
0225 
0226 template<typename T>
0227 RectStorage<T>::~RectStorage()
0228 {
0229     delete m_loader; // needs fixing if this ever gets to be multithreaded
0230 }
0231 
0232 template<typename T>
0233 T RectStorage<T>::contains(const QPoint& point) const
0234 {
0235     ensureLoaded();
0236 #ifdef CALLIGRA_SHEETS_MT
0237     QMutexLocker ml(&m_mutex);
0238 #endif
0239     if (!usedArea().contains(point))
0240         return T();
0241     // first, lookup point in the cache
0242     if (m_cache.contains(point)) {
0243         return *m_cache.object(point);
0244     }
0245     // not found, lookup in the tree
0246     QList<T> results = m_tree.contains(point);
0247     T data = results.isEmpty() ? T() : results.last();
0248     // insert style into the cache
0249     m_cache.insert(point, new T(data));
0250     m_cachedArea += QRect(point, point);
0251     return data;
0252 }
0253 
0254 template<typename T>
0255 QPair<QRectF, T> RectStorage<T>::containedPair(const QPoint& point) const
0256 {
0257     ensureLoaded();
0258     const QList< QPair<QRectF, T> > results = m_tree.intersectingPairs(QRect(point, point)).values();
0259     return results.isEmpty() ? qMakePair(QRectF(), T()) : results.last();
0260 }
0261 
0262 template<typename T>
0263 QList< QPair<QRectF, T> > RectStorage<T>::intersectingPairs(const Region& region) const
0264 {
0265     ensureLoaded();
0266     QList< QPair<QRectF, T> > result;
0267     Region::ConstIterator end = region.constEnd();
0268     for (Region::ConstIterator it = region.constBegin(); it != end; ++it)
0269         result += m_tree.intersectingPairs((*it)->rect()).values();
0270     return result;
0271 }
0272 
0273 template<typename T>
0274 QList< QPair<QRectF, T> > RectStorage<T>::undoData(const Region& region) const
0275 {
0276     ensureLoaded();
0277     QList< QPair<QRectF, T> > result;
0278     Region::ConstIterator end = region.constEnd();
0279     for (Region::ConstIterator it = region.constBegin(); it != end; ++it) {
0280         const QRect rect = (*it)->rect();
0281         QList< QPair<QRectF, T> > pairs = m_tree.intersectingPairs(rect).values();
0282         for (int i = 0; i < pairs.count(); ++i) {
0283             // trim the rects
0284             pairs[i].first = pairs[i].first.intersected(rect);
0285         }
0286         // Always add a default value even if there are no pairs.
0287         result << qMakePair(QRectF(rect), T()) << pairs;
0288     }
0289     return result;
0290 }
0291 
0292 template<typename T>
0293 QRect RectStorage<T>::usedArea() const
0294 {
0295     ensureLoaded();
0296     return m_tree.boundingBox().toRect();
0297 }
0298 
0299 template<typename T>
0300 void RectStorage<T>::load(const QList<QPair<QRegion, T> >& data)
0301 {
0302     Q_ASSERT(!m_loader);
0303     m_loader = new RectStorageLoader<T>(this, data);
0304 }
0305 
0306 template<typename T>
0307 void RectStorage<T>::insert(const Region& region, const T& _data)
0308 {
0309     ensureLoaded();
0310     T data;
0311     // lookup already used data
0312     int index = m_storedData.indexOf(_data);
0313     if (index != -1)
0314         data = m_storedData[index];
0315     else {
0316         data = _data;
0317         m_storedData.append(_data);
0318     }
0319 
0320     Region::ConstIterator end(region.constEnd());
0321     for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
0322         // insert data
0323         m_tree.insert((*it)->rect(), data);
0324         regionChanged((*it)->rect());
0325     }
0326 }
0327 
0328 template<typename T>
0329 void RectStorage<T>::remove(const Region& region, const T& data)
0330 {
0331     ensureLoaded();
0332     if (!m_storedData.contains(data)) {
0333         return;
0334     }
0335     const Region::ConstIterator end(region.constEnd());
0336     for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
0337         // remove data
0338         m_tree.remove((*it)->rect(), data);
0339         regionChanged((*it)->rect());
0340     }
0341 }
0342 
0343 template<typename T>
0344 QList< QPair<QRectF, T> > RectStorage<T>::insertRows(int position, int number)
0345 {
0346     ensureLoaded();
0347     const QRect invalidRect(1, position, KS_colMax, KS_rowMax);
0348     // invalidate the affected, cached styles
0349     invalidateCache(invalidRect);
0350     // process the tree
0351     QList< QPair<QRectF, T> > undoData;
0352     undoData << qMakePair(QRectF(1, KS_rowMax - number + 1, KS_colMax, number), T());
0353     undoData << m_tree.insertRows(position, number, RTree<T>::CopyCurrent);
0354     return undoData;
0355 }
0356 
0357 template<typename T>
0358 QList< QPair<QRectF, T> > RectStorage<T>::insertColumns(int position, int number)
0359 {
0360     ensureLoaded();
0361     const QRect invalidRect(position, 1, KS_colMax, KS_rowMax);
0362     // invalidate the affected, cached styles
0363     invalidateCache(invalidRect);
0364     // process the tree
0365     QList< QPair<QRectF, T> > undoData;
0366     undoData << qMakePair(QRectF(KS_colMax - number + 1, 1, number, KS_rowMax), T());
0367     undoData << m_tree.insertColumns(position, number, RTree<T>::CopyCurrent);
0368     return undoData;
0369 }
0370 
0371 template<typename T>
0372 QList< QPair<QRectF, T> > RectStorage<T>::removeRows(int position, int number)
0373 {
0374     ensureLoaded();
0375     const QRect invalidRect(1, position, KS_colMax, KS_rowMax);
0376     // invalidate the affected, cached styles
0377     invalidateCache(invalidRect);
0378     // process the tree
0379     QList< QPair<QRectF, T> > undoData;
0380     undoData << qMakePair(QRectF(1, position, KS_colMax, number), T());
0381     undoData << m_tree.removeRows(position, number);
0382     return undoData;
0383 }
0384 
0385 template<typename T>
0386 QList< QPair<QRectF, T> > RectStorage<T>::removeColumns(int position, int number)
0387 {
0388     ensureLoaded();
0389     const QRect invalidRect(position, 1, KS_colMax, KS_rowMax);
0390     // invalidate the affected, cached styles
0391     invalidateCache(invalidRect);
0392     // process the tree
0393     QList< QPair<QRectF, T> > undoData;
0394     undoData << qMakePair(QRectF(position, 1, number, KS_rowMax), T());
0395     undoData << m_tree.removeColumns(position, number);
0396     return undoData;
0397 }
0398 
0399 template<typename T>
0400 QList< QPair<QRectF, T> > RectStorage<T>::insertShiftRight(const QRect& rect)
0401 {
0402     ensureLoaded();
0403     const QRect invalidRect(rect.topLeft(), QPoint(KS_colMax, rect.bottom()));
0404     QList< QPair<QRectF, T> > undoData;
0405     undoData << qMakePair(QRectF(rect), T());
0406     undoData << m_tree.insertShiftRight(rect);
0407     regionChanged(invalidRect);
0408     return undoData;
0409 }
0410 
0411 template<typename T>
0412 QList< QPair<QRectF, T> > RectStorage<T>::insertShiftDown(const QRect& rect)
0413 {
0414     ensureLoaded();
0415     const QRect invalidRect(rect.topLeft(), QPoint(rect.right(), KS_rowMax));
0416     QList< QPair<QRectF, T> > undoData;
0417     undoData << qMakePair(QRectF(rect), T());
0418     undoData << m_tree.insertShiftDown(rect);
0419     regionChanged(invalidRect);
0420     return undoData;
0421 }
0422 
0423 template<typename T>
0424 QList< QPair<QRectF, T> > RectStorage<T>::removeShiftLeft(const QRect& rect)
0425 {
0426     ensureLoaded();
0427     const QRect invalidRect(rect.topLeft(), QPoint(KS_colMax, rect.bottom()));
0428     QList< QPair<QRectF, T> > undoData;
0429     undoData << qMakePair(QRectF(rect), T());
0430     undoData << m_tree.removeShiftLeft(rect);
0431     regionChanged(invalidRect);
0432     return undoData;
0433 }
0434 
0435 template<typename T>
0436 QList< QPair<QRectF, T> > RectStorage<T>::removeShiftUp(const QRect& rect)
0437 {
0438     ensureLoaded();
0439     const QRect invalidRect(rect.topLeft(), QPoint(rect.right(), KS_rowMax));
0440     QList< QPair<QRectF, T> > undoData;
0441     undoData << qMakePair(QRectF(rect), T());
0442     undoData << m_tree.removeShiftUp(rect);
0443     regionChanged(invalidRect);
0444     return undoData;
0445 }
0446 
0447 template<typename T>
0448 void RectStorage<T>::triggerGarbageCollection()
0449 {
0450 }
0451 
0452 template<typename T>
0453 void RectStorage<T>::garbageCollection()
0454 {
0455     if (m_loader && !m_loader->isFinished())
0456         return;
0457 
0458     // any possible garbage left?
0459     if (m_possibleGarbage.isEmpty())
0460         return;
0461 
0462     const int currentZIndex = m_possibleGarbage.constBegin().key();
0463     const QPair<QRectF, T> currentPair = m_possibleGarbage.take(currentZIndex);
0464 
0465     typedef QPair<QRectF, T> DataPair;
0466     QMap<int, DataPair> pairs = m_tree.intersectingPairs(currentPair.first.toRect());
0467     if (pairs.isEmpty())   // actually never true, just for sanity
0468         return;
0469     int zIndex = pairs.constBegin().key();
0470     DataPair pair = pairs[zIndex];
0471 
0472     // check whether the default style is placed first
0473     if (zIndex == currentZIndex &&
0474             currentPair.second == T() &&
0475             pair.second == T() &&
0476             pair.first == currentPair.first) {
0477         debugSheets << "RectStorage: removing default data at" << Region(currentPair.first.toRect()).name();
0478         m_tree.remove(currentPair.first.toRect(), currentPair.second);
0479         triggerGarbageCollection();
0480         return; // already done
0481     }
0482 
0483     bool found = false;
0484     typename QMap<int, DataPair>::ConstIterator end = pairs.constEnd();
0485     for (typename QMap<int, DataPair>::ConstIterator it = pairs.constFind(currentZIndex); it != end; ++it) {
0486         zIndex = it.key();
0487         pair = it.value();
0488 
0489         // as long as the substyle in question was not found, skip the substyle
0490         if (!found) {
0491             if (zIndex == currentZIndex &&
0492                     pair.first == currentPair.first &&
0493                     pair.second == currentPair.second) {
0494                 found = true;
0495             }
0496             continue;
0497         }
0498 
0499         // remove the current pair, if another substyle of the same type,
0500         // the default style or a named style follows and the rectangle
0501         // is completely covered
0502         if (zIndex != currentZIndex &&
0503                 (pair.second == currentPair.second || pair.second == T()) &&
0504                 pair.first.toRect().contains(currentPair.first.toRect())) {
0505             debugSheets << "RectStorage: removing data at" << Region(currentPair.first.toRect()).name();
0506             m_tree.remove(currentPair.first.toRect(), currentPair.second);
0507             break;
0508         }
0509     }
0510     triggerGarbageCollection();
0511 }
0512 
0513 template<typename T>
0514 void RectStorage<T>::regionChanged(const QRect& rect)
0515 {
0516     if (m_loader && !m_loader->isFinished())
0517         return;
0518     if (m_map->isLoading())
0519         return;
0520     // mark the possible garbage
0521     // NOTE Stefan: The map may contain multiple indices. The already existing possible garbage has
0522     // has to be inserted most recently, because it should be accessed first.
0523     m_possibleGarbage = m_tree.intersectingPairs(rect).unite(m_possibleGarbage);
0524     triggerGarbageCollection();
0525     // invalidate cache
0526     invalidateCache(rect);
0527 }
0528 
0529 template<typename T>
0530 void RectStorage<T>::invalidateCache(const QRect& invRect)
0531 {
0532     if (m_loader && !m_loader->isFinished())
0533         return;
0534 #ifdef CALLIGRA_SHEETS_MT
0535     QMutexLocker ml(&m_mutex);
0536 #endif
0537     const QVector<QRect> rects = m_cachedArea.intersected(invRect).rects();
0538     m_cachedArea = m_cachedArea.subtracted(invRect);
0539     foreach(const QRect& rect, rects) {
0540         for (int col = rect.left(); col <= rect.right(); ++col) {
0541             for (int row = rect.top(); row <= rect.bottom(); ++row)
0542                 m_cache.remove(QPoint(col, row));     // also deletes it
0543         }
0544     }
0545 }
0546 
0547 template<typename T>
0548 void RectStorage<T>::ensureLoaded() const
0549 {
0550     if (m_loader) {
0551         m_loader->waitForFinished();
0552         delete m_loader;
0553         const_cast<RectStorage<T>*>(this)->m_loader = 0;
0554     }
0555 }
0556 
0557 template<typename T>
0558 RectStorageLoader<T>::RectStorageLoader(RectStorage<T> *storage, const QList<QPair<QRegion, T> > &data)
0559     : m_storage(storage)
0560     , m_data(data)
0561 {
0562 }
0563 
0564 template<typename T>
0565 void RectStorageLoader<T>::run()
0566 {
0567     static int total = 0;
0568     debugSheets << "Loading conditional styles";
0569     QTime t; t.start();
0570 
0571     QList<QPair<QRegion, T> > treeData;
0572     typedef QPair<QRegion, T> TRegion;
0573     foreach (const TRegion& tr, m_data) {
0574         const QRegion& reg = tr.first;
0575         const T& d = tr.second;
0576 
0577         int index = m_storage->m_storedData.indexOf(d);
0578         if (index != -1) {
0579             treeData.append(qMakePair(reg, m_storage->m_storedData[index]));
0580         } else {
0581             treeData.append(tr);
0582             m_storage->m_storedData.append(d);
0583         }
0584     }
0585 
0586     m_storage->m_tree.load(treeData);
0587     int e = t.elapsed();
0588     total += e;
0589     debugSheets << "Time: " << e << total;
0590 }
0591 
0592 template<typename T>
0593 void RectStorageLoader<T>::waitForFinished()
0594 {
0595     run();
0596 }
0597 
0598 template<typename T>
0599 bool RectStorageLoader<T>::isFinished() const
0600 {
0601     return false;
0602 }
0603 
0604 template<typename T>
0605 QList<QPair<QRegion, T> > RectStorageLoader<T>::data() const
0606 {
0607      return m_data;
0608 }
0609 
0610 class CommentStorage : public QObject, public RectStorage<QString>
0611 {
0612     Q_OBJECT
0613 public:
0614     explicit CommentStorage(Map* map) : QObject(map), RectStorage<QString>(map) {}
0615     CommentStorage(const CommentStorage& other) : QObject(other.parent()), RectStorage<QString>(other) {}
0616 
0617 protected Q_SLOTS:
0618     void triggerGarbageCollection() override {
0619         QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
0620     }
0621     void garbageCollection() override {
0622         RectStorage<QString>::garbageCollection();
0623     }
0624 };
0625 
0626 
0627 
0628 class CALLIGRA_SHEETS_ODF_EXPORT FusionStorage : public QObject, public RectStorage<bool>
0629 {
0630     Q_OBJECT
0631 public:
0632     explicit FusionStorage(Map* map) : QObject(map), RectStorage<bool>(map) {}
0633     FusionStorage(const FusionStorage& other) : QObject(other.parent()), RectStorage<bool>(other) {}
0634 
0635 protected Q_SLOTS:
0636     void triggerGarbageCollection() override {
0637         QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
0638     }
0639     void garbageCollection() override {
0640         RectStorage<bool>::garbageCollection();
0641     }
0642 };
0643 
0644 
0645 
0646 class MatrixStorage : public QObject, public RectStorage<bool>
0647 {
0648     Q_OBJECT
0649 public:
0650     explicit MatrixStorage(Map* map) : QObject(map), RectStorage<bool>(map) {}
0651     MatrixStorage(const MatrixStorage& other) : QObject(other.parent()), RectStorage<bool>(other) {}
0652 
0653 protected Q_SLOTS:
0654     void triggerGarbageCollection() override {
0655         QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
0656     }
0657     void garbageCollection() override {
0658         RectStorage<bool>::garbageCollection();
0659     }
0660 };
0661 
0662 } // namespace Sheets
0663 } // namespace Calligra
0664 
0665 #endif // CALLIGRA_SHEETS_RECT_STORAGE