| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | #include <QFont> |
| | #include <QLocale> |
| |
|
| |
|
| | #include <App/Document.h> |
| | #include <Base/Interpreter.h> |
| | #include <Base/Tools.h> |
| | #include <Base/UnitsApi.h> |
| | #include <Gui/Application.h> |
| | #include <Gui/Command.h> |
| | #include <Mod/Spreadsheet/App/Sheet.h> |
| |
|
| | #include "SheetModel.h" |
| |
|
| |
|
| | using namespace SpreadsheetGui; |
| | using namespace Spreadsheet; |
| | using namespace App; |
| | namespace sp = std::placeholders; |
| |
|
| | SheetModel::SheetModel(Sheet* _sheet, QObject* parent) |
| | : QAbstractTableModel(parent) |
| | , sheet(_sheet) |
| | { |
| | |
| | cellUpdatedConnection = sheet->cellUpdated.connect( |
| | std::bind(&SheetModel::cellUpdated, this, sp::_1) |
| | ); |
| | rangeUpdatedConnection = sheet->rangeUpdated.connect( |
| | std::bind(&SheetModel::rangeUpdated, this, sp::_1) |
| | ); |
| | |
| |
|
| | ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( |
| | "User parameter:BaseApp/Preferences/Mod/Spreadsheet" |
| | ); |
| | aliasBgColor = QColor( |
| | QString::fromStdString(hGrp->GetASCII("AliasedCellBackgroundColor", "#feff9e")) |
| | ); |
| | textFgColor = QColor(QString::fromStdString(hGrp->GetASCII("TextColor", "#000000"))); |
| | positiveFgColor = QColor(QString::fromStdString(hGrp->GetASCII("PositiveNumberColor", "#000000"))); |
| | negativeFgColor = QColor(QString::fromStdString(hGrp->GetASCII("NegativeNumberColor", "#000000"))); |
| |
|
| |
|
| | const QStringList alphabet {QStringLiteral("A"), QStringLiteral("B"), QStringLiteral("C"), |
| | QStringLiteral("D"), QStringLiteral("E"), QStringLiteral("F"), |
| | QStringLiteral("G"), QStringLiteral("H"), QStringLiteral("I"), |
| | QStringLiteral("J"), QStringLiteral("K"), QStringLiteral("L"), |
| | QStringLiteral("M"), QStringLiteral("N"), QStringLiteral("O"), |
| | QStringLiteral("P"), QStringLiteral("Q"), QStringLiteral("R"), |
| | QStringLiteral("S"), QStringLiteral("T"), QStringLiteral("U"), |
| | QStringLiteral("V"), QStringLiteral("W"), QStringLiteral("X"), |
| | QStringLiteral("Y"), QStringLiteral("Z")}; |
| |
|
| | for (const QString& letter : alphabet) { |
| | columnLabels << letter; |
| | } |
| |
|
| | for (const QString& left : alphabet) { |
| | for (const QString& right : alphabet) { |
| | columnLabels << left + right; |
| | } |
| | } |
| |
|
| | for (int i = 1; i <= maxRowCount; i++) { |
| | rowLabels << QString::number(i); |
| | } |
| | } |
| |
|
| | SheetModel::~SheetModel() |
| | { |
| | cellUpdatedConnection.disconnect(); |
| | rangeUpdatedConnection.disconnect(); |
| | } |
| |
|
| | int SheetModel::rowCount(const QModelIndex& parent) const |
| | { |
| | Q_UNUSED(parent); |
| | return maxRowCount; |
| | } |
| |
|
| | int SheetModel::columnCount(const QModelIndex& parent) const |
| | { |
| | Q_UNUSED(parent); |
| | return maxColumnCount; |
| | } |
| |
|
| | namespace |
| | { |
| | QVariant formatCellDisplay(QString value, const Cell* cell) |
| | { |
| | std::string alias; |
| | static auto hGrpSpreadsheet = App::GetApplication().GetUserParameter().GetGroup( |
| | "BaseApp/Preferences/Mod/Spreadsheet" |
| | ); |
| | if (cell->getAlias(alias) && hGrpSpreadsheet->GetBool("showAliasName", false)) { |
| | QString formatStr = QString::fromStdString( |
| | hGrpSpreadsheet->GetASCII("DisplayAliasFormatString", "%V = %A") |
| | ); |
| | if (formatStr.contains(QLatin1String("%V")) || formatStr.contains(QLatin1String("%A"))) { |
| | formatStr.replace(QLatin1String("%A"), QString::fromStdString(alias)); |
| | formatStr.replace(QLatin1String("%V"), value); |
| | return QVariant(formatStr); |
| | } |
| | } |
| | return QVariant(value); |
| | } |
| | } |
| |
|
| | QVariant SheetModel::data(const QModelIndex& index, int role) const |
| | { |
| | static const Cell* emptyCell = new Cell(CellAddress(0, 0), nullptr); |
| | int row = index.row(); |
| | int col = index.column(); |
| | const Cell* cell = sheet->getCell(CellAddress(row, col)); |
| |
|
| | if (!cell) { |
| | cell = emptyCell; |
| | } |
| |
|
| | |
| | #ifdef DEBUG_DEPS |
| | if (role == Qt::ToolTipRole) { |
| | QString v; |
| |
|
| | std::set<std::string> deps = sheet->dependsOn(CellAddress(row, col)); |
| | std::set<std::string> provides; |
| |
|
| | sheet->providesTo(CellAddress(row, col), provides); |
| |
|
| | if (deps.size() > 0) { |
| | v += QStringLiteral("Depends on:"); |
| | for (std::set<std::string>::const_iterator i = deps.begin(); i != deps.end(); ++i) { |
| | v += QStringLiteral("\n\t") + Tools::fromStdString(*i); |
| | } |
| | v += QStringLiteral("\n"); |
| | } |
| | if (provides.size() > 0) { |
| | v += QStringLiteral("Used by:"); |
| | for (std::set<std::string>::const_iterator i = provides.begin(); i != provides.end(); |
| | ++i) { |
| | v += QStringLiteral("\n\t") + Tools::fromStdString(*i); |
| | } |
| | v += QStringLiteral("\n"); |
| | } |
| | return QVariant(v); |
| | } |
| | #else |
| | if (!cell->hasException() && role == Qt::ToolTipRole) { |
| | std::string alias; |
| | if (cell->getAlias(alias)) { |
| | return QVariant(QString::fromStdString(alias)); |
| | } |
| | } |
| | #endif |
| |
|
| | if (cell->hasException()) { |
| | switch (role) { |
| | case Qt::ToolTipRole: { |
| | QString txt(QString::fromStdString(cell->getException()).toHtmlEscaped()); |
| | return QVariant(QStringLiteral("<pre>%1</pre>").arg(txt)); |
| | } |
| | case Qt::DisplayRole: { |
| | #ifdef DEBUG_DEPS |
| | return QVariant::fromValue( |
| | QStringLiteral("#ERR: %1").arg(Tools::fromStdString(cell->getException())) |
| | ); |
| | #else |
| | std::string str; |
| | if (cell->getStringContent(str)) { |
| | return QVariant::fromValue(QString::fromUtf8(str.c_str())); |
| | } |
| | return QVariant::fromValue(QStringLiteral("#ERR")); |
| | #endif |
| | } |
| | case Qt::ForegroundRole: |
| | return QVariant::fromValue(QColor(255.0, 0, 0)); |
| | case Qt::TextAlignmentRole: |
| | return QVariant(Qt::AlignVCenter | Qt::AlignLeft); |
| | default: |
| | break; |
| | } |
| | } |
| |
|
| | |
| | if (role == Qt::EditRole || role == Qt::StatusTipRole) { |
| | std::string str; |
| |
|
| | if (cell->getStringContent(str)) { |
| | return QVariant(QString::fromUtf8(str.c_str())); |
| | } |
| | return {}; |
| | } |
| |
|
| | |
| | std::string address = CellAddress(row, col).toString(); |
| | Property* prop = sheet->getPropertyByName(address.c_str()); |
| |
|
| | if (role == Qt::BackgroundRole) { |
| | Base::Color color; |
| |
|
| | if (cell->getBackground(color)) { |
| | return QVariant::fromValue( |
| | QColor(255.0 * color.r, 255.0 * color.g, 255.0 * color.b, 255.0 * color.a) |
| | ); |
| | } |
| | else { |
| | std::string alias; |
| | if (cell->getAlias(alias)) { |
| | return QVariant::fromValue(aliasBgColor); |
| | } |
| | return {}; |
| | } |
| | } |
| |
|
| | int qtAlignment = 0; |
| |
|
| | int alignment; |
| | cell->getAlignment(alignment); |
| |
|
| | if (alignment & Cell::ALIGNMENT_LEFT) { |
| | qtAlignment |= Qt::AlignLeft; |
| | } |
| | if (alignment & Cell::ALIGNMENT_HCENTER) { |
| | qtAlignment |= Qt::AlignHCenter; |
| | } |
| | if (alignment & Cell::ALIGNMENT_RIGHT) { |
| | qtAlignment |= Qt::AlignRight; |
| | } |
| | if (alignment & Cell::ALIGNMENT_TOP) { |
| | qtAlignment |= Qt::AlignTop; |
| | } |
| | if (alignment & Cell::ALIGNMENT_VCENTER) { |
| | qtAlignment |= Qt::AlignVCenter; |
| | } |
| | if (alignment & Cell::ALIGNMENT_BOTTOM) { |
| | qtAlignment |= Qt::AlignBottom; |
| | } |
| |
|
| | std::set<std::string> style; |
| | if (role == Qt::FontRole && cell->getStyle(style)) { |
| | QFont f; |
| |
|
| | for (const auto& i : style) { |
| | if (i == "bold") { |
| | f.setBold(true); |
| | } |
| | else if (i == "italic") { |
| | f.setItalic(true); |
| | } |
| | else if (i == "underline") { |
| | f.setUnderline(true); |
| | } |
| | } |
| |
|
| | return QVariant::fromValue(f); |
| | } |
| |
|
| | auto dirtyCells = sheet->getCells()->getDirty(); |
| | auto dirty = (dirtyCells.find(CellAddress(row, col)) != dirtyCells.end()); |
| |
|
| | if (!prop || dirty) { |
| | switch (role) { |
| | case Qt::ForegroundRole: { |
| | return QColor( |
| | 0, |
| | 0, |
| | 255.0 |
| | ); |
| | } |
| | case Qt::TextAlignmentRole: { |
| | qtAlignment = Qt::AlignHCenter | Qt::AlignVCenter; |
| | return QVariant::fromValue(qtAlignment); |
| | } |
| | case Qt::DisplayRole: |
| | if (cell->getExpression()) { |
| | std::string str; |
| | if (cell->getStringContent(str)) { |
| | if (!str.empty() && str[0] == '=') { |
| | |
| | |
| | return QVariant(QLatin1String("#PENDING")); |
| | } |
| | else { |
| | |
| | |
| | |
| | return QVariant(QString::fromUtf8(str.c_str())); |
| | } |
| | } |
| | } |
| | return {}; |
| | default: |
| | return {}; |
| | } |
| | } |
| | else if (prop->isDerivedFrom<App::PropertyString>()) { |
| | |
| | const App::PropertyString* stringProp = static_cast<const App::PropertyString*>(prop); |
| |
|
| | switch (role) { |
| | case Qt::ForegroundRole: { |
| | Base::Color color; |
| |
|
| | if (cell->getForeground(color)) { |
| | return QVariant::fromValue( |
| | QColor(255.0 * color.r, 255.0 * color.g, 255.0 * color.b, 255.0 * color.a) |
| | ); |
| | } |
| | else { |
| | return QVariant(QColor(textFgColor)); |
| | } |
| | } |
| | case Qt::DisplayRole: { |
| | QString v = QString::fromUtf8(stringProp->getValue()); |
| | return formatCellDisplay(v, cell); |
| | } |
| | case Qt::TextAlignmentRole: { |
| | if (alignment & Cell::ALIGNMENT_HIMPLIED) { |
| | qtAlignment &= ~(Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight); |
| | qtAlignment |= Qt::AlignLeft; |
| | } |
| | if (alignment & Cell::ALIGNMENT_VIMPLIED) { |
| | qtAlignment &= ~(Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom); |
| | qtAlignment |= Qt::AlignVCenter; |
| | } |
| | return QVariant::fromValue(qtAlignment); |
| | } |
| | default: |
| | return {}; |
| | } |
| | } |
| | else if (prop->isDerivedFrom<App::PropertyQuantity>()) { |
| | |
| | const App::PropertyQuantity* floatProp = static_cast<const App::PropertyQuantity*>(prop); |
| |
|
| | switch (role) { |
| | case Qt::ForegroundRole: { |
| | Base::Color color; |
| |
|
| | if (cell->getForeground(color)) { |
| | return QVariant::fromValue( |
| | QColor(255.0 * color.r, 255.0 * color.g, 255.0 * color.b, 255.0 * color.a) |
| | ); |
| | } |
| | else { |
| | if (floatProp->getValue() < 0) { |
| | return QVariant::fromValue(QColor(negativeFgColor)); |
| | } |
| | else { |
| | return QVariant::fromValue(QColor(positiveFgColor)); |
| | } |
| | } |
| | } |
| | case Qt::TextAlignmentRole: { |
| | if (alignment & Cell::ALIGNMENT_HIMPLIED) { |
| | qtAlignment &= ~(Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight); |
| | qtAlignment |= Qt::AlignRight; |
| | } |
| | if (alignment & Cell::ALIGNMENT_VIMPLIED) { |
| | qtAlignment &= ~(Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom); |
| | qtAlignment |= Qt::AlignVCenter; |
| | } |
| | return QVariant::fromValue(qtAlignment); |
| | } |
| | case Qt::DisplayRole: { |
| | QString v; |
| | const Base::Unit& computedUnit = floatProp->getUnit(); |
| | DisplayUnit displayUnit; |
| |
|
| | |
| | if (cell->getDisplayUnit(displayUnit)) { |
| | if (computedUnit == Base::Unit::One || computedUnit == displayUnit.unit) { |
| | QString number = QLocale().toString( |
| | floatProp->getValue() / displayUnit.scaler, |
| | 'f', |
| | Base::UnitsApi::getDecimals() |
| | ); |
| | |
| | |
| | v = number + QString::fromStdString(" " + displayUnit.stringRep); |
| | } |
| | else { |
| | v = QStringLiteral("#ERR: unit"); |
| | } |
| | } |
| | else { |
| |
|
| | |
| | |
| | Base::Quantity value = floatProp->getQuantityValue(); |
| | v = QString::fromStdString(value.getUserString()); |
| | } |
| | return formatCellDisplay(v, cell); |
| | } |
| | default: |
| | return {}; |
| | } |
| | } |
| | else if (prop->isDerivedFrom<App::PropertyFloat>() |
| | || prop->isDerivedFrom<App::PropertyInteger>()) { |
| | |
| | double d {}; |
| | long l {}; |
| | bool isInteger = false; |
| | if (prop->isDerivedFrom<App::PropertyFloat>()) { |
| | d = static_cast<const App::PropertyFloat*>(prop)->getValue(); |
| | } |
| | else { |
| | isInteger = true; |
| | l = static_cast<const App::PropertyInteger*>(prop)->getValue(); |
| | d = l; |
| | } |
| |
|
| | switch (role) { |
| | case Qt::ForegroundRole: { |
| | Base::Color color; |
| |
|
| | if (cell->getForeground(color)) { |
| | return QVariant::fromValue( |
| | QColor(255.0 * color.r, 255.0 * color.g, 255.0 * color.b, 255.0 * color.a) |
| | ); |
| | } |
| | else { |
| | if (d < 0) { |
| | return QVariant::fromValue(QColor(negativeFgColor)); |
| | } |
| | else { |
| | return QVariant::fromValue(QColor(positiveFgColor)); |
| | } |
| | } |
| | } |
| | case Qt::TextAlignmentRole: { |
| | if (alignment & Cell::ALIGNMENT_HIMPLIED) { |
| | qtAlignment &= ~(Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight); |
| | qtAlignment |= Qt::AlignRight; |
| | } |
| | if (alignment & Cell::ALIGNMENT_VIMPLIED) { |
| | qtAlignment &= ~(Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom); |
| | qtAlignment |= Qt::AlignVCenter; |
| | } |
| | return QVariant::fromValue(qtAlignment); |
| | } |
| | case Qt::DisplayRole: { |
| | QString v; |
| | DisplayUnit displayUnit; |
| |
|
| | |
| | if (cell->getDisplayUnit(displayUnit)) { |
| | QString number = QLocale().toString( |
| | d / displayUnit.scaler, |
| | 'f', |
| | Base::UnitsApi::getDecimals() |
| | ); |
| | |
| | v = number + QString::fromStdString(" " + displayUnit.stringRep); |
| | } |
| | else if (!isInteger) { |
| | v = QLocale::system().toString(d, 'f', Base::UnitsApi::getDecimals()); |
| | |
| | } |
| | else { |
| | v = QString::number(l); |
| | } |
| | return formatCellDisplay(v, cell); |
| | } |
| | default: |
| | return {}; |
| | } |
| | } |
| | else if (prop->isDerivedFrom<App::PropertyPythonObject>()) { |
| | auto pyProp = static_cast<const App::PropertyPythonObject*>(prop); |
| |
|
| | switch (role) { |
| | case Qt::ForegroundRole: { |
| | Base::Color color; |
| |
|
| | if (cell->getForeground(color)) { |
| | return QVariant::fromValue( |
| | QColor(255.0 * color.r, 255.0 * color.g, 255.0 * color.b, 255.0 * color.a) |
| | ); |
| | } |
| | else { |
| | return QVariant(QColor(textFgColor)); |
| | } |
| | } |
| | case Qt::TextAlignmentRole: { |
| | if (alignment & Cell::ALIGNMENT_HIMPLIED) { |
| | qtAlignment &= ~(Qt::AlignLeft | Qt::AlignHCenter | Qt::AlignRight); |
| | qtAlignment |= Qt::AlignHCenter; |
| | } |
| | if (alignment & Cell::ALIGNMENT_VIMPLIED) { |
| | qtAlignment &= ~(Qt::AlignTop | Qt::AlignVCenter | Qt::AlignBottom); |
| | qtAlignment |= Qt::AlignVCenter; |
| | } |
| | return QVariant::fromValue(qtAlignment); |
| | } |
| | case Qt::DisplayRole: { |
| | Base::PyGILStateLocker lock; |
| | std::string value; |
| | try { |
| | value = pyProp->getValue().as_string(); |
| | } |
| | catch (Py::Exception&) { |
| | Base::PyException e; |
| | value = "#ERR: "; |
| | value += e.what(); |
| | } |
| | catch (Base::Exception& e) { |
| | value = "#ERR: "; |
| | value += e.what(); |
| | } |
| | catch (...) { |
| | value = "#ERR: unknown exception"; |
| | } |
| | QString v = QString::fromUtf8(value.c_str()); |
| | return formatCellDisplay(v, cell); |
| | } |
| | default: |
| | return {}; |
| | } |
| | } |
| |
|
| | return {}; |
| | } |
| |
|
| | QVariant SheetModel::headerData(int section, Qt::Orientation orientation, int role) const |
| | { |
| | if (role == Qt::SizeHintRole) { |
| | const int width |
| | = (orientation == Qt::Horizontal ? sheet->getColumnWidth(section) |
| | : PropertyColumnWidths::defaultHeaderWidth); |
| | const int height |
| | = (orientation == Qt::Horizontal ? PropertyRowHeights::defaultHeight |
| | : sheet->getRowHeight(section)); |
| | return QSize {width, height}; |
| | } |
| | if (role == Qt::DisplayRole) { |
| | return (orientation == Qt::Horizontal ? columnLabels.at(section) : rowLabels.at(section)); |
| | } |
| | return {}; |
| | } |
| |
|
| | void SheetModel::setCellData(QModelIndex index, QString str) |
| | { |
| | try { |
| | CellAddress address(index.row(), index.column()); |
| | Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Edit cell")); |
| | |
| | |
| | |
| |
|
| | sheet->setContent(address, str.toUtf8().constData()); |
| | Gui::Command::commitCommand(); |
| | Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); |
| | } |
| | catch (const Base::Exception& e) { |
| | e.reportException(); |
| | Gui::Command::abortCommand(); |
| | } |
| | } |
| |
|
| | bool SheetModel::setData(const QModelIndex& index, const QVariant& value, int role) |
| | { |
| | if (role == Qt::DisplayRole) { |
| | |
| | } |
| | else if (role == Qt::EditRole) { |
| | CellAddress address(index.row(), index.column()); |
| |
|
| | QString str = value.toString(); |
| |
|
| | |
| | auto cell = sheet->getCell(address); |
| | if (cell) { |
| | std::string oldContent; |
| | cell->getStringContent(oldContent); |
| | if (str == QString::fromStdString(oldContent)) { |
| | return true; |
| | } |
| | } |
| |
|
| | QMetaObject::invokeMethod( |
| | this, |
| | "setCellData", |
| | Qt::QueuedConnection, |
| | Q_ARG(QModelIndex, index), |
| | Q_ARG(QString, str) |
| | ); |
| | } |
| | return true; |
| | } |
| |
|
| | Qt::ItemFlags SheetModel::flags(const QModelIndex& ) const |
| | { |
| | return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; |
| | } |
| |
|
| | void SheetModel::cellUpdated(CellAddress address) |
| | { |
| | QModelIndex i = index(address.row(), address.col()); |
| |
|
| | Q_EMIT dataChanged(i, i); |
| | } |
| |
|
| | void SheetModel::rangeUpdated(const Range& range) |
| | { |
| | QModelIndex i = index(range.from().row(), range.from().col()); |
| | QModelIndex j = index(range.to().row(), range.to().col()); |
| |
|
| | Q_EMIT dataChanged(i, j); |
| | } |
| |
|
| | #include "moc_SheetModel.cpp" |
| |
|