Your IP : 216.73.216.48
/* This file is part of the KDE project
Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
Copyright 2006,2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef CALLIGRA_SHEETS_RECT_STORAGE
#define CALLIGRA_SHEETS_RECT_STORAGE
#include <QCache>
#include <QRegion>
#include <QTimer>
#include <QRunnable>
#include <QTime>
#ifdef CALLIGRA_SHEETS_MT
#include <QMutex>
#include <QMutexLocker>
#endif
#include "calligra_sheets_export.h"
#include "Map.h"
#include "Region.h"
#include "RTree.h"
static const int g_garbageCollectionTimeOut = 100;
namespace Calligra
{
namespace Sheets
{
template<typename T>
class RectStorageLoader;
/**
* \ingroup Storage
* A custom rectangular storage.
* Based on an R-Tree data structure.
* Usable for any kind of data attached to rectangular regions.
*
* Acts mainly as a wrapper around the R-Tree data structure to allow a future
* replacement of this backend. Decorated with some additional features like
* garbage collection, caching, used area tracking, etc.
*
* \author Stefan Nikolaus <stefan.nikolaus@kdemail.net>
*
* \note For data assigned to points use PointStorage.
*/
template<typename T>
class RectStorage
{
public:
explicit RectStorage(Map* map);
RectStorage(const RectStorage& other);
virtual ~RectStorage();
/**
* \return the stored value at the position \p point .
*/
T contains(const QPoint& point) const;
/**
* \return the stored rect/value pair at the position \p point .
*/
QPair<QRectF, T> containedPair(const QPoint& point) const;
QList< QPair<QRectF, T> > intersectingPairs(const Region& region) const;
QList< QPair<QRectF, T> > undoData(const Region& region) const;
/**
* Returns the area, which got data attached.
* \return the area using data
*/
QRect usedArea() const;
/**
* Mass loading of data, removes any existing data first
*/
void load(const QList<QPair<QRegion, T> >& data);
/**
* Assigns \p data to \p region .
*/
void insert(const Region& region, const T& data);
/**
* Removes \p data from \p region .
*/
void remove(const Region& region, const T& data);
/**
* Inserts \p number rows at the position \p position .
* It extends or shifts rectangles, respectively.
*/
QList< QPair<QRectF, T> > insertRows(int position, int number);
/**
* Inserts \p number columns at the position \p position .
* It extends or shifts rectangles, respectively.
*/
QList< QPair<QRectF, T> > insertColumns(int position, int number);
/**
* Deletes \p number rows at the position \p position .
* It shrinks or shifts rectangles, respectively.
*/
QList< QPair<QRectF, T> > removeRows(int position, int number);
/**
* Deletes \p number columns at the position \p position .
* It shrinks or shifts rectangles, respectively.
*/
QList< QPair<QRectF, T> > removeColumns(int position, int number);
/**
* Shifts the rows right of \p rect to the right by the width of \p rect .
* It extends or shifts rectangles, respectively.
*/
QList< QPair<QRectF, T> > insertShiftRight(const QRect& rect);
/**
* Shifts the columns at the bottom of \p rect to the bottom by the height of \p rect .
* It extends or shifts rectangles, respectively.
*/
QList< QPair<QRectF, T> > insertShiftDown(const QRect& rect);
/**
* Shifts the rows left of \p rect to the left by the width of \p rect .
* It shrinks or shifts rectangles, respectively.
* \return the former rectangle/data pairs
*/
QList< QPair<QRectF, T> > removeShiftLeft(const QRect& rect);
/**
* Shifts the columns on top of \p rect to the top by the height of \p rect .
* It shrinks or shifts rectangles, respectively.
* \return the former rectangle/data pairs
*/
QList< QPair<QRectF, T> > removeShiftUp(const QRect& rect);
protected:
virtual void triggerGarbageCollection();
virtual void garbageCollection();
/**
* Triggers all necessary actions after a change of \p rect .
* Calls invalidateCache() and adds the data in
* \p rect to the list of possible garbage.
*/
void regionChanged(const QRect& rect);
/**
* Invalidates all cached styles lying in \p rect .
*/
void invalidateCache(const QRect& rect);
/**
* Ensures that any load() operation has completed.
*/
void ensureLoaded() const;
private:
Map* m_map;
RTree<T> m_tree;
QRegion m_usedArea;
QMap<int, QPair<QRectF, T> > m_possibleGarbage;
QList<T> m_storedData;
mutable QCache<QPoint, T> m_cache;
#ifdef CALLIGRA_SHEETS_MT
mutable QMutex m_mutex;
#endif
mutable QRegion m_cachedArea;
RectStorageLoader<T>* m_loader;
friend class RectStorageLoader<T>;
};
template<typename T>
class RectStorageLoader : public QRunnable
{
public:
RectStorageLoader(RectStorage<T>* storage, const QList<QPair<QRegion, T> >& data);
virtual void run();
void waitForFinished();
bool isFinished() const;
QList<QPair<QRegion, T> > data() const;
private:
RectStorage<T>* m_storage;
QList<QPair<QRegion, T> > m_data;
};
template<typename T>
RectStorage<T>::RectStorage(Map* map)
: m_map(map), m_loader(0)
{
}
template<typename T>
RectStorage<T>::RectStorage(const RectStorage& other)
: m_map(other.m_map)
, m_usedArea(other.m_usedArea)
, m_storedData(other.m_storedData)
, m_loader(0)
{
m_tree = other.m_tree;
if (other.m_loader) {
m_loader = new RectStorageLoader<T>(this, other.m_loader->data());
}
}
template<typename T>
RectStorage<T>::~RectStorage()
{
delete m_loader; // needs fixing if this ever gets to be multithreaded
}
template<typename T>
T RectStorage<T>::contains(const QPoint& point) const
{
ensureLoaded();
#ifdef CALLIGRA_SHEETS_MT
QMutexLocker ml(&m_mutex);
#endif
if (!usedArea().contains(point))
return T();
// first, lookup point in the cache
if (m_cache.contains(point)) {
return *m_cache.object(point);
}
// not found, lookup in the tree
QList<T> results = m_tree.contains(point);
T data = results.isEmpty() ? T() : results.last();
// insert style into the cache
m_cache.insert(point, new T(data));
m_cachedArea += QRect(point, point);
return data;
}
template<typename T>
QPair<QRectF, T> RectStorage<T>::containedPair(const QPoint& point) const
{
ensureLoaded();
const QList< QPair<QRectF, T> > results = m_tree.intersectingPairs(QRect(point, point)).values();
return results.isEmpty() ? qMakePair(QRectF(), T()) : results.last();
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::intersectingPairs(const Region& region) const
{
ensureLoaded();
QList< QPair<QRectF, T> > result;
Region::ConstIterator end = region.constEnd();
for (Region::ConstIterator it = region.constBegin(); it != end; ++it)
result += m_tree.intersectingPairs((*it)->rect()).values();
return result;
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::undoData(const Region& region) const
{
ensureLoaded();
QList< QPair<QRectF, T> > result;
Region::ConstIterator end = region.constEnd();
for (Region::ConstIterator it = region.constBegin(); it != end; ++it) {
const QRect rect = (*it)->rect();
QList< QPair<QRectF, T> > pairs = m_tree.intersectingPairs(rect).values();
for (int i = 0; i < pairs.count(); ++i) {
// trim the rects
pairs[i].first = pairs[i].first.intersected(rect);
}
// Always add a default value even if there are no pairs.
result << qMakePair(QRectF(rect), T()) << pairs;
}
return result;
}
template<typename T>
QRect RectStorage<T>::usedArea() const
{
ensureLoaded();
return m_tree.boundingBox().toRect();
}
template<typename T>
void RectStorage<T>::load(const QList<QPair<QRegion, T> >& data)
{
Q_ASSERT(!m_loader);
m_loader = new RectStorageLoader<T>(this, data);
}
template<typename T>
void RectStorage<T>::insert(const Region& region, const T& _data)
{
ensureLoaded();
T data;
// lookup already used data
int index = m_storedData.indexOf(_data);
if (index != -1)
data = m_storedData[index];
else {
data = _data;
m_storedData.append(_data);
}
Region::ConstIterator end(region.constEnd());
for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
// insert data
m_tree.insert((*it)->rect(), data);
regionChanged((*it)->rect());
}
}
template<typename T>
void RectStorage<T>::remove(const Region& region, const T& data)
{
ensureLoaded();
if (!m_storedData.contains(data)) {
return;
}
const Region::ConstIterator end(region.constEnd());
for (Region::ConstIterator it(region.constBegin()); it != end; ++it) {
// remove data
m_tree.remove((*it)->rect(), data);
regionChanged((*it)->rect());
}
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::insertRows(int position, int number)
{
ensureLoaded();
const QRect invalidRect(1, position, KS_colMax, KS_rowMax);
// invalidate the affected, cached styles
invalidateCache(invalidRect);
// process the tree
QList< QPair<QRectF, T> > undoData;
undoData << qMakePair(QRectF(1, KS_rowMax - number + 1, KS_colMax, number), T());
undoData << m_tree.insertRows(position, number, RTree<T>::CopyCurrent);
return undoData;
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::insertColumns(int position, int number)
{
ensureLoaded();
const QRect invalidRect(position, 1, KS_colMax, KS_rowMax);
// invalidate the affected, cached styles
invalidateCache(invalidRect);
// process the tree
QList< QPair<QRectF, T> > undoData;
undoData << qMakePair(QRectF(KS_colMax - number + 1, 1, number, KS_rowMax), T());
undoData << m_tree.insertColumns(position, number, RTree<T>::CopyCurrent);
return undoData;
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::removeRows(int position, int number)
{
ensureLoaded();
const QRect invalidRect(1, position, KS_colMax, KS_rowMax);
// invalidate the affected, cached styles
invalidateCache(invalidRect);
// process the tree
QList< QPair<QRectF, T> > undoData;
undoData << qMakePair(QRectF(1, position, KS_colMax, number), T());
undoData << m_tree.removeRows(position, number);
return undoData;
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::removeColumns(int position, int number)
{
ensureLoaded();
const QRect invalidRect(position, 1, KS_colMax, KS_rowMax);
// invalidate the affected, cached styles
invalidateCache(invalidRect);
// process the tree
QList< QPair<QRectF, T> > undoData;
undoData << qMakePair(QRectF(position, 1, number, KS_rowMax), T());
undoData << m_tree.removeColumns(position, number);
return undoData;
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::insertShiftRight(const QRect& rect)
{
ensureLoaded();
const QRect invalidRect(rect.topLeft(), QPoint(KS_colMax, rect.bottom()));
QList< QPair<QRectF, T> > undoData;
undoData << qMakePair(QRectF(rect), T());
undoData << m_tree.insertShiftRight(rect);
regionChanged(invalidRect);
return undoData;
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::insertShiftDown(const QRect& rect)
{
ensureLoaded();
const QRect invalidRect(rect.topLeft(), QPoint(rect.right(), KS_rowMax));
QList< QPair<QRectF, T> > undoData;
undoData << qMakePair(QRectF(rect), T());
undoData << m_tree.insertShiftDown(rect);
regionChanged(invalidRect);
return undoData;
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::removeShiftLeft(const QRect& rect)
{
ensureLoaded();
const QRect invalidRect(rect.topLeft(), QPoint(KS_colMax, rect.bottom()));
QList< QPair<QRectF, T> > undoData;
undoData << qMakePair(QRectF(rect), T());
undoData << m_tree.removeShiftLeft(rect);
regionChanged(invalidRect);
return undoData;
}
template<typename T>
QList< QPair<QRectF, T> > RectStorage<T>::removeShiftUp(const QRect& rect)
{
ensureLoaded();
const QRect invalidRect(rect.topLeft(), QPoint(rect.right(), KS_rowMax));
QList< QPair<QRectF, T> > undoData;
undoData << qMakePair(QRectF(rect), T());
undoData << m_tree.removeShiftUp(rect);
regionChanged(invalidRect);
return undoData;
}
template<typename T>
void RectStorage<T>::triggerGarbageCollection()
{
}
template<typename T>
void RectStorage<T>::garbageCollection()
{
if (m_loader && !m_loader->isFinished())
return;
// any possible garbage left?
if (m_possibleGarbage.isEmpty())
return;
const int currentZIndex = m_possibleGarbage.constBegin().key();
const QPair<QRectF, T> currentPair = m_possibleGarbage.take(currentZIndex);
typedef QPair<QRectF, T> DataPair;
QMap<int, DataPair> pairs = m_tree.intersectingPairs(currentPair.first.toRect());
if (pairs.isEmpty()) // actually never true, just for sanity
return;
int zIndex = pairs.constBegin().key();
DataPair pair = pairs[zIndex];
// check whether the default style is placed first
if (zIndex == currentZIndex &&
currentPair.second == T() &&
pair.second == T() &&
pair.first == currentPair.first) {
kDebug(36001) << "RectStorage: removing default data at" << Region(currentPair.first.toRect()).name();
m_tree.remove(currentPair.first.toRect(), currentPair.second);
triggerGarbageCollection();
return; // already done
}
bool found = false;
typename QMap<int, DataPair>::ConstIterator end = pairs.constEnd();
for (typename QMap<int, DataPair>::ConstIterator it = pairs.constFind(currentZIndex); it != end; ++it) {
zIndex = it.key();
pair = it.value();
// as long as the substyle in question was not found, skip the substyle
if (!found) {
if (zIndex == currentZIndex &&
pair.first == currentPair.first &&
pair.second == currentPair.second) {
found = true;
}
continue;
}
// remove the current pair, if another substyle of the same type,
// the default style or a named style follows and the rectangle
// is completely covered
if (zIndex != currentZIndex &&
(pair.second == currentPair.second || pair.second == T()) &&
pair.first.toRect().contains(currentPair.first.toRect())) {
kDebug(36001) << "RectStorage: removing data at" << Region(currentPair.first.toRect()).name();
m_tree.remove(currentPair.first.toRect(), currentPair.second);
break;
}
}
triggerGarbageCollection();
}
template<typename T>
void RectStorage<T>::regionChanged(const QRect& rect)
{
if (m_loader && !m_loader->isFinished())
return;
if (m_map->isLoading())
return;
// mark the possible garbage
// NOTE Stefan: The map may contain multiple indices. The already existing possible garbage has
// has to be inserted most recently, because it should be accessed first.
m_possibleGarbage = m_tree.intersectingPairs(rect).unite(m_possibleGarbage);
triggerGarbageCollection();
// invalidate cache
invalidateCache(rect);
}
template<typename T>
void RectStorage<T>::invalidateCache(const QRect& invRect)
{
if (m_loader && !m_loader->isFinished())
return;
#ifdef CALLIGRA_SHEETS_MT
QMutexLocker ml(&m_mutex);
#endif
const QVector<QRect> rects = m_cachedArea.intersected(invRect).rects();
m_cachedArea = m_cachedArea.subtracted(invRect);
foreach(const QRect& rect, rects) {
for (int col = rect.left(); col <= rect.right(); ++col) {
for (int row = rect.top(); row <= rect.bottom(); ++row)
m_cache.remove(QPoint(col, row)); // also deletes it
}
}
}
template<typename T>
void RectStorage<T>::ensureLoaded() const
{
if (m_loader) {
m_loader->waitForFinished();
delete m_loader;
const_cast<RectStorage<T>*>(this)->m_loader = 0;
}
}
template<typename T>
RectStorageLoader<T>::RectStorageLoader(RectStorage<T> *storage, const QList<QPair<QRegion, T> > &data)
: m_storage(storage)
, m_data(data)
{
}
template<typename T>
void RectStorageLoader<T>::run()
{
static int total = 0;
kDebug(36001) << "Loading conditional styles";
QTime t; t.start();
QList<QPair<QRegion, T> > treeData;
typedef QPair<QRegion, T> TRegion;
QMap<T, int> indexCache;
foreach (const TRegion& tr, m_data) {
const QRegion& reg = tr.first;
const T& d = tr.second;
typename QMap<T, int>::iterator idx = indexCache.find(d);
int index = idx != indexCache.end() ? idx.value() : m_storage->m_storedData.indexOf(d);
if (index != -1) {
treeData.append(qMakePair(reg, m_storage->m_storedData[index]));
if (idx == indexCache.end()) indexCache.insert(d, index);
} else {
treeData.append(tr);
if (idx == indexCache.end()) indexCache.insert(d, m_storage->m_storedData.size());
m_storage->m_storedData.append(d);
}
}
m_storage->m_tree.load(treeData);
int e = t.elapsed();
total += e;
kDebug(36001) << "Time: " << e << total;
}
template<typename T>
void RectStorageLoader<T>::waitForFinished()
{
run();
}
template<typename T>
bool RectStorageLoader<T>::isFinished() const
{
return false;
}
template<typename T>
QList<QPair<QRegion, T> > RectStorageLoader<T>::data() const
{
return m_data;
}
class CommentStorage : public QObject, public RectStorage<QString>
{
Q_OBJECT
public:
explicit CommentStorage(Map* map) : QObject(map), RectStorage<QString>(map) {}
CommentStorage(const CommentStorage& other) : QObject(other.parent()), RectStorage<QString>(other) {}
protected Q_SLOTS:
virtual void triggerGarbageCollection() {
QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
}
virtual void garbageCollection() {
RectStorage<QString>::garbageCollection();
}
};
class CALLIGRA_SHEETS_ODF_EXPORT FusionStorage : public QObject, public RectStorage<bool>
{
Q_OBJECT
public:
explicit FusionStorage(Map* map) : QObject(map), RectStorage<bool>(map) {}
FusionStorage(const FusionStorage& other) : QObject(other.parent()), RectStorage<bool>(other) {}
protected Q_SLOTS:
virtual void triggerGarbageCollection() {
QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
}
virtual void garbageCollection() {
RectStorage<bool>::garbageCollection();
}
};
class MatrixStorage : public QObject, public RectStorage<bool>
{
Q_OBJECT
public:
explicit MatrixStorage(Map* map) : QObject(map), RectStorage<bool>(map) {}
MatrixStorage(const MatrixStorage& other) : QObject(other.parent()), RectStorage<bool>(other) {}
protected Q_SLOTS:
virtual void triggerGarbageCollection() {
QTimer::singleShot(g_garbageCollectionTimeOut, this, SLOT(garbageCollection()));
}
virtual void garbageCollection() {
RectStorage<bool>::garbageCollection();
}
};
} // namespace Sheets
} // namespace Calligra
#endif // CALLIGRA_SHEETS_RECT_STORAGE