diff --git a/sheets/PointStorage.h b/sheets/PointStorage.h index 1318d98573d..386d5ac268f 100644 --- a/sheets/PointStorage.h +++ b/sheets/PointStorage.h @@ -1,882 +1,882 @@ /* This file is part of the KDE project Copyright 2007 Stefan Nikolaus 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_POINT_STORAGE #define CALLIGRA_SHEETS_POINT_STORAGE #include #include #include #include "Region.h" #include "calligra_sheets_limits.h" #include // #define KSPREAD_POINT_STORAGE_HASH namespace Calligra { namespace Sheets { /** * \ingroup Storage * A custom pointwise storage. * Based on a compressed sparse matrix data structure. * Usable for any kind of data attached to 2D coordinates. * * Only non-default data with its coordinate is stored. Hence, the storage * has a small memory footprint nearly regardless of the data's location. * Each empty row before a location occupy an integer, which is not the case * for columns. Iterating over the data becomes fast compared to dense * matrix/array, where each location has to be traversed irrespective of * default or non-default data. * * The actual data is stored in the list m_data. It is grouped by rows in * ascending order. The rows' beginnings and ends are stored in the list * m_rows. Its index corresponds to the row index. The values denote the * starting index of a row in m_data. The row's end is determined by * the starting position of the next row. The entries in each row are ordered * by column. The corresponding column indices are stored in m_cols. Hence, * m_cols has the same amount of entries as m_data. * * \author Stefan Nikolaus * * \note If you fill the storage, do it row-wise. That's more performant. * \note For data assigned to rectangular regions use RectStorage. * \note It's QVector based. To boost performance a lot, declare the stored * data type as movable. */ template class PointStorage { friend class PointStorageBenchmark; friend class PointStorageTest; public: /** * Constructor. * Creates an empty storage. Actually, does nothing. */ PointStorage() {} /** * Destructor. */ ~PointStorage() {} /** * Clears the storage. */ void clear() { m_cols.clear(); m_rows.clear(); m_data.clear(); } /** * Returns the number of items in the storage. * Usable to iterate over all non-default data. * \return number of items * \see col() * \see row() * \see data() */ int count() const { return m_data.count(); } /** * Inserts \p data at \p col , \p row . * \return the overridden data (default data, if no overwrite) */ T insert(int col, int row, const T& data) { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // row's missing? if (row > m_rows.count()) { // insert missing rows m_rows.insert(m_rows.count(), row - m_rows.count(), m_data.count()); // append the actual data #ifdef KSPREAD_POINT_STORAGE_HASH m_data.append(*m_usedData.insert(data)); #else m_data.append(data); #endif // append the column index m_cols.append(col); } // the row exists else { const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); const QVector::const_iterator cit = std::lower_bound(cstart, cend, col); // column's missing? if (cit == cend || *cit != col) { // determine the index where the data and column has to be inserted const int index = m_rows.value(row - 1) + (cit - cstart); // insert the actual data #ifdef KSPREAD_POINT_STORAGE_HASH m_data.insert(index, *m_usedData.insert(data)); #else m_data.insert(index, data); #endif // insert the column index m_cols.insert(index, col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) ++m_rows[r]; } // column exists else { const int index = m_rows.value(row - 1) + (cit - cstart); const T oldData = m_data[ index ]; #ifdef KSPREAD_POINT_STORAGE_HASH m_data[ index ] = *m_usedData.insert(data); #else m_data[ index ] = data; #endif return oldData; } } squeezeRows(); return T(); } /** * Looks up the data at \p col , \p row . If no data was found returns a * default object. * \return the data at the given coordinate */ T lookup(int col, int row, const T& defaultVal = T()) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // is the row not present? if (row > m_rows.count()) return defaultVal; const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); - const QVector::const_iterator cit = qBinaryFind(cstart, cend, col); + const QVector::const_iterator cit = std::lower_bound(cstart, cend, col); // is the col not present? if (cit == cend) return defaultVal; return m_data.value(m_rows.value(row - 1) + (cit - cstart)); } /** * Removes data at \p col , \p row . * \return the removed data (default data, if none) */ T take(int col, int row, const T& defaultVal = T()) { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // row's missing? if (row > m_rows.count()) return defaultVal; const int rowStart = (row - 1 < m_rows.count()) ? m_rows.value(row - 1) : m_data.count(); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); - QVector::const_iterator cit = qBinaryFind(cols, col); + QVector::const_iterator cit = std::lower_bound(cols.begin(), cols.end(), col); // column's missing? if (cit == cols.constEnd()) return defaultVal; const int index = rowStart + (cit - cols.constBegin()); // save the old data const T oldData = m_data[ index ]; // remove the actual data m_data.remove(index); // remove the column index m_cols.remove(index); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; squeezeRows(); return oldData; } /** * Insert \p number columns at \p position . * \return the data, that became out of range (shifted over the end) */ QVector< QPair > insertColumns(int position, int number) { Q_ASSERT(1 <= position && position <= KS_colMax); QVector< QPair > oldData; for (int row = m_rows.count(); row >= 1; --row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); for (int col = cols.count(); col >= 0; --col) { if (cols.value(col) + number > KS_colMax) { oldData.append(qMakePair(QPoint(cols.value(col), row), m_data.value(rowStart + col))); m_cols.remove(rowStart + col); m_data.remove(rowStart + col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } else if (cols.value(col) >= position) m_cols[rowStart + col] += number; } } squeezeRows(); return oldData; } /** * Removes \p number columns at \p position . * \return the removed data */ QVector< QPair > removeColumns(int position, int number) { Q_ASSERT(1 <= position && position <= KS_colMax); QVector< QPair > oldData; for (int row = m_rows.count(); row >= 1; --row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); for (int col = cols.count() - 1; col >= 0; --col) { if (cols.value(col) >= position) { if (cols.value(col) < position + number) { oldData.append(qMakePair(QPoint(cols.value(col), row), m_data.value(rowStart + col))); m_cols.remove(rowStart + col); m_data.remove(rowStart + col); for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } else m_cols[rowStart + col] -= number; } } } squeezeRows(); return oldData; } /** * Insert \p number rows at \p position . * \return the data, that became out of range (shifted over the end) */ QVector< QPair > insertRows(int position, int number) { Q_ASSERT(1 <= position && position <= KS_rowMax); // row's missing? if (position > m_rows.count()) return QVector< QPair >(); QVector< QPair > oldData; int dataCount = 0; int rowCount = 0; // save the old data for (int row = KS_rowMax - number + 1; row <= m_rows.count() && row <= KS_rowMax; ++row) { const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); for (QVector::const_iterator cit = cstart; cit != cend; ++cit) oldData.append(qMakePair(QPoint(*cit, row), m_data.value(cit - m_cols.constBegin()))); dataCount += (cend - cstart); ++rowCount; } // remove the out of range data while (dataCount-- > 0) { m_data.remove(m_data.count() - 1); m_cols.remove(m_cols.count() - 1); } while (rowCount-- > 0) m_rows.remove(m_rows.count() - 1); // insert the new rows const int index = m_rows.value(position - 1); for (int r = 0; r < number; ++r) m_rows.insert(position, index); squeezeRows(); return oldData; } /** * Removes \p number rows at \p position . * \return the removed data */ QVector< QPair > removeRows(int position, int number) { Q_ASSERT(1 <= position && position <= KS_rowMax); // row's missing? if (position > m_rows.count()) return QVector< QPair >(); QVector< QPair > oldData; int dataCount = 0; int rowCount = 0; // save the old data for (int row = position; row <= m_rows.count() && row <= position + number - 1; ++row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); const QVector data = m_data.mid(rowStart, rowLength); for (int col = 0; col < cols.count(); ++col) oldData.append(qMakePair(QPoint(cols.value(col), row), data.value(col))); dataCount += data.count(); ++rowCount; } // adjust the offsets of the following rows for (int r = position + number - 1; r < m_rows.count(); ++r) m_rows[r] -= dataCount; // remove the out of range data while (dataCount-- > 0) { m_data.remove(m_rows.value(position - 1)); m_cols.remove(m_rows.value(position - 1)); } while (rowCount-- > 0) m_rows.remove(position - 1); squeezeRows(); return oldData; } /** * Shifts the data right of \p rect to the left by the width of \p rect . * The data formerly contained in \p rect becomes overridden. * \return the removed data */ QVector< QPair > removeShiftLeft(const QRect& rect) { Q_ASSERT(1 <= rect.left() && rect.left() <= KS_colMax); QVector< QPair > oldData; for (int row = qMin(rect.bottom(), m_rows.count()); row >= rect.top(); --row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); for (int col = cols.count() - 1; col >= 0; --col) { if (cols.value(col) >= rect.left()) { if (cols.value(col) <= rect.right()) { oldData.append(qMakePair(QPoint(cols.value(col), row), m_data.value(rowStart + col))); m_cols.remove(rowStart + col); m_data.remove(rowStart + col); for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } else m_cols[rowStart + col] -= rect.width(); } } } squeezeRows(); return oldData; } /** * Shifts the data in and right of \p rect to the right by the width of \p rect . * \return the data, that became out of range (shifted over the end) */ QVector< QPair > insertShiftRight(const QRect& rect) { Q_ASSERT(1 <= rect.left() && rect.left() <= KS_colMax); QVector< QPair > oldData; for (int row = rect.top(); row <= rect.bottom() && row <= m_rows.count(); ++row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); for (int col = cols.count(); col >= 0; --col) { if (cols.value(col) + rect.width() > KS_colMax) { oldData.append(qMakePair(QPoint(cols.value(col), row), m_data.value(rowStart + col))); m_cols.remove(rowStart + col); m_data.remove(rowStart + col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } else if (cols.value(col) >= rect.left()) m_cols[rowStart + col] += rect.width(); } } squeezeRows(); return oldData; } /** * Shifts the data below \p rect to the top by the height of \p rect . * The data formerly contained in \p rect becomes overridden. * \return the removed data */ QVector< QPair > removeShiftUp(const QRect& rect) { Q_ASSERT(1 <= rect.top() && rect.top() <= KS_rowMax); // row's missing? if (rect.top() > m_rows.count()) return QVector< QPair >(); QVector< QPair > oldData; for (int row = rect.top(); row <= m_rows.count() && row <= KS_rowMax - rect.height(); ++row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); const QVector data = m_data.mid(rowStart, rowLength); // first, iterate over the destination row for (int col = cols.count() - 1; col >= 0; --col) { const int column = cols.value(col); // real column value (1...KS_colMax) if (column >= rect.left() && column <= rect.right()) { // save the old data if (row <= rect.bottom()) oldData.append(qMakePair(QPoint(column, row), data.value(col))); // search const int srcRow = row + rect.height(); const QVector::const_iterator cstart2((srcRow - 1 < m_rows.count()) ? m_cols.begin() + m_rows.value(srcRow - 1) : m_cols.end()); const QVector::const_iterator cend2((srcRow < m_rows.count()) ? (m_cols.begin() + m_rows.value(srcRow)) : m_cols.end()); - const QVector::const_iterator cit2 = qBinaryFind(cstart2, cend2, column); + const QVector::const_iterator cit2 = std::lower_bound(cstart2, cend2, column); // column's missing? if (cit2 == cend2) { m_cols.remove(rowStart + col); m_data.remove(rowStart + col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } // column exists else { // copy m_data[rowStart + col] = m_data.value(cit2 - m_cols.constBegin()); // remove m_cols.remove(cit2 - m_cols.constBegin()); m_data.remove(cit2 - m_cols.constBegin()); // adjust the offsets of the following rows for (int r = row + rect.height(); r < m_rows.count(); ++r) --m_rows[r]; } } } // last, iterate over the source row const int srcRow = row + rect.height(); const int rowStart2 = (srcRow - 1 < m_rows.count()) ? m_rows.value(srcRow - 1) : m_data.count(); const int rowLength2 = (srcRow < m_rows.count()) ? m_rows.value(srcRow) - rowStart2 : -1; const QVector cols2 = m_cols.mid(rowStart2, rowLength2); const QVector data2 = m_data.mid(rowStart2, rowLength2); int offset = 0; for (int col = cols2.count() - 1; col >= 0; --col) { const int column = cols2.value(col); // real column value (1...KS_colMax) if (column >= rect.left() && column <= rect.right()) { // find the insertion position const QVector::const_iterator cstart((row - 1 < m_rows.count()) ? m_cols.begin() + m_rows.value(row - 1) : m_cols.end()); const QVector::const_iterator cend(((row < m_rows.count())) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); const QVector::const_iterator cit = qUpperBound(cstart, cend, cols2.value(col)); // Destination column: - const QVector::const_iterator dstcit = qBinaryFind(cols.begin(), cols.end(), column); + const QVector::const_iterator dstcit = std::lower_bound(cols.begin(), cols.end(), column); if (dstcit != cols.end()) { // destination column exists // replace the existing destination value const int dstCol = (dstcit - cols.constBegin()); m_data[rowStart + dstCol] = m_data.value(rowStart2 + col); // remove it from its old position m_data.remove(rowStart2 + col + 1); m_cols.remove(rowStart2 + col + 1); // The amount of values in the range from the // destination row to the source row have not changed. // adjust the offsets of the following rows for (int r = srcRow; r < m_rows.count(); ++r) { ++m_rows[r]; } } else { // destination column does not exist yet // copy it to its new position const int dstCol = cit - m_cols.constBegin(); m_data.insert(dstCol, data2.value(col)); m_cols.insert(dstCol, cols2.value(col)); // remove it from its old position m_data.remove(rowStart2 + col + 1 + offset); m_cols.remove(rowStart2 + col + 1 + offset); ++offset; // adjust the offsets of the following rows for (int r = row; r < srcRow; ++r) { ++m_rows[r]; } } } } } squeezeRows(); return oldData; } /** * Shifts the data in and below \p rect to the bottom by the height of \p rect . * \return the data, that became out of range (shifted over the end) */ QVector< QPair > insertShiftDown(const QRect& rect) { Q_ASSERT(1 <= rect.top() && rect.top() <= KS_rowMax); // row's missing? if (rect.top() > m_rows.count()) return QVector< QPair >(); QVector< QPair > oldData; for (int row = m_rows.count(); row >= rect.top(); --row) { const int rowStart = m_rows.value(row - 1); const int rowLength = (row < m_rows.count()) ? m_rows.value(row) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); const QVector data = m_data.mid(rowStart, rowLength); for (int col = cols.count() - 1; col >= 0; --col) { if (cols.value(col) >= rect.left() && cols.value(col) <= rect.right()) { if (row + rect.height() > KS_rowMax) { // save old data oldData.append(qMakePair(QPoint(cols.value(col), row), data.value(col))); } else { // insert missing rows if (row + rect.height() > m_rows.count()) m_rows.insert(m_rows.count(), row + rect.height() - m_rows.count(), m_data.count()); // copy the data down const int row2 = row + rect.height(); const QVector::const_iterator cstart2(m_cols.begin() + m_rows.value(row2 - 1)); const QVector::const_iterator cend2((row2 < m_rows.count()) ? (m_cols.begin() + m_rows.value(row2)) : m_cols.end()); const QVector::const_iterator cit2 = std::lower_bound(cstart2, cend2, cols.value(col)); // column's missing? if (cit2 == cend2 || *cit2 != cols.value(col)) { // determine the index where the data and column has to be inserted const int index = m_rows.value(row2 - 1) + (cit2 - cstart2); // insert the actual data m_data.insert(index, data.value(col)); // insert the column index m_cols.insert(index, cols.value(col)); // adjust the offsets of the following rows for (int r = row2; r < m_rows.count(); ++r) ++m_rows[r]; } // column exists else { const int index = m_rows.value(row2 - 1) + (cit2 - cstart2); m_data[ index ] = data.value(col); } } // remove the data m_cols.remove(rowStart + col); m_data.remove(rowStart + col); // adjust the offsets of the following rows for (int r = row; r < m_rows.count(); ++r) --m_rows[r]; } } } squeezeRows(); return oldData; } /** * Retrieve the first used data in \p col . * Can be used in conjunction with nextInColumn() to loop through a column. * \return the first used data in \p col or the default data, if the column is empty. */ T firstInColumn(int col, int* newRow = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); const int index = m_cols.indexOf(col); if (newRow) { if (index == -1) // not found *newRow = 0; else *newRow = qUpperBound(m_rows, index) - m_rows.begin(); } return m_data.value(index); } /** * Retrieve the first used data in \p row . * Can be used in conjunction with nextInRow() to loop through a row. * \return the first used data in \p row or the default data, if the row is empty. */ T firstInRow(int row, int* newCol = 0) const { Q_ASSERT(1 <= row && row <= KS_rowMax); // row's empty? if (row > m_rows.count() || ((row < m_rows.count()) && m_rows.value(row - 1) == m_rows.value(row))) { if (newCol) *newCol = 0; return T(); } if (newCol) *newCol = m_cols.value(m_rows.value(row - 1)); return m_data.value(m_rows.value(row - 1)); } /** * Retrieve the last used data in \p col . * Can be used in conjunction with prevInColumn() to loop through a column. * \return the last used data in \p col or the default data, if the column is empty. */ T lastInColumn(int col, int* newRow = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); const int index = m_cols.lastIndexOf(col); if (newRow) { if (index == -1) // not found *newRow = 0; else *newRow = qUpperBound(m_rows, index) - m_rows.begin(); } return m_data.value(index); } /** * Retrieve the last used data in \p row . * Can be used in conjunction with prevInRow() to loop through a row. * \return the last used data in \p row or the default data, if the row is empty. */ T lastInRow(int row, int* newCol = 0) const { Q_ASSERT(1 <= row && row <= KS_rowMax); // row's empty? if (m_rows.value(row - 1) == m_rows.value(row) || m_rows.value(row - 1) == m_data.count()) { if (newCol) *newCol = 0; return T(); } // last row ends on data vector end if (row == m_rows.count()) { if (newCol) *newCol = m_cols.value(m_data.count() - 1); return m_data.value(m_data.count() - 1); } if (newCol) *newCol = m_cols.value(m_rows.value(row) - 1); return m_data.value(m_rows.value(row) - 1); } /** * Retrieve the next used data in \p col after \p row . * Can be used in conjunction with firstInColumn() to loop through a column. * \return the next used data in \p col or the default data, there is no further data. */ T nextInColumn(int col, int row, int* newRow = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // no next row? if (row + 1 > m_rows.count()) { if (newRow) *newRow = 0; return T(); } // search beginning in rows after the specified row const int index = m_cols.indexOf(col, m_rows.value(row)); if (newRow) { if (index == -1) // not found *newRow = 0; else *newRow = qUpperBound(m_rows, index) - m_rows.begin(); } return m_data.value(index); } /** * Retrieve the next used data in \p row after \p col . * Can be used in conjunction with firstInRow() to loop through a row. * \return the next used data in \p row or the default data, if there is no further data. */ T nextInRow(int col, int row, int* newCol = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // is the row not present? if (row > m_rows.count()) { if (newCol) *newCol = 0; return T(); } const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); const QVector::const_iterator cit = qUpperBound(cstart, cend, col); if (cit == cend || *cit <= col) { if (newCol) *newCol = 0; return T(); } if (newCol) *newCol = m_cols.value(m_rows.value(row - 1) + (cit - cstart)); return m_data.value(m_rows.value(row - 1) + (cit - cstart)); } /** * Retrieve the previous used data in \p col after \p row . * Can be used in conjunction with lastInColumn() to loop through a column. * \return the previous used data in \p col or the default data, there is no further data. */ T prevInColumn(int col, int row, int* newRow = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); // first row? if (row <= m_rows.count() && m_rows.value(row - 1) == 0) { if (newRow) *newRow = 0; return T(); } const int index = m_cols.lastIndexOf(col, m_rows.value(row - 1) - 1); if (newRow) { if (index == -1) // not found *newRow = 0; else *newRow = qUpperBound(m_rows, index) - m_rows.begin(); } return m_data.value(index); } /** * Retrieve the previous used data in \p row after \p col . * Can be used in conjunction with lastInRow() to loop through a row. * \return the previous used data in \p row or the default data, if there is no further data. */ T prevInRow(int col, int row, int* newCol = 0) const { Q_ASSERT(1 <= col && col <= KS_colMax); Q_ASSERT(1 <= row && row <= KS_rowMax); const QVector::const_iterator cstart((row - 1 < m_rows.count()) ? m_cols.begin() + m_rows.value(row - 1) : m_cols.end()); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); const QVector::const_iterator cit = std::lower_bound(cstart, cend, col); if (cit == cstart) { if (newCol) *newCol = 0; return T(); } if (newCol) *newCol = m_cols.value(cit - 1 - m_cols.begin()); return m_data.value(cit - 1 - m_cols.begin()); } /** * For debugging/testing purposes. * \note only works with primitive/printable data */ QString dump() const { QString str; // determine the dimension of the matrix (the missing column number) int maxCols = 0; for (int row = 0; row < m_rows.count(); ++row) { const int rowStart = m_rows.value(row); const int rowLength = (row + 1 < m_rows.count()) ? m_rows.value(row + 1) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); maxCols = qMax(maxCols, cols.value(cols.count() - 1)); } for (int row = 0; row < m_rows.count(); ++row) { str += '('; const int rowStart = m_rows.value(row); const int rowLength = (row + 1 < m_rows.count()) ? m_rows.value(row + 1) - rowStart : -1; const QVector cols = m_cols.mid(rowStart, rowLength); const QVector data = m_data.mid(rowStart, rowLength); int lastCol = 0; for (int col = 0; col < cols.count(); ++col) { int counter = cols.value(col) - lastCol; while (counter-- > 1) str += " ,"; str += QString("%1,").arg(data.value(col), 2); // str += QString( "%1," ).arg( (data.value( col ) == T()) ? "" : "_", 2 ); lastCol = cols.value(col); } // fill the column up to the max int counter = maxCols - lastCol; while (counter-- > 0) str += " ,"; // replace the last comma str[str.length()-1] = ')'; str += '\n'; } return str.isEmpty() ? QString("()") : str.mid(0, str.length() - 1); } /** * Returns the column of the non-default data at \p index . * \return the data's column at \p index . * \see count() * \see row() * \see data() */ int col(int index) const { return m_cols.value(index); } /** * Returns the row of the non-default data at \p index . * \return the data's row at \p index . * \see count() * \see col() * \see data() */ int row(int index) const { return qUpperBound(m_rows, index) - m_rows.begin(); } /** * Returns the non-default data at \p index . * \return the data at \p index . * \see count() * \see col() * \see row() */ T data(int index) const { return m_data.value(index); } /** * The maximum occupied column, i.e. the horizontal storage dimension. * \return the maximum column */ int columns() const { int columns = 0; for (int c = 0; c < m_cols.count(); ++c) columns = qMax(m_cols.value(c), columns); return columns; } /** * The maximum occupied row, i.e. the vertical storage dimension. * \return the maximum row */ int rows() const { return m_rows.count(); } /** * Creates a substorage consisting of the values in \p region. * If \p keepOffset is \c true, the values' positions are not altered. * Otherwise, the upper left of \p region's bounding rect is used as new origin, * and all positions are adjusted. * \return a subset of the storage stripped down to the values in \p region */ PointStorage subStorage(const Region& region, bool keepOffset = true) const { // Determine the offset. const QPoint offset = keepOffset ? QPoint(0, 0) : region.boundingRect().topLeft() - QPoint(1, 1); // this generates an array of values PointStorage subStorage; Region::ConstIterator end(region.constEnd()); for (Region::ConstIterator it(region.constBegin()); it != end; ++it) { const QRect rect = (*it)->rect(); for (int row = rect.top(); row <= rect.bottom() && row <= m_rows.count(); ++row) { const QVector::const_iterator cstart(m_cols.begin() + m_rows.value(row - 1)); const QVector::const_iterator cend((row < m_rows.count()) ? (m_cols.begin() + m_rows.value(row)) : m_cols.end()); for (QVector::const_iterator cit = cstart; cit != cend; ++cit) { if (*cit >= rect.left() && *cit <= rect.right()) { if (keepOffset) subStorage.insert(*cit, row, m_data.value(cit - m_cols.begin())); else subStorage.insert(*cit - offset.x(), row - offset.y(), m_data.value(cit - m_cols.begin())); } } } } return subStorage; } /** * Equality operator. */ bool operator==(const PointStorage& o) const { return (m_rows == o.m_rows && m_cols == o.m_cols && m_data == o.m_data); } private: void squeezeRows() { int row = m_rows.count() - 1; while (m_rows.value(row) == m_data.count() && row >= 0) m_rows.remove(row--); } private: QVector m_cols; // stores the column indices (beginning with one) QVector m_rows; // stores the row offsets in m_data QVector m_data; // stores the actual non-default data #ifdef KSPREAD_POINT_STORAGE_HASH QSet m_usedData; #endif }; } // namespace Sheets } // namespace Calligra #endif // CALLIGRA_SHEETS_POINT_STORAGE