diff --git a/src/Mod/Measure/Gui/TaskMeasure.cpp b/src/Mod/Measure/Gui/TaskMeasure.cpp index cc7a139796..463f8410d2 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.cpp +++ b/src/Mod/Measure/Gui/TaskMeasure.cpp @@ -46,6 +46,10 @@ #include #include #include +#include + +#include +#include using namespace MeasureGui; @@ -57,6 +61,36 @@ constexpr auto taskMeasureAutoSaveSettingsName = "AutoSave"; constexpr auto taskMeasureGreedySelection = "GreedySelection"; using SelectionStyle = Gui::SelectionSingleton::SelectionStyle; + +constexpr std::array + lengthUnitLabels {"nm", "µm", "mm", "cm", "dm", "m", "km", "in", "ft", "thou", "yd", "mi"}; + +constexpr std::array angleUnitLabels {"deg", "rad", "gon"}; + +constexpr std::array areaUnitLabels {"mm²", "cm²", "m²", "km²", "in²", "ft²", "yd²", "mi²"}; + +template +QStringList toQStringList(const std::array& strings) +{ + QStringList result; + result.reserve(N); + for (const char* s : strings) { + result.append(QString::fromUtf8(s)); + } + return result; +} + +QString extractUnitFromResultString(const QString& resultString) +{ + std::string str = resultString.toStdString(); + auto lastSpace = str.find_last_of(' '); + + if (lastSpace != std::string::npos && lastSpace < str.length() - 1) { + return QString::fromStdString(str.substr(lastSpace + 1)); + } + + return QString(); +} } // namespace TaskMeasure::TaskMeasure() @@ -85,7 +119,7 @@ TaskMeasure::TaskMeasure() showDelta = new QCheckBox(); showDelta->setChecked(delta); - showDeltaLabel = new QLabel(tr("Show Delta:")); + showDeltaLabel = new QLabel(tr("Show Delta")); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(showDelta, &QCheckBox::checkStateChanged, this, &TaskMeasure::showDeltaChanged); #else @@ -140,6 +174,11 @@ TaskMeasure::TaskMeasure() // Connect dropdown's change signal to our onModeChange slot connect(modeSwitch, qOverload(&QComboBox::currentIndexChanged), this, &TaskMeasure::onModeChanged); + unitSwitch = new QComboBox(); + unitSwitch->addItem("-"); + connect(unitSwitch, qOverload(&QComboBox::currentIndexChanged), this, &TaskMeasure::onUnitChanged); + + // Result widget valueResult = new QLineEdit(); valueResult->setReadOnly(true); @@ -149,6 +188,7 @@ TaskMeasure::TaskMeasure() QFormLayout* formLayout = new QFormLayout(); formLayout->setHorizontalSpacing(10); + formLayout->setVerticalSpacing(6); // Note: How can the split between columns be kept in the middle? // formLayout->setFieldGrowthPolicy(QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow); formLayout->setFormAlignment(Qt::AlignCenter); @@ -157,9 +197,22 @@ TaskMeasure::TaskMeasure() settingsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); settingsLayout->addWidget(mSettings); formLayout->addRow(QLatin1String(), settingsLayout); - formLayout->addRow(tr("Mode:"), modeSwitch); - formLayout->addRow(showDeltaLabel, showDelta); - formLayout->addRow(tr("Result:"), valueResult); + formLayout->addRow(tr("Mode"), modeSwitch); + + auto* deltaLayout = new QHBoxLayout(); + deltaLayout->setContentsMargins(0, 0, 0, 0); + deltaLayout->setSpacing(8); + deltaLayout->addWidget(showDelta, 0, Qt::AlignVCenter | Qt::AlignLeft); + deltaLayout->addWidget(showDeltaLabel, 0, Qt::AlignVCenter | Qt::AlignLeft); + deltaLayout->addStretch(1); + + + auto* resultLayout = new QHBoxLayout(); + resultLayout->setSpacing(8); + resultLayout->addWidget(valueResult, 65); + resultLayout->addWidget(unitSwitch, 30); + formLayout->addRow(tr("Result"), resultLayout); + formLayout->addRow(deltaLayout); layout->addLayout(formLayout); Content.emplace_back(taskbox); @@ -307,13 +360,10 @@ void TaskMeasure::tryUpdate() if (!measureType) { - - // Note: If there's no valid measure type we might just restart the selection, - // however this requires enough coverage of measuretypes that we can access all of them - - // std::tuple sel = selection.back(); - // clearSelection(); - // addElement(measureModule.c_str(), get<0>(sel).c_str(), get<1>(sel).c_str()); + QSignalBlocker unitSwitchBlocker(unitSwitch); + unitSwitch->clear(); + unitSwitch->addItem(QLatin1String("-")); + mLastUnitSelection = QLatin1String("-"); // Reset measure object if (!explicitMode) { @@ -324,6 +374,8 @@ void TaskMeasure::tryUpdate() return; } + updateUnitDropdown(measureType); + // Update tool mode display setModeSilent(measureType); @@ -341,9 +393,9 @@ void TaskMeasure::tryUpdate() // Fill measure object's properties from selection _mMeasureObject->parseSelection(selection); - // Get result - valueResult->setText(_mMeasureObject->getResultString()); + setUnitFromResultString(); + updateResultWithUnit(); // Initialite the measurement's viewprovider initViewObject(_mMeasureObject); @@ -351,6 +403,101 @@ void TaskMeasure::tryUpdate() _mMeasureObject->purgeTouched(); } +void TaskMeasure::updateUnitDropdown(const App::MeasureType* measureType) +{ + const QString previousUnit = unitSwitch->currentText(); + QStringList units; + + if (measureType->identifier == "LENGTH" || measureType->identifier == "DISTANCE" + || measureType->identifier == "DISTANCEFREE" || measureType->identifier == "RADIUS" + || measureType->identifier == "DIAMETER" || measureType->identifier == "POSITION" + || measureType->identifier == "CENTEROFMASS") { + units = toQStringList(lengthUnitLabels); + } + else if (measureType->identifier == "ANGLE") { + units = toQStringList(angleUnitLabels); + } + else if (measureType->identifier == "AREA") { + units = toQStringList(areaUnitLabels); + } + else { + units.clear(); + } + + QSignalBlocker unitSwitchBlocker(unitSwitch); + + unitSwitch->clear(); + if (!units.isEmpty()) { + unitSwitch->addItems(units); + // If unit from the same category was previously selected keep it + if (!previousUnit.isEmpty()) { + int unitIndex = unitSwitch->findText(previousUnit); + if (unitIndex >= 0) { + unitSwitch->setCurrentIndex(unitIndex); + } + } + } +} + +void TaskMeasure::setUnitFromResultString() +{ + if (!_mMeasureObject) { + return; + } + + // Only set default unit if user hasn't made a selection yet + if (mLastUnitSelection != QLatin1String("-") && !mLastUnitSelection.isEmpty()) { + return; + } + + QString resultString = _mMeasureObject->getResultString(); + QString unitFromResult = extractUnitFromResultString(resultString); + + if (unitFromResult.isEmpty()) { + return; + } + + int unitIndex = unitSwitch->findText(unitFromResult); + if (unitIndex >= 0) { + QSignalBlocker unitSwitchBlocker(unitSwitch); + unitSwitch->setCurrentIndex(unitIndex); + + mLastUnitSelection = unitFromResult; + } +} + +void TaskMeasure::updateResultWithUnit() +{ + if (!_mMeasureObject) { + return; + } + + QString resultString = _mMeasureObject->getResultString(); + QString currentUnit = unitSwitch->currentText(); + + if (currentUnit != QLatin1String("-") && !resultString.isEmpty()) { + Base::Quantity resultQty = Base::Quantity::parse(resultString.toStdString()); + // Parse unit string like "1 mm" to get the target quantity + Base::Quantity targetUnit = Base::Quantity::parse(("1 " + currentUnit).toStdString()); + double convertedValue = resultQty.getValueAs(targetUnit); + + QString formattedValue; + // 4 decimal places, if between -1 and 1: 4 significant digits + if (std::abs(convertedValue) < 1.0 && convertedValue != 0.0) { + formattedValue = QString::number(convertedValue, 'g', 4); + } + else { + formattedValue = QString::number(convertedValue, 'f', 4); + } + + QString formattedResult = formattedValue + " " + currentUnit; + valueResult->setText(formattedResult); + } + else { + valueResult->setText(resultString); + } +} + void TaskMeasure::initViewObject(Measure::MeasureBase* measure) { @@ -559,6 +706,18 @@ void TaskMeasure::onModeChanged(int index) this->update(); } +void TaskMeasure::onUnitChanged(int index) +{ + const QString currentUnit = unitSwitch->itemText(index); + const auto dash = QLatin1String("-"); + + if (currentUnit != mLastUnitSelection && (mLastUnitSelection != dash || currentUnit != dash)) { + updateResultWithUnit(); + } + + mLastUnitSelection = currentUnit; +} + void TaskMeasure::showDeltaChanged(int checkState) { delta = checkState == Qt::CheckState::Checked; diff --git a/src/Mod/Measure/Gui/TaskMeasure.h b/src/Mod/Measure/Gui/TaskMeasure.h index 48dc6677d9..415650424b 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.h +++ b/src/Mod/Measure/Gui/TaskMeasure.h @@ -75,6 +75,8 @@ public: private: void setupShortcuts(QWidget* parent); void tryUpdate(); + void updateUnitDropdown(const App::MeasureType* measureType); + void setUnitFromResultString(); void onSelectionChanged(const Gui::SelectionChanges& msg) override; void onObjectDeleted(const App::DocumentObject& obj); void saveMeasurement(); @@ -84,6 +86,7 @@ private: QLineEdit* valueResult {nullptr}; QComboBox* modeSwitch {nullptr}; + QComboBox* unitSwitch {nullptr}; QCheckBox* showDelta {nullptr}; QLabel* showDeltaLabel {nullptr}; QAction* autoSaveAction {nullptr}; @@ -94,6 +97,7 @@ private: void removeObject(); void onModeChanged(int index); + void onUnitChanged(int index); void showDeltaChanged(int checkState); void autoSaveChanged(bool checked); void newMeasurementBehaviourChanged(bool checked); @@ -104,6 +108,7 @@ private: void ensureGroup(Measure::MeasureBase* measurement); void setDeltaPossible(bool possible); void initViewObject(Measure::MeasureBase* measure); + void updateResultWithUnit(); // Stores if the mode is explicitly set by the user or implicitly through the selection bool explicitMode = false; @@ -111,6 +116,7 @@ private: // Stores if delta measures shall be shown bool delta = true; bool mAutoSave = false; + QString mLastUnitSelection = QLatin1String("-"); }; } // namespace MeasureGui