diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 3a984804c..2925a452c 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -185,6 +185,7 @@ set(Gui_MOC_HDRS PythonConsole.h PythonDebugger.h PythonEditor.h + QuantitySpinBox.h ReportView.h SceneInspector.h SelectionView.h @@ -752,6 +753,7 @@ SET(Widget_CPP_SRCS PrefWidgets.cpp InputField.cpp ProgressBar.cpp + QuantitySpinBox.cpp SpinBox.cpp Splashscreen.cpp WidgetFactory.cpp @@ -764,6 +766,7 @@ SET(Widget_HPP_SRCS PrefWidgets.h InputField.h ProgressBar.h + QuantitySpinBox.h SpinBox.h Splashscreen.h WidgetFactory.h diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp new file mode 100644 index 000000000..98a2c6dc0 --- /dev/null +++ b/src/Gui/QuantitySpinBox.cpp @@ -0,0 +1,375 @@ +/*************************************************************************** + * Copyright (c) 2014 Werner Mayer * + * * + * 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 "QuantitySpinBox.h" +#include + +using namespace Gui; + +namespace Gui { +class QuantitySpinBoxPrivate +{ +public: + QuantitySpinBoxPrivate() : + validInput(true), + unitValue(0), + maximum(DOUBLE_MAX), + minimum(-DOUBLE_MAX), + singleStep(1.0) + { + } + ~QuantitySpinBoxPrivate() + { + } + + Base::Quantity validateAndInterpret(QString& input, int& pos, QValidator::State& state) const + { + Base::Quantity res; + try { + res = Base::Quantity::parse(input); + + double factor; + QString unitStr; + res.getUserString(factor, unitStr); + double value = res.getValue()/factor; + // disallow to enter numbers out of range + if (value > this->maximum || value < this->minimum) + state = QValidator::Invalid; + else + state = QValidator::Acceptable; + } + catch (Base::Exception&) { + // Actually invalid input but the newInput slot gives + // some feedback + state = QValidator::Intermediate; + } + + return res; + } + + bool validInput; + QString errorText; + Base::Quantity quantity; + Base::Unit unit; + double unitValue; + QString unitStr; + double maximum; + double minimum; + double singleStep; +}; +} + +QuantitySpinBox::QuantitySpinBox(QWidget *parent) + : QAbstractSpinBox(parent), d_ptr(new QuantitySpinBoxPrivate()) +{ + this->setContextMenuPolicy(Qt::DefaultContextMenu); + QObject::connect(lineEdit(), SIGNAL(textChanged(QString)), + this, SLOT(userInput(QString))); +} + +QuantitySpinBox::~QuantitySpinBox() +{ +} + +void QuantitySpinBox::updateText(const Base::Quantity& quant) +{ + Q_D(QuantitySpinBox); + + double dFactor; + QString txt = quant.getUserString(dFactor,d->unitStr); + d->unitValue = quant.getValue()/dFactor; + lineEdit()->setText(txt); +} + +Base::Quantity QuantitySpinBox::value() const +{ + Q_D(const QuantitySpinBox); + return d->quantity; +} + +void QuantitySpinBox::setValue(const Base::Quantity& value) +{ + Q_D(QuantitySpinBox); + d->quantity = value; + // check limits + if (d->quantity.getValue() > d->maximum) + d->quantity.setValue(d->maximum); + if (d->quantity.getValue() < d->minimum) + d->quantity.setValue(d->minimum); + + d->unit = value.getUnit(); + + updateText(value); +} + +void QuantitySpinBox::setValue(double value) +{ + Q_D(QuantitySpinBox); + setValue(Base::Quantity(value, d->unit)); +} + +bool QuantitySpinBox::hasValidInput() const +{ + Q_D(const QuantitySpinBox); + return d->validInput; +} + +void QuantitySpinBox::userInput(const QString & text) +{ + Q_D(QuantitySpinBox); + Base::Quantity res; + try { + res = Base::Quantity::parse(text); + } + catch (Base::Exception &e) { + d->errorText = QString::fromAscii(e.what()); + parseError(d->errorText); + d->validInput = false; + return; + } + + if (res.getUnit().isEmpty()) + res.setUnit(d->unit); + + // check if unit fits! + if (!d->unit.isEmpty() && !res.getUnit().isEmpty() && d->unit != res.getUnit()){ + parseError(QString::fromAscii("Wrong unit")); + d->validInput = false; + return; + } + + d->errorText.clear(); + d->validInput = true; + + if (res.getValue() > d->maximum){ + res.setValue(d->maximum); + d->errorText = tr("Maximum reached"); + } + if (res.getValue() < d->minimum){ + res.setValue(d->minimum); + d->errorText = tr("Minimum reached"); + } + + double dFactor; + res.getUserString(dFactor,d->unitStr); + d->unitValue = res.getValue()/dFactor; + d->quantity = res; + + // signaling + valueChanged(res); + valueChanged(res.getValue()); +} + +Base::Unit QuantitySpinBox::unit() const +{ + Q_D(const QuantitySpinBox); + return d->unit; +} + +void QuantitySpinBox::setUnit(const Base::Unit &unit) +{ + Q_D(QuantitySpinBox); + + d->unit = unit; + d->quantity.setUnit(unit); + updateText(d->quantity); +} + +void QuantitySpinBox::setUnitText(const QString& str) +{ + Base::Quantity quant = Base::Quantity::parse(str); + setUnit(quant.getUnit()); +} + +QString QuantitySpinBox::unitText(void) +{ + Q_D(QuantitySpinBox); + return d->unitStr; +} + +double QuantitySpinBox::singleStep() const +{ + Q_D(const QuantitySpinBox); + return d->singleStep; +} + +void QuantitySpinBox::setSingleStep(double value) +{ + Q_D(QuantitySpinBox); + + if (value >= 0) { + d->singleStep = value; + } +} + +double QuantitySpinBox::minimum() const +{ + Q_D(const QuantitySpinBox); + return d->minimum; +} + +void QuantitySpinBox::setMinimum(double minimum) +{ + Q_D(QuantitySpinBox); + d->minimum = minimum; +} + +double QuantitySpinBox::maximum() const +{ + Q_D(const QuantitySpinBox); + return d->maximum; +} + +void QuantitySpinBox::setMaximum(double maximum) +{ + Q_D(QuantitySpinBox); + d->maximum = maximum; +} + +void QuantitySpinBox::setRange(double minimum, double maximum) +{ + Q_D(QuantitySpinBox); + d->minimum = minimum; + d->maximum = maximum; +} + +QAbstractSpinBox::StepEnabled QuantitySpinBox::stepEnabled() const +{ + Q_D(const QuantitySpinBox); + if (isReadOnly() || !d->validInput) + return StepNone; + if (wrapping()) + return StepEnabled(StepUpEnabled | StepDownEnabled); + StepEnabled ret = StepNone; + if (d->quantity.getValue() < d->maximum) { + ret |= StepUpEnabled; + } + if (d->quantity.getValue() > d->minimum) { + ret |= StepDownEnabled; + } + return ret; +} + +void QuantitySpinBox::stepBy(int steps) +{ + Q_D(QuantitySpinBox); + + double step = d->singleStep * steps; + double val = d->unitValue + step; + if (val > d->maximum) + val = d->maximum; + else if (val < d->minimum) + val = d->minimum; + + setValue(val); + update(); + selectNumber(); +} + +void QuantitySpinBox::showEvent(QShowEvent * event) +{ + Q_D(QuantitySpinBox); + + QAbstractSpinBox::showEvent(event); + + bool selected = lineEdit()->hasSelectedText(); + updateText(d->quantity); + if (selected) + selectNumber(); +} + +void QuantitySpinBox::focusInEvent(QFocusEvent * event) +{ + if (event->reason() == Qt::TabFocusReason || + event->reason() == Qt::BacktabFocusReason || + event->reason() == Qt::ShortcutFocusReason) { + if (!lineEdit()->hasSelectedText()) + selectNumber(); + } +} + +void QuantitySpinBox::clear() +{ + QAbstractSpinBox::clear(); +} + +void QuantitySpinBox::selectNumber() +{ + QByteArray str = lineEdit()->text().toLatin1(); + unsigned int i = 0; + + for (QByteArray::iterator it = str.begin(); it != str.end(); ++it) { + if (*it >= '0' && *it <= '9') + i++; + else if (*it == ',' || *it == '.') + i++; + else if (*it == '-') + i++; + else // any non-number character + break; + } + + lineEdit()->setSelection(0, i); +} + +QString QuantitySpinBox::textFromValue(const Base::Quantity& value) const +{ + double factor; + QString unitStr; + QString str = value.getUserString(factor, unitStr); + if (qAbs(value.getValue()) >= 1000.0) { + str.remove(locale().groupSeparator()); + } + return str; +} + +Base::Quantity QuantitySpinBox::valueFromText(const QString &text) const +{ + Q_D(const QuantitySpinBox); + + QString copy = text; + int pos = lineEdit()->cursorPosition(); + QValidator::State state = QValidator::Acceptable; + return d->validateAndInterpret(copy, pos, state); +} + +QValidator::State QuantitySpinBox::validate(QString &text, int &pos) const +{ + Q_D(const QuantitySpinBox); + + QValidator::State state; + d->validateAndInterpret(text, pos, state); + return state; +} + +void QuantitySpinBox::fixup(QString &input) const +{ + input.remove(locale().groupSeparator()); +} + + +#include "moc_QuantitySpinBox.cpp" diff --git a/src/Gui/QuantitySpinBox.h b/src/Gui/QuantitySpinBox.h new file mode 100644 index 000000000..e03d77e74 --- /dev/null +++ b/src/Gui/QuantitySpinBox.h @@ -0,0 +1,138 @@ +/*************************************************************************** + * Copyright (c) 2014 Werner Mayer * + * * + * 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 GUI_QUANTITYSPINBOX_H +#define GUI_QUANTITYSPINBOX_H + +#include +#include + +#ifdef Q_MOC_RUN +Q_DECLARE_METATYPE(Base::Quantity) +#endif + +namespace Gui { + +class QuantitySpinBoxPrivate; +class GuiExport QuantitySpinBox : public QAbstractSpinBox +{ + Q_OBJECT + + Q_PROPERTY(QString unit READ unitText WRITE setUnitText) + Q_PROPERTY(double minimum READ minimum WRITE setMinimum) + Q_PROPERTY(double maximum READ maximum WRITE setMaximum) + Q_PROPERTY(double singleStep READ singleStep WRITE setSingleStep) + Q_PROPERTY(Base::Quantity value READ value WRITE setValue NOTIFY valueChanged USER true) + +public: + explicit QuantitySpinBox(QWidget *parent = 0); + virtual ~QuantitySpinBox(); + + /// Get the current quantity + Base::Quantity value() const; + + /// Gives the current state of the user input, gives true if it is a valid input with correct quantity + /// or returns false if the input is a unparsable string or has a wrong unit. + bool hasValidInput() const; + + /** Sets the Unit this widget is working with. + * After setting the Unit the widget will only accept + * user input with this unit type. Or if the user input + * a value without unit, this one will be added to the resulting + * Quantity. + */ + Base::Unit unit() const; + void setUnit(const Base::Unit &unit); + /// Set the unit property + void setUnitText(const QString&); + /// Get the unit property + QString unitText(void); + + /// Get the value of the singleStep property + double singleStep() const; + /// Set the value of the singleStep property + void setSingleStep(double val); + + /// Gets the value of the minimum property + double minimum() const; + /// Sets the value of the minimum property + void setMinimum(double min); + + /// Gets the value of the maximum property + double maximum() const; + /// Sets the value of the maximum property + void setMaximum(double max); + + /// Set the number portion selected + void selectNumber(); + + void setRange(double min, double max); + + Base::Quantity valueFromText(const QString &text) const; + QString textFromValue(const Base::Quantity& val) const; + virtual void stepBy(int steps); + virtual void clear(); + virtual QValidator::State validate(QString &input, int &pos) const; + virtual void fixup(QString &str) const; + +public Q_SLOTS: + /// Sets the field with a quantity + void setValue(const Base::Quantity& val); + /// Set a numerical value which gets converted to a quantity with the currently set unit type + void setValue(double); + +protected Q_SLOTS: + void userInput(const QString & text); + +protected: + virtual StepEnabled stepEnabled() const; + virtual void showEvent(QShowEvent * event); + virtual void focusInEvent(QFocusEvent * event); + +private: + void updateText(const Base::Quantity&); + +Q_SIGNALS: + /** Gets emitted if the user has entered a VALID input + * Valid means the user inputted string obeys all restrictions + * like: minimum, maximum and/or the right Unit (if specified). + */ + void valueChanged(const Base::Quantity&); + /** Gets emitted if the user has entered a VALID input + * Valid means the user inputted string obeys all restrictions + * like: minimum, maximum and/or the right Unit (if specified). + */ + void valueChanged(double); + + /// Signal for an invalid user input + void parseError(const QString& errorText); + +private: + QuantitySpinBoxPrivate * const d_ptr; + Q_DISABLE_COPY(QuantitySpinBox) + Q_DECLARE_PRIVATE(QuantitySpinBox) +}; + +} // namespace Gui + +#endif // GUI_QUANTITYSPINBOX_H