// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2006 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 * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "customwidgets.h" using namespace Gui; UrlLabel::UrlLabel(QWidget* parent, Qt::WindowFlags f) : QLabel("TextLabel", parent, f) { _url = "http://localhost"; setToolTip(this->_url); setCursor(Qt::PointingHandCursor); } UrlLabel::~UrlLabel() {} void UrlLabel::mouseReleaseEvent(QMouseEvent*) { QMessageBox::information(this, "Browser", QString("This starts your browser with url %1").arg(_url)); } QString UrlLabel::url() const { return this->_url; } void UrlLabel::setUrl(const QString& u) { this->_url = u; setToolTip(this->_url); } LocationWidget::LocationWidget(QWidget* parent) : QWidget(parent) { box = new QGridLayout(); xValue = new QDoubleSpinBox(this); xValue->setMinimum(-2.14748e+09); xValue->setMaximum(2.14748e+09); xLabel = new QLabel(this); box->addWidget(xLabel, 0, 0, 1, 1); box->addWidget(xValue, 0, 1, 1, 1); yValue = new QDoubleSpinBox(this); yValue->setMinimum(-2.14748e+09); yValue->setMaximum(2.14748e+09); yLabel = new QLabel(this); box->addWidget(yLabel, 1, 0, 1, 1); box->addWidget(yValue, 1, 1, 1, 1); zValue = new QDoubleSpinBox(this); zValue->setMinimum(-2.14748e+09); zValue->setMaximum(2.14748e+09); zLabel = new QLabel(this); box->addWidget(zLabel, 2, 0, 1, 1); box->addWidget(zValue, 2, 1, 1, 1); dLabel = new QLabel(this); dValue = new QComboBox(this); dValue->setCurrentIndex(-1); box->addWidget(dLabel, 3, 0, 1, 1); box->addWidget(dValue, 3, 1, 1, 1); QGridLayout* gridLayout = new QGridLayout(this); gridLayout->addLayout(box, 0, 0, 1, 2); retranslateUi(); } LocationWidget::~LocationWidget() {} QSize LocationWidget::sizeHint() const { return QSize(150, 100); } void LocationWidget::changeEvent(QEvent* e) { if (e->type() == QEvent::LanguageChange) { this->retranslateUi(); } QWidget::changeEvent(e); } void LocationWidget::retranslateUi() { xLabel->setText(QApplication::translate("Gui::LocationWidget", "X:")); yLabel->setText(QApplication::translate("Gui::LocationWidget", "Y:")); zLabel->setText(QApplication::translate("Gui::LocationWidget", "Z:")); dLabel->setText(QApplication::translate("Gui::LocationWidget", "Direction:")); } FileChooser::FileChooser(QWidget* parent) : QWidget(parent) , md(File) , _filter(QString()) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(6); lineEdit = new QLineEdit(this); layout->addWidget(lineEdit); connect(lineEdit, &QLineEdit::textChanged, this, &FileChooser::fileNameChanged); button = new QPushButton("...", this); button->setFixedWidth(2 * button->fontMetrics().horizontalAdvance(" ... ")); layout->addWidget(button); connect(button, &QPushButton::clicked, this, &FileChooser::chooseFile); setFocusProxy(lineEdit); } FileChooser::~FileChooser() {} QString FileChooser::fileName() const { return lineEdit->text(); } void FileChooser::setFileName(const QString& fn) { lineEdit->setText(fn); } void FileChooser::chooseFile() { QFileDialog::Options dlgOpt = QFileDialog::DontUseNativeDialog; QString fn; if (mode() == File) { fn = QFileDialog::getOpenFileName(this, tr("Select a file"), lineEdit->text(), _filter, 0, dlgOpt); } else { QFileDialog::Options option = QFileDialog::ShowDirsOnly | dlgOpt; fn = QFileDialog::getExistingDirectory(this, tr("Select a directory"), lineEdit->text(), option); } if (!fn.isEmpty()) { lineEdit->setText(fn); Q_EMIT fileNameSelected(fn); } } FileChooser::Mode FileChooser::mode() const { return md; } void FileChooser::setMode(Mode m) { md = m; } QString FileChooser::filter() const { return _filter; } void FileChooser::setFilter(const QString& filter) { _filter = filter; } void FileChooser::setButtonText(const QString& txt) { button->setText(txt); int w1 = 2 * button->fontMetrics().horizontalAdvance(txt); int w2 = 2 * button->fontMetrics().horizontalAdvance(" ... "); button->setFixedWidth((w1 > w2 ? w1 : w2)); } QString FileChooser::buttonText() const { return button->text(); } // ------------------------------------------------------------------------------ PrefFileChooser::PrefFileChooser(QWidget* parent) : FileChooser(parent) {} PrefFileChooser::~PrefFileChooser() {} QByteArray PrefFileChooser::entryName() const { return m_sPrefName; } QByteArray PrefFileChooser::paramGrpPath() const { return m_sPrefGrp; } void PrefFileChooser::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefFileChooser::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- AccelLineEdit::AccelLineEdit(QWidget* parent) : QLineEdit(parent) { setPlaceholderText(tr("Press a keyboard shortcut")); setClearButtonEnabled(true); keyPressedCount = 0; } bool AccelLineEdit::isNone() const { return text().isEmpty(); } void AccelLineEdit::keyPressEvent(QKeyEvent* e) { if (isReadOnly()) { QLineEdit::keyPressEvent(e); return; } QString txtLine = text(); int key = e->key(); Qt::KeyboardModifiers state = e->modifiers(); // Backspace clears the shortcut if text is present, else sets Backspace as shortcut. // If a modifier is pressed without any other key, return. // AltGr is not a modifier but doesn't have a QString representation. switch (key) { case Qt::Key_Backspace: case Qt::Key_Delete: if (state == Qt::NoModifier) { keyPressedCount = 0; if (isNone()) { QKeySequence ks(key); setText(ks.toString(QKeySequence::NativeText)); } else { clear(); } } case Qt::Key_Control: case Qt::Key_Shift: case Qt::Key_Alt: case Qt::Key_Meta: case Qt::Key_AltGr: return; default: break; } if (txtLine.isEmpty()) { // Text maybe cleared by QLineEdit's built in clear button keyPressedCount = 0; } else { // 4 keys are allowed for QShortcut switch (keyPressedCount) { case 4: keyPressedCount = 0; txtLine.clear(); break; case 0: txtLine.clear(); break; default: txtLine += QString::fromLatin1(","); break; } } // Handles modifiers applying a mask. if ((state & Qt::ControlModifier) == Qt::ControlModifier) { QKeySequence ks(Qt::CTRL); txtLine += ks.toString(QKeySequence::NativeText); } if ((state & Qt::AltModifier) == Qt::AltModifier) { QKeySequence ks(Qt::ALT); txtLine += ks.toString(QKeySequence::NativeText); } if ((state & Qt::ShiftModifier) == Qt::ShiftModifier) { QKeySequence ks(Qt::SHIFT); txtLine += ks.toString(QKeySequence::NativeText); } if ((state & Qt::MetaModifier) == Qt::MetaModifier) { QKeySequence ks(Qt::META); txtLine += ks.toString(QKeySequence::NativeText); } // Handles normal keys QKeySequence ks(key); txtLine += ks.toString(QKeySequence::NativeText); setText(txtLine); keyPressedCount++; } // ------------------------------------------------------------------------------ ActionSelector::ActionSelector(QWidget* parent) : QWidget(parent) { addButton = new QPushButton(this); addButton->setMinimumSize(QSize(30, 30)); QIcon icon; icon.addFile(QString::fromUtf8(":/icons/button_right.svg"), QSize(), QIcon::Normal, QIcon::Off); addButton->setIcon(icon); gridLayout = new QGridLayout(this); gridLayout->addWidget(addButton, 1, 1, 1, 1); spacerItem = new QSpacerItem(33, 57, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacerItem, 5, 1, 1, 1); spacerItem1 = new QSpacerItem(33, 58, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacerItem1, 0, 1, 1, 1); removeButton = new QPushButton(this); removeButton->setMinimumSize(QSize(30, 30)); QIcon icon1; icon1.addFile(QString::fromUtf8(":/icons/button_left.svg"), QSize(), QIcon::Normal, QIcon::Off); removeButton->setIcon(icon1); removeButton->setAutoDefault(true); removeButton->setDefault(false); gridLayout->addWidget(removeButton, 2, 1, 1, 1); upButton = new QPushButton(this); upButton->setMinimumSize(QSize(30, 30)); QIcon icon3; icon3.addFile(QString::fromUtf8(":/icons/button_up.svg"), QSize(), QIcon::Normal, QIcon::Off); upButton->setIcon(icon3); gridLayout->addWidget(upButton, 3, 1, 1, 1); downButton = new QPushButton(this); downButton->setMinimumSize(QSize(30, 30)); QIcon icon2; icon2.addFile(QString::fromUtf8(":/icons/button_down.svg"), QSize(), QIcon::Normal, QIcon::Off); downButton->setIcon(icon2); downButton->setAutoDefault(true); gridLayout->addWidget(downButton, 4, 1, 1, 1); vboxLayout = new QVBoxLayout(); vboxLayout->setContentsMargins(0, 0, 0, 0); labelAvailable = new QLabel(this); vboxLayout->addWidget(labelAvailable); availableWidget = new QTreeWidget(this); availableWidget->setRootIsDecorated(false); availableWidget->setHeaderLabels(QStringList() << QString()); availableWidget->header()->hide(); vboxLayout->addWidget(availableWidget); gridLayout->addLayout(vboxLayout, 0, 0, 6, 1); vboxLayout1 = new QVBoxLayout(); vboxLayout1->setContentsMargins(0, 0, 0, 0); labelSelected = new QLabel(this); vboxLayout1->addWidget(labelSelected); selectedWidget = new QTreeWidget(this); selectedWidget->setRootIsDecorated(false); selectedWidget->setHeaderLabels(QStringList() << QString()); selectedWidget->header()->hide(); vboxLayout1->addWidget(selectedWidget); gridLayout->addLayout(vboxLayout1, 0, 2, 6, 1); addButton->setText(QString()); removeButton->setText(QString()); upButton->setText(QString()); downButton->setText(QString()); labelAvailable->setText(QApplication::translate("Gui::ActionSelector", "Available:")); labelSelected->setText(QApplication::translate("Gui::ActionSelector", "Selected:")); addButton->setToolTip(QApplication::translate("Gui::ActionSelector", "Add")); removeButton->setToolTip(QApplication::translate("Gui::ActionSelector", "Remove")); upButton->setToolTip(QApplication::translate("Gui::ActionSelector", "Move up")); downButton->setToolTip(QApplication::translate("Gui::ActionSelector", "Move down")); } ActionSelector::~ActionSelector() {} // -------------------------------------------------------------------- InputField::InputField(QWidget* parent) : QLineEdit(parent) , Value(0) , Maximum(std::numeric_limits::max()) , Minimum(-std::numeric_limits::max()) , StepSize(1.0) , HistorySize(5) {} InputField::~InputField() {} /** Sets the preference path to \a path. */ void InputField::setParamGrpPath(const QByteArray& path) { m_sPrefGrp = path; } /** Returns the widget's preferences path. */ QByteArray InputField::paramGrpPath() const { return m_sPrefGrp; } /// sets the field with a quantity void InputField::setValue(double quant) { Value = quant; setText(QString("%1 %2").arg(Value).arg(UnitStr)); } /// sets the field with a quantity double InputField::getQuantity() const { return Value; } /// get the value of the singleStep property double InputField::singleStep(void) const { return StepSize; } /// set the value of the singleStep property void InputField::setSingleStep(double s) { StepSize = s; } /// get the value of the maximum property double InputField::maximum(void) const { return Maximum; } /// set the value of the maximum property void InputField::setMaximum(double m) { Maximum = m; } /// get the value of the minimum property double InputField::minimum(void) const { return Minimum; } /// set the value of the minimum property void InputField::setMinimum(double m) { Minimum = m; } void InputField::setUnitText(QString str) { UnitStr = str; setText(QString("%1 %2").arg(Value).arg(UnitStr)); } QString InputField::getUnitText(void) { return UnitStr; } // get the value of the minimum property int InputField::historySize(void) const { return HistorySize; } // set the value of the minimum property void InputField::setHistorySize(int i) { HistorySize = i; } // -------------------------------------------------------------------- ExpressionLineEdit::ExpressionLineEdit(QWidget* parent) : QLineEdit(parent) , exactMatch {false} { completer = new QCompleter(this); connect(this, &QLineEdit::textEdited, this, &ExpressionLineEdit::slotTextChanged); } void ExpressionLineEdit::setExactMatch(bool enabled) { exactMatch = enabled; if (completer) { completer->setFilterMode(exactMatch ? Qt::MatchStartsWith : Qt::MatchContains); } } void ExpressionLineEdit::slotTextChanged(const QString& text) { Q_EMIT textChanged2(text, cursorPosition()); } void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, bool isActivated) { Q_UNUSED(completionPrefix) Q_UNUSED(isActivated) } void ExpressionLineEdit::slotCompleteTextHighlighted(const QString& completionPrefix) { slotCompleteText(completionPrefix, false); } void ExpressionLineEdit::slotCompleteTextSelected(const QString& completionPrefix) { slotCompleteText(completionPrefix, true); } void ExpressionLineEdit::keyPressEvent(QKeyEvent* e) { QLineEdit::keyPressEvent(e); } void ExpressionLineEdit::contextMenuEvent(QContextMenuEvent* event) { QMenu* menu = createStandardContextMenu(); if (completer) { menu->addSeparator(); QAction* match = menu->addAction(tr("Exact match")); match->setCheckable(true); match->setChecked(completer->filterMode() == Qt::MatchStartsWith); QObject::connect(match, &QAction::toggled, this, &Gui::ExpressionLineEdit::setExactMatch); } menu->setAttribute(Qt::WA_DeleteOnClose); menu->popup(event->globalPos()); } // -------------------------------------------------------------------- namespace Base { Unit::Unit() {} Unit::Unit(const QString& u) : unit(u) {} bool Unit::isEmpty() const { return unit.isEmpty(); } bool Unit::operator==(const Unit& that) { return this->unit == that.unit; } bool Unit::operator!=(const Unit& that) { return this->unit != that.unit; } const QString& Unit::getString() const { return unit; } int QuantityFormat::defaultDenominator = 8; // for 1/8" QuantityFormat::QuantityFormat() : option(OmitGroupSeparator | RejectGroupSeparator) , format(Fixed) , precision(4) , denominator(defaultDenominator) {} Quantity::Quantity() : value(0) , unit() {} Quantity::Quantity(double v, const Unit& u) : value(v) , unit(u) {} Quantity Quantity::parse(const QString& str) { bool ok; QString txt = str; QString unit; while (!txt.isEmpty() && txt[txt.length() - 1].isLetter()) { unit.prepend(txt[txt.length() - 1]); txt.chop(1); } double v = QLocale::system().toDouble(txt, &ok); // if (!ok && !txt.isEmpty()) // throw Base::Exception(); return Quantity(v, Unit(unit)); } void Quantity::setValue(double v) { value = v; } double Quantity::getValue() const { return value; } void Quantity::setUnit(const Unit& u) { unit = u; } Unit Quantity::getUnit() const { return unit; } QString Quantity::getUserString() const { QLocale Lc; const QuantityFormat& format = getFormat(); if (format.option != QuantityFormat::None) { uint opt = static_cast(format.option); Lc.setNumberOptions(static_cast(opt)); } QString Ln = Lc.toString(value, format.toFormat(), format.precision); return QString::fromUtf8("%1 %2").arg(Ln, unit.getString()); } QString Quantity::getUserString(double& factor, QString& unitString) const { factor = 1; unitString = unit.getString(); QLocale Lc; const QuantityFormat& format = getFormat(); if (format.option != QuantityFormat::None) { uint opt = static_cast(format.option); Lc.setNumberOptions(static_cast(opt)); } QString Ln = Lc.toString(value, format.toFormat(), format.precision); return QString::fromUtf8("%1 %2").arg(Ln, unit.getString()); } } // namespace Base namespace Gui { class QuantitySpinBoxPrivate { public: QuantitySpinBoxPrivate() : validInput(true) , pendingEmit(false) , unitValue(0) , maximum(std::numeric_limits::max()) , minimum(-std::numeric_limits::max()) , singleStep(1.0) {} ~QuantitySpinBoxPrivate() {} QString stripped(const QString& t, int* pos) const { QString text = t; const int s = text.size(); text = text.trimmed(); if (pos) { (*pos) -= (s - text.size()); } return text; } bool validate(QString& input, Base::Quantity& result) const { bool success = false; QString tmp = input; int pos = 0; QValidator::State state; Base::Quantity res = validateAndInterpret(tmp, pos, state); res.setFormat(quantity.getFormat()); if (state == QValidator::Acceptable) { success = true; result = res; input = tmp; } else if (state == QValidator::Intermediate) { tmp = tmp.trimmed(); tmp += QLatin1Char(' '); tmp += unitStr; Base::Quantity res2 = validateAndInterpret(tmp, pos, state); res2.setFormat(quantity.getFormat()); if (state == QValidator::Acceptable) { success = true; result = res2; input = tmp; } } return success; } Base::Quantity validateAndInterpret(QString& input, int& pos, QValidator::State& state) const { Base::Quantity res; const double max = this->maximum; const double min = this->minimum; QString copy = input; int len = copy.size(); const bool plus = max >= 0; const bool minus = min <= 0; switch (len) { case 0: state = max != min ? QValidator::Intermediate : QValidator::Invalid; goto end; case 1: if (copy.at(0) == locale.decimalPoint()) { state = QValidator::Intermediate; copy.prepend(QLatin1Char('0')); pos++; len++; goto end; } else if (copy.at(0) == QLatin1Char('+')) { // the quantity parser doesn't allow numbers of the form '+1.0' state = QValidator::Invalid; goto end; } else if (copy.at(0) == QLatin1Char('-')) { if (minus) { state = QValidator::Intermediate; } else { state = QValidator::Invalid; } goto end; } break; case 2: if (copy.at(1) == locale.decimalPoint() && (plus && copy.at(0) == QLatin1Char('+'))) { state = QValidator::Intermediate; goto end; } if (copy.at(1) == locale.decimalPoint() && (minus && copy.at(0) == QLatin1Char('-'))) { state = QValidator::Intermediate; copy.insert(1, QLatin1Char('0')); pos++; len++; goto end; } break; default: break; } { if (copy.at(0) == locale.groupSeparator()) { state = QValidator::Invalid; goto end; } else if (len > 1) { bool decOccurred = false; for (int i = 0; i < copy.size(); i++) { if (copy.at(i) == locale.decimalPoint()) { // Disallow multiple decimal points within the same numeric substring if (decOccurred) { state = QValidator::Invalid; goto end; } decOccurred = true; } // Reset decOcurred if non-numeric character found else if (!(copy.at(i) == locale.groupSeparator() || copy.at(i).isDigit())) { decOccurred = false; } } } bool ok = false; double value = min; QChar plus = QLatin1Char('+'), minus = QLatin1Char('-'); if (locale.negativeSign() != minus) { copy.replace(locale.negativeSign(), minus); } if (locale.positiveSign() != plus) { copy.replace(locale.positiveSign(), plus); } try { QString copy2 = copy; copy2.remove(locale.groupSeparator()); res = Base::Quantity::parse(copy2); value = res.getValue(); ok = true; } catch (Base::Exception&) { } if (!ok) { // input may not be finished state = QValidator::Intermediate; } else if (value >= min && value <= max) { if (copy.endsWith(locale.decimalPoint())) { // input shouldn't end with a decimal point state = QValidator::Intermediate; } else if (res.getUnit().isEmpty() && !this->unit.isEmpty()) { // if not dimensionless the input should have a dimension state = QValidator::Intermediate; } else if (res.getUnit() != this->unit) { state = QValidator::Invalid; } else { state = QValidator::Acceptable; } } else if (max == min) { // when max and min is the same the only non-Invalid input is // max (or min) state = QValidator::Invalid; } else { if ((value >= 0 && value > max) || (value < 0 && value < min)) { state = QValidator::Invalid; } else { state = QValidator::Intermediate; } } } end: if (state != QValidator::Acceptable) { res.setValue(max > 0 ? min : max); } input = copy; return res; } QLocale locale; bool validInput; bool pendingEmit; QString validStr; Base::Quantity quantity; Base::Quantity cached; Base::Unit unit; double unitValue; QString unitStr; double maximum; double minimum; double singleStep; }; } // namespace Gui QuantitySpinBox::QuantitySpinBox(QWidget* parent) : QAbstractSpinBox(parent) , d_ptr(new QuantitySpinBoxPrivate()) { d_ptr->locale = locale(); this->setContextMenuPolicy(Qt::DefaultContextMenu); connect(lineEdit(), &QLineEdit::textChanged, this, &QuantitySpinBox::userInput); connect(this, &QuantitySpinBox::editingFinished, this, &QuantitySpinBox::handlePendingEmit); } QuantitySpinBox::~QuantitySpinBox() {} void QuantitySpinBox::resizeEvent(QResizeEvent* event) { QAbstractSpinBox::resizeEvent(event); } void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent* event) { QAbstractSpinBox::keyPressEvent(event); } void QuantitySpinBox::updateText(const Base::Quantity& quant) { Q_D(QuantitySpinBox); double dFactor; QString txt = getUserString(quant, dFactor, d->unitStr); d->unitValue = quant.getValue() / dFactor; lineEdit()->setText(txt); handlePendingEmit(); } Base::Quantity QuantitySpinBox::value() const { Q_D(const QuantitySpinBox); return d->quantity; } double QuantitySpinBox::rawValue() const { Q_D(const QuantitySpinBox); return d->quantity.getValue(); } 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; } // Gets called after call of 'validateAndInterpret' void QuantitySpinBox::userInput(const QString& text) { Q_D(QuantitySpinBox); d->pendingEmit = true; QString tmp = text; Base::Quantity res; if (d->validate(tmp, res)) { d->validStr = tmp; d->validInput = true; } else { d->validInput = false; return; } if (keyboardTracking()) { d->cached = res; handlePendingEmit(); } else { d->cached = res; } } void QuantitySpinBox::handlePendingEmit() { updateFromCache(true); } void QuantitySpinBox::updateFromCache(bool notify) { Q_D(QuantitySpinBox); if (d->pendingEmit) { double factor; const Base::Quantity& res = d->cached; QString text = getUserString(res, factor, d->unitStr); d->unitValue = res.getValue() / factor; d->quantity = res; // signaling if (notify) { d->pendingEmit = false; valueChanged(res); valueChanged(res.getValue()); textChanged(text); } } } 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) { try { Base::Quantity quant = Base::Quantity::parse(str); setUnit(quant.getUnit()); } catch (const Base::Exception&) { } } 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; } int QuantitySpinBox::decimals() const { Q_D(const QuantitySpinBox); return d->quantity.getFormat().precision; } void QuantitySpinBox::setDecimals(int v) { Q_D(QuantitySpinBox); Base::QuantityFormat f = d->quantity.getFormat(); f.precision = v; d->quantity.setFormat(f); updateText(d->quantity); } void QuantitySpinBox::clearSchema() { Q_D(QuantitySpinBox); updateText(d->quantity); } QString QuantitySpinBox::getUserString(const Base::Quantity& val, double& factor, QString& unitString) const { return val.getUserString(factor, unitString); } QString QuantitySpinBox::getUserString(const Base::Quantity& val) const { return val.getUserString(); } 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); updateFromCache(false); 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; } lineEdit()->setText(QString::fromUtf8("%L1 %2").arg(val).arg(d->unitStr)); updateFromCache(true); update(); selectNumber(); } QSize QuantitySpinBox::sizeHint() const { ensurePolished(); const QFontMetrics fm(fontMetrics()); int frameWidth = lineEdit()->style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth); int iconHeight = fm.height() - frameWidth; int h = lineEdit()->sizeHint().height(); int w = 0; QString s = QLatin1String("000000000000000000"); QString fixedContent = QLatin1String(" "); s += fixedContent; w = fm.horizontalAdvance(s); w += 2; // cursor blinking space w += iconHeight; QStyleOptionSpinBox opt; initStyleOption(&opt); QSize hint(w, h); QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this); return size; } QSize QuantitySpinBox::minimumSizeHint() const { ensurePolished(); const QFontMetrics fm(fontMetrics()); int frameWidth = lineEdit()->style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth); int iconHeight = fm.height() - frameWidth; int h = lineEdit()->minimumSizeHint().height(); int w = 0; QString s = QLatin1String("000000000000000000"); QString fixedContent = QLatin1String(" "); s += fixedContent; w = fm.horizontalAdvance(s); w += 2; // cursor blinking space w += iconHeight; QStyleOptionSpinBox opt; initStyleOption(&opt); QSize hint(w, h); QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this); return size; } void QuantitySpinBox::showEvent(QShowEvent* event) { Q_D(QuantitySpinBox); QAbstractSpinBox::showEvent(event); bool selected = lineEdit()->hasSelectedText(); updateText(d->quantity); if (selected) { selectNumber(); } } void QuantitySpinBox::hideEvent(QHideEvent* event) { handlePendingEmit(); QAbstractSpinBox::hideEvent(event); } void QuantitySpinBox::closeEvent(QCloseEvent* event) { handlePendingEmit(); QAbstractSpinBox::closeEvent(event); } void QuantitySpinBox::focusInEvent(QFocusEvent* event) { bool hasSel = lineEdit()->hasSelectedText(); QAbstractSpinBox::focusInEvent(event); if (event->reason() == Qt::TabFocusReason || event->reason() == Qt::BacktabFocusReason || event->reason() == Qt::ShortcutFocusReason) { if (!hasSel) { selectNumber(); } } } void QuantitySpinBox::focusOutEvent(QFocusEvent* event) { Q_D(QuantitySpinBox); int pos = 0; QString text = lineEdit()->text(); QValidator::State state; d->validateAndInterpret(text, pos, state); if (state != QValidator::Acceptable) { lineEdit()->setText(d->validStr); } handlePendingEmit(); QToolTip::hideText(); QAbstractSpinBox::focusOutEvent(event); } void QuantitySpinBox::clear() { QAbstractSpinBox::clear(); } void QuantitySpinBox::selectNumber() { QString expr = QString::fromLatin1("^([%1%2]?[0-9\\%3]*)\\%4?([0-9]+(%5[%1%2]?[0-9]+)?)") .arg(locale().negativeSign()) .arg(locale().positiveSign()) .arg(locale().groupSeparator()) .arg(locale().decimalPoint()) .arg(locale().exponential()); auto rmatch = QRegularExpression(expr).match(lineEdit()->text()); if (rmatch.hasMatch()) { lineEdit()->setSelection(0, rmatch.capturedLength()); } } QString QuantitySpinBox::textFromValue(const Base::Quantity& value) const { double factor; QString unitStr; QString str = getUserString(value, 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; Base::Quantity quant = d->validateAndInterpret(copy, pos, state); if (state != QValidator::Acceptable) { fixup(copy); quant = d->validateAndInterpret(copy, pos, state); } return quant; } 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()); } // ------------------------------------------------------------------------------ PrefUnitSpinBox::PrefUnitSpinBox(QWidget* parent) : QuantitySpinBox(parent) {} PrefUnitSpinBox::~PrefUnitSpinBox() {} QByteArray PrefUnitSpinBox::entryName() const { return m_sPrefName; } QByteArray PrefUnitSpinBox::paramGrpPath() const { return m_sPrefGrp; } void PrefUnitSpinBox::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefUnitSpinBox::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- PrefQuantitySpinBox::PrefQuantitySpinBox(QWidget* parent) : QuantitySpinBox(parent) {} PrefQuantitySpinBox::~PrefQuantitySpinBox() {} QByteArray PrefQuantitySpinBox::entryName() const { return m_sPrefName; } QByteArray PrefQuantitySpinBox::paramGrpPath() const { return m_sPrefGrp; } void PrefQuantitySpinBox::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefQuantitySpinBox::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- CommandIconView::CommandIconView(QWidget* parent) : QListWidget(parent) { connect(this, &QListWidget::currentItemChanged, this, &CommandIconView::onSelectionChanged); } CommandIconView::~CommandIconView() {} void CommandIconView::startDrag(Qt::DropActions /*supportedActions*/) { QList items = selectedItems(); QByteArray itemData; QDataStream dataStream(&itemData, QIODevice::WriteOnly); QPixmap pixmap; dataStream << items.count(); for (QList::ConstIterator it = items.begin(); it != items.end(); ++it) { if (it == items.begin()) { pixmap = ((*it)->data(Qt::UserRole)).value(); } dataStream << (*it)->text(); } QMimeData* mimeData = new QMimeData; mimeData->setData("text/x-action-items", itemData); QDrag* drag = new QDrag(this); drag->setMimeData(mimeData); drag->setHotSpot(QPoint(pixmap.width() / 2, pixmap.height() / 2)); drag->setPixmap(pixmap); drag->exec(Qt::MoveAction); } void CommandIconView::onSelectionChanged(QListWidgetItem* item, QListWidgetItem*) { if (item) { emitSelectionChanged(item->toolTip()); } } // ------------------------------------------------------------------------------ namespace Gui { class UnsignedValidator: public QValidator { public: UnsignedValidator(QObject* parent); UnsignedValidator(uint minimum, uint maximum, QObject* parent); ~UnsignedValidator(); QValidator::State validate(QString&, int&) const; void setBottom(uint); void setTop(uint); virtual void setRange(uint bottom, uint top); uint bottom() const { return b; } uint top() const { return t; } private: uint b, t; }; UnsignedValidator::UnsignedValidator(QObject* parent) : QValidator(parent) { b = 0; t = std::numeric_limits::max(); } UnsignedValidator::UnsignedValidator(uint minimum, uint maximum, QObject* parent) : QValidator(parent) { b = minimum; t = maximum; } UnsignedValidator::~UnsignedValidator() {} QValidator::State UnsignedValidator::validate(QString& input, int&) const { QString stripped; // = input.stripWhiteSpace(); if (stripped.isEmpty()) { return Intermediate; } bool ok; uint entered = input.toUInt(&ok); if (!ok) { return Invalid; } else if (entered < b) { return Intermediate; } else if (entered > t) { return Invalid; } // else if ( entered < b || entered > t ) // return Invalid; else { return Acceptable; } } void UnsignedValidator::setRange(uint minimum, uint maximum) { b = minimum; t = maximum; } void UnsignedValidator::setBottom(uint bottom) { setRange(bottom, top()); } void UnsignedValidator::setTop(uint top) { setRange(bottom(), top); } class UIntSpinBoxPrivate { public: UnsignedValidator* mValidator; UIntSpinBoxPrivate() : mValidator(0) {} unsigned mapToUInt(int v) const { using int_limits = std::numeric_limits; using uint_limits = std::numeric_limits; unsigned ui; if (v == int_limits::min()) { ui = 0; } else if (v == int_limits::max()) { ui = uint_limits::max(); } else if (v < 0) { v -= int_limits::min(); ui = static_cast(v); } else { ui = static_cast(v); ui -= int_limits::min(); } return ui; } int mapToInt(unsigned v) const { using int_limits = std::numeric_limits; using uint_limits = std::numeric_limits; int in; if (v == uint_limits::max()) { in = int_limits::max(); } else if (v == 0) { in = int_limits::min(); } else if (v > int_limits::max()) { v += int_limits::min(); in = static_cast(v); } else { in = v; in += int_limits::min(); } return in; } }; } // namespace Gui // ------------------------------------------------------------- UIntSpinBox::UIntSpinBox(QWidget* parent) : QSpinBox(parent) { d = new UIntSpinBoxPrivate; d->mValidator = new UnsignedValidator(this->minimum(), this->maximum(), this); connect(this, qOverload(&QSpinBox::valueChanged), this, &UIntSpinBox::valueChange); setRange(0, 99); setValue(0); updateValidator(); } UIntSpinBox::~UIntSpinBox() { delete d->mValidator; delete d; d = 0; } void UIntSpinBox::setRange(uint minVal, uint maxVal) { int iminVal = d->mapToInt(minVal); int imaxVal = d->mapToInt(maxVal); QSpinBox::setRange(iminVal, imaxVal); updateValidator(); } QValidator::State UIntSpinBox::validate(QString& input, int& pos) const { return d->mValidator->validate(input, pos); } uint UIntSpinBox::value() const { return d->mapToUInt(QSpinBox::value()); } void UIntSpinBox::setValue(uint value) { QSpinBox::setValue(d->mapToInt(value)); } void UIntSpinBox::valueChange(int value) { unsignedChanged(d->mapToUInt(value)); } uint UIntSpinBox::minimum() const { return d->mapToUInt(QSpinBox::minimum()); } void UIntSpinBox::setMinimum(uint minVal) { uint maxVal = maximum(); if (maxVal < minVal) { maxVal = minVal; } setRange(minVal, maxVal); } uint UIntSpinBox::maximum() const { return d->mapToUInt(QSpinBox::maximum()); } void UIntSpinBox::setMaximum(uint maxVal) { uint minVal = minimum(); if (minVal > maxVal) { minVal = maxVal; } setRange(minVal, maxVal); } QString UIntSpinBox::textFromValue(int v) const { uint val = d->mapToUInt(v); QString s; s.setNum(val); return s; } int UIntSpinBox::valueFromText(const QString& text) const { bool ok; QString s = text; uint newVal = s.toUInt(&ok); if (!ok && !(prefix().isEmpty() && suffix().isEmpty())) { s = cleanText(); newVal = s.toUInt(&ok); } return d->mapToInt(newVal); } void UIntSpinBox::updateValidator() { d->mValidator->setRange(this->minimum(), this->maximum()); } // -------------------------------------------------------------------- IntSpinBox::IntSpinBox(QWidget* parent) : QSpinBox(parent) {} IntSpinBox::~IntSpinBox() {} // -------------------------------------------------------------------- PrefSpinBox::PrefSpinBox(QWidget* parent) : QSpinBox(parent) {} PrefSpinBox::~PrefSpinBox() {} QByteArray PrefSpinBox::entryName() const { return m_sPrefName; } QByteArray PrefSpinBox::paramGrpPath() const { return m_sPrefGrp; } void PrefSpinBox::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefSpinBox::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- DoubleSpinBox::DoubleSpinBox(QWidget* parent) : QDoubleSpinBox(parent) {} DoubleSpinBox::~DoubleSpinBox() {} // -------------------------------------------------------------------- PrefDoubleSpinBox::PrefDoubleSpinBox(QWidget* parent) : QDoubleSpinBox(parent) {} PrefDoubleSpinBox::~PrefDoubleSpinBox() {} QByteArray PrefDoubleSpinBox::entryName() const { return m_sPrefName; } QByteArray PrefDoubleSpinBox::paramGrpPath() const { return m_sPrefGrp; } void PrefDoubleSpinBox::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefDoubleSpinBox::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // ------------------------------------------------------------- ColorButton::ColorButton(QWidget* parent) : QPushButton(parent) , _allowChange(true) , _allowTransparency(false) , _drawFrame(true) { _col = palette().color(QPalette::Active, QPalette::Midlight); connect(this, &ColorButton::clicked, this, &ColorButton::onChooseColor); } ColorButton::~ColorButton() {} void ColorButton::setColor(const QColor& c) { _col = c; update(); } QColor ColorButton::color() const { return _col; } void ColorButton::setAllowChangeColor(bool ok) { _allowChange = ok; } bool ColorButton::allowChangeColor() const { return _allowChange; } void ColorButton::setAllowTransparency(bool ok) { _allowTransparency = ok; } bool ColorButton::allowTransparency() const { return _allowTransparency; } void ColorButton::setDrawFrame(bool ok) { _drawFrame = ok; } bool ColorButton::drawFrame() const { return _drawFrame; } void ColorButton::paintEvent(QPaintEvent* e) { // first paint the complete button QPushButton::paintEvent(e); // repaint the rectangle area QPalette::ColorGroup group = isEnabled() ? hasFocus() ? QPalette::Active : QPalette::Inactive : QPalette::Disabled; QColor pen = palette().color(group, QPalette::ButtonText); { QPainter paint(this); paint.setPen(pen); if (_drawFrame) { paint.setBrush(QBrush(_col)); paint.drawRect(5, 5, width() - 10, height() - 10); } else { paint.fillRect(5, 5, width() - 10, height() - 10, QBrush(_col)); } } // overpaint the rectangle to paint icon and text QStyleOptionButton opt; opt.initFrom(this); opt.text = text(); opt.icon = icon(); opt.iconSize = iconSize(); QStylePainter p(this); p.drawControl(QStyle::CE_PushButtonLabel, opt); } void ColorButton::onChooseColor() { if (!_allowChange) { return; } QColor c = QColorDialog::getColor(_col, this); if (c.isValid()) { setColor(c); Q_EMIT changed(); } } // ------------------------------------------------------------------------------ PrefColorButton::PrefColorButton(QWidget* parent) : ColorButton(parent) {} PrefColorButton::~PrefColorButton() {} QByteArray PrefColorButton::entryName() const { return m_sPrefName; } QByteArray PrefColorButton::paramGrpPath() const { return m_sPrefGrp; } void PrefColorButton::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefColorButton::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- PrefLineEdit::PrefLineEdit(QWidget* parent) : QLineEdit(parent) {} PrefLineEdit::~PrefLineEdit() {} QByteArray PrefLineEdit::entryName() const { return m_sPrefName; } QByteArray PrefLineEdit::paramGrpPath() const { return m_sPrefGrp; } void PrefLineEdit::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefLineEdit::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- PrefComboBox::PrefComboBox(QWidget* parent) : QComboBox(parent) { setEditable(false); } PrefComboBox::~PrefComboBox() {} QByteArray PrefComboBox::entryName() const { return m_sPrefName; } QByteArray PrefComboBox::paramGrpPath() const { return m_sPrefGrp; } void PrefComboBox::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefComboBox::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- PrefCheckBox::PrefCheckBox(QWidget* parent) : QCheckBox(parent) { setText("CheckBox"); } PrefCheckBox::~PrefCheckBox() {} QByteArray PrefCheckBox::entryName() const { return m_sPrefName; } QByteArray PrefCheckBox::paramGrpPath() const { return m_sPrefGrp; } void PrefCheckBox::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefCheckBox::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- PrefRadioButton::PrefRadioButton(QWidget* parent) : QRadioButton(parent) { setText("RadioButton"); } PrefRadioButton::~PrefRadioButton() {} QByteArray PrefRadioButton::entryName() const { return m_sPrefName; } QByteArray PrefRadioButton::paramGrpPath() const { return m_sPrefGrp; } void PrefRadioButton::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefRadioButton::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- PrefSlider::PrefSlider(QWidget* parent) : QSlider(parent) {} PrefSlider::~PrefSlider() {} QByteArray PrefSlider::entryName() const { return m_sPrefName; } QByteArray PrefSlider::paramGrpPath() const { return m_sPrefGrp; } void PrefSlider::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefSlider::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; } // -------------------------------------------------------------------- PrefFontBox::PrefFontBox(QWidget* parent) : QFontComboBox(parent) {} PrefFontBox::~PrefFontBox() {} QByteArray PrefFontBox::entryName() const { return m_sPrefName; } QByteArray PrefFontBox::paramGrpPath() const { return m_sPrefGrp; } void PrefFontBox::setEntryName(const QByteArray& name) { m_sPrefName = name; } void PrefFontBox::setParamGrpPath(const QByteArray& name) { m_sPrefGrp = name; }