Measurement: Add on the fly measurement unit change (#27462)
Enables the user to change the unit of the measurement temporarily to convert to a different unit. This is useful when the user sometimes needs to get a dimension of the model in a different unit than the one it was designed in.
This commit is contained in:
@@ -46,6 +46,10 @@
|
||||
#include <QMenu>
|
||||
#include <QShortcut>
|
||||
#include <QToolTip>
|
||||
#include <QSignalBlocker>
|
||||
|
||||
#include <Base/Quantity.h>
|
||||
#include <array>
|
||||
|
||||
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<std::size_t N>
|
||||
QStringList toQStringList(const std::array<const char*, N>& 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<int>(&QComboBox::currentIndexChanged), this, &TaskMeasure::onModeChanged);
|
||||
|
||||
unitSwitch = new QComboBox();
|
||||
unitSwitch->addItem("-");
|
||||
connect(unitSwitch, qOverload<int>(&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<std::string, std::string> 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user