Skip to content

Add 3D ViewCube navigation overlay - #193

Merged
fernandotonon merged 5 commits into
masterfrom
feature/view-cube
Mar 13, 2026
Merged

Add 3D ViewCube navigation overlay#193
fernandotonon merged 5 commits into
masterfrom
feature/view-cube

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Mar 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a QML Canvas2D-rendered 3D navigation cube overlay that tracks the active viewport camera orientation in real-time
  • Click-to-snap for all 6 faces, 12 edges, and 8 corners with smooth quaternion interpolation via SpaceCamera::animateToOrientation()
  • Arcball drag rotation on the cube rotates the actual scene camera
  • Auto-hides when no viewport is active; reappears on viewport focus; toggleable via Options → Show View Cube
  • Fixes use-after-free crash when switching from multi-viewport to single-viewport layout (uses deleteLater() instead of delete)
  • Fixes dangling pointer in TransformOperator when viewport widgets are destroyed
  • Closes all child windows (ViewCube, material editor, material list) when the main window is closed via QApplication::quit()
  • Bumps version to 2.14.0

New Files

  • src/ViewCube/ViewCubeController.h/cpp — C++ controller bridging QML overlay with Ogre SpaceCamera
  • src/ViewCube/ViewCubeController_test.cpp — 19 unit tests covering controller logic
  • qml/ViewCubeWindow.qml — QML Canvas2D rendering with face/edge/corner hit-testing

Modified Files

  • src/SpaceCamera.h/cpp — Added animateToOrientation() and isAnimating() for smooth camera transitions
  • src/mainwindow.h/cpp — ViewCube initialization, lifecycle, closeEvent override
  • src/TransformOperator.cpp — Safe widget destruction handling via QObject::destroyed
  • src/OgreWidget.h — Added focusOnWidget signal
  • ui_files/mainwindow.ui — Added "Show View Cube" menu action
  • src/qml_resources.qrc — ViewCube QML resource entry
  • CLAUDE.md — ViewCube architecture documentation

Test plan

  • 19 unit tests pass for ViewCubeController (all faces, edges, corners, visibility logic, singleton, signals)
  • Manual: click each face/edge/corner → camera snaps correctly
  • Manual: drag on cube → camera rotates in matching direction
  • Manual: switch viewport layouts → no crash, cube repositions
  • Manual: close all viewports → cube hides; open new → cube reappears
  • Manual: close main window → all child windows close
  • CI: builds on Windows, macOS, Linux

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Interactive 3D View Cube: drag-to-rotate, click-to-snap (Front/Back/Left/Right/Top/Bottom), smooth animated orientation transitions, toggle via "Show View Cube" (checked by default).
  • Behavior

    • Auto-hides when no viewport is active, reappears on focus; positioned top-right, frameless, always-on-top; uses software rendering to avoid GL conflicts.
  • Documentation

    • Added ViewCube usage docs; duplicate insertions addressed.
  • Tests

    • Added ViewCubeController tests and expanded SpaceCamera tests.
  • Chores

    • Project version bumped to 2.14.0.

Adds a QML-rendered 3D navigation cube that tracks the active viewport's
camera orientation in real-time. Supports click-to-snap for all 6 faces,
12 edges, and 8 corners, plus arcball drag rotation. The cube auto-hides
when no viewport is active and reappears on focus, with a toggle in the
Options menu. Also fixes a use-after-free crash when switching viewport
layouts and ensures all child windows close with the main window.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a ViewCube 3D navigation feature: new QML Window and QML-singleton C++ controller, SpaceCamera orientation animation, MainWindow/OgreWidget integration, build/resource/test additions, a View menu action, and duplicated ViewCube docs in CLAUDE.md.

Changes

Cohort / File(s) Summary
ViewCube Controller & QML
src/ViewCube/ViewCubeController.h, src/ViewCube/ViewCubeController.cpp, qml/ViewCubeWindow.qml
Introduces a QML-singleton ViewCubeController (properties: qw,qx,qy,qz,windowX,windowY,visible; methods: snapToView, snapToDirection, rotateByDelta, setActiveWidget) and a frameless QML Window that renders an interactive 3D cube with hit-testing, hover/drag/click handling, and bindings to the controller.
Camera Animation
src/SpaceCamera.h, src/SpaceCamera.cpp
Adds animateToOrientation(target, duration) with SLERP + smoothstep, per-frame animation state (start/target, elapsed, duration), isAnimating(), and mouse-press cancellation of animations.
MainWindow Integration
src/mainwindow.h, src/mainwindow.cpp, ui_files/mainwindow.ui
MainWindow creates/registers ViewCubeController and QQmlApplicationEngine, loads the QML window, wires a checkable View menu action actionShow_View_Cube, forwards active OgreWidget to controller, updates orientation each frame, and adds closeEvent override plus controller/engine members.
Ogre Widget API
src/OgreWidget.h
Adds SpaceCamera* getSpaceCamera() const accessor to expose the widget's SpaceCamera pointer.
Build & Resources
CMakeLists.txt, src/CMakeLists.txt, src/qml_resources.qrc
Bumps project version 2.13.0→2.14.0; adds ViewCubeController source/header to build; registers QML resource /ViewCube/ViewCubeWindow.qml.
Tests
src/ViewCube/ViewCubeController_test.cpp, src/SpaceCamera_test.cpp, tests/CMakeLists.txt
Adds comprehensive unit tests for ViewCubeController; expands SpaceCamera tests (some duplicated blocks); includes controller files in test CMake.
Transform & Safety Fixes
src/TransformOperator.cpp
Improves widget lifecycle handling by disconnecting previous destroyed signal and reconnecting for the new active widget to avoid dangling callbacks.
Documentation
CLAUDE.md
Inserts ViewCube documentation blocks; the same block is duplicated in the file.

Sequence Diagram

