From 897f8b78b8168261606c118682d5474934d85a88 Mon Sep 17 00:00:00 2001 From: Eivind Kvedalen Date: Mon, 7 Sep 2015 00:08:54 +0200 Subject: [PATCH] Added expression support to QuantitySpinBox and InputField classes. --- src/Gui/CMakeLists.txt | 9 ++ src/Gui/DlgExpressionInput.cpp | 173 ++++++++++++++++++++++ src/Gui/DlgExpressionInput.h | 85 +++++++++++ src/Gui/DlgExpressionInput.ui | 213 +++++++++++++++++++++++++++ src/Gui/ExpressionBinding.cpp | 154 +++++++++++++++++++ src/Gui/ExpressionBinding.h | 63 ++++++++ src/Gui/Icons/bound-expression.svg | 78 ++++++++++ src/Gui/Icons/resource.qrc | 1 + src/Gui/InputField.cpp | 89 +++++++++-- src/Gui/InputField.h | 22 ++- src/Gui/QuantitySpinBox.cpp | 227 ++++++++++++++++++++++++++++- src/Gui/QuantitySpinBox.h | 15 +- src/Gui/QuantitySpinBox_p.h | 43 ++++++ 13 files changed, 1153 insertions(+), 19 deletions(-) create mode 100644 src/Gui/DlgExpressionInput.cpp create mode 100644 src/Gui/DlgExpressionInput.h create mode 100644 src/Gui/DlgExpressionInput.ui create mode 100644 src/Gui/ExpressionBinding.cpp create mode 100644 src/Gui/ExpressionBinding.h create mode 100644 src/Gui/Icons/bound-expression.svg create mode 100644 src/Gui/QuantitySpinBox_p.h diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 9e27c1cc4..562017eb9 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -205,6 +205,7 @@ set(Gui_MOC_HDRS DocumentRecovery.h EditorView.h ExpressionCompleter.h + DlgExpressionInput.h FileDialog.h Flag.h GraphicsViewZoom.h @@ -228,6 +229,7 @@ set(Gui_MOC_HDRS PythonEditor.h QuantitySpinBox.h QListWidgetCustom.h + QuantitySpinBox_p.h ReportView.h SceneInspector.h SelectionView.h @@ -308,6 +310,7 @@ SET(Gui_UIC_SRCS DocumentRecovery.ui DownloadManager.ui DownloadItem.ui + DlgExpressionInput.ui MouseButtons.ui SceneInspector.ui InputVector.ui @@ -367,6 +370,7 @@ SET(Dialog_CPP_SRCS DlgProjectUtility.cpp DlgPropertyLink.cpp DlgTipOfTheDayImp.cpp + DlgExpressionInput.cpp TaskDlgRelocation.cpp DlgUndoRedo.cpp InputVector.cpp @@ -398,6 +402,7 @@ SET(Dialog_HPP_SRCS DlgProjectUtility.h DlgPropertyLink.h DlgTipOfTheDayImp.h + DlgExpressionInput.h TaskDlgRelocation.h DlgUndoRedo.h InputVector.h @@ -435,6 +440,7 @@ SET(Dialog_SRCS DlgPropertyLink.ui DlgTipOfTheDay.ui DlgTreeWidget.ui + DlgExpressionInput.ui DownloadManager.ui DownloadItem.ui DocumentRecovery.ui @@ -894,6 +900,7 @@ SET(Widget_HPP_SRCS InputField.h ProgressBar.h QuantitySpinBox.h + QuantitySpinBox_p.h SpinBox.h Splashscreen.h WidgetFactory.h @@ -967,6 +974,7 @@ SET(FreeCADGui_CPP_SRCS Document.cpp DocumentModel.cpp DocumentPyImp.cpp + ExpressionBinding.cpp GraphicsViewZoom.cpp ExpressionCompleter.cpp GuiApplicationNativeEventAware.cpp @@ -988,6 +996,7 @@ SET(FreeCADGui_SRCS BitmapFactory.h Document.h DocumentModel.h + ExpressionBinding.cpp ExpressionCompleter.h FreeCADGuiInit.py GraphicsViewZoom.h diff --git a/src/Gui/DlgExpressionInput.cpp b/src/Gui/DlgExpressionInput.cpp new file mode 100644 index 000000000..a5bc87eed --- /dev/null +++ b/src/Gui/DlgExpressionInput.cpp @@ -0,0 +1,173 @@ +/*************************************************************************** + * Copyright (c) 2015 Eivind Kvedalen * + * * + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#include +#include + +#include "DlgExpressionInput.h" +#include "ui_DlgExpressionInput.h" +#include "ExpressionCompleter.h" +#include +#include +#include + +using namespace App; +using namespace Gui::Dialog; + +const int DlgExpressionInput::h = 15; +const int DlgExpressionInput::l = 30; +const int DlgExpressionInput::r = 30; +const int DlgExpressionInput::d = 7; + +DlgExpressionInput::DlgExpressionInput(const App::ObjectIdentifier & _path, boost::shared_ptr _expression, const Base::Unit & _impliedUnit, QWidget *parent) : + QDialog(parent), + ui(new Ui::DlgExpressionInput), + expression(_expression ? _expression->copy() : 0), + path(_path), + discarded(false), + impliedUnit(_impliedUnit) +{ + assert(path.getDocumentObject() != 0); + + // Setup UI + ui->setupUi(this); + + if (expression) { + ui->expression->setText(Base::Tools::fromStdString(expression->toString())); + textChanged(Base::Tools::fromStdString(expression->toString())); + } + + // Connect signal(s) + connect(ui->expression, SIGNAL(textChanged(QString)), this, SLOT(textChanged(QString))); + connect(ui->discardBtn, SIGNAL(clicked()), this, SLOT(setDiscarded())); + + // Set document object on line edit to create auto completer + DocumentObject * docObj = path.getDocumentObject(); + ui->expression->setDocumentObject(docObj); + + setWindowFlags(Qt::Widget | Qt::FramelessWindowHint); + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_TranslucentBackground, true); + setParent(0); + + ui->expression->setFocus(); + + QDesktopWidget widget; + setMinimumWidth(widget.availableGeometry(widget.primaryScreen()).width()/2); + +#ifndef FC_DEBUG + ui->parsedExpr->setVisible(false); +#endif + +} + +DlgExpressionInput::~DlgExpressionInput() +{ + delete ui; +} + +QPoint DlgExpressionInput::tip() const +{ + return QPoint(l - d, 0); +} + +void DlgExpressionInput::paintEvent(QPaintEvent * event) { + QPainter painter(this); + QPainterPath path; + + path.moveTo(0, h + r / 2); + path.arcTo(QRect(0, h, r, r), 180, -90); + path.lineTo(l, h); + path.lineTo(l - d, 0); + path.lineTo(l + d, h); + path.lineTo(width() - r - 1, h); + path.arcTo(QRect(width() - r - 1, h, r, r), 90, -90); + path.lineTo(width() - 1, height() - r); + path.arcTo(QRect(width() - r - 1, height() - r - 1, r, r), 0, -90); + path.lineTo(r, height() - 1); + path.arcTo(QRect(0, height() - r - 1, r, r), -90, -90); + path.lineTo(0, h + r/2); + + QPen pen(Qt::black); + QBrush brush(QColor(250, 250, 180)); + pen.setWidthF(2.0); + painter.setBrush(brush); + painter.setPen(pen); + + painter.fillPath(path, brush); + painter.drawPath(path); +} + +void DlgExpressionInput::textChanged(const QString &text) +{ + try { + boost::shared_ptr expr(ExpressionParser::parse(path.getDocumentObject(), text.toUtf8().constData())); + + if (expr) { + std::string error = path.getDocumentObject()->ExpressionEngine.validateExpression(path, expr); + + if (error.size() > 0) + throw Base::Exception(error.c_str()); + +#ifdef FC_DEBUG + ui->parsedExpr->setText(Base::Tools::fromStdString(expr->toString())); +#endif + + std::auto_ptr result(expr->eval()); + + expression = expr; + ui->okBtn->setEnabled(true); + ui->errorMsg->setText(QString::fromUtf8("")); + + NumberExpression * n = Base::freecad_dynamic_cast(result.get()); + if (n) { + Base::Quantity value = n->getQuantity(); + + if (!value.getUnit().isEmpty() && value.getUnit() != impliedUnit) + throw Base::Exception("Unit mismatch between result and required unit"); + + value.setUnit(impliedUnit); + + ui->result->setText(value.getUserString()); + } + else + ui->result->setText(Base::Tools::fromStdString(result->toString())); + } + } + catch (Base::Exception & e) { + ui->errorMsg->setText(QString::fromUtf8(e.what())); + QPalette p(ui->errorMsg->palette()); + p.setColor(QPalette::WindowText, Qt::red); + ui->errorMsg->setPalette(p); + ui->okBtn->setDisabled(true); + ui->result->setText(QString::fromAscii("--")); + } +} + +void DlgExpressionInput::setDiscarded() +{ + discarded = true; + reject(); +} + +#include "moc_DlgExpressionInput.cpp" diff --git a/src/Gui/DlgExpressionInput.h b/src/Gui/DlgExpressionInput.h new file mode 100644 index 000000000..dd985d836 --- /dev/null +++ b/src/Gui/DlgExpressionInput.h @@ -0,0 +1,85 @@ +/*************************************************************************** + * Copyright (c) 2015 Eivind Kvedalen * + * * + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + +#ifndef GUI_DIALOG_DLGEXPRESSIONINPUT_H +#define GUI_DIALOG_DLGEXPRESSIONINPUT_H + +#include +#include +#include +#include + +namespace Ui { +class DlgExpressionInput; +} + +namespace Base { +class Quantity; +} + +namespace App { +class Path; +class Expression; +class DocumentObject; +} + +namespace Gui { + +namespace Dialog { + +class GuiExport DlgExpressionInput : public QDialog +{ + Q_OBJECT + +public: + explicit DlgExpressionInput(const App::ObjectIdentifier & _path, boost::shared_ptr _expression, const Base::Unit &_impliedUnit, QWidget *parent = 0); + ~DlgExpressionInput(); + + boost::shared_ptr getExpression() const { return expression; } + + bool discardedFormula() const { return discarded; } + + void paintEvent(QPaintEvent *event); + + QPoint tip() const; + +private Q_SLOTS: + void textChanged(const QString & text); + void setDiscarded(); + +private: + ::Ui::DlgExpressionInput *ui; + boost::shared_ptr expression; + App::ObjectIdentifier path; + bool discarded; + const Base::Unit impliedUnit; + + static const int h; + static const int l; + static const int r; + static const int d; +}; + +} +} + +#endif // GUI_DIALOG_EXPRESSIONINPUT_H diff --git a/src/Gui/DlgExpressionInput.ui b/src/Gui/DlgExpressionInput.ui new file mode 100644 index 000000000..b7b06f27b --- /dev/null +++ b/src/Gui/DlgExpressionInput.ui @@ -0,0 +1,213 @@ + + + DlgExpressionInput + + + + 0 + 0 + 600 + 134 + + + + + 0 + 0 + + + + + 600 + 0 + + + + Formula editor + + + + 25 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Discard formula + + + false + + + false + + + + + + + Cancel + + + false + + + + + + + Ok + + + true + + + + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 190 + 190 + 190 + + + + + + + + + + + + + + + => + + + + + + + + + + + 50 + 0 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + Gui::ExpressionLineEdit + QLineEdit +
ExpressionCompleter.h
+
+
+ + + + cancelBtn + clicked() + DlgExpressionInput + reject() + + + 399 + 69 + + + 392 + 85 + + + + + okBtn + clicked() + DlgExpressionInput + accept() + + + 505 + 68 + + + 505 + 94 + + + + +
diff --git a/src/Gui/ExpressionBinding.cpp b/src/Gui/ExpressionBinding.cpp new file mode 100644 index 000000000..4795edb9e --- /dev/null +++ b/src/Gui/ExpressionBinding.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (c) 2015 Eivind Kvedalen * + * * + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#endif +#include "ExpressionBinding.h" +#include "Command.h" +#include +#include +#include +#include +#include + +using namespace Gui; +using namespace App; + +ExpressionBinding::ExpressionBinding() +{ +} + + +ExpressionBinding::~ExpressionBinding() +{ +} + +bool ExpressionBinding::isBound() const +{ + return path.getDocumentObject() != 0; +} + +void Gui::ExpressionBinding::setExpression(boost::shared_ptr expr) +{ + DocumentObject * docObj = path.getDocumentObject(); + + if (expr) { + const std::string error = docObj->ExpressionEngine.validateExpression(path, expr); + + if (error.size()) + throw Base::Exception(error.c_str()); + + } + + docObj->ExpressionEngine.setValue(path, expr); +} + +void ExpressionBinding::bind(const App::ObjectIdentifier &_path) +{ + const Property * prop = _path.getProperty(); + + Q_ASSERT(prop != 0); + + path = prop->canonicalPath(_path); +} + +void ExpressionBinding::bind(const Property &prop) +{ + bind(App::ObjectIdentifier(prop)); +} + +bool ExpressionBinding::hasExpression() const +{ + return isBound() && getExpression() != 0; +} + +boost::shared_ptr ExpressionBinding::getExpression() const +{ + DocumentObject * docObj = path.getDocumentObject(); + + Q_ASSERT(isBound() && docObj != 0); + + return docObj->getExpression(path).expression; +} + +std::string ExpressionBinding::getExpressionString() const +{ + if (!getExpression()) + throw Base::Exception("No expression found."); + + return getExpression()->toString(); +} + +std::string ExpressionBinding::getEscapedExpressionString() const +{ + return Base::Tools::escapedUnicodeFromUtf8(getExpressionString().c_str()); +} + +bool ExpressionBinding::apply(const std::string & propName) +{ + if (hasExpression()) { + DocumentObject * docObj = path.getDocumentObject(); + + if (!docObj) + throw Base::Exception("Document object not found."); + + Gui::Command::doCommand(Gui::Command::Doc,"App.getDocument('%s').%s.setExpression('%s', '%s')", + docObj->getDocument()->getName(), + docObj->getNameInDocument(), + path.toEscapedString().c_str(), + getEscapedExpressionString().c_str()); + return true; + } + else { + if (isBound()) { + DocumentObject * docObj = path.getDocumentObject(); + + if (!docObj) + throw Base::Exception("Document object not found."); + + Gui::Command::doCommand(Gui::Command::Doc,"App.getDocument('%s').%s.setExpression('%s', None)", + docObj->getDocument()->getName(), + docObj->getNameInDocument(), + path.toEscapedString().c_str()); + return true; + } + + return false; + } +} + +bool ExpressionBinding::apply() +{ + Property * prop(path.getProperty()); + + assert(prop != 0); + + DocumentObject * docObj(path.getDocumentObject()); + + if (!docObj) + throw Base::Exception("Document object not found."); + + std::string name = docObj->getNameInDocument(); + + return apply("App.ActiveDocument." + name + "." + std::string(prop->getName())); +} diff --git a/src/Gui/ExpressionBinding.h b/src/Gui/ExpressionBinding.h new file mode 100644 index 000000000..1a70cf881 --- /dev/null +++ b/src/Gui/ExpressionBinding.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * Copyright (c) 2015 Eivind Kvedalen * + * * + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + +#ifndef EXPRESSIONBINDING_H +#define EXPRESSIONBINDING_H + +#include +#include +#include + +namespace App { +class Expression; +} + +namespace Gui { + +class GuiExport ExpressionBinding +{ +public: + ExpressionBinding(); + ~ExpressionBinding(); + + virtual void bind(const App::ObjectIdentifier & _path); + virtual void bind(const App::Property & prop); + bool isBound() const; + virtual bool apply(const std::string &propName); + virtual bool apply(); + bool hasExpression() const; + +protected: + const App::ObjectIdentifier & getPath() const { return path; } + boost::shared_ptr getExpression() const; + std::string getExpressionString() const; + std::string getEscapedExpressionString() const; + virtual void setExpression(boost::shared_ptr expr); + +private: + App::ObjectIdentifier path; + //boost::shared_ptr expression; +}; + +} + +#endif // EXPRESSIONBINDING_H diff --git a/src/Gui/Icons/bound-expression.svg b/src/Gui/Icons/bound-expression.svg new file mode 100644 index 000000000..15fb1138b --- /dev/null +++ b/src/Gui/Icons/bound-expression.svg @@ -0,0 +1,78 @@ + + + + + + + + + + image/svg+xml + + + + + + + + f(x) + + diff --git a/src/Gui/Icons/resource.qrc b/src/Gui/Icons/resource.qrc index b94fe0c77..b155405eb 100644 --- a/src/Gui/Icons/resource.qrc +++ b/src/Gui/Icons/resource.qrc @@ -41,6 +41,7 @@ ClassBrowser/type_enum.svg ClassBrowser/type_module.svg Std_ViewScreenShot.svg + bound-expression.svg breakpoint.svg debug-marker.svg debug-start.svg diff --git a/src/Gui/InputField.cpp b/src/Gui/InputField.cpp index 3793f3c8e..2d3b2bb57 100644 --- a/src/Gui/InputField.cpp +++ b/src/Gui/InputField.cpp @@ -31,12 +31,17 @@ #include #include #include +#include #include - +#include +#include +#include "ExpressionCompleter.h" +#include "Command.h" #include "InputField.h" #include "BitmapFactory.h" using namespace Gui; +using namespace App; using namespace Base; // -------------------------------------------------------------------- @@ -59,7 +64,8 @@ private: // -------------------------------------------------------------------- InputField::InputField(QWidget * parent) - : QLineEdit(parent), + : ExpressionLineEdit(parent), + ExpressionBinding(), validInput(true), actUnitValue(0), Maximum(DOUBLE_MAX), @@ -92,6 +98,43 @@ InputField::~InputField() { } +void InputField::bind(const App::ObjectIdentifier &_path) +{ + ExpressionBinding::bind(_path); + + PropertyQuantity * prop = freecad_dynamic_cast(getPath().getProperty()); + + if (prop) + actQuantity = prop->getValue(); + + DocumentObject * docObj = getPath().getDocumentObject(); + + if (docObj) { + boost::shared_ptr expr(docObj->getExpression(getPath()).expression); + + if (expr) + newInput(Tools::fromStdString(expr->toString())); + } + + // Create document object, to initialize completer + setDocumentObject(docObj); +} + +bool InputField::apply(const std::string &propName) +{ + if (!ExpressionBinding::apply(propName)) { + Gui::Command::doCommand(Gui::Command::Doc,"%s = %f", propName.c_str(), getQuantity().getValue()); + return true; + } + else + return false; +} + +bool InputField::apply() +{ + return ExpressionBinding::apply(); +} + QPixmap InputField::getValidationIcon(const char* name, const QSize& size) const { QString key = QString::fromAscii("%1_%2x%3") @@ -110,6 +153,15 @@ QPixmap InputField::getValidationIcon(const char* name, const QSize& size) const void InputField::updateText(const Base::Quantity& quant) { + if (isBound()) { + boost::shared_ptr e(getPath().getDocumentObject()->getExpression(getPath()).expression); + + if (e) { + setText(Tools::fromStdString(e->toString())); + return; + } + } + double dFactor; QString txt = quant.getUserString(dFactor,actUnitStr); actUnitValue = quant.getValue()/dFactor; @@ -182,7 +234,22 @@ void InputField::newInput(const QString & text) try { QString input = text; fixup(input); - res = Quantity::parse(input); + + if (isBound()) { + boost::shared_ptr e(ExpressionParser::parse(getPath().getDocumentObject(), input.toUtf8())); + + setExpression(e); + + std::auto_ptr evalRes(getExpression()->eval()); + + NumberExpression * value = freecad_dynamic_cast(evalRes.get()); + if (value) { + res.setValue(value->getValue()); + res.setUnit(value->getUnit()); + } + } + else + res = Quantity::parse(input); } catch(Base::Exception &e){ ErrorText = e.what(); @@ -485,14 +552,14 @@ void InputField::showEvent(QShowEvent * event) void InputField::focusInEvent(QFocusEvent * event) { - if (event->reason() == Qt::TabFocusReason || - event->reason() == Qt::BacktabFocusReason || - event->reason() == Qt::ShortcutFocusReason) { - if (!this->hasSelectedText()) - selectNumber(); - } - - QLineEdit::focusInEvent(event); + if (event->reason() == Qt::TabFocusReason || + event->reason() == Qt::BacktabFocusReason || + event->reason() == Qt::ShortcutFocusReason) { + if (!this->hasSelectedText()) + selectNumber(); + } + + QLineEdit::focusInEvent(event); } void InputField::keyPressEvent(QKeyEvent *event) diff --git a/src/Gui/InputField.h b/src/Gui/InputField.h index 2bb2cffd2..3c13e2c78 100644 --- a/src/Gui/InputField.h +++ b/src/Gui/InputField.h @@ -25,17 +25,27 @@ #define GUI_INPUTFIELD_H #include +#include +#include #include #include #include "Widgets.h" #include "Window.h" #include "SpinBox.h" #include "FileDialog.h" +#include "ExpressionBinding.h" +#include "ExpressionCompleter.h" #ifdef Q_MOC_RUN Q_DECLARE_METATYPE(Base::Quantity) #endif +namespace App { +class DocumentObject; +class ObjectIdentifier; +class Expression; +} + namespace Gui { @@ -46,9 +56,9 @@ namespace Gui { * and managing default and history values. * Although it's derived from a QLineEdit widget, it supports most of the properties and signals * of a spin box. - * \author Jürgen Riegel + * \author Jürgen Riegel */ -class GuiExport InputField : public QLineEdit +class GuiExport InputField : public ExpressionLineEdit, public ExpressionBinding { Q_OBJECT @@ -62,7 +72,7 @@ class GuiExport InputField : public QLineEdit public: - InputField ( QWidget * parent = 0 ); + InputField (QWidget * parent = 0); virtual ~InputField(); /// set the field with a quantity @@ -132,6 +142,10 @@ public: std::vector getSavedValues(void); //@} + void bind(const App::ObjectIdentifier &_path); + bool apply(const std::string &propName); + bool apply(); + Q_SIGNALS: /** gets emitted if the user has entered a VALID input * Valid means the user inputted string obeys all restrictions @@ -187,6 +201,8 @@ private: double StepSize; int HistorySize; int SaveSize; + + QPalette defaultPalette; }; } // namespace Gui diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index d37948b42..5f42030cd 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -25,14 +25,31 @@ #ifndef _PreComp_ # include # include +# include +# include +# include +# include +# include +# include +# include #endif #include "QuantitySpinBox.h" +#include "QuantitySpinBox_p.h" +#include "DlgExpressionInput.h" +#include "BitmapFactory.h" +#include "Command.h" +#include #include +#include +#include using namespace Gui; +using namespace App; +using namespace Base; namespace Gui { + class QuantitySpinBoxPrivate { public: @@ -210,23 +227,183 @@ end: double maximum; double minimum; double singleStep; + QPalette defaultPalette; + QLabel* iconLabel; }; } QuantitySpinBox::QuantitySpinBox(QWidget *parent) - : QAbstractSpinBox(parent), d_ptr(new QuantitySpinBoxPrivate()) + : QAbstractSpinBox(parent), + ExpressionBinding(), + d_ptr(new QuantitySpinBoxPrivate()) { d_ptr->locale = locale(); this->setContextMenuPolicy(Qt::DefaultContextMenu); QObject::connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(userInput(QString))); + d_ptr->defaultPalette = lineEdit()->palette(); + + /* Icon for f(x) */ + d_ptr->iconLabel = new ExpressionLabel(lineEdit()); + d_ptr->iconLabel->setCursor(Qt::ArrowCursor); + QPixmap pixmap = getIcon(":/icons/bound-expression.svg", QSize(lineEdit()->sizeHint().height(), lineEdit()->sizeHint().height())); + d_ptr->iconLabel->setPixmap(pixmap); + d_ptr->iconLabel->setStyleSheet(QString::fromAscii("QLabel { border: none; padding: 0px; }")); + d_ptr->iconLabel->hide(); + int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth); + lineEdit()->setStyleSheet(QString::fromAscii("QLineEdit { padding-right: %1px } ").arg(d_ptr->iconLabel->sizeHint().width() + frameWidth + 1)); + + QObject::connect(d_ptr->iconLabel, SIGNAL(clicked()), this, SLOT(openFormulaDialog())); } QuantitySpinBox::~QuantitySpinBox() { } -void QuantitySpinBox::updateText(const Base::Quantity& quant) +QPixmap QuantitySpinBox::getIcon(const char* name, const QSize& size) const +{ + QString key = QString::fromAscii("%1_%2x%3") + .arg(QString::fromAscii(name)) + .arg(size.width()) + .arg(size.height()); + QPixmap icon; + if (QPixmapCache::find(key, icon)) + return icon; + + icon = BitmapFactory().pixmapFromSvg(name, size); + if (!icon.isNull()) + QPixmapCache::insert(key, icon); + return icon; +} + +void QuantitySpinBox::bind(const App::ObjectIdentifier &_path) +{ + Q_D(QuantitySpinBox); + + ExpressionBinding::bind(_path); + + d->iconLabel->show(); +} + +void Gui::QuantitySpinBox::setExpression(boost::shared_ptr expr) +{ + Q_D(QuantitySpinBox); + + Q_ASSERT(isBound()); + + try { + ExpressionBinding::setExpression(expr); + + if (getExpression()) { + std::auto_ptr result(getExpression()->eval()); + NumberExpression * value = freecad_dynamic_cast(result.get()); + + if (value) { + std::stringstream s; + s << value->getValue(); + + lineEdit()->setText(value->getQuantity().getUserString()); + setReadOnly(true); + + QPalette p(lineEdit()->palette()); + p.setColor(QPalette::Text, Qt::lightGray); + lineEdit()->setPalette(p); + } + setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + } + else { + setReadOnly(false); + QPalette p(lineEdit()->palette()); + p.setColor(QPalette::Active, QPalette::Text, d->defaultPalette.color(QPalette::Text)); + lineEdit()->setPalette(p); + + } + d->iconLabel->setToolTip(QString()); + } + catch (const Base::Exception & e) { + setReadOnly(true); + QPalette p(lineEdit()->palette()); + p.setColor(QPalette::Active, QPalette::Text, Qt::red); + lineEdit()->setPalette(p); + d->iconLabel->setToolTip(QString::fromAscii(e.what())); + } +} + +bool QuantitySpinBox::apply(const std::string & propName) +{ + if (!ExpressionBinding::apply(propName)) { + Gui::Command::doCommand(Gui::Command::Doc, "%s = %f", propName.c_str(), value().getValue()); + return true; + } + else + return false; +} + +bool QuantitySpinBox::apply() +{ + return ExpressionBinding::apply(); +} + +void QuantitySpinBox::resizeEvent(QResizeEvent * event) +{ + Q_D(const QuantitySpinBox); + + QAbstractSpinBox::resizeEvent(event); + + int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth); + + QPixmap pixmap = getIcon(":/icons/bound-expression.svg", QSize(lineEdit()->rect().height() - frameWidth * 2, + lineEdit()->rect().height() - frameWidth * 2)); + d->iconLabel->setPixmap(pixmap); + QSize sz = d->iconLabel->sizeHint(); + d->iconLabel->move(lineEdit()->rect().right() - frameWidth - sz.width(), + lineEdit()->rect().top() - frameWidth); + + try { + if (isBound() && getExpression()) { + std::auto_ptr result(getExpression()->eval()); + NumberExpression * value = freecad_dynamic_cast(result.get()); + + if (value) { + setReadOnly(true); + + QPalette p(lineEdit()->palette()); + p.setColor(QPalette::Text, Qt::lightGray); + lineEdit()->setPalette(p); + } + setToolTip(Base::Tools::fromStdString(getExpression()->toString())); + } + else { + setReadOnly(false); + QPalette p(lineEdit()->palette()); + p.setColor(QPalette::Active, QPalette::Text, d->defaultPalette.color(QPalette::Text)); + lineEdit()->setPalette(p); + + } + d->iconLabel->setToolTip(QString()); + } + catch (const Base::Exception & e) { + setReadOnly(true); + QPalette p(lineEdit()->palette()); + p.setColor(QPalette::Active, QPalette::Text, Qt::red); + lineEdit()->setPalette(p); + d->iconLabel->setToolTip(QString::fromAscii(e.what())); + } + +} + +void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Equal && isBound()) + openFormulaDialog(); + else { + if (!hasExpression()) + QAbstractSpinBox::keyPressEvent(event); + } +} + + +void QuantitySpinBox::updateText(const Quantity &quant) { Q_D(QuantitySpinBox); @@ -312,6 +489,21 @@ void QuantitySpinBox::userInput(const QString & text) valueChanged(res.getValue()); } +void QuantitySpinBox::openFormulaDialog() +{ + Q_ASSERT(isBound()); + + Q_D(const QuantitySpinBox); + Gui::Dialog::DlgExpressionInput box(getPath(), getExpression(), d->unit, this); + + QPoint pos = mapToGlobal(QPoint(width() / 2, height() / 2)); + box.setGeometry(pos.x() - box.tip().x(), pos.y() - box.tip().y(), width(), height()); + if (box.exec() == QDialog::Accepted) + setExpression(box.getExpression()); + else if (box.discardedFormula()) + setExpression(boost::shared_ptr()); +} + Base::Unit QuantitySpinBox::unit() const { Q_D(const QuantitySpinBox); @@ -430,6 +622,23 @@ void QuantitySpinBox::showEvent(QShowEvent * event) selectNumber(); } +bool QuantitySpinBox::event(QEvent * event) +{ + if (event->type() == QEvent::ToolTip) { + if (isBound() && getExpression() && lineEdit()->isReadOnly()) { + QHelpEvent * helpEvent = static_cast(event); + + QToolTip::showText( helpEvent->globalPos(), Base::Tools::fromStdString(getExpression()->toString()), this); + event->accept(); + return true; + } + else + return QAbstractSpinBox::event(event); + } + else + return QAbstractSpinBox::event(event); +} + void QuantitySpinBox::focusInEvent(QFocusEvent * event) { bool hasSel = lineEdit()->hasSelectedText(); @@ -438,8 +647,16 @@ void QuantitySpinBox::focusInEvent(QFocusEvent * event) if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason || event->reason() == Qt::ShortcutFocusReason) { - if (!hasSel) - selectNumber(); + + if (isBound() && getExpression() && lineEdit()->isReadOnly()) { + QHelpEvent * helpEvent = new QHelpEvent(QEvent::ToolTip, QPoint( 0, rect().height() ), mapToGlobal( QPoint( 0, rect().height() ) )); + QApplication::postEvent(this, helpEvent); + lineEdit()->setSelection(0, 0); + } + else { + if (!hasSel) + selectNumber(); + } } } @@ -454,6 +671,7 @@ void QuantitySpinBox::focusOutEvent(QFocusEvent * event) if (state != QValidator::Acceptable) { lineEdit()->setText(d->validStr); } + QToolTip::hideText(); QAbstractSpinBox::focusOutEvent(event); } @@ -530,3 +748,4 @@ void QuantitySpinBox::fixup(QString &input) const #include "moc_QuantitySpinBox.cpp" +#include "moc_QuantitySpinBox_p.cpp" diff --git a/src/Gui/QuantitySpinBox.h b/src/Gui/QuantitySpinBox.h index c904e1059..5206b72ef 100644 --- a/src/Gui/QuantitySpinBox.h +++ b/src/Gui/QuantitySpinBox.h @@ -26,6 +26,7 @@ #include #include +#include "ExpressionBinding.h" #ifdef Q_MOC_RUN Q_DECLARE_METATYPE(Base::Quantity) @@ -34,7 +35,7 @@ Q_DECLARE_METATYPE(Base::Quantity) namespace Gui { class QuantitySpinBoxPrivate; -class GuiExport QuantitySpinBox : public QAbstractSpinBox +class GuiExport QuantitySpinBox : public QAbstractSpinBox, public ExpressionBinding { Q_OBJECT @@ -95,6 +96,13 @@ public: virtual QValidator::State validate(QString &input, int &pos) const; virtual void fixup(QString &str) const; + bool event(QEvent *event); + + void setExpression(boost::shared_ptr expr); + void bind(const App::ObjectIdentifier &_path); + bool apply(const std::string &propName); + bool apply(); + public Q_SLOTS: /// Sets the field with a quantity void setValue(const Base::Quantity& val); @@ -103,14 +111,19 @@ public Q_SLOTS: protected Q_SLOTS: void userInput(const QString & text); + void openFormulaDialog(); protected: virtual StepEnabled stepEnabled() const; virtual void showEvent(QShowEvent * event); virtual void focusInEvent(QFocusEvent * event); virtual void focusOutEvent(QFocusEvent * event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void resizeEvent(QResizeEvent *event); private: + + QPixmap getIcon(const char *name, const QSize &size) const; void updateText(const Base::Quantity&); Q_SIGNALS: diff --git a/src/Gui/QuantitySpinBox_p.h b/src/Gui/QuantitySpinBox_p.h new file mode 100644 index 000000000..147d08012 --- /dev/null +++ b/src/Gui/QuantitySpinBox_p.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (c) 2015 Eivind Kvedalen * + * * + * 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., 51 Franklin Street, * + * Fifth Floor, Boston, MA 02110-1301, USA * + * * + ***************************************************************************/ + +#ifndef QUANTITYSPINBOX_P_H +#define QUANTITYSPINBOX_P_H + +#include + +class ExpressionLabel : public QLabel +{ + Q_OBJECT +public: + ExpressionLabel(QWidget * parent) : QLabel(parent) { } +protected: + void mouseReleaseEvent(QMouseEvent * event) { + if (rect().contains(event->pos())) + Q_EMIT clicked(); + } + +Q_SIGNALS: + void clicked(); +}; + +#endif // QUANTITYSPINBOX_P_H