Add the Spreadsheet example
The example demonstrates a Spreadsheet that provides adding, editing,
and deleting data, and also the ability to write formulas for numeric
data. Also, it's possible to select cells, rows, and columns for
deleting them or their data, copying or cutting the data, and dragging
them to other places. The user can hide columns or rows, and also show
them again. Thanks to the reordering API, columns and rows can be
reordered and also can be reset to the default order.
There is a SpreadModel class which handles the entered data. It only
stores the data of the cells that is provided by the user. It means
that it does not create any empty data structure for empty cells, in
order to reduce memory usage.
Task-number: QTBUG-125767
Pick-to: 6.8
Change-Id: I1d9cc5b4b8d902257e9ed508d4a712b0574490f3
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
2024-05-27 16:06:47 +00:00
|
|
|
// Copyright (C) 2024 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
|
|
|
|
|
|
#include "datamodel.h"
|
|
|
|
#include "spreadrole.h"
|
|
|
|
|
|
|
|
bool DataModel::empty() const
|
|
|
|
{
|
|
|
|
return m_keys.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<SpreadKey, SpreadKey> DataModel::clearHighlight()
|
|
|
|
{
|
|
|
|
SpreadKey top_left{INT_MAX, INT_MAX};
|
|
|
|
SpreadKey bottom_right{-1, -1};
|
|
|
|
|
|
|
|
for (auto it = m_cells.begin(); it != m_cells.end();) {
|
|
|
|
it.value().set(spread::Role::Hightlight, false);
|
|
|
|
if (it.key().first < top_left.first)
|
|
|
|
top_left.first = it.key().first;
|
|
|
|
if (it.key().second < top_left.second)
|
|
|
|
top_left.second = it.key().second;
|
|
|
|
if (bottom_right.first < it.key().first)
|
|
|
|
bottom_right.first = it.key().first;
|
|
|
|
if (bottom_right.second < it.key().second)
|
|
|
|
bottom_right.second = it.key().second;
|
|
|
|
if (it.value().isNull()) {
|
|
|
|
m_keys.remove(it.value().id);
|
|
|
|
auto cit = spread::make_const(m_cells, it);
|
|
|
|
it = m_cells.erase(cit);
|
|
|
|
} else {
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::make_pair(top_left, bottom_right);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DataModel::setHighlight(const SpreadKey &key, bool highlight)
|
|
|
|
{
|
|
|
|
if (auto it = m_cells.find(key); it != m_cells.end()) {
|
|
|
|
it.value().set(spread::Role::Hightlight, highlight);
|
|
|
|
if (it.value().isNull()) {
|
|
|
|
m_keys.remove(it.value().id);
|
|
|
|
auto cit = spread::make_const(m_cells, it);
|
|
|
|
m_cells.erase(cit);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (highlight) {
|
|
|
|
SpreadCell cell;
|
|
|
|
cell.set(spread::Role::Hightlight, true);
|
|
|
|
cell.id = ++lastId;
|
|
|
|
m_cells.insert(key, cell);
|
|
|
|
m_keys.insert(cell.id, key);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// we skipped false highlight for non-existing cell
|
|
|
|
// because we don't store cells with only false hightlight data
|
|
|
|
// to save memory
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant DataModel::getData(int id, int role) const
|
|
|
|
{
|
|
|
|
auto it_key = m_keys.find(id);
|
|
|
|
if (it_key == m_keys.end())
|
|
|
|
return QVariant{};
|
|
|
|
const SpreadKey &key = it_key.value();
|
|
|
|
auto it_cell = m_cells.find(key);
|
|
|
|
if (it_cell == m_cells.end())
|
|
|
|
return QVariant{};
|
|
|
|
return it_cell.value().get(role);
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant DataModel::getData(const SpreadKey &key, int role) const
|
|
|
|
{
|
|
|
|
auto it = m_cells.find(key);
|
|
|
|
return it == m_cells.end() ? QVariant{} : it.value().get(role);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DataModel::setData(const SpreadKey &key, const QVariant &value, int role)
|
|
|
|
{
|
|
|
|
// special roles
|
|
|
|
switch (role) {
|
|
|
|
case spread::Role::Hightlight:
|
|
|
|
return setHighlight(key, value.toBool());
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// no special handling for the role
|
|
|
|
if (auto it = m_cells.find(key); it != m_cells.end()) {
|
|
|
|
it.value().set(role, value);
|
|
|
|
if (it.value().isNull()) {
|
|
|
|
clearData(key);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
SpreadCell cell;
|
|
|
|
cell.set(role, value);
|
|
|
|
cell.id = ++lastId;
|
|
|
|
if (!cell.isNull()) {
|
|
|
|
m_cells.insert(key, cell);
|
|
|
|
m_keys.insert(cell.id, key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DataModel::clearData(const SpreadKey &key)
|
|
|
|
{
|
|
|
|
auto find_key = [&key](const auto &i) { return i == key; };
|
|
|
|
auto it = std::find_if(m_keys.cbegin(), m_keys.cend(), find_key);
|
|
|
|
if (it == m_keys.cend())
|
|
|
|
return 0;
|
|
|
|
m_keys.erase(it);
|
|
|
|
return m_cells.remove(key) > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DataModel::shiftColumns(int from, int count)
|
|
|
|
{
|
|
|
|
if (count > 0) {
|
|
|
|
// the reason for reverse iteration is because of the coverage of
|
|
|
|
// the updated keys (bigger keys) and existing keys (next keys)
|
|
|
|
QMapIterator i(m_cells);
|
|
|
|
i.toBack();
|
|
|
|
while (i.hasPrevious()) {
|
|
|
|
i.previous();
|
|
|
|
if (i.key().second >= from) {
|
|
|
|
SpreadKey key = i.key();
|
|
|
|
SpreadCell cell = i.value();
|
|
|
|
m_cells.remove(key);
|
|
|
|
key.second += count;
|
|
|
|
m_cells.insert(key, cell);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (count < 0) {
|
|
|
|
// the reason for normal iteration is because of the coverage of
|
|
|
|
// the updated keys (smaller keys) and existing keys (previous keys)
|
|
|
|
for (auto it = m_cells.begin(); it != m_cells.end(); ++it) {
|
|
|
|
if (it.key().second >= from) {
|
|
|
|
SpreadKey key = it.key();
|
|
|
|
SpreadCell cell = it.value();
|
|
|
|
m_cells.remove(key);
|
|
|
|
key.second += count;
|
|
|
|
m_cells.insert(key, cell);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count != 0) {
|
|
|
|
for (auto it = m_keys.begin(); it != m_keys.end(); ++it) {
|
|
|
|
SpreadKey &key = it.value();
|
|
|
|
if (key.second >= from)
|
|
|
|
key.second += count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DataModel::removeColumnCells(int column)
|
|
|
|
{
|
|
|
|
for (auto it = m_cells.begin(); it != m_cells.end(); ) {
|
|
|
|
if (it.key().second == column) {
|
|
|
|
auto cit = spread::make_const(m_cells, it);
|
|
|
|
it = m_cells.erase(cit);
|
|
|
|
} else {
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto it = m_keys.begin(); it != m_keys.end(); ) {
|
|
|
|
if (it.value().second == column) {
|
|
|
|
auto cit = spread::make_const(m_keys, it);
|
|
|
|
it = m_keys.erase(cit);
|
|
|
|
} else {
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DataModel::shiftRows(int from, int count)
|
|
|
|
{
|
|
|
|
if (count > 0) {
|
|
|
|
// the reason for reverse iteration is because of the coverage of
|
|
|
|
// the updated keys (bigger keys) and existing keys (next keys)
|
|
|
|
QMapIterator i(m_cells);
|
|
|
|
i.toBack();
|
|
|
|
while (i.hasPrevious()) {
|
2024-07-10 08:42:35 +00:00
|
|
|
i.previous();
|
Add the Spreadsheet example
The example demonstrates a Spreadsheet that provides adding, editing,
and deleting data, and also the ability to write formulas for numeric
data. Also, it's possible to select cells, rows, and columns for
deleting them or their data, copying or cutting the data, and dragging
them to other places. The user can hide columns or rows, and also show
them again. Thanks to the reordering API, columns and rows can be
reordered and also can be reset to the default order.
There is a SpreadModel class which handles the entered data. It only
stores the data of the cells that is provided by the user. It means
that it does not create any empty data structure for empty cells, in
order to reduce memory usage.
Task-number: QTBUG-125767
Pick-to: 6.8
Change-Id: I1d9cc5b4b8d902257e9ed508d4a712b0574490f3
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
2024-05-27 16:06:47 +00:00
|
|
|
if (i.key().first < from)
|
|
|
|
break;
|
|
|
|
SpreadKey key = i.key();
|
|
|
|
SpreadCell cell = i.value();
|
|
|
|
m_cells.remove(key);
|
|
|
|
key.first += count;
|
|
|
|
m_cells.insert(key, cell);
|
|
|
|
}
|
|
|
|
} else if (count < 0) {
|
|
|
|
// the reason for normal iteration is because of the coverage of
|
|
|
|
// the updated keys (smaller keys) and existing keys (previous keys)
|
|
|
|
for (auto it = m_cells.begin(); it != m_cells.end(); ++it) {
|
|
|
|
if (it.key().first >= from) {
|
|
|
|
SpreadKey key = it.key();
|
|
|
|
SpreadCell cell = it.value();
|
|
|
|
m_cells.remove(key);
|
|
|
|
key.first += count;
|
|
|
|
m_cells.insert(key, cell);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count != 0) {
|
|
|
|
for (auto it = m_keys.begin(); it != m_keys.end(); ++it) {
|
|
|
|
SpreadKey &key = it.value();
|
|
|
|
if (key.first >= from)
|
|
|
|
key.first += count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DataModel::removeRowCells(int row)
|
|
|
|
{
|
|
|
|
for (auto it = m_cells.begin(); it != m_cells.end(); ) {
|
|
|
|
if (it.key().first == row) {
|
|
|
|
auto cit = spread::make_const(m_cells, it);
|
|
|
|
it = m_cells.erase(cit);
|
|
|
|
} else {
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto it = m_keys.begin(); it != m_keys.end(); ) {
|
|
|
|
if (it.value().first == row) {
|
|
|
|
auto cit = spread::make_const(m_keys, it);
|
|
|
|
it = m_keys.erase(cit);
|
|
|
|
} else {
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int DataModel::createId(const SpreadKey &key)
|
|
|
|
{
|
|
|
|
auto find_key = [&key](const auto &elem) { return key == elem; };
|
|
|
|
auto it = std::find_if(m_keys.begin(), m_keys.end(), find_key);
|
|
|
|
if (it != m_keys.end())
|
|
|
|
return it.key();
|
|
|
|
const int id = ++lastId;
|
|
|
|
m_keys.insert(id, key);
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DataModel::getId(const SpreadKey &key) const
|
|
|
|
{
|
|
|
|
auto find_key = [&key](const auto &elem) { return key == elem; };
|
|
|
|
auto it = std::find_if(m_keys.begin(), m_keys.end(), find_key);
|
|
|
|
if (it == m_keys.end())
|
|
|
|
return 0;
|
|
|
|
return it.key();
|
|
|
|
}
|
|
|
|
|
|
|
|
SpreadKey DataModel::getKey(int id) const
|
|
|
|
{
|
|
|
|
auto it = m_keys.find(id);
|
|
|
|
return it == m_keys.end() ? SpreadKey{-1, -1} : it.value();
|
|
|
|
}
|