Fix in-place submodel editing context and navigation

In-place editing could use the tab model's step and focus, lose its active
state after switching tabs, or resolve a nested instance through the wrong
parent. Entering before the final animation key also moved geometry when
drilling into a child.

Use the edited model for step controls, step edits, visibility, and focus
coordinates. Restore that editing target when returning to its tab, include
animation keys and finite hide steps in the last step, and retain the
selected instance path and transforms during nested editing.

Show the editing path only during in-place edits, with links to ancestors
and a close control to return to the tab model. Stack Find/Replace beneath
it and preserve full names in accessible text and tooltips. Add translated
source entries without changing existing translations.

Add a regression test for final-key tracking through time and key edits.
Validated with the macOS Qt 6 application build, all 8 Qt Test cases, and
51 focused checks against the compiled application for steps, tab switching,
focus status, breadcrumbs, and Find/Replace layout. Visually checked the
viewport overlay and its placement during nested editing.
This commit is contained in:
Leonardo Zide committed 2026-10-24 12:39:25 -07:00
1 parent c9f473b1bf
commit baae697960
28 files changed
+558 -38

No files matched your search

+5
View File
@@ -792,6 +792,11 @@ bool lcCamera::SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool
return false; return false;
} }
lcStep lcCamera::GetLastStep() const
{
return std::max({ mPosition.GetLastKeyFrame(), mTargetPosition.GetLastKeyFrame(), mUpVector.GetLastKeyFrame() });
}
bool lcCamera::HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const bool lcCamera::HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const
{ {
switch (PropertyId) switch (PropertyId)
+1
View File
@@ -189,6 +189,7 @@ public:
QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override; QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override;
bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override; bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override;
bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override; bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override;
lcStep GetLastStep() const override;
bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override; bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override;
void RemoveKeyFrames() override; void RemoveKeyFrames() override;
lcCameraHistoryState GetHistoryState() const; lcCameraHistoryState GetHistoryState() const;
-1
View File
@@ -126,7 +126,6 @@ lcFindReplaceWidget::lcFindReplaceWidget(QWidget* Parent, lcModel* Model, bool R
mFindPartComboBox->setFocus(); mFindPartComboBox->setFocus();
adjustSize(); adjustSize();
move(1, 1);
show(); show();
} }
+18 -15
View File
@@ -2386,8 +2386,11 @@ void lcMainWindow::UpdateSelectedObjects(bool SelectionChanged)
} }
mStatusBarLabel->setText(Message); mStatusBarLabel->setText(Message);
lcVector3 Position;
lcGetActiveModel()->GetFocusPosition(Position); lcVector3 Position(0.0f, 0.0f, 0.0f);
if (ActiveModel)
ActiveModel->GetFocusPosition(Position);
QString Label("X: %1 Y: %2 Z: %3"); QString Label("X: %1 Y: %2 Z: %3");
Label = Label.arg(QLocale::system().toString(Position[0], 'f', 2), QLocale::system().toString(Position[1], 'f', 2), QLocale::system().toString(Position[2], 'f', 2)); Label = Label.arg(QLocale::system().toString(Position[0], 'f', 2), QLocale::system().toString(Position[1], 'f', 2), QLocale::system().toString(Position[2], 'f', 2));
@@ -2410,9 +2413,13 @@ void lcMainWindow::UpdatePaste(bool Enabled)
void lcMainWindow::UpdateCurrentStep() void lcMainWindow::UpdateCurrentStep()
{ {
lcModel* Model = lcGetActiveModel(); lcModel* ActiveModel = GetActiveModel();
lcStep CurrentStep = Model->GetCurrentStep();
lcStep LastStep = Model->GetLastStep(); if (!ActiveModel)
return;
lcStep CurrentStep = ActiveModel->GetCurrentStep();
lcStep LastStep = ActiveModel->GetLastStep();
mActions[LC_VIEW_TIME_FIRST]->setEnabled(CurrentStep != 1); mActions[LC_VIEW_TIME_FIRST]->setEnabled(CurrentStep != 1);
mActions[LC_VIEW_TIME_PREVIOUS]->setEnabled(CurrentStep > 1); mActions[LC_VIEW_TIME_PREVIOUS]->setEnabled(CurrentStep > 1);
@@ -3387,14 +3394,7 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId)
case LC_PIECE_EDIT_END_SUBMODEL: case LC_PIECE_EDIT_END_SUBMODEL:
if (ActiveView) if (ActiveView)
{
ActiveView->SetTopSubmodelActive(); ActiveView->SetTopSubmodelActive();
if (ActiveModel)
{
std::vector<lcModel*> UpdatedModels;
ActiveModel->UpdatePieceData(UpdatedModels);
}
}
break; break;
case LC_PIECE_EDIT_SELECTED_SUBMODEL: case LC_PIECE_EDIT_SELECTED_SUBMODEL:
@@ -3532,15 +3532,18 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId)
break; break;
case LC_VIEW_TIME_INSERT_BEFORE: case LC_VIEW_TIME_INSERT_BEFORE:
lcGetActiveModel()->InsertStepAction(lcGetActiveModel()->GetCurrentStep()); if (ActiveModel)
ActiveModel->InsertStepAction(ActiveModel->GetCurrentStep());
break; break;
case LC_VIEW_TIME_INSERT_AFTER: case LC_VIEW_TIME_INSERT_AFTER:
lcGetActiveModel()->InsertStepAction(lcGetActiveModel()->GetCurrentStep() + 1); if (ActiveModel)
ActiveModel->InsertStepAction(ActiveModel->GetCurrentStep() + 1);
break; break;
case LC_VIEW_TIME_DELETE: case LC_VIEW_TIME_DELETE:
lcGetActiveModel()->RemoveStepAction(lcGetActiveModel()->GetCurrentStep()); if (ActiveModel)
ActiveModel->RemoveStepAction(ActiveModel->GetCurrentStep());
break; break;
case LC_VIEW_VIEWPOINT_FRONT: case LC_VIEW_VIEWPOINT_FRONT:
+10 -2
View File
@@ -1662,7 +1662,9 @@ void lcModel::AddSubModelRenderMeshes(lcScene* Scene, const lcMatrix44& WorldMat
{ {
for (const std::unique_ptr<lcPiece>& Piece : mPieces) for (const std::unique_ptr<lcPiece>& Piece : mPieces)
{ {
if (Piece->IsVisibleInSubModel()) const bool Visible = ParentActive ? (Piece->IsSelected() || Piece->IsVisible(mCurrentStep)) : Piece->IsVisibleInSubModel();
if (Visible)
{ {
if (Piece->IsFocused()) if (Piece->IsFocused())
UpdateTrainTrackConnections(Piece.get(), false); UpdateTrainTrackConnections(Piece.get(), false);
@@ -2649,7 +2651,13 @@ lcStep lcModel::GetLastStep() const
lcStep Step = 1; lcStep Step = 1;
for (const std::unique_ptr<lcPiece>& Piece : mPieces) for (const std::unique_ptr<lcPiece>& Piece : mPieces)
Step = lcMax(Step, Piece->GetStepShow()); Step = lcMax(Step, Piece->GetLastStep());
for (const std::unique_ptr<lcCamera>& Camera : mCameras)
Step = lcMax(Step, Camera->GetLastStep());
for (const std::unique_ptr<lcLight>& Light : mLights)
Step = lcMax(Step, Light->GetLastStep());
return Step; return Step;
} }
+5
View File
@@ -99,6 +99,11 @@ public:
mKeys.clear(); mKeys.clear();
} }
lcStep GetLastKeyFrame() const
{
return mKeys.empty() ? 0 : mKeys.back().Step;
}
void Update(lcStep Step); void Update(lcStep Step);
bool ChangeKey(const T& Value, lcStep Step, bool AddKey); bool ChangeKey(const T& Value, lcStep Step, bool AddKey);
void InsertTime(lcStep Start, lcStep Time); void InsertTime(lcStep Start, lcStep Time);
+60 -18
View File
@@ -105,7 +105,10 @@ void lcView::MakeCurrent()
void lcView::Redraw() void lcView::Redraw()
{ {
if (mWidget) if (mWidget)
{
mWidget->UpdateModelEditingPath();
mWidget->update(); mWidget->update();
}
} }
void lcView::SetOffscreenContext() void lcView::SetOffscreenContext()
@@ -145,44 +148,82 @@ lcModel* lcView::GetActiveModel() const
return !mActiveSubmodelInstance ? mModel : mActiveSubmodelInstance->mPieceData->GetModel(); return !mActiveSubmodelInstance ? mModel : mActiveSubmodelInstance->mPieceData->GetModel();
} }
void lcView::SetTopSubmodelActive() std::vector<lcModel*> lcView::GetModelEditingPath() const
{ {
std::vector<lcModel*> Models = { mModel };
for (const std::pair<lcPiece*, lcMatrix44>& Entry : mSubmodelEditingPath)
Models.push_back(Entry.first->mPieceData->GetModel());
return Models;
}
void lcView::SetSubmodelActive(size_t Depth)
{
if (Depth > mSubmodelEditingPath.size())
return;
StopTracking(true);
lcModel* ActiveModel = GetActiveModel(); lcModel* ActiveModel = GetActiveModel();
if (mActiveSubmodelInstance) if (mActiveSubmodelInstance)
{ {
ActiveModel->SetActive(false); ActiveModel->SetActive(false);
mActiveSubmodelInstance = nullptr;
std::vector<lcModel*> UpdatedModels;
ActiveModel->UpdatePieceData(UpdatedModels);
} }
mSubmodelEditingPath.resize(Depth);
mActiveSubmodelInstance = nullptr;
if (!mSubmodelEditingPath.empty())
{
mActiveSubmodelInstance = mSubmodelEditingPath.back().first;
mActiveSubmodelTransform = mSubmodelEditingPath.back().second;
}
GetActiveModel()->SetActive(true);
GetActiveModel()->UpdateInterface(); GetActiveModel()->UpdateInterface();
} }
void lcView::SetTopSubmodelActive()
{
SetSubmodelActive(0);
}
void lcView::SetSelectedSubmodelActive() void lcView::SetSelectedSubmodelActive()
{ {
lcModel* ActiveModel = GetActiveModel(); lcModel* ActiveModel = GetActiveModel();
lcObject* Object = ActiveModel->GetFocusObject(); lcObject* Object = ActiveModel->GetFocusObject();
if (!Object || !Object->IsPiece())
return;
lcPiece* Piece = (lcPiece*)Object;
if (!Piece->mPieceData->IsModel())
return;
// Preserve the selected instance path and capture its transform before SetActive(false) changes the step.
lcMatrix44 SubmodelTransform = Piece->mModelWorld;
if (mActiveSubmodelInstance)
SubmodelTransform = lcMul(SubmodelTransform, mActiveSubmodelTransform);
if (mActiveSubmodelInstance) if (mActiveSubmodelInstance)
{
ActiveModel->SetActive(false); ActiveModel->SetActive(false);
mActiveSubmodelInstance = nullptr;
}
if (Object && Object->IsPiece()) mSubmodelEditingPath.emplace_back(Piece, SubmodelTransform);
{ mActiveSubmodelTransform = SubmodelTransform;
lcPiece* Piece = (lcPiece*)Object; mActiveSubmodelInstance = Piece;
if (Piece->mPieceData->IsModel()) ActiveModel = mActiveSubmodelInstance->mPieceData->GetModel();
{ ActiveModel->SetTemporaryStep(ActiveModel->GetLastStep());
mActiveSubmodelTransform = lcMatrix44Identity(); ActiveModel->SetActive(true);
mModel->GetPieceWorldMatrix(Piece, mActiveSubmodelTransform);
mActiveSubmodelInstance = Piece; RemoveCamera();
ActiveModel = mActiveSubmodelInstance->mPieceData->GetModel();
ActiveModel->SetActive(true);
RemoveCamera();
}
}
GetActiveModel()->UpdateInterface(); GetActiveModel()->UpdateInterface();
} }
@@ -422,6 +463,7 @@ void lcView::ShowFindReplaceWidget(bool Replace)
delete mFindWidget; delete mFindWidget;
mFindReplaceParams = lcFindReplaceParams(); mFindReplaceParams = lcFindReplaceParams();
mFindWidget = new lcFindReplaceWidget(mWidget, GetActiveModel(), Replace); mFindWidget = new lcFindReplaceWidget(mWidget, GetActiveModel(), Replace);
mWidget->SetFindReplaceWidget(mFindWidget);
} }
lcVector3 lcView::GetMoveDirection(const lcVector3& Direction) const lcVector3 lcView::GetMoveDirection(const lcVector3& Direction) const
+3
View File
@@ -235,6 +235,8 @@ public:
void SetMouseModifiers(Qt::KeyboardModifiers MouseModifiers); void SetMouseModifiers(Qt::KeyboardModifiers MouseModifiers);
lcModel* GetActiveModel() const; lcModel* GetActiveModel() const;
std::vector<lcModel*> GetModelEditingPath() const;
void SetSubmodelActive(size_t Depth);
void SetTopSubmodelActive(); void SetTopSubmodelActive();
void SetSelectedSubmodelActive(); void SetSelectedSubmodelActive();
@@ -393,6 +395,7 @@ protected:
lcModel* mModel = nullptr; lcModel* mModel = nullptr;
lcPiece* mActiveSubmodelInstance = nullptr; lcPiece* mActiveSubmodelInstance = nullptr;
lcMatrix44 mActiveSubmodelTransform; lcMatrix44 mActiveSubmodelTransform;
std::vector<std::pair<lcPiece*, lcMatrix44>> mSubmodelEditingPath;
lcCamera* mCamera = nullptr; lcCamera* mCamera = nullptr;
+194
View File
@@ -11,6 +11,8 @@
#include "lc_mesh.h" #include "lc_mesh.h"
#include "lc_profile.h" #include "lc_profile.h"
#include "lc_previewwidget.h" #include "lc_previewwidget.h"
#include "lc_qutils.h"
#include "lc_viewsphere.h"
lcViewWidget::lcViewWidget(QWidget* Parent, lcView* View) lcViewWidget::lcViewWidget(QWidget* Parent, lcView* View)
: QOpenGLWidget(Parent), mView(View) : QOpenGLWidget(Parent), mView(View)
@@ -23,6 +25,62 @@ lcViewWidget::lcViewWidget(QWidget* Parent, lcView* View)
{ {
setFocusPolicy(Qt::StrongFocus); setFocusPolicy(Qt::StrongFocus);
setAcceptDrops(true); setAcceptDrops(true);
mOverlay = new QWidget(this);
mOverlay->setObjectName(QStringLiteral("ViewOverlay"));
mOverlayLayout = new QVBoxLayout(mOverlay);
mOverlayLayout->setContentsMargins(0, 0, 0, 0);
mOverlayLayout->setSpacing(2);
mOverlayLayout->setSizeConstraint(QLayout::SetNoConstraint);
mOverlay->installEventFilter(this);
mModelEditingBar = new QWidget(mOverlay);
mModelEditingBar->setObjectName(QStringLiteral("ModelEditingBar"));
mModelEditingBar->setAutoFillBackground(true);
QPalette Palette = palette();
Palette.setColor(QPalette::Window, QApplication::palette().color(QPalette::Button));
mModelEditingBar->setPalette(Palette);
mModelEditingBar->setWhatsThis(tr("<p><b>In-place Editing</b></p><p>The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab's model.</p>"));
QHBoxLayout* EditingLayout = new QHBoxLayout(mModelEditingBar);
EditingLayout->setContentsMargins(5, 3, 5, 3);
mModelEditingLabel = new QLabel(mModelEditingBar);
mModelEditingLabel->setObjectName(QStringLiteral("ModelEditingPath"));
mModelEditingLabel->setTextFormat(Qt::RichText);
mModelEditingLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
mModelEditingLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
mModelEditingLabel->setMinimumWidth(0);
EditingLayout->addWidget(mModelEditingLabel, 1);
mEndEditingButton = new QToolButton(mModelEditingBar);
mEndEditingButton->setIcon(QIcon(":/stylesheet/close.svg"));
mEndEditingButton->setAccessibleName(tr("End Editing"));
mEndEditingButton->setToolTip(tr("Return to editing the tab's model."));
mEndEditingButton->setAutoRaise(true);
EditingLayout->addWidget(mEndEditingButton);
mOverlayLayout->addWidget(mModelEditingBar);
connect(mModelEditingLabel, &QLabel::linkActivated, this, [this](const QString& Link)
{
bool Valid;
const uint Depth = Link.toUInt(&Valid);
if (!Valid || Depth + 1 >= static_cast<uint>(mModelEditingNames.size()))
return;
setFocus(Qt::OtherFocusReason);
mView->SetSubmodelActive(Depth);
});
connect(mEndEditingButton, &QToolButton::clicked, this, [this]()
{
setFocus(Qt::OtherFocusReason);
mView->SetTopSubmodelActive();
});
lcSetupWidgetHelp(mModelEditingBar, nullptr);
UpdateModelEditingPath();
} }
} }
@@ -68,6 +126,142 @@ void lcViewWidget::SetView(lcView* View)
} }
mView = std::unique_ptr<lcView>(View); mView = std::unique_ptr<lcView>(View);
UpdateModelEditingPath();
}
void lcViewWidget::UpdateModelEditingPath()
{
if (!mOverlay || !mView)
return;
QStringList Names;
for (const lcModel* Model : mView->GetModelEditingPath())
Names.append(Model->GetProperties().mFileName);
if (Names != mModelEditingNames)
{
mModelEditingNames = Names;
const QString Path = Names.join(QStringLiteral(" \u203a "));
mModelEditingLabel->setToolTip(Path);
mModelEditingLabel->setAccessibleName(tr("Editing model: %1").arg(Path));
mModelEditingBar->setVisible(Names.size() > 1);
}
UpdateOverlayGeometry();
}
void lcViewWidget::SetFindReplaceWidget(QWidget* Widget)
{
if (!mOverlay)
return;
mFindReplaceWidget = Widget;
mOverlayLayout->addWidget(Widget);
UpdateOverlayGeometry();
}
void lcViewWidget::UpdateOverlayGeometry()
{
if (!mOverlay)
return;
const bool Editing = mModelEditingNames.size() > 1;
mOverlay->setVisible(Editing || mFindReplaceWidget);
if (!Editing && !mFindReplaceWidget)
return;
int Left = 1;
int Right = width() - 1;
const lcPreferences& Preferences = lcGetPreferences();
if (Preferences.mViewSphereEnabled && Preferences.mViewSphereSize > 0)
{
if (Preferences.mViewSphereLocation == lcViewSphereLocation::TopLeft)
Left += Preferences.mViewSphereSize + 2;
else if (Preferences.mViewSphereLocation == lcViewSphereLocation::TopRight)
Right -= Preferences.mViewSphereSize + 2;
}
int PreferredWidth = 0;
if (Editing)
{
QFont Font = mModelEditingLabel->font();
Font.setBold(true);
const QFontMetrics Metrics(Font);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
PreferredWidth = Metrics.horizontalAdvance(mModelEditingNames.join(QStringLiteral(" \u203a ")));
#else
PreferredWidth = Metrics.width(mModelEditingNames.join(QStringLiteral(" \u203a ")));
#endif
PreferredWidth += mEndEditingButton->sizeHint().width() + mModelEditingBar->layout()->spacing() + 10;
}
if (mFindReplaceWidget)
PreferredWidth = lcMax(PreferredWidth, mFindReplaceWidget->sizeHint().width());
const int OverlayWidth = lcClamp(PreferredWidth, 1, lcMax(1, Right - Left));
mOverlay->setGeometry(Left, 1, OverlayWidth, mOverlayLayout->sizeHint().height());
mOverlayLayout->activate();
mModelEditingBar->layout()->activate();
UpdateModelEditingLabel();
}
void lcViewWidget::UpdateModelEditingLabel()
{
if (mModelEditingNames.size() < 2)
return;
const QString Separator = QStringLiteral(" \u203a ");
QFont Font = mModelEditingLabel->font();
Font.setBold(true);
const QFontMetrics Metrics(Font);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
const int SeparatorWidth = Metrics.horizontalAdvance(Separator);
const int PathWidth = Metrics.horizontalAdvance(mModelEditingNames.join(Separator));
#else
const int SeparatorWidth = Metrics.width(Separator);
const int PathWidth = Metrics.width(mModelEditingNames.join(Separator));
#endif
const int NameCount = static_cast<int>(mModelEditingNames.size());
const int NameWidth = lcMax(0, (mModelEditingLabel->width() - SeparatorWidth * (NameCount - 1)) / NameCount);
QStringList Segments;
for (int Index = 0; Index < mModelEditingNames.size(); Index++)
{
QString Name = mModelEditingNames[Index];
if (PathWidth > mModelEditingLabel->width())
Name = Metrics.elidedText(Name, Qt::ElideMiddle, NameWidth);
Name = Name.toHtmlEscaped();
if (Index + 1 == mModelEditingNames.size())
Segments.append(QStringLiteral("<b>%1</b>").arg(Name));
else
Segments.append(QStringLiteral("<a href=\"%1\">%2</a>").arg(Index).arg(Name));
}
mModelEditingLabel->setText(Segments.join(Separator));
}
bool lcViewWidget::eventFilter(QObject* Object, QEvent* Event)
{
if (Object == mOverlay && Event->type() == QEvent::LayoutRequest)
UpdateOverlayGeometry();
return QOpenGLWidget::eventFilter(Object, Event);
}
void lcViewWidget::resizeEvent(QResizeEvent* Event)
{
QOpenGLWidget::resizeEvent(Event);
UpdateOverlayGeometry();
} }
void lcViewWidget::UpdateMousePosition() void lcViewWidget::UpdateMousePosition()
+14
View File
@@ -12,6 +12,8 @@ public:
void SetView(lcView* View); void SetView(lcView* View);
void UpdateMousePosition(); void UpdateMousePosition();
void UpdateModelEditingPath();
void SetFindReplaceWidget(QWidget* Widget);
QSize sizeHint() const override; QSize sizeHint() const override;
@@ -29,6 +31,8 @@ protected:
void resizeGL(int Width, int Height) override; void resizeGL(int Width, int Height) override;
void paintGL() override; void paintGL() override;
bool event(QEvent* Event) override; bool event(QEvent* Event) override;
bool eventFilter(QObject* Object, QEvent* Event) override;
void resizeEvent(QResizeEvent* Event) override;
void focusInEvent(QFocusEvent* FocusEvent) override; void focusInEvent(QFocusEvent* FocusEvent) override;
void focusOutEvent(QFocusEvent* FocusEvent) override; void focusOutEvent(QFocusEvent* FocusEvent) override;
void keyPressEvent(QKeyEvent* KeyEvent) override; void keyPressEvent(QKeyEvent* KeyEvent) override;
@@ -44,7 +48,17 @@ protected:
void dragMoveEvent(QDragMoveEvent* DragMoveEvent) override; void dragMoveEvent(QDragMoveEvent* DragMoveEvent) override;
void dropEvent(QDropEvent* DropEvent) override; void dropEvent(QDropEvent* DropEvent) override;
void UpdateOverlayGeometry();
void UpdateModelEditingLabel();
std::unique_ptr<lcView> mView; std::unique_ptr<lcView> mView;
QWidget* mOverlay = nullptr;
QVBoxLayout* mOverlayLayout = nullptr;
QWidget* mModelEditingBar = nullptr;
QLabel* mModelEditingLabel = nullptr;
QToolButton* mEndEditingButton = nullptr;
QPointer<QWidget> mFindReplaceWidget;
QStringList mModelEditingNames;
QSize mPreferredSize; QSize mPreferredSize;
int mWheelAccumulator = 0; int mWheelAccumulator = 0;
}; };
+9
View File
@@ -1309,6 +1309,15 @@ bool lcLight::SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool
return false; return false;
} }
lcStep lcLight::GetLastStep() const
{
return std::max({ mPosition.GetLastKeyFrame(), mRotation.GetLastKeyFrame(), mColor.GetLastKeyFrame(),
mBlenderPower.GetLastKeyFrame(), mBlenderRadius.GetLastKeyFrame(), mBlenderAngle.GetLastKeyFrame(),
mPOVRayPower.GetLastKeyFrame(), mPOVRayFadeDistance.GetLastKeyFrame(), mPOVRayFadePower.GetLastKeyFrame(),
mSpotConeAngle.GetLastKeyFrame(), mSpotPenumbraAngle.GetLastKeyFrame(), mPOVRaySpotTightness.GetLastKeyFrame(),
mAreaSizeX.GetLastKeyFrame(), mAreaSizeY.GetLastKeyFrame(), mPOVRayAreaGridX.GetLastKeyFrame(), mPOVRayAreaGridY.GetLastKeyFrame() });
}
bool lcLight::HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const bool lcLight::HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const
{ {
switch (PropertyId) switch (PropertyId)
+1
View File
@@ -195,6 +195,7 @@ public:
QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override; QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override;
bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override; bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override;
bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override; bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override;
lcStep GetLastStep() const override;
bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override; bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override;
void RemoveKeyFrames() override; void RemoveKeyFrames() override;
lcLightHistoryState GetHistoryState() const; lcLightHistoryState GetHistoryState() const;
+1
View File
@@ -144,6 +144,7 @@ public:
virtual QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const = 0; virtual QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const = 0;
virtual bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) = 0; virtual bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) = 0;
virtual bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const = 0; virtual bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const = 0;
virtual lcStep GetLastStep() const = 0;
virtual bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) = 0; virtual bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) = 0;
virtual void RemoveKeyFrames() = 0; virtual void RemoveKeyFrames() = 0;
virtual QString GetName() const = 0; virtual QString GetName() const = 0;
+10
View File
@@ -864,6 +864,16 @@ bool lcPiece::SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool
return false; return false;
} }
lcStep lcPiece::GetLastStep() const
{
lcStep Step = std::max({ mStepShow, mPosition.GetLastKeyFrame(), mRotation.GetLastKeyFrame() });
if (mStepHide != LC_STEP_MAX)
Step = lcMax(Step, mStepHide);
return Step;
}
bool lcPiece::HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const bool lcPiece::HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const
{ {
switch (PropertyId) switch (PropertyId)
+1
View File
@@ -93,6 +93,7 @@ public:
QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override; QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override;
bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override; bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override;
bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override; bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override;
lcStep GetLastStep() const override;
bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override; bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override;
void RemoveKeyFrames() override; void RemoveKeyFrames() override;
lcPieceHistoryState GetHistoryState() const; lcPieceHistoryState GetHistoryState() const;
+8 -1
View File
@@ -233,7 +233,14 @@ void Project::SetActiveModel(lcModel* ActiveModel, bool UpdateInterface)
if (!mIsPreview && gMainWindow && UpdateInterface) if (!mIsPreview && gMainWindow && UpdateInterface)
{ {
gMainWindow->SetCurrentModelTab(mActiveModel, true); gMainWindow->SetCurrentModelTab(mActiveModel, true);
mActiveModel->UpdateInterface();
// The tab may still be editing a nested submodel at its current step.
lcModel* EditingModel = gMainWindow->GetActiveModel();
if (EditingModel)
{
EditingModel->SetActive(true);
EditingModel->UpdateInterface();
}
} }
} }
+23
View File
@@ -16114,4 +16114,27 @@ Nepodporované grafické karty používají odstín obličeje a barevné okraje.
%2</translation> %2</translation>
</message> </message>
</context> </context>
<context>
<name>lcViewWidget</name>
<message>
<location filename="../common/lc_viewwidget.cpp" line="43"/>
<source>&lt;p&gt;&lt;b&gt;In-place Editing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab&apos;s model.&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="58"/>
<source>End Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="59"/>
<source>Return to editing the tab&apos;s model.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="147"/>
<source>Editing model: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS> </TS>
+23
View File
@@ -16325,4 +16325,27 @@ Nicht unterstützte Grafikkarten verwenden Gesichtstönung und farbige Kanten.</
%2</translation> %2</translation>
</message> </message>
</context> </context>
<context>
<name>lcViewWidget</name>
<message>
<location filename="../common/lc_viewwidget.cpp" line="43"/>
<source>&lt;p&gt;&lt;b&gt;In-place Editing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab&apos;s model.&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="58"/>
<source>End Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="59"/>
<source>Return to editing the tab&apos;s model.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="147"/>
<source>Editing model: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS> </TS>
+23
View File
@@ -15969,4 +15969,27 @@ Las tarjetas de video no compatibles usan tinte frontal y bordes coloreados.</tr
%2</translation> %2</translation>
</message> </message>
</context> </context>
<context>
<name>lcViewWidget</name>
<message>
<location filename="../common/lc_viewwidget.cpp" line="43"/>
<source>&lt;p&gt;&lt;b&gt;In-place Editing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab&apos;s model.&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="58"/>
<source>End Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="59"/>
<source>Return to editing the tab&apos;s model.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="147"/>
<source>Editing model: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS> </TS>
+23
View File
@@ -16319,4 +16319,27 @@ Les cartes vidéo non prises en charge utilisent une teinte de visage et des bor
%2</translation> %2</translation>
</message> </message>
</context> </context>
<context>
<name>lcViewWidget</name>
<message>
<location filename="../common/lc_viewwidget.cpp" line="43"/>
<source>&lt;p&gt;&lt;b&gt;In-place Editing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab&apos;s model.&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="58"/>
<source>End Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="59"/>
<source>Return to editing the tab&apos;s model.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="147"/>
<source>Editing model: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS> </TS>
+23
View File
@@ -16327,4 +16327,27 @@ As placas de vídeo não suportadas utilizam a tonalidade da face e bordas color
%2</translation> %2</translation>
</message> </message>
</context> </context>
<context>
<name>lcViewWidget</name>
<message>
<location filename="../common/lc_viewwidget.cpp" line="43"/>
<source>&lt;p&gt;&lt;b&gt;In-place Editing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab&apos;s model.&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="58"/>
<source>End Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="59"/>
<source>Return to editing the tab&apos;s model.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="147"/>
<source>Editing model: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS> </TS>
+23
View File
@@ -15845,4 +15845,27 @@ Unsupported video cards use face tint and colored edges.</source>
%2</translation> %2</translation>
</message> </message>
</context> </context>
<context>
<name>lcViewWidget</name>
<message>
<location filename="../common/lc_viewwidget.cpp" line="43"/>
<source>&lt;p&gt;&lt;b&gt;In-place Editing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab&apos;s model.&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="58"/>
<source>End Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="59"/>
<source>Return to editing the tab&apos;s model.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="147"/>
<source>Editing model: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS> </TS>
+23
View File
@@ -16451,4 +16451,27 @@ Unsupported video cards use face tint and colored edges.</source>
%2</translation> %2</translation>
</message> </message>
</context> </context>
<context>
<name>lcViewWidget</name>
<message>
<location filename="../common/lc_viewwidget.cpp" line="43"/>
<source>&lt;p&gt;&lt;b&gt;In-place Editing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab&apos;s model.&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="58"/>
<source>End Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="59"/>
<source>Return to editing the tab&apos;s model.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="147"/>
<source>Editing model: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS> </TS>
+23
View File
@@ -16073,4 +16073,27 @@ Unsupported video cards use face tint and colored edges.</source>
%2</translation> %2</translation>
</message> </message>
</context> </context>
<context>
<name>lcViewWidget</name>
<message>
<location filename="../common/lc_viewwidget.cpp" line="43"/>
<source>&lt;p&gt;&lt;b&gt;In-place Editing&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The path shows the model being edited inside this view. The last name is the current editing target. Choose an earlier name to edit that ancestor, or the close button to return to the tab&apos;s model.&lt;/p&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="58"/>
<source>End Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="59"/>
<source>Return to editing the tab&apos;s model.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../common/lc_viewwidget.cpp" line="147"/>
<source>Editing model: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS> </TS>
+2 -1
View File
@@ -1,7 +1,8 @@
# LeoCAD tests # LeoCAD tests
All tests run in one Qt Test executable, `leocad_tests`. The current tests cover All tests run in one Qt Test executable, `leocad_tests`. The current tests cover
object history and property-key comparisons, including 8,000 deterministic object history, property-key comparisons, and the final animation key after
step insertion, removal, and key deletion, including 8,000 deterministic
randomized undo/redo and merge transitions. randomized undo/redo and merge transitions.
The executable creates a `QCoreApplication`; it does not open windows or require The executable creates a `QCoreApplication`; it does not open windows or require
+29
View File
@@ -0,0 +1,29 @@
#include "lc_global.h"
#include "lc_tests.h"
#include "lc_objectproperty.h"
void lcTests::LastKeyFrameTracksTimeEdits()
{
lcObjectProperty<float> Position(0.0f);
QCOMPARE(Position.GetLastKeyFrame(), lcStep(0));
// Animation can extend past the step that introduces the piece.
QVERIFY(Position.ChangeKey(0.0f, 1, true));
QVERIFY(Position.ChangeKey(200.0f, 2, true));
QCOMPARE(Position.GetLastKeyFrame(), lcStep(2));
Position.Update(Position.GetLastKeyFrame());
QCOMPARE(static_cast<const float&>(Position), 200.0f);
Position.InsertTime(2, 3);
QCOMPARE(Position.GetLastKeyFrame(), lcStep(5));
Position.RemoveTime(2, 2);
QCOMPARE(Position.GetLastKeyFrame(), lcStep(3));
QVERIFY(Position.SetKeyFrame(3, false));
QCOMPARE(Position.GetLastKeyFrame(), lcStep(1));
Position.Update(Position.GetLastKeyFrame());
QCOMPARE(static_cast<const float&>(Position), 0.0f);
Position.RemoveAllKeys();
QCOMPARE(Position.GetLastKeyFrame(), lcStep(0));
}
+1
View File
@@ -8,6 +8,7 @@ class lcTests : public QObject
private slots: private slots:
void PropertyKeysAffectEquality(); void PropertyKeysAffectEquality();
void LastKeyFrameTracksTimeEdits();
void HistoryStoresOnlyChangedObjects(); void HistoryStoresOnlyChangedObjects();
void HistoryCancelsPendingEdit(); void HistoryCancelsPendingEdit();
void HistoryReplaysAndMergesEdits(); void HistoryReplaysAndMergesEdits();
+2
View File
@@ -15,5 +15,7 @@ INCLUDEPATH += ../common
HEADERS += lc_tests.h HEADERS += lc_tests.h
SOURCES += \ SOURCES += \
../common/lc_objectproperty.cpp \
lc_objecthistory_test.cpp \ lc_objecthistory_test.cpp \
lc_objectproperty_test.cpp \
main.cpp main.cpp