sequenceDiagram
    participant User
    participant ViewCubeWindow as ViewCubeWindow.qml
    participant Controller as ViewCubeController
    participant Camera as SpaceCamera
    participant Widget as OgreWidget

    User->>ViewCubeWindow: Click face / Drag to rotate
    ViewCubeWindow->>ViewCubeWindow: Hit-test (corner/edge/face)

    alt Snap (click)
        ViewCubeWindow->>Controller: snapToView / snapToDirection
        Controller->>Camera: animateToOrientation(targetQuat, duration)
        Camera->>Camera: frameStarted — SLERP interpolate
        Camera->>Widget: apply orientation
        Widget->>ViewCubeWindow: render updated view
    else Drag (rotate)
        ViewCubeWindow->>Controller: rotateByDelta(dx,dy)
        Controller->>Camera: set orientation immediately (cancel animation)
        Camera->>Widget: apply orientation
        Widget->>ViewCubeWindow: render updated view
    end

    rect rgba(100,150,200,0.5)
    ViewCubeWindow->>ViewCubeWindow: Canvas2D render cube, labels, axis
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I sketched a cube that spins so bright,

Quats and slerps in silver light.
Click a corner, snap a view,
Drag to twirl — the world feels new.
The rabbit hops and cheers, "Woohoo!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The pull request description provides comprehensive information but lacks adherence to the required template structure with explicit 'Features' and 'Bugfixes' sections. Reorganize the description to follow the template: move feature bullets under '### ✨ Features' and bugfix items under '### 🐛 Bugfixes' sections.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately and concisely summarizes the main feature: adding a 3D ViewCube navigation overlay. It is specific, clear, and reflects the primary change.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/view-cube
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f7147d02e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +54 to +55
{ "Left", 0.7071, 0.0, -0.7071, 0.0 },
{ "Right", 0.7071, 0.0, 0.7071, 0.0 },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Swap Left/Right snap quaternions

The face mapping in snapToView has Left/Right reversed: the current "Left" quaternion rotates the reference view direction (0,0,-1) toward +X, while "Right" rotates toward -X (the opposite of the stated intent in the comment). In practice, clicking Left and Right on the cube will snap to opposite side views, which makes camera navigation incorrect.

Useful? React with 👍 / 👎.

Comment thread src/ViewCube/ViewCubeController.cpp Outdated
Comment on lines +134 to +138
emit visibilityChanged(m_visible);
});
}

emit visibilityChanged(m_visible);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Emit effective visibility state from notify signal

The visible property is computed by isVisible() (m_visible && m_activeWidget), but this path emits visibilityChanged(m_visible). When the active widget is cleared while the toggle remains enabled, the signal payload reports true even though the property value is false, so listeners that use the signal argument can become inconsistent with the actual ViewCube visibility.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@qml/ViewCubeWindow.qml`:
- Around line 314-401: hitTest() and handleClick() currently only detect corners
and faces, so edge targets never get chosen; add an edge-detection step between
the corner check and face check that queries cubeCanvas.getEdgeDefs(), computes
each edge's projected midpoint (use the two vertex indices on
edge.vertIdxA/vertIdxB from projected[]), test a small hit radius and depth like
corners, and pick the nearest visible edge (z < 0) similar to how corners are
chosen; then update handleClick() to also iterate cubeCanvas.getEdgeDefs() and
when an edge name matches call ViewCubeController.snapToDirection(edge.dir[0],
edge.dir[1], edge.dir[2]) (or the appropriate snapping call for edges) so clicks
on edges snap to the 12 edge orientations.

In `@src/ViewCube/ViewCubeController.cpp`:
- Around line 11-18: Implement a proper destructor (~ViewCubeController) that
clears the global singleton by setting s_instance to nullptr when the object is
destroyed (e.g., if (s_instance == this) s_instance = nullptr), so instance() /
qmlInstance() won’t return freed memory; also update qmlInstance()’s fallback
allocation to avoid leaking raw memory (replace the raw new fallback with a
static local instance or a managed/smart-pointered singleton) so the fallback
path does not leak if ever used.
- Around line 173-190: The eventFilter branch for Hide/Close/Destroy leaves
m_activeWidget set for Hide events and never emits visibilityChanged(), causing
isVisible() to be wrong; in ViewCubeController::eventFilter, when obj ==
m_activeWidget and event type is QEvent::Hide, QEvent::Close or QEvent::Destroy,
set m_activeWidget = nullptr unconditionally, then emit visibilityChanged() (or
call the method that updates/hides the overlay) before returning; this ensures
isVisible() reflects the hidden state and the overlay is removed when the active
viewport hides/closes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5944b7d-9137-4d4f-a0f7-ed40c32fd73f

📥 Commits

Reviewing files that changed from the base of the PR and between 920fba1 and 5f7147d.

📒 Files selected for processing (15)
  • CLAUDE.md
  • CMakeLists.txt
  • qml/ViewCubeWindow.qml
  • src/CMakeLists.txt
  • src/OgreWidget.h
  • src/SpaceCamera.cpp
  • src/SpaceCamera.h
  • src/TransformOperator.cpp
  • src/ViewCube/ViewCubeController.cpp
  • src/ViewCube/ViewCubeController.h
  • src/ViewCube/ViewCubeController_test.cpp
  • src/mainwindow.cpp
  • src/mainwindow.h
  • src/qml_resources.qrc
  • ui_files/mainwindow.ui

Comment thread qml/ViewCubeWindow.qml
Comment on lines +11 to +18
ViewCubeController::ViewCubeController(QWidget* mainWindow, QObject* parent)
: QObject(parent)
, m_mainWindow(mainWindow)
{
s_instance = this;
if (m_mainWindow)
m_mainWindow->installEventFilter(this);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset the singleton on teardown.

s_instance is overwritten on construction but never cleared again. After delete controller in src/ViewCube/ViewCubeController_test.cpp Line 21, instance() / qmlInstance() can hand out freed memory until another controller is constructed, and the fallback allocation in qmlInstance() leaks if that path ever runs first.

🧹 Proposed fix
--- a/src/ViewCube/ViewCubeController.h
+++ b/src/ViewCube/ViewCubeController.h
@@
 public:
     explicit ViewCubeController(QWidget* mainWindow, QObject* parent = nullptr);
+    ~ViewCubeController() override;
 
     static ViewCubeController* qmlInstance(QQmlEngine* engine, QJSEngine* scriptEngine);
--- a/src/ViewCube/ViewCubeController.cpp
+++ b/src/ViewCube/ViewCubeController.cpp
@@
 ViewCubeController::ViewCubeController(QWidget* mainWindow, QObject* parent)
     : QObject(parent)
     , m_mainWindow(mainWindow)
 {
     s_instance = this;
     if (m_mainWindow)
         m_mainWindow->installEventFilter(this);
 }
+
+ViewCubeController::~ViewCubeController()
+{
+    if (s_instance == this)
+        s_instance = nullptr;
+}
@@
 ViewCubeController* ViewCubeController::qmlInstance(QQmlEngine* engine, QJSEngine* scriptEngine)
 {
     Q_UNUSED(engine)
     Q_UNUSED(scriptEngine)
-
-    if (!s_instance)
-        s_instance = new ViewCubeController(nullptr);
-    return s_instance;
+    Q_ASSERT_X(s_instance, "ViewCubeController::qmlInstance",
+               "MainWindow must create the ViewCubeController before QML loads");
+    return s_instance;
 }

Also applies to: 25-32

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController.cpp` around lines 11 - 18, Implement a proper
destructor (~ViewCubeController) that clears the global singleton by setting
s_instance to nullptr when the object is destroyed (e.g., if (s_instance ==
this) s_instance = nullptr), so instance() / qmlInstance() won’t return freed
memory; also update qmlInstance()’s fallback allocation to avoid leaking raw
memory (replace the raw new fallback with a static local instance or a
managed/smart-pointered singleton) so the fallback path does not leak if ever
used.

