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
|
|
|
|
|
2024-09-09 10:05:51 +00:00
|
|
|
#include "spreadformula.h"
|
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
|
|
|
#include "spreadmodel.h"
|
|
|
|
#include "spreadrole.h"
|
|
|
|
|
|
|
|
#include <QPoint>
|
|
|
|
#include <QRegularExpression>
|
|
|
|
|
|
|
|
int SpreadModel::columnNumberFromName(const QString &text)
|
|
|
|
{
|
|
|
|
if (text.size() == 1)
|
|
|
|
return getModelColumn(text[0].toLatin1() - 'A');
|
|
|
|
else if (text.size() == 2)
|
|
|
|
return getModelColumn((text[0].toLatin1() - 'A' + 1) * 26 + (text[1].toLatin1() - 'A'));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int SpreadModel::rowNumberFromName(const QString &text)
|
|
|
|
{
|
|
|
|
return getModelRow(text.toInt() - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
SpreadModel *SpreadModel::instance()
|
|
|
|
{
|
|
|
|
return s_instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
SpreadModel *SpreadModel::create(QQmlEngine *, QJSEngine *)
|
|
|
|
{
|
|
|
|
if (!s_instance)
|
|
|
|
s_instance = new SpreadModel {nullptr};
|
|
|
|
return s_instance;
|
|
|
|
}
|
|
|
|
|
2024-06-06 11:06:15 +00:00
|
|
|
void SpreadModel::update(int row, int column)
|
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
|
|
|
{
|
2024-06-06 11:06:15 +00:00
|
|
|
if (!m_updateMutex.tryLock())
|
|
|
|
return;
|
|
|
|
column = getModelColumn(column);
|
|
|
|
row = getModelRow(row);
|
|
|
|
const QModelIndex index = this->index(row, column);
|
|
|
|
emit dataChanged(index, index, {spread::Role::Display,});
|
|
|
|
m_updateMutex.unlock();
|
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
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadModel::clearHighlight()
|
|
|
|
{
|
|
|
|
if (m_dataModel.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
const auto [top_left, bottom_right] = m_dataModel.clearHighlight();
|
|
|
|
|
|
|
|
emit dataChanged(index(top_left.first, top_left.second),
|
|
|
|
index(bottom_right.first, bottom_right.second),
|
|
|
|
{spread::Role::Hightlight,});
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadModel::setHighlight(const QModelIndex &index, bool highlight)
|
|
|
|
{
|
|
|
|
if (!index.isValid())
|
|
|
|
return;
|
|
|
|
if (m_dataModel.setHighlight(SpreadKey{index.row(), index.column()}, highlight))
|
|
|
|
emit dataChanged(index, index, {spread::Role::Hightlight,});
|
|
|
|
}
|
|
|
|
|
|
|
|
int SpreadModel::rowCount(const QModelIndex &parent) const
|
|
|
|
{
|
|
|
|
return m_size.first;
|
|
|
|
}
|
|
|
|
|
|
|
|
int SpreadModel::columnCount(const QModelIndex &parent) const
|
|
|
|
{
|
|
|
|
return m_size.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant SpreadModel::data(const QModelIndex &index, int role) const
|
|
|
|
{
|
|
|
|
if (!index.isValid())
|
|
|
|
return QVariant{};
|
|
|
|
|
|
|
|
const int row = index.row();
|
|
|
|
const int column = index.column();
|
|
|
|
|
|
|
|
return m_dataModel.getData(SpreadKey{row, column}, role);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
|
|
{
|
|
|
|
if (!index.isValid())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const int row = index.row();
|
|
|
|
const int column = index.column();
|
|
|
|
|
|
|
|
if (m_dataModel.setData(SpreadKey{row, column}, value, role))
|
|
|
|
emit dataChanged(index, index);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::clearItemData(const QModelIndex &index)
|
|
|
|
{
|
|
|
|
if (!index.isValid())
|
|
|
|
return false;
|
|
|
|
if (m_dataModel.clearData(SpreadKey{index.row(), index.column()})) {
|
|
|
|
emit dataChanged(index, index);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags SpreadModel::flags(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
if (!index.isValid())
|
|
|
|
return Qt::NoItemFlags;
|
|
|
|
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
|
|
|
}
|
|
|
|
|
|
|
|
QHash<int, QByteArray> SpreadModel::roleNames() const
|
|
|
|
{
|
|
|
|
return {{spread::Role::Display, "display"},
|
|
|
|
{spread::Role::Edit, "edit"},
|
|
|
|
{spread::Role::ColumnName, "columnName"},
|
|
|
|
{spread::Role::RowName, "rowName"},
|
|
|
|
{spread::Role::Hightlight, "highlight"}};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant SpreadModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
|
|
{
|
|
|
|
switch (role) {
|
|
|
|
case spread::Role::ColumnName:
|
|
|
|
case spread::Role::RowName:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return QVariant{};
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (orientation) {
|
|
|
|
case Qt::Horizontal: {
|
|
|
|
constexpr char A = 'A';
|
|
|
|
const int view_section = getViewColumn(section);
|
|
|
|
if (view_section < 26) {
|
|
|
|
return QString{static_cast<char>(view_section + A)};
|
|
|
|
} else {
|
|
|
|
const int first = view_section / 26 - 1;
|
|
|
|
const int second = view_section % 26;
|
|
|
|
QString title{static_cast<char>(first + A)};
|
|
|
|
title += static_cast<char>(second + A);
|
|
|
|
return title;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case Qt::Vertical: {
|
|
|
|
return getViewRow(section) + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return QVariant{};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::insertColumns(int column, int count, const QModelIndex &parent)
|
|
|
|
{
|
|
|
|
if (count != 1) // TODO: implement inserting more than 1 columns
|
|
|
|
return false;
|
|
|
|
|
|
|
|
beginInsertColumns(QModelIndex{}, column, column + count - 1);
|
|
|
|
m_size.second += count;
|
|
|
|
|
|
|
|
// update model
|
|
|
|
m_dataModel.shiftColumns(column, count);
|
|
|
|
|
|
|
|
QMap<int, int> old_columns;
|
|
|
|
std::swap(old_columns, m_viewColumns);
|
|
|
|
for (auto it = old_columns.begin(); it != old_columns.end(); ++it) {
|
|
|
|
const int new_view_column = (it.value() < column) ? it.value() : it.value() + count;
|
|
|
|
const int new_model_column = (it.key() < column) ? it.key() : it.key() + count;
|
|
|
|
m_viewColumns.insert(new_model_column, new_view_column);
|
|
|
|
}
|
|
|
|
|
|
|
|
endInsertColumns();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::insertRows(int row, int count, const QModelIndex &parent)
|
|
|
|
{
|
|
|
|
if (count != 1) // TODO: implement inserting more than 1 rows
|
|
|
|
return false;
|
|
|
|
|
|
|
|
beginInsertRows(QModelIndex{}, row, row + count - 1);
|
|
|
|
m_size.first += count;
|
|
|
|
|
|
|
|
// update model
|
|
|
|
m_dataModel.shiftRows(row, count);
|
|
|
|
|
|
|
|
endInsertRows();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::removeColumns(int column, int count, const QModelIndex &parent)
|
|
|
|
{
|
|
|
|
if (count != 1) // TODO: implement removing more than 1 columns
|
|
|
|
return false;
|
|
|
|
|
|
|
|
beginRemoveColumns(QModelIndex{}, column, column + count - 1);
|
|
|
|
m_size.second -= count;
|
|
|
|
|
|
|
|
// update model
|
|
|
|
m_dataModel.removeColumnCells(column);
|
|
|
|
m_dataModel.shiftColumns(column + 1, -count);
|
|
|
|
|
|
|
|
endRemoveColumns();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::removeRows(int row, int count, const QModelIndex &parent)
|
|
|
|
{
|
|
|
|
if (count != 1) // TODO: implement removing more than 1 rows
|
|
|
|
return false;
|
|
|
|
|
|
|
|
beginRemoveRows(QModelIndex{}, row, row + count - 1);
|
|
|
|
m_size.first -= count;
|
|
|
|
|
|
|
|
// update model
|
|
|
|
m_dataModel.removeRowCells(row);
|
|
|
|
m_dataModel.shiftRows(row + 1, -count);
|
|
|
|
|
|
|
|
endRemoveRows();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::clearItemData(const QModelIndexList &indexes)
|
|
|
|
{
|
|
|
|
bool ok = true;
|
|
|
|
for (const QModelIndex &index : indexes)
|
|
|
|
ok &= clearItemData(index);
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::removeColumns(QModelIndexList indexes)
|
|
|
|
{
|
|
|
|
auto greater = [](const QModelIndex &lhs, const QModelIndex &rhs)
|
|
|
|
{
|
|
|
|
return lhs.column() > rhs.column();
|
|
|
|
};
|
|
|
|
std::sort(indexes.begin(), indexes.end(), greater);
|
|
|
|
for (const QModelIndex &index : indexes)
|
|
|
|
removeColumn(index.column());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SpreadModel::removeRows(QModelIndexList indexes)
|
|
|
|
{
|
|
|
|
auto greater = [](const QModelIndex &lhs, const QModelIndex &rhs)
|
|
|
|
{
|
|
|
|
return lhs.row() > rhs.row();
|
|
|
|
};
|
|
|
|
std::sort(indexes.begin(), indexes.end(), greater);
|
|
|
|
for (const QModelIndex &index : indexes)
|
|
|
|
removeRow(index.row());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadModel::mapColumn(int model, int view)
|
|
|
|
{
|
|
|
|
if (model == view)
|
|
|
|
m_viewColumns.remove(model);
|
|
|
|
else
|
|
|
|
m_viewColumns[model] = view;
|
|
|
|
emit headerDataChanged(Qt::Horizontal, model, view);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadModel::mapRow(int model, int view)
|
|
|
|
{
|
|
|
|
if (model == view)
|
|
|
|
m_viewRows.remove(model);
|
|
|
|
else
|
|
|
|
m_viewRows[model] = view;
|
|
|
|
emit headerDataChanged(Qt::Vertical, model, view);
|
|
|
|
}
|
|
|
|
|
|
|
|
Formula SpreadModel::parseFormulaString(const QString &qText)
|
|
|
|
{
|
|
|
|
if (qText.isEmpty())
|
|
|
|
return Formula{};
|
|
|
|
|
|
|
|
QRegularExpression pattern_re;
|
|
|
|
|
|
|
|
// is formula
|
|
|
|
pattern_re.setPattern("^\\s*=.*");
|
|
|
|
QRegularExpressionMatch match = pattern_re.match(qText);
|
|
|
|
if (!match.hasMatch())
|
|
|
|
return Formula{};
|
|
|
|
|
|
|
|
// is Assignment: e.g. =A1
|
|
|
|
pattern_re.setPattern("^\\s*=\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*$");
|
|
|
|
match = pattern_re.match(qText);
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
const QString column_label = match.captured(1);
|
|
|
|
const QString row_label = match.captured(2);
|
|
|
|
const int column = columnNumberFromName(column_label);
|
|
|
|
const int row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
return Formula::create(Formula::Operator::Assign, cell_id, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// is Addition: e.g. =A1+A2
|
|
|
|
pattern_re.setPattern("^\\s*=\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*\\+\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*$");
|
|
|
|
match = pattern_re.match(qText);
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
// first argument
|
|
|
|
QString column_label = match.captured(1);
|
|
|
|
QString row_label = match.captured(2);
|
|
|
|
int column = columnNumberFromName(column_label);
|
|
|
|
int row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_1 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// second argument
|
|
|
|
column_label = match.captured(3);
|
|
|
|
row_label = match.captured(4);
|
|
|
|
column = columnNumberFromName(column_label);
|
|
|
|
row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_2 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// create formula
|
|
|
|
return Formula::create(Formula::Operator::Add, cell_id_1, cell_id_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// is Subtraction: e.g. =A1-A2
|
|
|
|
pattern_re.setPattern("^\\s*=\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*\\-\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*$");
|
|
|
|
match = pattern_re.match(qText);
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
// first argument
|
|
|
|
QString column_label = match.captured(1);
|
|
|
|
QString row_label = match.captured(2);
|
|
|
|
int column = columnNumberFromName(column_label);
|
|
|
|
int row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_1 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// second argument
|
|
|
|
column_label = match.captured(3);
|
|
|
|
row_label = match.captured(4);
|
|
|
|
column = columnNumberFromName(column_label);
|
|
|
|
row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_2 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// create formula
|
|
|
|
return Formula::create(Formula::Operator::Sub, cell_id_1, cell_id_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// is Multiply: e.g. =A1*A2
|
|
|
|
pattern_re.setPattern("^\\s*=\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*\\*\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*$");
|
|
|
|
match = pattern_re.match(qText);
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
// first argument
|
|
|
|
QString column_label = match.captured(1);
|
|
|
|
QString row_label = match.captured(2);
|
|
|
|
int column = columnNumberFromName(column_label);
|
|
|
|
int row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_1 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// second argument
|
|
|
|
column_label = match.captured(3);
|
|
|
|
row_label = match.captured(4);
|
|
|
|
column = columnNumberFromName(column_label);
|
|
|
|
row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_2 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// create formula
|
|
|
|
return Formula::create(Formula::Operator::Mul, cell_id_1, cell_id_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// is Division: e.g. =A1/A2
|
|
|
|
pattern_re.setPattern("^\\s*=\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*\\/\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*$");
|
|
|
|
match = pattern_re.match(qText);
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
// first argument
|
|
|
|
QString column_label = match.captured(1);
|
|
|
|
QString row_label = match.captured(2);
|
|
|
|
int column = columnNumberFromName(column_label);
|
|
|
|
int row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_1 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// second argument
|
|
|
|
column_label = match.captured(3);
|
|
|
|
row_label = match.captured(4);
|
|
|
|
column = columnNumberFromName(column_label);
|
|
|
|
row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_2 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// create formula
|
|
|
|
return Formula::create(Formula::Operator::Div, cell_id_1, cell_id_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// is Summation: e.g. =SUM A1:A2
|
|
|
|
pattern_re.setPattern("^\\s*=\\s*[Ss][Uu][Mm]\\s+([a-zA-Z]+)([1-9][0-9]*)\\s*\\:\\s*([a-zA-Z]+)([1-9][0-9]*)\\s*$");
|
|
|
|
match = pattern_re.match(qText);
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
// first argument
|
|
|
|
QString column_label = match.captured(1);
|
|
|
|
QString row_label = match.captured(2);
|
|
|
|
int column = columnNumberFromName(column_label);
|
|
|
|
int row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_1 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// second argument
|
|
|
|
column_label = match.captured(3);
|
|
|
|
row_label = match.captured(4);
|
|
|
|
column = columnNumberFromName(column_label);
|
|
|
|
row = rowNumberFromName(row_label);
|
|
|
|
const int cell_id_2 = m_dataModel.createId(SpreadKey{row, column});
|
|
|
|
// create formula
|
|
|
|
return Formula::create(Formula::Operator::Sum, cell_id_1, cell_id_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Formula{};
|
|
|
|
}
|
|
|
|
|
|
|
|
QString SpreadModel::formulaValueText(const Formula &formula)
|
|
|
|
{
|
|
|
|
switch (formula.getOperator()) {
|
|
|
|
case Formula::Operator::Assign: {
|
|
|
|
const QVariant value = m_dataModel.getData(formula.firstOperandId(), spread::Role::Display);
|
|
|
|
return value.isNull() ? QString{} : value.toString();
|
|
|
|
}
|
|
|
|
case Formula::Operator::Add: {
|
|
|
|
const QVariant value_1 = m_dataModel.getData(formula.firstOperandId(), spread::Role::Display);
|
|
|
|
const QVariant value_2 = m_dataModel.getData(formula.secondOperandId(), spread::Role::Display);
|
|
|
|
if (value_1.isNull() && value_2.isNull())
|
|
|
|
return "0";
|
|
|
|
// check int values
|
|
|
|
bool is_int_1 = true;
|
|
|
|
bool is_int_2 = true;
|
|
|
|
const int int_1 = value_1.isNull() ? 0 : value_1.toInt(&is_int_1);
|
|
|
|
const int int_2 = value_2.isNull() ? 0 : value_2.toInt(&is_int_2);
|
|
|
|
if (is_int_1 && is_int_2)
|
|
|
|
return QString::number(int_1 + int_2);
|
|
|
|
// check double values
|
|
|
|
bool is_double_1 = true;
|
|
|
|
bool is_double_2 = true;
|
|
|
|
const double double_1 = value_1.isNull() ? 0 : value_1.toDouble(&is_double_1);
|
|
|
|
const double double_2 = value_2.isNull() ? 0 : value_2.toDouble(&is_double_2);
|
|
|
|
if (is_double_1 && is_double_2)
|
|
|
|
return QString::number(double_1 + double_2);
|
|
|
|
return "#ERROR!";
|
|
|
|
}
|
|
|
|
case Formula::Operator::Sub: {
|
|
|
|
const QVariant value_1 = m_dataModel.getData(formula.firstOperandId(), spread::Role::Display);
|
|
|
|
const QVariant value_2 = m_dataModel.getData(formula.secondOperandId(), spread::Role::Display);
|
|
|
|
if (value_1.isNull() && value_2.isNull())
|
|
|
|
return "0";
|
|
|
|
// check int values
|
|
|
|
bool is_int_1 = true;
|
|
|
|
bool is_int_2 = true;
|
|
|
|
const int int_1 = value_1.isNull() ? 0 : value_1.toInt(&is_int_1);
|
|
|
|
const int int_2 = value_2.isNull() ? 0 : value_2.toInt(&is_int_2);
|
|
|
|
if (is_int_1 && is_int_2)
|
|
|
|
return QString::number(int_1 - int_2);
|
|
|
|
// check double values
|
|
|
|
bool is_double_1 = true;
|
|
|
|
bool is_double_2 = true;
|
|
|
|
const double double_1 = value_1.isNull() ? 0 : value_1.toDouble(&is_double_1);
|
|
|
|
const double double_2 = value_2.isNull() ? 0 : value_2.toDouble(&is_double_2);
|
|
|
|
if (is_double_1 && is_double_2)
|
|
|
|
return QString::number(double_1 - double_2);
|
|
|
|
return "#ERROR!";
|
|
|
|
}
|
|
|
|
case Formula::Operator::Mul: {
|
|
|
|
const QVariant value_1 = m_dataModel.getData(formula.firstOperandId(), spread::Role::Display);
|
|
|
|
const QVariant value_2 = m_dataModel.getData(formula.secondOperandId(), spread::Role::Display);
|
|
|
|
if (value_1.isNull() || value_2.isNull())
|
|
|
|
return "0";
|
|
|
|
// check int values
|
|
|
|
bool is_int_1 = true;
|
|
|
|
bool is_int_2 = true;
|
|
|
|
const int int_1 = value_1.toInt(&is_int_1);
|
|
|
|
const int int_2 = value_2.toInt(&is_int_2);
|
|
|
|
if (is_int_1 && is_int_2)
|
|
|
|
return QString::number(int_1 * int_2);
|
|
|
|
// check double values
|
|
|
|
bool is_double_1 = true;
|
|
|
|
bool is_double_2 = true;
|
|
|
|
const double double_1 = value_1.toDouble(&is_double_1);
|
|
|
|
const double double_2 = value_2.toDouble(&is_double_2);
|
|
|
|
if (is_double_1 && is_double_2)
|
|
|
|
return QString::number(double_1 * double_2);
|
|
|
|
return "#ERROR!";
|
|
|
|
}
|
|
|
|
case Formula::Operator::Div: {
|
|
|
|
const QVariant value_1 = m_dataModel.getData(formula.firstOperandId(), spread::Role::Display);
|
|
|
|
const QVariant value_2 = m_dataModel.getData(formula.secondOperandId(), spread::Role::Display);
|
|
|
|
if (value_1.isNull() && value_2.isNull())
|
|
|
|
return "#ERROR!";
|
|
|
|
// check int values
|
|
|
|
bool is_int_1 = true;
|
|
|
|
bool is_int_2 = true;
|
|
|
|
const int int_1 = value_1.isNull() ? 0 : value_1.toInt(&is_int_1);
|
|
|
|
const int int_2 = value_2.isNull() ? 0 : value_2.toInt(&is_int_2);
|
|
|
|
if (is_int_1 && is_int_2) {
|
|
|
|
if (int_2 == 0)
|
|
|
|
return "#ERROR!";
|
|
|
|
return QString::number(int_1 / int_2);
|
|
|
|
}
|
|
|
|
// check double values
|
|
|
|
bool is_double_1 = true;
|
|
|
|
bool is_double_2 = true;
|
|
|
|
const double double_1 = value_1.isNull() ? 0 : value_1.toDouble(&is_double_1);
|
|
|
|
const double double_2 = value_2.isNull() ? 0 : value_2.toDouble(&is_double_2);
|
|
|
|
if (is_double_1 && is_double_2) {
|
|
|
|
if (double_2 == 0)
|
|
|
|
return "#ERROR!";
|
|
|
|
return QString::number(double_1 / double_2);
|
|
|
|
}
|
|
|
|
return "#ERROR!";
|
|
|
|
}
|
|
|
|
case Formula::Operator::Sum: {
|
|
|
|
SpreadKey top_left = m_dataModel.getKey(formula.firstOperandId());
|
|
|
|
SpreadKey bottom_right = m_dataModel.getKey(formula.secondOperandId());
|
|
|
|
top_left.first = getViewRow(top_left.first);
|
|
|
|
top_left.second = getViewColumn(top_left.second);
|
|
|
|
bottom_right.first = getViewRow(bottom_right.first);
|
|
|
|
bottom_right.second = getViewColumn(bottom_right.second);
|
|
|
|
if (bottom_right.first < top_left.first)
|
|
|
|
std::swap(top_left.first, bottom_right.first);
|
|
|
|
if (bottom_right.second < top_left.second)
|
|
|
|
std::swap(top_left.second, bottom_right.second);
|
|
|
|
double sum = 0;
|
|
|
|
for (int row = top_left.first; row <= bottom_right.first; ++row) {
|
|
|
|
for (int column = top_left.second; column <= bottom_right.second; ++column) {
|
|
|
|
const int model_row = getModelRow(row);
|
|
|
|
const int model_column = getModelColumn(column);
|
|
|
|
const SpreadKey key {model_row, model_column};
|
|
|
|
const QVariant value = m_dataModel.getData(key, spread::Role::Display);
|
|
|
|
if (value.isNull())
|
|
|
|
continue;
|
|
|
|
bool is_double = false;
|
|
|
|
const double d = value.toDouble(&is_double);
|
|
|
|
if (is_double)
|
|
|
|
sum += d;
|
|
|
|
else
|
|
|
|
return "#ERROR!";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QString::number(sum);
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return QString{};
|
|
|
|
}
|
|
|
|
|
|
|
|
int SpreadModel::getViewColumn(int modelColumn) const
|
|
|
|
{
|
|
|
|
auto it = m_viewColumns.find(modelColumn);
|
|
|
|
return (it != m_viewColumns.end()) ? it.value() : modelColumn;
|
|
|
|
}
|
|
|
|
|
|
|
|
int SpreadModel::getModelColumn(int viewColumn) const
|
|
|
|
{
|
|
|
|
auto find_view_column = [viewColumn](const auto &item) {
|
|
|
|
return item == viewColumn;
|
|
|
|
};
|
|
|
|
auto it = std::find_if(m_viewColumns.begin(), m_viewColumns.end(), find_view_column);
|
|
|
|
return it != m_viewColumns.end() ? it.key() : viewColumn;
|
|
|
|
}
|
|
|
|
|
|
|
|
int SpreadModel::getViewRow(int modelRow) const
|
|
|
|
{
|
|
|
|
auto it = m_viewRows.find(modelRow);
|
|
|
|
return (it != m_viewRows.end()) ? it.value() : modelRow;
|
|
|
|
}
|
|
|
|
|
|
|
|
int SpreadModel::getModelRow(int viewRow) const
|
|
|
|
{
|
|
|
|
auto find_view_row = [viewRow](const auto &item){
|
|
|
|
return item == viewRow;
|
|
|
|
};
|
|
|
|
auto it = std::find_if(m_viewRows.begin(), m_viewRows.end(), find_view_row);
|
|
|
|
return (it != m_viewRows.end()) ? it.key() : viewRow;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadSelectionModel::toggleColumn(int column)
|
|
|
|
{
|
|
|
|
isColumnSelected(column) ? deselectColumn(column) : selectColumn(column, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadSelectionModel::deselectColumn(int column)
|
|
|
|
{
|
|
|
|
const QAbstractItemModel *model = this->model();
|
|
|
|
const QModelIndex first = model->index(0, column);
|
|
|
|
const QModelIndex last = model->index(model->rowCount() - 1, column);
|
|
|
|
QModelIndexList selectedRows = this->selectedRows(column);
|
|
|
|
if (selectedRows.empty()) {
|
|
|
|
select(QItemSelection{first, last}, SelectionFlag::Deselect);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto topToBottom = [](const QModelIndex &lhs, const QModelIndex &rhs) -> bool
|
|
|
|
{
|
|
|
|
return lhs.row() < rhs.row();
|
|
|
|
};
|
|
|
|
std::sort(selectedRows.begin(), selectedRows.end(), topToBottom);
|
|
|
|
|
|
|
|
QModelIndex index = first;
|
|
|
|
for (const QModelIndex &selectedRow : selectedRows) {
|
|
|
|
if (index.row() < selectedRow.row())
|
|
|
|
select(QItemSelection{index, model->index(selectedRow.row() - 1, column)},
|
|
|
|
SelectionFlag::Deselect);
|
|
|
|
index = model->index(selectedRow.row() + 1, column);
|
|
|
|
}
|
|
|
|
if (index.row() <= last.row())
|
|
|
|
select(QItemSelection{index, last}, SelectionFlag::Deselect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadSelectionModel::selectColumn(int column, bool clear)
|
|
|
|
{
|
|
|
|
if (clear)
|
|
|
|
this->clear();
|
|
|
|
const QAbstractItemModel *model = this->model();
|
|
|
|
const QModelIndex first = model->index(0, column);
|
|
|
|
const QModelIndex last = model->index(model->rowCount() - 1, column);
|
|
|
|
select(QItemSelection{first, last}, SelectionFlag::Select);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadSelectionModel::toggleRow(int row)
|
|
|
|
{
|
|
|
|
isRowSelected(row) ? deselectRow(row) : selectRow(row, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadSelectionModel::deselectRow(int row)
|
|
|
|
{
|
|
|
|
const QAbstractItemModel *model = this->model();
|
|
|
|
const QModelIndex first = model->index(row, 0);
|
|
|
|
const QModelIndex last = model->index(row, model->columnCount() - 1);
|
|
|
|
QModelIndexList selectedColumns = this->selectedColumns(row);
|
|
|
|
if (selectedColumns.empty()) {
|
|
|
|
select(QItemSelection{first, last}, SelectionFlag::Deselect);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto leftToRight = [](const QModelIndex &lhs, const QModelIndex &rhs) -> bool
|
|
|
|
{
|
|
|
|
return lhs.column() < rhs.column();
|
|
|
|
};
|
|
|
|
std::sort(selectedColumns.begin(), selectedColumns.end(), leftToRight);
|
|
|
|
|
|
|
|
QModelIndex index = first;
|
|
|
|
for (const QModelIndex &selectedColumn : selectedColumns) {
|
|
|
|
if (index.column() < selectedColumn.column())
|
|
|
|
select(QItemSelection{index, model->index(row, selectedColumn.column() - 1)},
|
|
|
|
SelectionFlag::Deselect);
|
|
|
|
index = model->index(row, selectedColumn.column() + 1);
|
|
|
|
}
|
|
|
|
if (index.column() <= last.column())
|
|
|
|
select(QItemSelection{index, last}, SelectionFlag::Deselect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadSelectionModel::selectRow(int row, bool clear)
|
|
|
|
{
|
|
|
|
if (clear)
|
|
|
|
this->clear();
|
|
|
|
const QAbstractItemModel *model = this->model();
|
|
|
|
const QModelIndex first = model->index(row, 0);
|
|
|
|
const QModelIndex last = model->index(row, model->columnCount() - 1);
|
|
|
|
select(QItemSelection{first, last}, SelectionFlag::Select);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpreadSelectionModel::setBehavior(Behavior behavior)
|
|
|
|
{
|
|
|
|
if (behavior == m_behavior)
|
|
|
|
return;
|
|
|
|
m_behavior = behavior;
|
|
|
|
emit behaviorChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeaderSelectionModel::setCurrent(int current)
|
|
|
|
{
|
|
|
|
switch (m_orientation) {
|
|
|
|
case Qt::Horizontal:
|
|
|
|
QItemSelectionModel::setCurrentIndex(model()->index(0, current), SelectionFlag::Current);
|
|
|
|
break;
|
|
|
|
case Qt::Vertical:
|
|
|
|
QItemSelectionModel::setCurrentIndex(model()->index(current, 0), SelectionFlag::Current);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeaderSelectionModel::setSelectionModel(SpreadSelectionModel *selectionModel)
|
|
|
|
{
|
|
|
|
if (selectionModel == m_selectionModel)
|
|
|
|
return;
|
|
|
|
if (m_selectionModel)
|
|
|
|
disconnect(m_selectionModel);
|
|
|
|
m_selectionModel = selectionModel;
|
|
|
|
if (m_selectionModel)
|
|
|
|
connect(m_selectionModel, &SpreadSelectionModel::selectionChanged, this,
|
|
|
|
&HeaderSelectionModel::onSelectionChanged);
|
|
|
|
emit selectionModelChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeaderSelectionModel::setOrientation(Qt::Orientation orientation)
|
|
|
|
{
|
|
|
|
if (orientation == m_orientation)
|
|
|
|
return;
|
|
|
|
m_orientation = orientation;
|
|
|
|
emit orientationChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeaderSelectionModel::onSelectionChanged(const QItemSelection &selected,
|
|
|
|
const QItemSelection &deselected)
|
|
|
|
{
|
|
|
|
const QAbstractItemModel *model = this->model();
|
|
|
|
for (const QModelIndex &index : selected.indexes()) {
|
|
|
|
switch (m_orientation) {
|
|
|
|
case Qt::Horizontal:
|
|
|
|
if (m_selectionModel->isColumnSelected(index.column()))
|
|
|
|
select(model->index(0, index.column()), SelectionFlag::Select);
|
|
|
|
break;
|
|
|
|
case Qt::Vertical:
|
|
|
|
if (m_selectionModel->isRowSelected(index.row()))
|
|
|
|
select(model->index(index.row(), 0), SelectionFlag::Select);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const QModelIndex &index : deselected.indexes()) {
|
|
|
|
switch (m_orientation) {
|
|
|
|
case Qt::Horizontal:
|
|
|
|
if (!m_selectionModel->isColumnSelected(index.column()))
|
|
|
|
select(model->index(0, index.column()), SelectionFlag::Deselect);
|
|
|
|
break;
|
|
|
|
case Qt::Vertical:
|
|
|
|
if (!m_selectionModel->isRowSelected(index.row()))
|
|
|
|
select(model->index(index.row(), 0), SelectionFlag::Deselect);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|