Revision control
++ // 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;
++ }
++ }
++ }