Comment on lines +173 to +190
bool ViewCubeController::eventFilter(QObject* obj, QEvent* event)
{
auto type = event->type();

if (obj == m_activeWidget &&
(type == QEvent::Hide || type == QEvent::Close || type == QEvent::Destroy)) {
if (type != QEvent::Hide)
m_activeWidget = nullptr;
return QObject::eventFilter(obj, event);
}

// Reposition when active widget or main window moves/resizes
if (type == QEvent::Move || type == QEvent::Resize) {
if (m_visible && m_activeWidget &&
(obj == m_activeWidget || obj == m_mainWindow))
reposition();
}
return QObject::eventFilter(obj, event);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hide the overlay when the active viewport hides or closes.

This branch never emits visibilityChanged(), and isVisible() still treats any non-null widget as visible. During layout changes the ViewCube can stay rendered over a hidden/closing viewport until destruction, which breaks the advertised auto-hide behavior.

🙈 Proposed fix
--- a/src/ViewCube/ViewCubeController.h
+++ b/src/ViewCube/ViewCubeController.h
@@
-    bool isVisible() const { return m_visible && m_activeWidget; }
+    bool isVisible() const;
--- a/src/ViewCube/ViewCubeController.cpp
+++ b/src/ViewCube/ViewCubeController.cpp
@@
+bool ViewCubeController::isVisible() const
+{
+    return m_visible && m_activeWidget && m_activeWidget->isVisible();
+}
+
 bool ViewCubeController::eventFilter(QObject* obj, QEvent* event)
 {
     auto type = event->type();
 
     if (obj == m_activeWidget &&
-        (type == QEvent::Hide || type == QEvent::Close || type == QEvent::Destroy)) {
-        if (type != QEvent::Hide)
-            m_activeWidget = nullptr;
+        (type == QEvent::Show || type == QEvent::Hide ||
+         type == QEvent::Close || type == QEvent::Destroy)) {
+        if (type == QEvent::Close || type == QEvent::Destroy)
+            m_activeWidget = nullptr;
+        emit visibilityChanged(m_visible);
         return QObject::eventFilter(obj, event);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController.cpp` around lines 173 - 190, The eventFilter
branch for Hide/Close/Destroy leaves m_activeWidget set for Hide events and
never emits visibilityChanged(), causing isVisible() to be wrong; in
ViewCubeController::eventFilter, when obj == m_activeWidget and event type is
QEvent::Hide, QEvent::Close or QEvent::Destroy, set m_activeWidget = nullptr
unconditionally, then emit visibilityChanged() (or call the method that
updates/hides the overlay) before returning; this ensures isVisible() reflects
the hidden state and the overlay is removed when the active viewport
hides/closes.

- Add ViewCubeController source to tests/CMakeLists.txt (fixes
  undefined reference errors in MaterialEditorQML test targets)
- Add destructor that clears s_instance to prevent dangling singleton
- Emit isVisible() instead of m_visible in signals for payload
  consistency (isVisible requires both visible flag AND active widget)
- Emit visibilityChanged in eventFilter on Hide/Close/Destroy events
- Add edge snapping: 12 edge definitions with hit-testing between
  corner and face checks in QML, so clicking cube edges snaps to
  the corresponding diagonal view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/ViewCube/ViewCubeController.h (1)

39-39: ⚠️ Potential issue | 🟠 Major

Make visible depend on the widget's actual visibility.

This accessor still reports true for a hidden-but-not-destroyed viewport, so the QEvent::Hide path in src/ViewCube/ViewCubeController.cpp can leave the QML window visible during layout switches. Declare isVisible() here and implement it in the .cpp against m_activeWidget->isVisible(); if the same widget can reappear, the QEvent::Show path should notify again.

🔧 Minimal header change
-    bool isVisible() const { return m_visible && m_activeWidget; }
+    bool isVisible() const;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController.h` at line 39, Update the inline isVisible()
accessor to be a declaration in ViewCubeController.h and implement it in
ViewCubeController.cpp so it checks the widget's actual visibility;
specifically, change isVisible() to return m_visible && m_activeWidget &&
m_activeWidget->isVisible() in the .cpp (use the existing m_activeWidget and
isVisible() call) and ensure the QEvent::Show path in ViewCubeController.cpp
re-notifies when the widget becomes visible again after being hidden.
src/ViewCube/ViewCubeController.cpp (1)

31-38: ⚠️ Potential issue | 🟠 Major

Don't create a fallback controller in the singleton provider.

If QML resolves the singleton before MainWindow constructs the real controller, this branch hands the engine a ViewCubeController(nullptr) with no main-window or active-widget wiring. Later overwriting s_instance does not replace the object QML already cached, so the overlay can stay bound to the wrong controller for the lifetime of the engine.

🔧 Safer provider
 ViewCubeController* ViewCubeController::qmlInstance(QQmlEngine* engine, QJSEngine* scriptEngine)
 {
     Q_UNUSED(engine)
     Q_UNUSED(scriptEngine)

-    if (!s_instance)
-        s_instance = new ViewCubeController(nullptr);
+    Q_ASSERT_X(s_instance, "ViewCubeController::qmlInstance",
+               "Construct ViewCubeController before loading QML");
     return s_instance;
 }
#!/bin/bash
# Verify whether the QML registration path can call ViewCubeController::qmlInstance
# before the application-owned controller is constructed.
rg -n -C3 -e 'ViewCubeController::qmlInstance|qmlRegisterSingleton(Type|Instance)|ViewCubeModule' src qml tests
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController.cpp` around lines 31 - 38, The qmlInstance
provider currently creates a fallback ViewCubeController(nullptr) which can be
cached by QML and never properly wired; change ViewCubeController::qmlInstance
to NOT construct a new controller when s_instance is null — simply return
nullptr (or qmlNullValue) so QML gets no stub; update any call-sites or QML
registration to tolerate a null singleton or delay qmlRegisterSingleton until
the real controller (s_instance) is constructed; target the
ViewCubeController::qmlInstance function and the s_instance static so no new
ViewCubeController(nullptr) is ever created in the provider.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/CMakeLists.txt`:
- Around line 62-63: The test file src/ViewCube/ViewCubeController_test.cpp is
not being added as a CTest target, so the 19 controller tests won't run; update
tests/CMakeLists.txt to create and register a real test target: add an
add_executable(ViewCubeController_test
src/ViewCube/ViewCubeController_test.cpp), link it with the appropriate
libraries via target_link_libraries(ViewCubeController_test PRIVATE gtest_main
<your_lib_target>), and register it with add_test(NAME ViewCubeController_test
COMMAND ViewCubeController_test) so CTest/CI discovers and runs the tests;
ensure the source path and target names match the existing
ViewCubeController.cpp inclusion.

---

Duplicate comments:
In `@src/ViewCube/ViewCubeController.cpp`:
- Around line 31-38: The qmlInstance provider currently creates a fallback
ViewCubeController(nullptr) which can be cached by QML and never properly wired;
change ViewCubeController::qmlInstance to NOT construct a new controller when
s_instance is null — simply return nullptr (or qmlNullValue) so QML gets no
stub; update any call-sites or QML registration to tolerate a null singleton or
delay qmlRegisterSingleton until the real controller (s_instance) is
constructed; target the ViewCubeController::qmlInstance function and the
s_instance static so no new ViewCubeController(nullptr) is ever created in the
provider.

In `@src/ViewCube/ViewCubeController.h`:
- Line 39: Update the inline isVisible() accessor to be a declaration in
ViewCubeController.h and implement it in ViewCubeController.cpp so it checks the
widget's actual visibility; specifically, change isVisible() to return m_visible
&& m_activeWidget && m_activeWidget->isVisible() in the .cpp (use the existing
m_activeWidget and isVisible() call) and ensure the QEvent::Show path in
ViewCubeController.cpp re-notifies when the widget becomes visible again after
being hidden.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 668fcccd-6316-474a-ab9e-01953df93925

📥 Commits

Reviewing files that changed from the base of the PR and between 5f7147d and 391f953.

📒 Files selected for processing (4)
  • qml/ViewCubeWindow.qml
  • src/ViewCube/ViewCubeController.cpp
  • src/ViewCube/ViewCubeController.h
  • tests/CMakeLists.txt

Comment thread tests/CMakeLists.txt
Comment on lines +62 to 63
${CMAKE_CURRENT_SOURCE_DIR}/../src/ViewCube/ViewCubeController.cpp
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Register ViewCubeController_test.cpp as a real test target.

Adding the controller sources here fixes link inputs, but this file still never creates or discovers src/ViewCube/ViewCubeController_test.cpp. As written, the new 19 controller tests will not run under CTest/CI.

🔧 Minimal follow-up
+    if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../src/ViewCube/ViewCubeController_test.cpp")
+        create_test_executable(ViewCubeController_test
+            "${CMAKE_CURRENT_SOURCE_DIR}/../src/ViewCube/ViewCubeController_test.cpp"
+        )
+    endif()
+
+    if(TARGET ViewCubeController_test)
+        gtest_discover_tests(ViewCubeController_test)
+    endif()
+
     add_custom_target(run_all_materialeditorthml_tests
         COMMAND ctest --verbose
         DEPENDS MaterialEditorQML_qml_test_runner
+        $<$<TARGET_EXISTS:ViewCubeController_test>:ViewCubeController_test>
         $<$<TARGET_EXISTS:MaterialEditorQML_test>:MaterialEditorQML_test>
         $<$<TARGET_EXISTS:MaterialEditorQML_qml_test>:MaterialEditorQML_qml_test>
         $<$<TARGET_EXISTS:MaterialEditorQML_perf_test>:MaterialEditorQML_perf_test>

Based on learnings: Add Google Test unit tests for new functionality in the src/ directory with the _test.cpp suffix.

Also applies to: 113-114

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/CMakeLists.txt` around lines 62 - 63, The test file
src/ViewCube/ViewCubeController_test.cpp is not being added as a CTest target,
so the 19 controller tests won't run; update tests/CMakeLists.txt to create and
register a real test target: add an add_executable(ViewCubeController_test
src/ViewCube/ViewCubeController_test.cpp), link it with the appropriate
libraries via target_link_libraries(ViewCubeController_test PRIVATE gtest_main
<your_lib_target>), and register it with add_test(NAME ViewCubeController_test
COMMAND ViewCubeController_test) so CTest/CI discovers and runs the tests;
ensure the source path and target names match the existing
ViewCubeController.cpp inclusion.

The 50px window was clipping cube corners on diagonal orientations.
Increased to 64px and locked dimensions with min/max constraints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/ViewCube/ViewCubeController.cpp (2)

179-198: ⚠️ Potential issue | 🟠 Major

ViewCube remains visible when viewport hides.

When QEvent::Hide fires, m_activeWidget stays non-null and isVisible() returns true (only checks m_visible && m_activeWidget). The ViewCube overlay remains rendered over a hidden viewport until the widget is closed or destroyed.

🔧 Proposed fix in ViewCubeController.h

Update isVisible() to also check the widget's visibility state:

-    bool isVisible() const { return m_visible && m_activeWidget; }
+    bool isVisible() const { return m_visible && m_activeWidget && m_activeWidget->isVisible(); }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController.cpp` around lines 179 - 198, The ViewCube
remains visible on QEvent::Hide because isVisible() only checks m_visible and
m_activeWidget but not the widget's actual visibility; update the visibility
logic used by ViewCubeController::isVisible() so it also queries the current
widget's visibility (e.g., ensure isVisible() returns m_visible &&
m_activeWidget && m_activeWidget->isVisible()), and keep the existing handling
in ViewCubeController::eventFilter for QEvent::Hide/Close/Destroy so emit
visibilityChanged(isVisible()) reflects the widget's true visible state.

31-39: ⚠️ Potential issue | 🟠 Major

Fallback allocation in qmlInstance() risks memory leak and silent malfunction.

If QML loads before MainWindow creates the controller, this path allocates a controller with nullptr mainWindow (no event filter installed), and the allocation is never freed. Replace with an assertion to fail fast during development.

🔧 Proposed fix
 ViewCubeController* ViewCubeController::qmlInstance(QQmlEngine* engine, QJSEngine* scriptEngine)
 {
     Q_UNUSED(engine)
     Q_UNUSED(scriptEngine)
 
-    if (!s_instance)
-        s_instance = new ViewCubeController(nullptr);
+    Q_ASSERT_X(s_instance, "ViewCubeController::qmlInstance",
+               "MainWindow must create the ViewCubeController before QML loads");
     return s_instance;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController.cpp` around lines 31 - 39, The current QML
instantiation in ViewCubeController::qmlInstance creates a leaked controller
with a nullptr MainWindow; remove the fallback allocation (the new
ViewCubeController(nullptr) path) and replace it with a failing assertion so the
app fails fast during development. Specifically, in
ViewCubeController::qmlInstance, do not assign s_instance = new
ViewCubeController(nullptr); instead call a Q_ASSERT_X or qFatal (e.g.,
Q_ASSERT_X(false, "ViewCubeController::qmlInstance", "ViewCubeController must be
created by MainWindow before QML loads")) and return s_instance, ensuring the
only proper creation path is the MainWindow-created instance referenced by
s_instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/ViewCube/ViewCubeController.cpp`:
- Around line 179-198: The ViewCube remains visible on QEvent::Hide because
isVisible() only checks m_visible and m_activeWidget but not the widget's actual
visibility; update the visibility logic used by ViewCubeController::isVisible()
so it also queries the current widget's visibility (e.g., ensure isVisible()
returns m_visible && m_activeWidget && m_activeWidget->isVisible()), and keep
the existing handling in ViewCubeController::eventFilter for
QEvent::Hide/Close/Destroy so emit visibilityChanged(isVisible()) reflects the
widget's true visible state.
- Around line 31-39: The current QML instantiation in
ViewCubeController::qmlInstance creates a leaked controller with a nullptr
MainWindow; remove the fallback allocation (the new ViewCubeController(nullptr)
path) and replace it with a failing assertion so the app fails fast during
development. Specifically, in ViewCubeController::qmlInstance, do not assign
s_instance = new ViewCubeController(nullptr); instead call a Q_ASSERT_X or
qFatal (e.g., Q_ASSERT_X(false, "ViewCubeController::qmlInstance",
"ViewCubeController must be created by MainWindow before QML loads")) and return
s_instance, ensuring the only proper creation path is the MainWindow-created
instance referenced by s_instance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1be7501a-53c9-4eec-864d-e9f1ccf57848

📥 Commits

Reviewing files that changed from the base of the PR and between 391f953 and a1544f2.

📒 Files selected for processing (2)
  • qml/ViewCubeWindow.qml
  • src/ViewCube/ViewCubeController.cpp

ViewCubeController (8 new tests):
- Constructor with mainWindow: verifies event filter installation
- Event filter with Move/Resize/unrelated events on mainWindow
- Destructor preserving singleton when different instance exists
- qmlInstance creating new controller when singleton is null
- Signal count verification for visibility toggles
- updateOrientation no-emit when orientation unchanged
- Window position defaults with no active widget

SpaceCamera (8 new tests):
- isAnimating() default false
- getCamera() returns null for mock
- mousePressEvent sets animating false
- frameStarted with zero/large/negative time deltas
- Control key speed transition cycles
- Extreme camera speed values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fernandotonon fernandotonon linked an issue Mar 13, 2026 that may be closed by this pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/SpaceCamera_test.cpp (1)

687-708: Control-speed transition coverage is duplicated

This test overlaps with existing assertions in Line 138-152 and Line 250-261. Consider consolidating into a single parameterized/control-transition test to reduce duplication and maintenance cost.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SpaceCamera_test.cpp` around lines 687 - 708, The ControlKey
speed-transition assertions are duplicated across tests; consolidate them by
removing this redundant TEST(SpaceCamera, ControlKeySpeedTransitionCycle) and
instead add a single parameterized test (or extend the existing parameterized
control-transition test) that covers the Control press/release cycle for
MockSpaceCamera: use setCameraSpeed to set initial speed, call keyPressEvent and
keyReleaseEvent on the same QKeyEvent instances, and assert getCameraSpeed
values for precision and restored modes; update or reuse the existing test
harness around the existing tests at (the ones covering Control key transitions)
so all control-press/release scenarios are covered in one place rather than
duplicated.
src/ViewCube/ViewCubeController_test.cpp (2)

123-161: Consider using EXPECT_NO_THROW for crash-safety tests.

The EXPECT_TRUE(true) pattern works but doesn't clearly communicate intent. For "no crash" tests, EXPECT_NO_THROW or ASSERT_NO_FATAL_FAILURE would be more expressive.

✨ Optional: More expressive crash-safety assertions
 TEST_F(ViewCubeControllerTest, SnapToViewInvalidFaceNoOp)
 {
-    controller->snapToView("InvalidFace");
-    controller->snapToView("");
-    EXPECT_TRUE(true);
+    EXPECT_NO_THROW(controller->snapToView("InvalidFace"));
+    EXPECT_NO_THROW(controller->snapToView(""));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController_test.cpp` around lines 123 - 161, These tests
currently use EXPECT_TRUE(true) after calling controller methods which hides
intent; wrap the call sequences in EXPECT_NO_THROW (or ASSERT_NO_FATAL_FAILURE
if you need to stop on failure) so the test explicitly asserts "no
exception/crash" — e.g. in tests SnapToViewInvalidFaceNoOp and
SnapToViewValidFacesNoCamera wrap controller->snapToView(...) calls, in
SnapToViewCaseInsensitive wrap the snapToView(...) calls, in
SnapToDirectionWithoutCamera wrap controller->snapToDirection(...) calls, and in
RotateByDeltaWithoutCamera wrap controller->rotateByDelta(...) calls with
EXPECT_NO_THROW (or ASSERT_NO_FATAL_FAILURE) and remove the trailing
EXPECT_TRUE(true).

308-319: Test has implicit ordering dependency.

Line 311 asserts instance() == nullptr as a precondition. While this should hold after ViewCubeControllerTest::TearDown runs, the test relies on no other test having left a singleton instance alive.

This is likely fine given Google Test's execution model, but consider adding a defensive cleanup at the start:

✨ Optional: Make precondition explicit and self-healing
 TEST(ViewCubeControllerLifetime, QmlInstanceCreatesNewWhenSingletonIsNull)
 {
-    // Precondition: no singleton exists
-    EXPECT_EQ(ViewCubeController::instance(), nullptr);
+    // Ensure clean state (defensive against test ordering)
+    if (auto* existing = ViewCubeController::instance()) {
+        delete existing;
+    }
+    ASSERT_EQ(ViewCubeController::instance(), nullptr);
 
     auto* inst = ViewCubeController::qmlInstance(nullptr, nullptr);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController_test.cpp` around lines 308 - 319, The test
implicitly assumes no existing singleton; make it self-healing by checking
ViewCubeController::instance() at the start of TEST(ViewCubeControllerLifetime,
QmlInstanceCreatesNewWhenSingletonIsNull) and deleting/resetting it if non-null
before the EXPECT_EQ precondition. Concretely, add a defensive cleanup at test
start that calls delete on the existing ViewCubeController::instance() (or
otherwise resets the singleton) so the subsequent
EXPECT_EQ(ViewCubeController::instance(), nullptr) is guaranteed to hold, then
proceed to call ViewCubeController::qmlInstance and the rest of the assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/SpaceCamera_test.cpp`:
- Around line 645-653: The test is vacuous because it never sets SpaceCamera
into an animating state; update TEST(SpaceCamera, MousePressSetsAnimatingFalse)
to first put the camera into animating mode (e.g., call
MockSpaceCamera::setAnimating(true) or use the real
startAnimation()/beginAnimation() method on MockSpaceCamera), assert that
spaceCamera.isAnimating() is true as a precondition, then send the QMouseEvent
via spaceCamera.mousePressEvent(&pressEvent) and finally
EXPECT_FALSE(spaceCamera.isAnimating()); ensure you reference MockSpaceCamera,
mousePressEvent, and isAnimating in the change.

---

Nitpick comments:
In `@src/SpaceCamera_test.cpp`:
- Around line 687-708: The ControlKey speed-transition assertions are duplicated
across tests; consolidate them by removing this redundant TEST(SpaceCamera,
ControlKeySpeedTransitionCycle) and instead add a single parameterized test (or
extend the existing parameterized control-transition test) that covers the
Control press/release cycle for MockSpaceCamera: use setCameraSpeed to set
initial speed, call keyPressEvent and keyReleaseEvent on the same QKeyEvent
instances, and assert getCameraSpeed values for precision and restored modes;
update or reuse the existing test harness around the existing tests at (the ones
covering Control key transitions) so all control-press/release scenarios are
covered in one place rather than duplicated.

In `@src/ViewCube/ViewCubeController_test.cpp`:
- Around line 123-161: These tests currently use EXPECT_TRUE(true) after calling
controller methods which hides intent; wrap the call sequences in
EXPECT_NO_THROW (or ASSERT_NO_FATAL_FAILURE if you need to stop on failure) so
the test explicitly asserts "no exception/crash" — e.g. in tests
SnapToViewInvalidFaceNoOp and SnapToViewValidFacesNoCamera wrap
controller->snapToView(...) calls, in SnapToViewCaseInsensitive wrap the
snapToView(...) calls, in SnapToDirectionWithoutCamera wrap
controller->snapToDirection(...) calls, and in RotateByDeltaWithoutCamera wrap
controller->rotateByDelta(...) calls with EXPECT_NO_THROW (or
ASSERT_NO_FATAL_FAILURE) and remove the trailing EXPECT_TRUE(true).
- Around line 308-319: The test implicitly assumes no existing singleton; make
it self-healing by checking ViewCubeController::instance() at the start of
TEST(ViewCubeControllerLifetime, QmlInstanceCreatesNewWhenSingletonIsNull) and
deleting/resetting it if non-null before the EXPECT_EQ precondition. Concretely,
add a defensive cleanup at test start that calls delete on the existing
ViewCubeController::instance() (or otherwise resets the singleton) so the
subsequent EXPECT_EQ(ViewCubeController::instance(), nullptr) is guaranteed to
hold, then proceed to call ViewCubeController::qmlInstance and the rest of the
assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 067b4fdd-785c-44b9-8101-710ddc769b1f

📥 Commits

Reviewing files that changed from the base of the PR and between a1544f2 and 2fef76f.

📒 Files selected for processing (2)
  • src/SpaceCamera_test.cpp
  • src/ViewCube/ViewCubeController_test.cpp

Comment thread src/SpaceCamera_test.cpp
Comment on lines +645 to +653
TEST(SpaceCamera, MousePressSetsAnimatingFalse)
{
MockSpaceCamera spaceCamera;
// mousePressEvent always sets mAnimating = false (cancels animation)
QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(50, 50),
Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier);
spaceCamera.mousePressEvent(&pressEvent);
EXPECT_FALSE(spaceCamera.isAnimating());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

MousePressSetsAnimatingFalse is currently a vacuous test

This test never puts SpaceCamera into an animating state before the press, so it can pass even if mouse press does not cancel an active animation.

✅ Proposed test fix
 TEST(SpaceCamera, MousePressSetsAnimatingFalse)
 {
     MockSpaceCamera spaceCamera;
-    // mousePressEvent always sets mAnimating = false (cancels animation)
+    // Arrange: start animation first
+    spaceCamera.animateToOrientation(Ogre::Quaternion::IDENTITY, 0.5f);
+    ASSERT_TRUE(spaceCamera.isAnimating());
+
+    // Act: mouse press should cancel animation
     QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(50, 50),
                           Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier);
     spaceCamera.mousePressEvent(&pressEvent);
+
+    // Assert
     EXPECT_FALSE(spaceCamera.isAnimating());
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
TEST(SpaceCamera, MousePressSetsAnimatingFalse)
{
MockSpaceCamera spaceCamera;
// mousePressEvent always sets mAnimating = false (cancels animation)
QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(50, 50),
Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier);
spaceCamera.mousePressEvent(&pressEvent);
EXPECT_FALSE(spaceCamera.isAnimating());
}
TEST(SpaceCamera, MousePressSetsAnimatingFalse)
{
MockSpaceCamera spaceCamera;
// Arrange: start animation first
spaceCamera.animateToOrientation(Ogre::Quaternion::IDENTITY, 0.5f);
ASSERT_TRUE(spaceCamera.isAnimating());
// Act: mouse press should cancel animation
QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(50, 50),
Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier);
spaceCamera.mousePressEvent(&pressEvent);
// Assert
EXPECT_FALSE(spaceCamera.isAnimating());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SpaceCamera_test.cpp` around lines 645 - 653, The test is vacuous because
it never sets SpaceCamera into an animating state; update TEST(SpaceCamera,
MousePressSetsAnimatingFalse) to first put the camera into animating mode (e.g.,
call MockSpaceCamera::setAnimating(true) or use the real
startAnimation()/beginAnimation() method on MockSpaceCamera), assert that
spaceCamera.isAnimating() is true as a precondition, then send the QMouseEvent
via spaceCamera.mousePressEvent(&pressEvent) and finally
EXPECT_FALSE(spaceCamera.isAnimating()); ensure you reference MockSpaceCamera,
mousePressEvent, and isAnimating in the change.

Address code review feedback:
- isVisible() now checks m_activeWidget->isVisible() so the ViewCube
  hides when the viewport widget is hidden (e.g., tab switch)
- eventFilter handles QEvent::Show to restore ViewCube when viewport
  reappears
- Clarify SpaceCamera test limitation comment (animateToOrientation
  requires Ogre mTarget, unavailable in MockSpaceCamera)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/ViewCube/ViewCubeController.cpp (1)

31-39: ⚠️ Potential issue | 🟠 Major

Fallback allocation in qmlInstance() can leak memory.

If QML loads before MainWindow creates the controller, this fallback creates an orphaned instance with nullptr mainWindow. When MainWindow later creates its own instance, s_instance is overwritten and the first instance leaks (no parent ownership).

Consider asserting that the instance exists rather than creating a fallback:

🔧 Proposed fix
 ViewCubeController* ViewCubeController::qmlInstance(QQmlEngine* engine, QJSEngine* scriptEngine)
 {
     Q_UNUSED(engine)
     Q_UNUSED(scriptEngine)
 
-    if (!s_instance)
-        s_instance = new ViewCubeController(nullptr);
+    Q_ASSERT_X(s_instance, "ViewCubeController::qmlInstance",
+               "MainWindow must create ViewCubeController before QML loads");
     return s_instance;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ViewCube/ViewCubeController.cpp` around lines 31 - 39, The qmlInstance()
fallback allocation (creating a new ViewCubeController(nullptr) when s_instance
is null) can leak if MainWindow later creates the real controller; change
ViewCubeController::qmlInstance to not allocate a fallback: instead reference
the existing s_instance and assert it exists (e.g., Q_ASSERT(s_instance)) and
return it, or return nullptr after logging a warning if you prefer a
runtime-safe path—remove the "new ViewCubeController(nullptr)" code and any
unconditional assignment to s_instance so MainWindow remains the sole
creator/owner.
src/SpaceCamera_test.cpp (1)

645-656: ⚠️ Potential issue | 🟡 Minor

MousePressSetsAnimatingFalse is still vacuous for cancellation behavior.

At Line 645, the test name implies canceling an active animation, but no animating precondition is established before the mouse press. This can pass even if cancellation regresses.

✅ Minimal fix to avoid misleading intent
-TEST(SpaceCamera, MousePressSetsAnimatingFalse)
+TEST(SpaceCamera, MousePressKeepsAnimatingFalseWhenNotAnimating)
 {
     MockSpaceCamera spaceCamera;
-    // mousePressEvent always sets mAnimating = false (cancels animation).
-    // Note: we cannot set mAnimating=true first via animateToOrientation()
-    // because that method dereferences mTarget which is null in MockSpaceCamera.
-    // Full animation cancellation is tested in SpaceCameraOgreTest fixtures.
+    // This only verifies default non-animating state remains false after press.
+    // Add a separate Ogre-backed test that first enters an animating state,
+    // then verifies mousePressEvent cancels it.
     QMouseEvent pressEvent(QEvent::MouseButtonPress, QPointF(50, 50),
                           Qt::MiddleButton, Qt::MiddleButton, Qt::NoModifier);
     spaceCamera.mousePressEvent(&pressEvent);
     EXPECT_FALSE(spaceCamera.isAnimating());
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SpaceCamera_test.cpp` around lines 645 - 656, The test currently never
puts the camera into an animating state, so make the precondition explicit:
before calling spaceCamera.mousePressEvent(&pressEvent) set the camera to
animating (e.g. call spaceCamera.setAnimating(true) or, if that setter doesn't
exist, add a small accessor/mutator on MockSpaceCamera to set its mAnimating
flag or make mAnimating public for the test), then assert
EXPECT_TRUE(spaceCamera.isAnimating()) if desired and call mousePressEvent and
EXPECT_FALSE(spaceCamera.isAnimating()); update MockSpaceCamera accordingly to
allow the test to establish the animating precondition.
🧹 Nitpick comments (1)
src/SpaceCamera_test.cpp (1)

690-711: Clarify “restore” vs “reset” semantics in the control-speed cycle test.

At Line 701, comments say speed is “restored,” but assertions expect 0.1f after starting from 1.0f. Either rename/comment this as reset-to-default behavior or assert restoration to the prior value.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/SpaceCamera_test.cpp` around lines 690 - 711, The test
ControlKeySpeedTransitionCycle is ambiguous: update the test to reflect intended
semantics of Control modifier on MockSpaceCamera by either (A) changing the
comment and assertions to indicate a reset-to-default behavior (i.e., after
releasing Control expect default speed 0.1f) or (B) changing the assertions to
verify true restoration of the prior speed (i.e., after releasing Control expect
the original 1.0f). Locate the TEST named ControlKeySpeedTransitionCycle and the
calls to MockSpaceCamera::setCameraSpeed, getCameraSpeed, keyPressEvent, and
keyReleaseEvent and modify the comments and expected values accordingly so they
consistently match the chosen behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/SpaceCamera_test.cpp`:
- Around line 645-656: The test currently never puts the camera into an
animating state, so make the precondition explicit: before calling
spaceCamera.mousePressEvent(&pressEvent) set the camera to animating (e.g. call
spaceCamera.setAnimating(true) or, if that setter doesn't exist, add a small
accessor/mutator on MockSpaceCamera to set its mAnimating flag or make
mAnimating public for the test), then assert
EXPECT_TRUE(spaceCamera.isAnimating()) if desired and call mousePressEvent and
EXPECT_FALSE(spaceCamera.isAnimating()); update MockSpaceCamera accordingly to
allow the test to establish the animating precondition.

In `@src/ViewCube/ViewCubeController.cpp`:
- Around line 31-39: The qmlInstance() fallback allocation (creating a new
ViewCubeController(nullptr) when s_instance is null) can leak if MainWindow
later creates the real controller; change ViewCubeController::qmlInstance to not
allocate a fallback: instead reference the existing s_instance and assert it
exists (e.g., Q_ASSERT(s_instance)) and return it, or return nullptr after
logging a warning if you prefer a runtime-safe path—remove the "new
ViewCubeController(nullptr)" code and any unconditional assignment to s_instance
so MainWindow remains the sole creator/owner.

---

Nitpick comments:
In `@src/SpaceCamera_test.cpp`:
- Around line 690-711: The test ControlKeySpeedTransitionCycle is ambiguous:
update the test to reflect intended semantics of Control modifier on
MockSpaceCamera by either (A) changing the comment and assertions to indicate a
reset-to-default behavior (i.e., after releasing Control expect default speed
0.1f) or (B) changing the assertions to verify true restoration of the prior
speed (i.e., after releasing Control expect the original 1.0f). Locate the TEST
named ControlKeySpeedTransitionCycle and the calls to
MockSpaceCamera::setCameraSpeed, getCameraSpeed, keyPressEvent, and
keyReleaseEvent and modify the comments and expected values accordingly so they
consistently match the chosen behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 41232f63-9b60-49dc-81e6-44969b6602bc

📥 Commits

Reviewing files that changed from the base of the PR and between 2fef76f and 9c6f2ff.

📒 Files selected for processing (3)
  • src/SpaceCamera_test.cpp
  • src/ViewCube/ViewCubeController.cpp
  • src/ViewCube/ViewCubeController.h

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a ViewCube to control the camera

1 participant