Spreadsheet: add menu action 'Configuration table...'

To make it easy for user to create dynamically switchable configuration
tables using spreadsheet.
This commit is contained in:
Zheng, Lei
2019-12-28 09:21:32 +08:00
committed by Chris Hennes
parent 830bd52af0
commit 7ebb142b2c
6 changed files with 527 additions and 0 deletions

View File

@@ -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}
)

View File

@@ -0,0 +1,297 @@
/****************************************************************************
* Copyright (c) 2019 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
* *
* 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 <boost/algorithm/string/predicate.hpp>
#include <QMessageBox>
#include "DlgSheetConf.h"
#include <Base/Tools.h>
#include <App/Range.h>
#include <App/Document.h>
#include <App/Application.h>
#include <App/ExpressionParser.h>
#include <App/AutoTransaction.h>
#include <Gui/CommandT.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, 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<App::VariableExpression*>(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<FunctionExpression>(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<VariableExpression>(expr);
if(vexpr) {
auto prop = Base::freecad_dynamic_cast<PropertyEnumeration>(
vexpr->getPath().getProperty());
if(prop) {
auto obj = Base::freecad_dynamic_cast<DocumentObject>(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; 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(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; 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(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"

View File

@@ -0,0 +1,58 @@
/****************************************************************************
* Copyright (c) 2019 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
* *
* 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 <QDialog>
#include <Mod/Spreadsheet/App/Sheet.h>
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

View File

@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DlgSheetConf</class>
<widget class="QDialog" name="DlgSheetConf">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>366</width>
<height>146</height>
</rect>
</property>
<property name="windowTitle">
<string>Setup Configuration Table</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Property:</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="lineEditEnd">
<property name="toolTip">
<string>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.
</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Cell range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEditStart">
<property name="toolTip">
<string>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.
</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="Gui::ExpressionLineEdit" name="lineEditProp">
<property name="toolTip">
<string>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.</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="lineEditGroup">
<property name="toolTip">
<string>Optional property group name.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Group:</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btnDiscard">
<property name="text">
<string>Unsetup</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bthCancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnOk">
<property name="text">
<string>OK</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::ExpressionLineEdit</class>
<extends>QLineEdit</extends>
<header location="global">Gui/ExpressionCompleter.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>btnOk</sender>
<signal>clicked()</signal>
<receiver>DlgSheetConf</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>313</x>
<y>122</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>72</y>
</hint>
</hints>
</connection>
<connection>
<sender>bthCancel</sender>
<signal>clicked()</signal>
<receiver>DlgSheetConf</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>222</x>
<y>122</y>
</hint>
<hint type="destinationlabel">
<x>182</x>
<y>72</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -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<PropertiesDialog> dialog(new PropertiesDialog(sheet, selectedRanges(), this));

View File

@@ -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);