// SPDX-License-Identifier: LGPL-2.1-or-later /**************************************************************************** * Copyright (c) 2019 Zheng, Lei (realthunder) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ****************************************************************************/ #include #include #include #include #include #include #include #include "DlgSheetConf.h" #include "ui_DlgSheetConf.h" using namespace App; using namespace Spreadsheet; using namespace SpreadsheetGui; DlgSheetConf::DlgSheetConf(Sheet* sheet, Range range, QWidget* parent) : QDialog(parent) , sheet(sheet) , ui(new Ui::DlgSheetConf) { ui->setupUi(this); if (range.colCount() == 1) { auto to = range.to(); to.setCol(CellAddress::MAX_COLUMNS - 1); range = Range(range.from(), to); } ui->lineEditStart->setText(QString::fromLatin1(range.from().toString().c_str())); ui->lineEditEnd->setText(QString::fromLatin1(range.to().toString().c_str())); ui->lineEditProp->setDocumentObject(sheet, false); connect(ui->btnDiscard, &QPushButton::clicked, this, &DlgSheetConf::onDiscard); CellAddress from, to; std::string rangeConf; ObjectIdentifier path; auto prop = prepare(from, to, rangeConf, path, true); if (prop) { ui->lineEditProp->setText(QString::fromUtf8(path.toString().c_str())); if (auto group = prop->getGroup()) { ui->lineEditGroup->setText(QString::fromUtf8(group)); } } ui->lineEditStart->setText(QString::fromLatin1(from.toString().c_str())); ui->lineEditEnd->setText(QString::fromLatin1(to.toString().c_str())); } DlgSheetConf::~DlgSheetConf() { delete ui; } App::Property* DlgSheetConf::prepare( CellAddress& from, CellAddress& to, std::string& rangeConf, ObjectIdentifier& path, bool init ) { from = sheet->getCellAddress(ui->lineEditStart->text().trimmed().toLatin1().constData()); to = sheet->getCellAddress(ui->lineEditEnd->text().trimmed().toLatin1().constData()); if (from.col() >= to.col()) { FC_THROWM(Base::RuntimeError, "Invalid cell range"); } // Setup row as parameters, and column as configurations to.setRow(from.row()); CellAddress confFrom(from.row() + 1, from.col()); rangeConf = confFrom.toString(); // rangeConf is supposed to hold the range of string cells, each // holding the name of a configuration. The '|' below indicates a // growing but continuous column, so that we can auto include new // configurations. We'll bind the string list to a // PropertyEnumeration for dynamical switching of the // configuration. rangeConf += ":|"; if (!init) { std::string exprTxt(ui->lineEditProp->text().trimmed().toUtf8().constData()); ExpressionPtr expr; try { expr.reset(App::Expression::parse(sheet, exprTxt)); } catch (Base::Exception& e) { e.reportException(); FC_THROWM(Base::RuntimeError, "Failed to parse expression for property"); } if (expr->hasComponent() || !expr->isDerivedFrom()) { FC_THROWM(Base::RuntimeError, "Invalid property expression: " << expr->toString()); } path = static_cast(expr.get())->getPath(); auto obj = path.getDocumentObject(); if (!obj) { FC_THROWM(Base::RuntimeError, "Invalid object referenced in: " << expr->toString()); } int pseudoType; auto prop = path.getProperty(&pseudoType); if (pseudoType || (prop && (!prop->isDerivedFrom() || !prop->testStatus(App::Property::PropDynamic)))) { FC_THROWM(Base::RuntimeError, "Invalid property referenced in: " << expr->toString()); } return prop; } Cell* cell = sheet->getCell(from); if (cell && cell->getExpression()) { auto expr = cell->getExpression(); if (expr->isDerivedFrom()) { auto fexpr = freecad_cast(cell->getExpression()); if (fexpr && (fexpr->getFunction() == FunctionExpression::HREF || fexpr->getFunction() == FunctionExpression::HIDDENREF) && fexpr->getArgs().size() == 1) { expr = fexpr->getArgs().front(); } } auto vexpr = freecad_cast(expr); if (vexpr) { auto prop = freecad_cast(vexpr->getPath().getProperty()); if (prop) { auto obj = freecad_cast(prop->getContainer()); if (obj && prop->hasName()) { path = ObjectIdentifier(sheet); path.setDocumentObjectName(obj, true); path << ObjectIdentifier::SimpleComponent(prop->getName()); return prop; } } } } return nullptr; } void DlgSheetConf::accept() { bool commandActive = false; try { std::string rangeConf; CellAddress from, to; ObjectIdentifier path; App::Property* prop = prepare(from, to, rangeConf, path, false); Range range(from, to); // check rangeConf, make sure it is a sequence of string only Range r(sheet->getRange(rangeConf.c_str())); do { auto cell = sheet->getCell(*r); if (cell && cell->getExpression()) { ExpressionPtr expr(cell->getExpression()->eval()); if (expr->isDerivedFrom()) { continue; } } FC_THROWM( Base::RuntimeError, "Expects cell " << r.address() << " evaluates to string.\n" << rangeConf << " is supposed to contain a list of configuration names" ); } while (r.next()); std::string exprTxt(ui->lineEditProp->text().trimmed().toUtf8().constData()); App::ExpressionPtr expr(App::Expression::parse(sheet, exprTxt)); if (expr->hasComponent() || !expr->isDerivedFrom()) { FC_THROWM(Base::RuntimeError, "Invalid property expression: " << expr->toString()); } AutoTransaction guard("Setup conf table"); commandActive = true; // unbind any previous binding int count = range.rowCount() * range.colCount(); for (int i = 0; i < count; ++i) { auto r = range; auto binding = sheet->getCellBinding(r); if (!binding) { break; } Gui::cmdAppObjectArgs( sheet, "setExpression('.cells.%s.%s.%s', None)", binding == PropertySheet::BindingNormal ? "Bind" : "BindHiddenRef", r.from().toString(), r.to().toString() ); } auto obj = path.getDocumentObject(); if (!obj) { FC_THROWM(Base::RuntimeError, "Object not found"); } // Add a dynamic PropertyEnumeration for user to switch the configuration std::string propName = path.getPropertyName(); QString groupName = ui->lineEditGroup->text().trimmed(); if (!prop) { prop = obj->addDynamicProperty( "App::PropertyEnumeration", propName.c_str(), groupName.toUtf8().constData() ); } else if (groupName.size()) { obj->changeDynamicProperty(prop, groupName.toUtf8().constData(), nullptr); } prop->setStatus(App::Property::CopyOnChange, true); // Bind the enumeration items to the column of configuration names Gui::cmdAppObjectArgs( obj, "setExpression('%s.Enum', '%s.cells[<<%s>>]')", propName, sheet->getFullName(), rangeConf ); Gui::cmdAppObjectArgs(obj, "recompute()"); // Bind the first cell to string value of the PropertyEnumeration. We // could have just bind the entire row as below, but binding the first // cell separately using a simpler expression can make it easy for us // to extract the name of the PropertyEnumeration for editing or unsetup. Gui::cmdAppObjectArgs( sheet, "set('%s', '=hiddenref(%s.String)')", from.toString(CellAddress::Cell::ShowRowColumn), prop->getFullName() ); // Adjust the range to skip the first cell range = Range(from.row(), from.col() + 1, to.row(), to.col()); // Formulate expression to calculate the row binding using // PropertyEnumeration Gui::cmdAppObjectArgs( sheet, "setExpression('.cells.Bind.%s.%s', " "'tuple(.cells, <<%s>> + str(hiddenref(%s)+%d), <<%s>> + str(hiddenref(%s)+%d))')", range.from().toString(CellAddress::Cell::ShowRowColumn), range.to().toString(CellAddress::Cell::ShowRowColumn), range.from().toString(CellAddress::Cell::ShowColumn), prop->getFullName(), from.row() + 2, range.to().toString(CellAddress::Cell::ShowColumn), prop->getFullName(), from.row() + 2 ); Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); Gui::Command::commitCommand(); QDialog::accept(); } catch (Base::Exception& e) { e.reportException(); QMessageBox::critical(this, tr("Setup Configuration Table"), QString::fromUtf8(e.what())); if (commandActive) { Gui::Command::abortCommand(); } } } void DlgSheetConf::onDiscard() { bool commandActive = false; try { std::string rangeConf; CellAddress from, to; ObjectIdentifier path; auto prop = prepare(from, to, rangeConf, path, true); Range range(from, to); AutoTransaction guard("Unsetup conf table"); commandActive = true; // unbind any previous binding int count = range.rowCount() * range.colCount(); for (int i = 0; i < count; ++i) { auto r = range; auto binding = sheet->getCellBinding(r); if (!binding) { break; } Gui::cmdAppObjectArgs( sheet, "setExpression('.cells.%s.%s.%s', None)", binding == PropertySheet::BindingNormal ? "Bind" : "BindHiddenRef", r.from().toString(), r.to().toString() ); } Gui::cmdAppObjectArgs(sheet, "clear('%s')", from.toString(CellAddress::Cell::ShowRowColumn)); if (prop && prop->getName()) { auto obj = path.getDocumentObject(); if (!obj) { FC_THROWM(Base::RuntimeError, "Object not found"); } Gui::cmdAppObjectArgs(obj, "setExpression('%s.Enum', None)", prop->getName()); if (prop->testStatus(Property::PropDynamic)) { Gui::cmdAppObjectArgs(obj, "removeProperty('%s')", prop->getName()); } } Gui::Command::doCommand(Gui::Command::Doc, "App.ActiveDocument.recompute()"); Gui::Command::commitCommand(); QDialog::accept(); } catch (Base::Exception& e) { e.reportException(); QMessageBox::critical(this, tr("Unsetup Configuration Table"), QString::fromUtf8(e.what())); if (commandActive) { Gui::Command::abortCommand(); } } } #include "moc_DlgSheetConf.cpp"