From 5c1126519dcf9099b8d4060df58e1aa066781d86 Mon Sep 17 00:00:00 2001 From: Leonardo Zide Date: Fri, 23 Oct 2026 13:04:18 -0700 Subject: [PATCH] Reject invalid Array and transform input with optional results Rename the localized float parser to lcParseFloatValueLocalized and return std::optional so conversion failures and nonfinite values cannot silently become zero. Return std::optional from GetTransformAmount and apply transforms only when every nonempty axis is valid, preserving blank axes as zero. Validate all Array offsets and rotations before updating dialog results, and show an informational message with focus on invalid input. Verified with a macOS Qt debug build and actual Array/transform checks in US English, German, and Arabic locales, including blank input, overflow, NaN, and infinity. --- common/lc_arraydialog.cpp | 59 +++++++++++++++++++++++++-------------- common/lc_mainwindow.cpp | 39 +++++++++++++++++++++----- common/lc_mainwindow.h | 2 +- qt/lc_qutils.cpp | 10 +++++-- qt/lc_qutils.h | 2 +- 5 files changed, 80 insertions(+), 32 deletions(-) diff --git a/common/lc_arraydialog.cpp b/common/lc_arraydialog.cpp index fef44df6..0b6ba199 100644 --- a/common/lc_arraydialog.cpp +++ b/common/lc_arraydialog.cpp @@ -76,27 +76,44 @@ void lcArrayDialog::accept() return; } - mCounts[0] = Counts[0]; - mCounts[1] = Counts[1]; - mCounts[2] = Counts[2]; - mOffsets[0].x = lcParseValueLocalized(ui->offset1x->text()); - mOffsets[0].y = lcParseValueLocalized(ui->offset1y->text()); - mOffsets[0].z = lcParseValueLocalized(ui->offset1z->text()); - mOffsets[1].x = lcParseValueLocalized(ui->offset2x->text()); - mOffsets[1].y = lcParseValueLocalized(ui->offset2y->text()); - mOffsets[1].z = lcParseValueLocalized(ui->offset2z->text()); - mOffsets[2].x = lcParseValueLocalized(ui->offset3x->text()); - mOffsets[2].y = lcParseValueLocalized(ui->offset3y->text()); - mOffsets[2].z = lcParseValueLocalized(ui->offset3z->text()); - mRotations[0].x = lcParseValueLocalized(ui->rotation1x->text()); - mRotations[0].y = lcParseValueLocalized(ui->rotation1y->text()); - mRotations[0].z = lcParseValueLocalized(ui->rotation1z->text()); - mRotations[1].x = lcParseValueLocalized(ui->rotation2x->text()); - mRotations[1].y = lcParseValueLocalized(ui->rotation2y->text()); - mRotations[1].z = lcParseValueLocalized(ui->rotation2z->text()); - mRotations[2].x = lcParseValueLocalized(ui->rotation3x->text()); - mRotations[2].y = lcParseValueLocalized(ui->rotation3y->text()); - mRotations[2].z = lcParseValueLocalized(ui->rotation3z->text()); + QLineEdit* Edits[2][3][3] = + { + { + { ui->offset1x, ui->offset1y, ui->offset1z }, + { ui->offset2x, ui->offset2y, ui->offset2z }, + { ui->offset3x, ui->offset3y, ui->offset3z } + }, + { + { ui->rotation1x, ui->rotation1y, ui->rotation1z }, + { ui->rotation2x, ui->rotation2y, ui->rotation2z }, + { ui->rotation3x, ui->rotation3y, ui->rotation3z } + } + }; + lcVector3 Values[2][3]; + + for (int Type = 0; Type < 2; Type++) + for (int Dimension = 0; Dimension < 3; Dimension++) + for (int Axis = 0; Axis < 3; Axis++) + { + QLineEdit* Edit = Edits[Type][Dimension][Axis]; + const std::optional Value = lcParseFloatValueLocalized(Edit->text(), Edit->validator()->locale()); + + if (!Value || !Edit->hasAcceptableInput()) + { + QMessageBox::information(this, windowTitle(), tr("Please enter a valid value for '%1'.").arg(Edit->accessibleName())); + Edit->setFocus(); + return; + } + + Values[Type][Dimension][Axis] = *Value; + } + + for (int Dimension = 0; Dimension < 3; Dimension++) + { + mCounts[Dimension] = Counts[Dimension]; + mOffsets[Dimension] = Values[0][Dimension]; + mRotations[Dimension] = Values[1][Dimension]; + } QDialog::accept(); } diff --git a/common/lc_mainwindow.cpp b/common/lc_mainwindow.cpp index eaece88b..0916a41c 100644 --- a/common/lc_mainwindow.cpp +++ b/common/lc_mainwindow.cpp @@ -2073,15 +2073,35 @@ void lcMainWindow::SetInsertPieceData(lcPieceData* PieceData) lcGetPiecesLibrary()->RequestPieceDataLoad(mInsertPieceData.Get(), lcLoadPriority::Normal); } -lcVector3 lcMainWindow::GetTransformAmount() +std::optional lcMainWindow::GetTransformAmount() { - lcVector3 Transform; + QLineEdit* Edits[] = { mTransformXEdit, mTransformYEdit, mTransformZEdit }; + lcVector3 Values; - Transform.x = lcParseValueLocalized(mTransformXEdit->text()); - Transform.y = lcParseValueLocalized(mTransformYEdit->text()); - Transform.z = lcParseValueLocalized(mTransformZEdit->text()); + for (int Axis = 0; Axis < 3; Axis++) + { + QLineEdit* Edit = Edits[Axis]; + const QString Text = Edit->text().trimmed(); - return Transform; + if (Text.isEmpty()) + { + Values[Axis] = 0.0f; + continue; + } + + const std::optional Value = lcParseFloatValueLocalized(Text, Edit->locale()); + + if (!Value) + { + QMessageBox::information(this, tr("Transform"), tr("Please enter a valid value for '%1'.").arg(Edit->accessibleName())); + Edit->setFocus(); + return std::nullopt; + } + + Values[Axis] = *Value; + } + + return Values; } void lcMainWindow::SplitView(Qt::Orientation Orientation) @@ -3730,7 +3750,12 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId) case LC_EDIT_TRANSFORM: if (ActiveModel) - ActiveModel->TransformSelectedObjects(GetTransformType(), GetTransformAmount()); + { + const std::optional Transform = GetTransformAmount(); + + if (Transform) + ActiveModel->TransformSelectedObjects(GetTransformType(), *Transform); + } break; case LC_EDIT_TRANSFORM_ABSOLUTE_TRANSLATION: diff --git a/common/lc_mainwindow.h b/common/lc_mainwindow.h index 50290b1f..b3bca34b 100644 --- a/common/lc_mainwindow.h +++ b/common/lc_mainwindow.h @@ -259,7 +259,7 @@ public: void UpdateModified(bool Modified); void UpdateShortcuts(); - lcVector3 GetTransformAmount(); + std::optional GetTransformAmount(); bool eventFilter(QObject* Object, QEvent* Event) override; diff --git a/qt/lc_qutils.cpp b/qt/lc_qutils.cpp index 82f87d06..9c9b0b53 100644 --- a/qt/lc_qutils.cpp +++ b/qt/lc_qutils.cpp @@ -90,9 +90,15 @@ QString lcFormatIntegerLocalized(qlonglong Value, QLocale Locale) return Locale.toString(Value); } -float lcParseValueLocalized(const QString& Value) +std::optional lcParseFloatValueLocalized(const QString& Value, QLocale Locale) { - return QLocale::system().toFloat(Value); + bool Ok; + const float Number = Locale.toFloat(Value, &Ok); + + if (!Ok || !std::isfinite(Number)) + return std::nullopt; + + return Number; } #ifdef Q_OS_WIN diff --git a/qt/lc_qutils.h b/qt/lc_qutils.h index ac3cf5b7..1ae9d65c 100644 --- a/qt/lc_qutils.h +++ b/qt/lc_qutils.h @@ -14,7 +14,7 @@ constexpr int LC_MAX_AREA_LIGHT_GRID = 64; QString lcFormatValue(float Value, int Precision); QString lcFormatValueLocalized(float Value); QString lcFormatIntegerLocalized(qlonglong Value, QLocale Locale); -float lcParseValueLocalized(const QString& Value); +std::optional lcParseFloatValueLocalized(const QString& Value, QLocale Locale); void lcSetupDialogHelp(QDialog* Dialog, QDialogButtonBox* ButtonBox); void lcSetupWidgetHelp(QWidget* Root, QDialogButtonBox* ButtonBox);