732 lines
25 KiB
C++
732 lines
25 KiB
C++
// Copyright (C) 2024 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
|
|
#include "spreadformula.h"
|
|
#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;
|
|
}
|
|
|
|
void SpreadModel::update(int row, int column)
|
|
{
|
|
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();
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|