diff --git a/src/Mod/Spreadsheet/Gui/CMakeLists.txt b/src/Mod/Spreadsheet/Gui/CMakeLists.txt index 5f6a54b3cf..ae932c3245 100644 --- a/src/Mod/Spreadsheet/Gui/CMakeLists.txt +++ b/src/Mod/Spreadsheet/Gui/CMakeLists.txt @@ -52,6 +52,7 @@ set(SpreadsheetGui_UIC_SRCS Sheet.ui PropertiesDialog.ui DlgBindSheet.ui + DlgSheetConf.ui ) if(BUILD_QT5) @@ -92,6 +93,8 @@ SET(SpreadsheetGui_SRCS PropertiesDialog.cpp DlgBindSheet.h DlgBindSheet.cpp + DlgSheetConf.h + DlgSheetConf.cpp ${SpreadsheetGui_UIC_HDRS} ) diff --git a/src/Mod/Spreadsheet/Gui/DlgSheetConf.cpp b/src/Mod/Spreadsheet/Gui/DlgSheetConf.cpp new file mode 100644 index 0000000000..314183ec52 --- /dev/null +++ b/src/Mod/Spreadsheet/Gui/DlgSheetConf.cpp @@ -0,0 +1,297 @@ +/**************************************************************************** + * 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 "PreCompiled.h" +#include +#include +#include "DlgSheetConf.h" +#include +#include +#include +#include +#include +#include +#include +#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, SIGNAL(clicked()), this, SLOT(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(App::VariableExpression::getClassTypeId())) + 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(App::PropertyEnumeration::getClassTypeId()) + || !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(FunctionExpression::getClassTypeId())) { + auto fexpr = Base::freecad_dynamic_cast(cell->getExpression()); + if(fexpr && (fexpr->getFunction()==FunctionExpression::HREF + || fexpr->getFunction()==FunctionExpression::HIDDENREF) + && fexpr->getArgs().size()==1) + expr = fexpr->getArgs().front(); + } + auto vexpr = Base::freecad_dynamic_cast(expr); + if(vexpr) { + auto prop = Base::freecad_dynamic_cast( + vexpr->getPath().getProperty()); + if(prop) { + auto obj = Base::freecad_dynamic_cast(prop->getContainer()); + if(obj) { + path = ObjectIdentifier(sheet); + path.setDocumentObjectName(obj,true); + path << ObjectIdentifier::SimpleComponent(prop->getName()); + return prop; + } + } + } + } + return 0; +} + +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(StringExpression::getClassTypeId())) + 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(App::VariableExpression::getClassTypeId())) + 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; igetCellBinding(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(true), 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(true), range.to().toString(true), + range.from().toString(true,false,true), prop->getFullName(), from.row()+2, + range.to().toString(true,false,true), 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; igetCellBinding(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(true)); + + 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" diff --git a/src/Mod/Spreadsheet/Gui/DlgSheetConf.h b/src/Mod/Spreadsheet/Gui/DlgSheetConf.h new file mode 100644 index 0000000000..aaf4e66b10 --- /dev/null +++ b/src/Mod/Spreadsheet/Gui/DlgSheetConf.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * 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 * + * * + ****************************************************************************/ + +#ifndef DLG_SHEETCONF_H +#define DLG_SHEETCONF_H + +#include +#include + +namespace Ui { +class DlgSheetConf; +} + +namespace SpreadsheetGui { + +class DlgSheetConf : public QDialog +{ + Q_OBJECT + +public: + explicit DlgSheetConf(Spreadsheet::Sheet *sheet, App::Range range, QWidget *parent = 0); + ~DlgSheetConf(); + + void accept(); + + App::Property *prepare(App::CellAddress &from, App::CellAddress &to, + std::string &rangeConf, App::ObjectIdentifier &path, bool init); + +public Q_SLOTS: + void onDiscard(); + +private: + Spreadsheet::Sheet * sheet; + Ui::DlgSheetConf *ui; +}; + +} + +#endif // DLG_SHEETCONF_H diff --git a/src/Mod/Spreadsheet/Gui/DlgSheetConf.ui b/src/Mod/Spreadsheet/Gui/DlgSheetConf.ui new file mode 100644 index 0000000000..49601c27b2 --- /dev/null +++ b/src/Mod/Spreadsheet/Gui/DlgSheetConf.ui @@ -0,0 +1,155 @@ + + + DlgSheetConf + + + + 0 + 0 + 366 + 146 + + + + Setup Configuration Table + + + + + + Property: + + + + + + + Ending cell address. + +The first column of the range is assumed to contain a list of configuration +names, which will be used to generate a string list and bind to the given +property for user to dynamically switch configuration. + +The first row of the range will be bound to whatever row (indirectly) selected +by that property. + + + + + + + + Cell range: + + + + + + + Starting cell address. + +The first column of the range is assumed to contain a list of configuration +names, which will be used to generate a string list and bind to the given +property for user to dynamically switch configuration. + +The first row of the range will be bound to whatever row (indirectly) selected +by that property. + + + + + + + + Type in an expression to specify the object and property name to dynamically +switch the design configuration. The property will be created if not exist. + + + + + + + Optional property group name. + + + + + + + Group: + + + + + + + + + Unsetup + + + + + + + Cancel + + + + + + + OK + + + true + + + + + + + + + + Gui::ExpressionLineEdit + QLineEdit +
Gui/ExpressionCompleter.h
+
+
+ + + + btnOk + clicked() + DlgSheetConf + accept() + + + 313 + 122 + + + 182 + 72 + + + + + bthCancel + clicked() + DlgSheetConf + reject() + + + 222 + 122 + + + 182 + 72 + + + + +
diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp index 5a7a2f673c..acf95dddc7 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.cpp +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.cpp @@ -48,6 +48,7 @@ #include "LineEdit.h" #include "PropertiesDialog.h" #include "DlgBindSheet.h" +#include "DlgSheetConf.h" using namespace SpreadsheetGui; using namespace Spreadsheet; @@ -183,6 +184,10 @@ SheetTableView::SheetTableView(QWidget *parent) connect(actionBind, SIGNAL(triggered()), this, SLOT(onBind())); contextMenu->addAction(actionBind); + QAction *actionConf = new QAction(tr("Configuration table..."),this); + connect(actionConf, SIGNAL(triggered()), this, SLOT(onConfSetup())); + contextMenu->addAction(actionConf); + horizontalHeader()->addAction(actionBind); verticalHeader()->addAction(actionBind); @@ -222,6 +227,14 @@ void SheetTableView::onBind() { } } +void SheetTableView::onConfSetup() { + auto ranges = selectedRanges(); + if(ranges.empty()) + return; + DlgSheetConf dlg(sheet,ranges.back(),this); + dlg.exec(); +} + void SheetTableView::cellProperties() { std::unique_ptr dialog(new PropertiesDialog(sheet, selectedRanges(), this)); diff --git a/src/Mod/Spreadsheet/Gui/SheetTableView.h b/src/Mod/Spreadsheet/Gui/SheetTableView.h index 47ae6534e0..741fa1cf38 100644 --- a/src/Mod/Spreadsheet/Gui/SheetTableView.h +++ b/src/Mod/Spreadsheet/Gui/SheetTableView.h @@ -82,6 +82,7 @@ protected Q_SLOTS: void cellProperties(); void onRecompute(); void onBind(); + void onConfSetup(); protected: bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event);