mirror of
https://github.com/leozide/leocad.git
synced 2026-10-26 22:10:32 +00:00
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:
1 parent
c9f473b1bf
commit
baae697960
28 files changed
+555
-35
No files matched your search
@@ -792,6 +792,11 @@ bool lcCamera::SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool
|
||||
return false;
|
||||
}
|
||||
|
||||
lcStep lcCamera::GetLastStep() const
|
||||
{
|
||||
return std::max({ mPosition.GetLastKeyFrame(), mTargetPosition.GetLastKeyFrame(), mUpVector.GetLastKeyFrame() });
|
||||
}
|
||||
|
||||
bool lcCamera::HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const
|
||||
{
|
||||
switch (PropertyId)
|
||||
|
||||
@@ -189,6 +189,7 @@ public:
|
||||
QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override;
|
||||
bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override;
|
||||
bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override;
|
||||
lcStep GetLastStep() const override;
|
||||
bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override;
|
||||
void RemoveKeyFrames() override;
|
||||
lcCameraHistoryState GetHistoryState() const;
|
||||
|
||||
@@ -126,7 +126,6 @@ lcFindReplaceWidget::lcFindReplaceWidget(QWidget* Parent, lcModel* Model, bool R
|
||||
|
||||
mFindPartComboBox->setFocus();
|
||||
adjustSize();
|
||||
move(1, 1);
|
||||
show();
|
||||
}
|
||||
|
||||
|
||||
+18
-15
@@ -2386,8 +2386,11 @@ void lcMainWindow::UpdateSelectedObjects(bool SelectionChanged)
|
||||
}
|
||||
|
||||
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");
|
||||
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()
|
||||
{
|
||||
lcModel* Model = lcGetActiveModel();
|
||||
lcStep CurrentStep = Model->GetCurrentStep();
|
||||
lcStep LastStep = Model->GetLastStep();
|
||||
lcModel* ActiveModel = GetActiveModel();
|
||||
|
||||
if (!ActiveModel)
|
||||
return;
|
||||
|
||||
lcStep CurrentStep = ActiveModel->GetCurrentStep();
|
||||
lcStep LastStep = ActiveModel->GetLastStep();
|
||||
|
||||
mActions[LC_VIEW_TIME_FIRST]->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:
|
||||
if (ActiveView)
|
||||
{
|
||||
ActiveView->SetTopSubmodelActive();
|
||||
if (ActiveModel)
|
||||
{
|
||||
std::vector<lcModel*> UpdatedModels;
|
||||
ActiveModel->UpdatePieceData(UpdatedModels);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LC_PIECE_EDIT_SELECTED_SUBMODEL:
|
||||
@@ -3532,15 +3532,18 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId)
|
||||
break;
|
||||
|
||||
case LC_VIEW_TIME_INSERT_BEFORE:
|
||||
lcGetActiveModel()->InsertStepAction(lcGetActiveModel()->GetCurrentStep());
|
||||
if (ActiveModel)
|
||||
ActiveModel->InsertStepAction(ActiveModel->GetCurrentStep());
|
||||
break;
|
||||
|
||||
case LC_VIEW_TIME_INSERT_AFTER:
|
||||
lcGetActiveModel()->InsertStepAction(lcGetActiveModel()->GetCurrentStep() + 1);
|
||||
if (ActiveModel)
|
||||
ActiveModel->InsertStepAction(ActiveModel->GetCurrentStep() + 1);
|
||||
break;
|
||||
|
||||
case LC_VIEW_TIME_DELETE:
|
||||
lcGetActiveModel()->RemoveStepAction(lcGetActiveModel()->GetCurrentStep());
|
||||
if (ActiveModel)
|
||||
ActiveModel->RemoveStepAction(ActiveModel->GetCurrentStep());
|
||||
break;
|
||||
|
||||
case LC_VIEW_VIEWPOINT_FRONT:
|
||||
|
||||
+10
-2
@@ -1662,7 +1662,9 @@ void lcModel::AddSubModelRenderMeshes(lcScene* Scene, const lcMatrix44& WorldMat
|
||||
{
|
||||
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())
|
||||
UpdateTrainTrackConnections(Piece.get(), false);
|
||||
@@ -2649,7 +2651,13 @@ lcStep lcModel::GetLastStep() const
|
||||
lcStep Step = 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -99,6 +99,11 @@ public:
|
||||
mKeys.clear();
|
||||
}
|
||||
|
||||
lcStep GetLastKeyFrame() const
|
||||
{
|
||||
return mKeys.empty() ? 0 : mKeys.back().Step;
|
||||
}
|
||||
|
||||
void Update(lcStep Step);
|
||||
bool ChangeKey(const T& Value, lcStep Step, bool AddKey);
|
||||
void InsertTime(lcStep Start, lcStep Time);
|
||||
|
||||
+57
-15
@@ -105,7 +105,10 @@ void lcView::MakeCurrent()
|
||||
void lcView::Redraw()
|
||||
{
|
||||
if (mWidget)
|
||||
{
|
||||
mWidget->UpdateModelEditingPath();
|
||||
mWidget->update();
|
||||
}
|
||||
}
|
||||
|
||||
void lcView::SetOffscreenContext()
|
||||
@@ -145,44 +148,82 @@ lcModel* lcView::GetActiveModel() const
|
||||
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();
|
||||
|
||||
if (mActiveSubmodelInstance)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
void lcView::SetTopSubmodelActive()
|
||||
{
|
||||
SetSubmodelActive(0);
|
||||
}
|
||||
|
||||
void lcView::SetSelectedSubmodelActive()
|
||||
{
|
||||
lcModel* ActiveModel = GetActiveModel();
|
||||
lcObject* Object = ActiveModel->GetFocusObject();
|
||||
|
||||
if (mActiveSubmodelInstance)
|
||||
{
|
||||
ActiveModel->SetActive(false);
|
||||
mActiveSubmodelInstance = nullptr;
|
||||
}
|
||||
if (!Object || !Object->IsPiece())
|
||||
return;
|
||||
|
||||
if (Object && Object->IsPiece())
|
||||
{
|
||||
lcPiece* Piece = (lcPiece*)Object;
|
||||
|
||||
if (Piece->mPieceData->IsModel())
|
||||
{
|
||||
mActiveSubmodelTransform = lcMatrix44Identity();
|
||||
mModel->GetPieceWorldMatrix(Piece, mActiveSubmodelTransform);
|
||||
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)
|
||||
ActiveModel->SetActive(false);
|
||||
|
||||
mSubmodelEditingPath.emplace_back(Piece, SubmodelTransform);
|
||||
mActiveSubmodelTransform = SubmodelTransform;
|
||||
mActiveSubmodelInstance = Piece;
|
||||
|
||||
ActiveModel = mActiveSubmodelInstance->mPieceData->GetModel();
|
||||
ActiveModel->SetTemporaryStep(ActiveModel->GetLastStep());
|
||||
ActiveModel->SetActive(true);
|
||||
|
||||
RemoveCamera();
|
||||
}
|
||||
}
|
||||
|
||||
GetActiveModel()->UpdateInterface();
|
||||
}
|
||||
@@ -422,6 +463,7 @@ void lcView::ShowFindReplaceWidget(bool Replace)
|
||||
delete mFindWidget;
|
||||
mFindReplaceParams = lcFindReplaceParams();
|
||||
mFindWidget = new lcFindReplaceWidget(mWidget, GetActiveModel(), Replace);
|
||||
mWidget->SetFindReplaceWidget(mFindWidget);
|
||||
}
|
||||
|
||||
lcVector3 lcView::GetMoveDirection(const lcVector3& Direction) const
|
||||
|
||||
@@ -235,6 +235,8 @@ public:
|
||||
void SetMouseModifiers(Qt::KeyboardModifiers MouseModifiers);
|
||||
|
||||
lcModel* GetActiveModel() const;
|
||||
std::vector<lcModel*> GetModelEditingPath() const;
|
||||
void SetSubmodelActive(size_t Depth);
|
||||
void SetTopSubmodelActive();
|
||||
void SetSelectedSubmodelActive();
|
||||
|
||||
@@ -393,6 +395,7 @@ protected:
|
||||
lcModel* mModel = nullptr;
|
||||
lcPiece* mActiveSubmodelInstance = nullptr;
|
||||
lcMatrix44 mActiveSubmodelTransform;
|
||||
std::vector<std::pair<lcPiece*, lcMatrix44>> mSubmodelEditingPath;
|
||||
|
||||
lcCamera* mCamera = nullptr;
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "lc_mesh.h"
|
||||
#include "lc_profile.h"
|
||||
#include "lc_previewwidget.h"
|
||||
#include "lc_qutils.h"
|
||||
#include "lc_viewsphere.h"
|
||||
|
||||
lcViewWidget::lcViewWidget(QWidget* Parent, lcView* View)
|
||||
: QOpenGLWidget(Parent), mView(View)
|
||||
@@ -23,6 +25,62 @@ lcViewWidget::lcViewWidget(QWidget* Parent, lcView* View)
|
||||
{
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
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);
|
||||
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()
|
||||
|
||||
@@ -12,6 +12,8 @@ public:
|
||||
void SetView(lcView* View);
|
||||
|
||||
void UpdateMousePosition();
|
||||
void UpdateModelEditingPath();
|
||||
void SetFindReplaceWidget(QWidget* Widget);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
@@ -29,6 +31,8 @@ protected:
|
||||
void resizeGL(int Width, int Height) override;
|
||||
void paintGL() override;
|
||||
bool event(QEvent* Event) override;
|
||||
bool eventFilter(QObject* Object, QEvent* Event) override;
|
||||
void resizeEvent(QResizeEvent* Event) override;
|
||||
void focusInEvent(QFocusEvent* FocusEvent) override;
|
||||
void focusOutEvent(QFocusEvent* FocusEvent) override;
|
||||
void keyPressEvent(QKeyEvent* KeyEvent) override;
|
||||
@@ -44,7 +48,17 @@ protected:
|
||||
void dragMoveEvent(QDragMoveEvent* DragMoveEvent) override;
|
||||
void dropEvent(QDropEvent* DropEvent) override;
|
||||
|
||||
void UpdateOverlayGeometry();
|
||||
void UpdateModelEditingLabel();
|
||||
|
||||
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;
|
||||
int mWheelAccumulator = 0;
|
||||
};
|
||||
@@ -1309,6 +1309,15 @@ bool lcLight::SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool
|
||||
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
|
||||
{
|
||||
switch (PropertyId)
|
||||
|
||||
@@ -195,6 +195,7 @@ public:
|
||||
QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override;
|
||||
bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override;
|
||||
bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override;
|
||||
lcStep GetLastStep() const override;
|
||||
bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override;
|
||||
void RemoveKeyFrames() override;
|
||||
lcLightHistoryState GetHistoryState() const;
|
||||
|
||||
@@ -144,6 +144,7 @@ public:
|
||||
virtual QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const = 0;
|
||||
virtual bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) = 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 void RemoveKeyFrames() = 0;
|
||||
virtual QString GetName() const = 0;
|
||||
|
||||
@@ -864,6 +864,16 @@ bool lcPiece::SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool
|
||||
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
|
||||
{
|
||||
switch (PropertyId)
|
||||
|
||||
@@ -93,6 +93,7 @@ public:
|
||||
QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override;
|
||||
bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override;
|
||||
bool HasKeyFrame(lcObjectPropertyId PropertyId, lcStep Time) const override;
|
||||
lcStep GetLastStep() const override;
|
||||
bool SetKeyFrame(lcObjectPropertyId PropertyId, lcStep Time, bool KeyFrame) override;
|
||||
void RemoveKeyFrames() override;
|
||||
lcPieceHistoryState GetHistoryState() const;
|
||||
|
||||
+8
-1
@@ -233,7 +233,14 @@ void Project::SetActiveModel(lcModel* ActiveModel, bool UpdateInterface)
|
||||
if (!mIsPreview && gMainWindow && UpdateInterface)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16114,4 +16114,27 @@ Nepodporované grafické karty používají odstín obličeje a barevné okraje.
|
||||
%2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>lcViewWidget</name>
|
||||
<message>
|
||||
<location filename="../common/lc_viewwidget.cpp" line="43"/>
|
||||
<source><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></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'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>
|
||||
@@ -16325,4 +16325,27 @@ Nicht unterstützte Grafikkarten verwenden Gesichtstönung und farbige Kanten.</
|
||||
%2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>lcViewWidget</name>
|
||||
<message>
|
||||
<location filename="../common/lc_viewwidget.cpp" line="43"/>
|
||||
<source><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></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'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>
|
||||
@@ -15969,4 +15969,27 @@ Las tarjetas de video no compatibles usan tinte frontal y bordes coloreados.</tr
|
||||
%2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>lcViewWidget</name>
|
||||
<message>
|
||||
<location filename="../common/lc_viewwidget.cpp" line="43"/>
|
||||
<source><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></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'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>
|
||||
@@ -16319,4 +16319,27 @@ Les cartes vidéo non prises en charge utilisent une teinte de visage et des bor
|
||||
%2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>lcViewWidget</name>
|
||||
<message>
|
||||
<location filename="../common/lc_viewwidget.cpp" line="43"/>
|
||||
<source><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></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'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>
|
||||
@@ -16327,4 +16327,27 @@ As placas de vídeo não suportadas utilizam a tonalidade da face e bordas color
|
||||
%2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>lcViewWidget</name>
|
||||
<message>
|
||||
<location filename="../common/lc_viewwidget.cpp" line="43"/>
|
||||
<source><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></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'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>
|
||||
@@ -15845,4 +15845,27 @@ Unsupported video cards use face tint and colored edges.</source>
|
||||
%2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>lcViewWidget</name>
|
||||
<message>
|
||||
<location filename="../common/lc_viewwidget.cpp" line="43"/>
|
||||
<source><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></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'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>
|
||||
@@ -16451,4 +16451,27 @@ Unsupported video cards use face tint and colored edges.</source>
|
||||
%2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>lcViewWidget</name>
|
||||
<message>
|
||||
<location filename="../common/lc_viewwidget.cpp" line="43"/>
|
||||
<source><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></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'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>
|
||||
@@ -16073,4 +16073,27 @@ Unsupported video cards use face tint and colored edges.</source>
|
||||
%2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>lcViewWidget</name>
|
||||
<message>
|
||||
<location filename="../common/lc_viewwidget.cpp" line="43"/>
|
||||
<source><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></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'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>
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
# LeoCAD tests
|
||||
|
||||
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.
|
||||
|
||||
The executable creates a `QCoreApplication`; it does not open windows or require
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -8,6 +8,7 @@ class lcTests : public QObject
|
||||
|
||||
private slots:
|
||||
void PropertyKeysAffectEquality();
|
||||
void LastKeyFrameTracksTimeEdits();
|
||||
void HistoryStoresOnlyChangedObjects();
|
||||
void HistoryCancelsPendingEdit();
|
||||
void HistoryReplaysAndMergesEdits();
|
||||
|
||||
@@ -15,5 +15,7 @@ INCLUDEPATH += ../common
|
||||
HEADERS += lc_tests.h
|
||||
|
||||
SOURCES += \
|
||||
../common/lc_objectproperty.cpp \
|
||||
lc_objecthistory_test.cpp \
|
||||
lc_objectproperty_test.cpp \
|
||||
main.cpp
|
||||
Reference in new issue
Block a user