diff --git a/src/Gui/EditableDatumLabel.cpp b/src/Gui/EditableDatumLabel.cpp index dfe2df7786..bf09bf8362 100644 --- a/src/Gui/EditableDatumLabel.cpp +++ b/src/Gui/EditableDatumLabel.cpp @@ -29,9 +29,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -41,6 +43,8 @@ #include #include "EditableDatumLabel.h" +#include "Base/Console.h" +#include "Gui/QuantitySpinBox.h" using namespace Gui; @@ -175,6 +179,8 @@ void EditableDatumLabel::startEdit(double val, QObject* eventFilteringObj, bool spinBox->setAutoNormalize(false); spinBox->setKeyboardTracking(true); spinBox->installEventFilter(this); + spinBox->setAutoAdjustWidth(true); + spinBox->setMaxExpectedDigits(16); if (eventFilteringObj) { spinBox->installEventFilter(eventFilteringObj); @@ -185,10 +191,7 @@ void EditableDatumLabel::startEdit(double val, QObject* eventFilteringObj, bool } spinBox->show(); - setSpinboxValue(val); - // Note: adjustSize apparently uses the Min/Max values to set the size. So if we don't set them - // to INT_MAX, the spinbox are much too big. - spinBox->adjustSize(); + updateGeometry(); setFocusToSpinbox(); const auto validateAndFinish = [this]() { @@ -217,6 +220,9 @@ void EditableDatumLabel::startEdit(double val, QObject* eventFilteringObj, bool }; connect(spinBox, qOverload(&QuantitySpinBox::valueChanged), this, validateAndFinish); + if (auto* edit = spinBox->findChild()) { + connect(edit, &QLineEdit::textChanged, this, &EditableDatumLabel::updateGeometry); + } } bool EditableDatumLabel::eventFilter(QObject* watched, QEvent* event) @@ -364,8 +370,9 @@ void EditableDatumLabel::positionSpinbox() // Update lock icon position inside the spinbox if it exists and is visible if (lockIconLabel && lockIconLabel->isVisible()) { - int iconSize = 14; - int padding = 4; + const QFontMetrics fm(spinBox->fontMetrics()); + int iconSize = fm.height(); + int padding = spinBox->getMargin(); QSize spinboxSize = spinBox->size(); lockIconLabel->setGeometry( spinboxSize.width() - iconSize - padding, @@ -424,6 +431,14 @@ void EditableDatumLabel::setPlacement(const Base::Placement& plc) label->norm.setValue(SbVec3f(float(RN.x), float(RN.y), float(RN.z))); } +void EditableDatumLabel::updateGeometry() +{ + if (!spinBox) { + return; + } + spinBox->adjustSize(); +} + // NOLINTNEXTLINE void EditableDatumLabel::setColor(SbColor color) { @@ -510,56 +525,47 @@ void EditableDatumLabel::setSpinboxVisibleToMouse(bool val) void EditableDatumLabel::setLockedAppearance(bool locked) { + if (!locked) { + this->hasFinishedEditing = false; + } + if (!spinBox) { + return; + } + const QFontMetrics fm(spinBox->fontMetrics()); + int iconSize = fm.height(); + int padding = spinBox->getMargin(); if (locked) { - if (spinBox) { + spinBox->addIconSpace(true); + spinBox->adjustSize(); + // create lock icon label it it doesn't exist, if it does - show it + if (!lockIconLabel) { + lockIconLabel = new QLabel(spinBox); + lockIconLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true); + lockIconLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - // create lock icon label it it doesn't exist, if it does - show it - if (!lockIconLabel) { - lockIconLabel = new QLabel(spinBox); - lockIconLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true); - lockIconLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + // load icon and scale it to fit in spinbox + QPixmap lockIcon = Gui::BitmapFactory().pixmap("Constraint_Lock"); + QPixmap scaledIcon + = lockIcon.scaled(iconSize, iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + lockIconLabel->setPixmap(scaledIcon); - // load icon and scale it to fit in spinbox - QPixmap lockIcon = Gui::BitmapFactory().pixmap("Constraint_Lock"); - QPixmap scaledIcon - = lockIcon.scaled(14, 14, Qt::KeepAspectRatio, Qt::SmoothTransformation); - lockIconLabel->setPixmap(scaledIcon); - - // position lock icon inside the spinbox - int iconSize = 14; - int padding = 4; - QSize spinboxSize = spinBox->size(); - lockIconLabel->setGeometry( - spinboxSize.width() - iconSize - padding, - (spinboxSize.height() - iconSize) / 2, - iconSize, - iconSize - ); - // style spinbox and add padding for lock - QString styleSheet = QString::fromLatin1( - "QSpinBox { " - "padding-right: %1px; " - "}" - ) - .arg(iconSize + padding + 2); - - spinBox->setStyleSheet(styleSheet); - } - - lockIconLabel->show(); + // position lock icon inside the spinbox + QSize spinboxSize = spinBox->size(); + lockIconLabel->setGeometry( + spinboxSize.width() - iconSize - padding, + (spinboxSize.height() - iconSize) / 2, + iconSize, + iconSize + ); } + lockIconLabel->show(); } else { - this->hasFinishedEditing = false; - - // if spinbox exists, reset its appearance - if (spinBox) { - spinBox->setStyleSheet(QString()); - - // hide lock icon if it exists for later reuse - if (lockIconLabel) { - lockIconLabel->hide(); - } + // hide lock icon if it exists for later reuse + if (lockIconLabel) { + lockIconLabel->hide(); + spinBox->addIconSpace(false); + spinBox->adjustSize(); } } } diff --git a/src/Gui/EditableDatumLabel.h b/src/Gui/EditableDatumLabel.h index 05dd83b7bb..ff569bf718 100644 --- a/src/Gui/EditableDatumLabel.h +++ b/src/Gui/EditableDatumLabel.h @@ -92,6 +92,7 @@ public: void setLockedAppearance(bool locked); ///< Sets visual appearance to indicate locked state ///< (finished editing) void resetLockedState(); ///< Resets both hasFinishedEditing flag and locked appearance + void updateGeometry(); Function getFunction(); diff --git a/src/Gui/QuantitySpinBox.cpp b/src/Gui/QuantitySpinBox.cpp index 608fab4e0f..f0bbed169b 100644 --- a/src/Gui/QuantitySpinBox.cpp +++ b/src/Gui/QuantitySpinBox.cpp @@ -66,6 +66,9 @@ public: , pendingEmit(false) , normalize(true) , checkRangeInExpression(false) + , adjustableWidth(false) + , maxExpectedDigits(4) + , addIconSpace(false) , unitValue(0) , maximum(std::numeric_limits::max()) , minimum(-std::numeric_limits::max()) @@ -306,6 +309,9 @@ public: bool pendingEmit; bool normalize; bool checkRangeInExpression; + bool adjustableWidth; + int maxExpectedDigits; + bool addIconSpace; QString validStr; Base::Quantity quantity; Base::Quantity cached; @@ -341,6 +347,9 @@ void QuantitySpinBox::bind(const App::ObjectIdentifier& _path) void QuantitySpinBox::showIcon() { + addIconSpace(true); + + adjustSize(); iconLabel->show(); } @@ -444,6 +453,19 @@ void QuantitySpinBox::resizeEvent(QResizeEvent* event) { QAbstractSpinBox::resizeEvent(event); resizeWidget(); + moveCursor(); +} + +void QuantitySpinBox::moveCursor() +{ + Q_D(QuantitySpinBox); + QLineEdit* edit = lineEdit(); + int cursor = edit->cursorPosition(); + const int maxCursor = qMax(0, edit->displayText().size() - 1 /*space*/ - d->unitStr.size()); + + if (cursor > maxCursor) { + edit->setCursorPosition(maxCursor); + } } void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent* event) @@ -467,6 +489,9 @@ void Gui::QuantitySpinBox::keyPressEvent(QKeyEvent* event) if (isEnter) { returnPressed(); } + else { + moveCursor(); + } } void Gui::QuantitySpinBox::paintEvent(QPaintEvent*) @@ -504,7 +529,7 @@ void QuantitySpinBox::updateEdit(const QString& text) edit->setSelection(0, cursor); } else { - edit->setCursorPosition(empty ? 0 : cursor); + moveCursor(); } } @@ -601,6 +626,42 @@ void QuantitySpinBox::setAutoNormalize(bool normalize) d->normalize = normalize; } +bool QuantitySpinBox::autoAdjustWidth() const +{ + Q_D(const QuantitySpinBox); + return d->adjustableWidth; +} + +void QuantitySpinBox::setAutoAdjustWidth(bool adjust) +{ + Q_D(QuantitySpinBox); + d->adjustableWidth = adjust; +} + +bool QuantitySpinBox::isIconSpaceAdded() const +{ + Q_D(const QuantitySpinBox); + return d->addIconSpace; +} + +void QuantitySpinBox::addIconSpace(bool addIconSpace) +{ + Q_D(QuantitySpinBox); + d->addIconSpace = addIconSpace; +} + +int QuantitySpinBox::getMaxExpectedDigits() +{ + Q_D(const QuantitySpinBox); + return d->maxExpectedDigits; +} + +void QuantitySpinBox::setMaxExpectedDigits(int digits) +{ + Q_D(QuantitySpinBox); + d->maxExpectedDigits = digits; +} + bool QuantitySpinBox::hasValidInput() const { Q_D(const QuantitySpinBox); @@ -611,7 +672,6 @@ bool QuantitySpinBox::hasValidInput() const void QuantitySpinBox::userInput(const QString& text) { Q_D(QuantitySpinBox); - d->pendingEmit = true; QString tmp = text; @@ -911,44 +971,57 @@ QSize QuantitySpinBox::sizeForText(const QString& txt) const QSize QuantitySpinBox::sizeHint() const { - return sizeHintCalculator(lineEdit()->sizeHint().height()); + Q_D(const QuantitySpinBox); + auto le = lineEdit(); + if (le && d->adjustableWidth) { + // limit number of typed characters to keep unit visible + le->setMaxLength(getMaxStrLength(d->maxExpectedDigits)); + } + return sizeHintForDigits(d->maxExpectedDigits); } -QSize QuantitySpinBox::minimumSizeHint() const -{ - return sizeHintCalculator(lineEdit()->minimumSizeHint().height()); -} - -QSize QuantitySpinBox::sizeHintCalculator(int h) const +QSize QuantitySpinBox::sizeHintForDigits(int digits) const { Q_D(const QuantitySpinBox); ensurePolished(); + const int maxLen = getMaxStrLength(digits); + int length = maxLen; + + if (d->adjustableWidth) { + int currenLen = qMax(lineEdit()->text().length(), 4); + length = currenLen < maxLen ? currenLen : maxLen; + } + + QString longestString = QStringLiteral("8").repeated(length); + const QFontMetrics fm(fontMetrics()); - int w = 0; - constexpr int maxStrLen = 9; - - QString s; - QString fixedContent = QLatin1String(" "); - - Base::Quantity q(d->quantity); - q.setValue(d->maximum); - s = textFromValue(q); - s.truncate(maxStrLen); - s += fixedContent; - w = qMax(w, QtTools::horizontalAdvance(fm, s)); - - w += 2; // cursor blinking space - w += iconHeight; - + int w = qMax(0, QtTools::horizontalAdvance(fm, longestString)); + w += 4; // cursor blinking space + if (d->addIconSpace) { + w += iconHeight; + } QStyleOptionSpinBox opt; initStyleOption(&opt); - QSize hint(w, h); + QSize hint(w, lineEdit()->sizeHint().height()); QSize size = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this); return size; } +int QuantitySpinBox::getMaxStrLength(int digits) const +{ + // Calculates the length of the longest string allowed + Q_D(const QuantitySpinBox); + ensurePolished(); + QString unit = QString::fromStdString(d->unit.getString()); + int decimals = App::GetApplication() + .GetUserParameter() + .GetGroup("BaseApp/Preferences/Units") + ->GetInt("Decimals", 2); + return digits + 1 /*separator*/ + decimals + 1 /*space*/ + unit.length(); +} + void QuantitySpinBox::showEvent(QShowEvent* event) { Q_D(QuantitySpinBox); diff --git a/src/Gui/QuantitySpinBox.h b/src/Gui/QuantitySpinBox.h index 2f38a8f41b..e4c73f82db 100644 --- a/src/Gui/QuantitySpinBox.h +++ b/src/Gui/QuantitySpinBox.h @@ -54,6 +54,15 @@ class GuiExport QuantitySpinBox: public QAbstractSpinBox, public ExpressionSpinB Q_PROPERTY( bool autoNormalize READ autoNormalize WRITE setAutoNormalize ) // clazy:exclude=qproperty-without-notify + Q_PROPERTY( + bool autoAdjustWidth READ autoAdjustWidth WRITE setAutoAdjustWidth + ) // clazy:exclude=qproperty-without-notify + Q_PROPERTY( + bool addIconSpace READ isIconSpaceAdded WRITE addIconSpace + ) // clazy:exclude=qproperty-without-notify + Q_PROPERTY( + int maxExpectedDigits READ getMaxExpectedDigits WRITE setMaxExpectedDigits + ) // clazy:exclude=qproperty-without-notify public: explicit QuantitySpinBox(QWidget* parent = nullptr); @@ -99,6 +108,11 @@ public: /// Sets the value of the maximum property void setMaximum(double max); + /// Adjust how many digits in the integer part we should expect + /// Affects minimum size if width is not adjustable + void setMaxExpectedDigits(int digits); + int getMaxExpectedDigits(); + /// Gets the number of decimals int decimals() const; /// Sets the number of decimals @@ -109,6 +123,16 @@ public: /// Enables or disables automatic normalization on enter void setAutoNormalize(bool normalize); + /// Returns if automatic width adjustment is enabled for this input + bool autoAdjustWidth() const; + /// Enables or disables automatic width adjustement + void setAutoAdjustWidth(bool adjust); + + /// Returns if icon space is added for this input + bool isIconSpaceAdded() const; + /// Enables or disables icon space addition + void addIconSpace(bool addIconSpace); + /// Sets a specific unit schema to handle quantities. /// The system-wide schema won't be used any more. void setSchema(int s); @@ -142,7 +166,6 @@ public: /// This is a helper function to determine the size this widget requires to fully display the text QSize sizeForText(const QString&) const; QSize sizeHint() const override; - QSize minimumSizeHint() const override; bool event(QEvent* event) override; void setNumberExpression(App::NumberExpression*) override; @@ -175,13 +198,16 @@ protected: void paintEvent(QPaintEvent* event) override; private: + void moveCursor(); void validateInput() override; void updateText(const Base::Quantity&); void updateEdit(const QString& text); void updateFromCache(bool notify, bool updateUnit = true); QString getUserString(const Base::Quantity& val, double& factor, QString& unitString) const; QString getUserString(const Base::Quantity& val) const; - QSize sizeHintCalculator(int height) const; + + QSize sizeHintForDigits(int digits) const; + int getMaxStrLength(int digits) const; Q_SIGNALS: /** Gets emitted if the user has entered a VALID input diff --git a/src/Gui/SpinBox.h b/src/Gui/SpinBox.h index fe8e5612c5..9b7cd20118 100644 --- a/src/Gui/SpinBox.h +++ b/src/Gui/SpinBox.h @@ -45,6 +45,7 @@ public: void bind(const App::ObjectIdentifier& _path) override; void setExpression(std::shared_ptr expr) override; + int getMargin(); protected: /*! Expression handling */ @@ -65,7 +66,6 @@ protected: virtual void showIcon(); virtual void validateInput(); void resizeWidget(); - int getMargin(); bool handleKeyEvent(const QString&); virtual void openFormulaDialog();