Repository: RetroAchievements/RAIntegration
Pull request #979 (2023-02-23 00:03:30)

make indirect note field readonly instead of disabled
implements #955 


Patch:
diff --git a/src/ui/viewmodels/MemoryInspectorViewModel.cpp b/src/ui/viewmodels/MemoryInspectorViewModel.cpp
index b9b9fad6..df1c7260 100644
--- a/src/ui/viewmodels/MemoryInspectorViewModel.cpp
+++ b/src/ui/viewmodels/MemoryInspectorViewModel.cpp
@@ -27,6 +27,7 @@ const BoolModelProperty MemoryInspectorViewModel::CurrentBitsVisibleProperty("Me
 const BoolModelProperty MemoryInspectorViewModel::CanEditCurrentAddressNoteProperty("MemoryInspectorViewModel", "CanEditCurrentAddressNote", true);
 const BoolModelProperty MemoryInspectorViewModel::CanRevertCurrentAddressNoteProperty("MemoryInspectorViewModel", "CanRevertCurrentAddressNote", false);
 const BoolModelProperty MemoryInspectorViewModel::CanPublishCurrentAddressNoteProperty("MemoryInspectorViewModel", "CanPublishCurrentAddressNote", false);
+const BoolModelProperty MemoryInspectorViewModel::IsCurrentAddressNoteReadOnlyProperty("MemoryInspectorViewModel", "IsCurrentAddressNoteReadOnly", false);
 
 MemoryInspectorViewModel::MemoryInspectorViewModel()
 {
@@ -160,15 +161,18 @@ void MemoryInspectorViewModel::UpdateNoteButtons()
     const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes();
     if (pCodeNotes == nullptr || !CanModifyNotes())
     {
+        SetValue(IsCurrentAddressNoteReadOnlyProperty, true);
         SetValue(CanEditCurrentAddressNoteProperty, false);
         SetValue(CanPublishCurrentAddressNoteProperty, false);
         SetValue(CanRevertCurrentAddressNoteProperty, false);
     }
     else
     {
+        SetValue(CanEditCurrentAddressNoteProperty, true);
+
         if (m_bNoteIsIndirect)
         {
-            SetValue(CanEditCurrentAddressNoteProperty, false);
+            SetValue(IsCurrentAddressNoteReadOnlyProperty, true);
             SetValue(CanPublishCurrentAddressNoteProperty, false);
             SetValue(CanRevertCurrentAddressNoteProperty, false);
         }
@@ -179,7 +183,7 @@ void MemoryInspectorViewModel::UpdateNoteButtons()
             const auto nAddress = GetCurrentAddress();
             const auto bModified = pCodeNotes->IsNoteModified(nAddress);
 
-            SetValue(CanEditCurrentAddressNoteProperty, true);
+            SetValue(IsCurrentAddressNoteReadOnlyProperty, false);
             SetValue(CanPublishCurrentAddressNoteProperty, !bOffline && bModified);
             SetValue(CanRevertCurrentAddressNoteProperty, bModified);
         }
diff --git a/src/ui/viewmodels/MemoryInspectorViewModel.hh b/src/ui/viewmodels/MemoryInspectorViewModel.hh
index 1226980e..6163f449 100644
--- a/src/ui/viewmodels/MemoryInspectorViewModel.hh
+++ b/src/ui/viewmodels/MemoryInspectorViewModel.hh
@@ -90,6 +90,16 @@ public:
     /// </summary>
     const bool CanEditCurrentAddressNote() const { return GetValue(CanEditCurrentAddressNoteProperty); }
 
+    /// <summary>
+    /// The <see cref="ModelProperty" /> for whether or not the current address note is readonly.
+    /// </summary>
+    static const BoolModelProperty IsCurrentAddressNoteReadOnlyProperty;
+
+    /// <summary>
+    /// Gets whether or not the current address note is readonly.
+    /// </summary>
+    const bool IsCurrentAddressNoteReadOnly() const { return GetValue(IsCurrentAddressNoteReadOnlyProperty); }
+
     /// <summary>
     /// The <see cref="ModelProperty" /> for whether or not the current address note is modified.
     /// </summary>
diff --git a/src/ui/win32/MemoryInspectorDialog.cpp b/src/ui/win32/MemoryInspectorDialog.cpp
index e524fbce..38d2566f 100644
--- a/src/ui/win32/MemoryInspectorDialog.cpp
+++ b/src/ui/win32/MemoryInspectorDialog.cpp
@@ -232,6 +232,7 @@ MemoryInspectorDialog::MemoryInspectorDialog(MemoryInspectorViewModel& vmMemoryI
         return true;
     });
     m_bindNoteText.BindText(MemoryInspectorViewModel::CurrentAddressNoteProperty, ra::ui::win32::bindings::TextBoxBinding::UpdateMode::Typing);
+    m_bindNoteText.BindReadOnly(MemoryInspectorViewModel::IsCurrentAddressNoteReadOnlyProperty);
     m_bindWindow.BindEnabled(IDC_RA_NOTE_TEXT, MemoryInspectorViewModel::CanEditCurrentAddressNoteProperty);
     m_bindWindow.BindEnabled(IDC_RA_PUBLISH_NOTE, MemoryInspectorViewModel::CanPublishCurrentAddressNoteProperty);
     m_bindWindow.BindEnabled(IDC_RA_REVERT_NOTE, MemoryInspectorViewModel::CanRevertCurrentAddressNoteProperty);
diff --git a/src/ui/win32/bindings/TextBoxBinding.hh b/src/ui/win32/bindings/TextBoxBinding.hh
index 8028bff4..7f0e4f8d 100644
--- a/src/ui/win32/bindings/TextBoxBinding.hh
+++ b/src/ui/win32/bindings/TextBoxBinding.hh
@@ -43,6 +43,13 @@ public:
         UpdateBoundText();
     }
 
+    void BindReadOnly(const BoolModelProperty& pReadOnlyProperty)
+    {
+        m_pReadOnlyProperty = &pReadOnlyProperty;
+
+        UpdateReadOnly();
+    }
+
     void OnLostFocus() override
     {
         if (m_pTextUpdateMode == UpdateMode::LostFocus)
@@ -88,6 +95,12 @@ protected:
             UpdateBoundText();
     }
 
+    void OnViewModelBoolValueChanged(const BoolModelProperty::ChangeArgs& args) override
+    {
+        if (m_pReadOnlyProperty && *m_pReadOnlyProperty == args.Property)
+            UpdateReadOnly();
+    }
+
     INT_PTR CALLBACK WndProc(HWND hControl, UINT uMsg, WPARAM wParam, LPARAM lParam) override
     {
         switch (uMsg)
@@ -107,6 +120,13 @@ protected:
         return ControlBinding::WndProc(hControl, uMsg, wParam, lParam);
     }
 
+    void UpdateReadOnly()
+    {
+        const bool bReadOnly = (m_pReadOnlyProperty) ? GetValue(*m_pReadOnlyProperty) : false;
+        if (m_hWnd)
+            SendMessage(m_hWnd, EM_SETREADONLY, bReadOnly ? TRUE : FALSE, 0);
+    }
+
     virtual void UpdateBoundText()
     {
         if (m_hWnd && m_pTextBoundProperty)
@@ -140,6 +160,7 @@ protected:
 
 private:
     const StringModelProperty* m_pTextBoundProperty = nullptr;
+    const BoolModelProperty* m_pReadOnlyProperty = nullptr;
     std::map<unsigned int, std::function<bool()>> m_mKeyHandlers;
 };
 


Test patch:
diff --git a/tests/ui/viewmodels/MemoryInspectorViewModel_Tests.cpp b/tests/ui/viewmodels/MemoryInspectorViewModel_Tests.cpp
index d0f99c08..fdf89d6c 100644
--- a/tests/ui/viewmodels/MemoryInspectorViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/MemoryInspectorViewModel_Tests.cpp
@@ -143,6 +143,7 @@ public:
         Assert::AreEqual(std::wstring(L"0x0003"), inspector.GetCurrentAddressText());
         Assert::AreEqual(std::wstring(), inspector.GetCurrentAddressNote());
         Assert::AreEqual(std::wstring(L"0 0 0 0 0 0 1 1"), inspector.GetCurrentAddressBits());
+        Assert::IsFalse(inspector.IsCurrentAddressNoteReadOnly());
         Assert::IsTrue(inspector.CanEditCurrentAddressNote());
         Assert::IsFalse(inspector.CanPublishCurrentAddressNote());
         Assert::IsFalse(inspector.CanRevertCurrentAddressNote());
@@ -162,6 +163,7 @@ public:
         Assert::AreEqual(std::wstring(L"3"), inspector.GetCurrentAddressText()); /* don't update text when user types in partial address */
         Assert::AreEqual(std::wstring(), inspector.GetCurrentAddressNote());
         Assert::AreEqual(std::wstring(L"0 0 0 0 0 0 1 1"), inspector.GetCurrentAddressBits());
+        Assert::IsFalse(inspector.IsCurrentAddressNoteReadOnly());
         Assert::IsTrue(inspector.CanEditCurrentAddressNote());
         Assert::IsFalse(inspector.CanPublishCurrentAddressNote());
         Assert::IsFalse(inspector.CanRevertCurrentAddressNote());
@@ -183,6 +185,7 @@ public:
         Assert::AreEqual(std::wstring(L"0x0003"), inspector.GetCurrentAddressText());
         Assert::AreEqual(std::wstring(L"Note on 3"), inspector.GetCurrentAddressNote());
         Assert::AreEqual(std::wstring(L"0 0 0 0 0 0 1 1"), inspector.GetCurrentAddressBits());
+        Assert::IsFalse(inspector.IsCurrentAddressNoteReadOnly());
         Assert::IsTrue(inspector.CanEditCurrentAddressNote());
         Assert::IsFalse(inspector.CanPublishCurrentAddressNote());
         Assert::IsFalse(inspector.CanRevertCurrentAddressNote());
@@ -190,6 +193,7 @@ public:
         // update note directly as SetCurrentNoteAddress doesn't cause UpdateNoteButtons to be called
         inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({3U}, L"Modified Note on 3");
 
+        Assert::IsFalse(inspector.IsCurrentAddressNoteReadOnly());
         Assert::IsTrue(inspector.CanEditCurrentAddressNote());
         Assert::IsTrue(inspector.CanPublishCurrentAddressNote());
         Assert::IsTrue(inspector.CanRevertCurrentAddressNote());
@@ -208,6 +212,7 @@ public:
         Assert::AreEqual(std::wstring(L"0x0003"), inspector.GetCurrentAddressText());
         Assert::AreEqual(std::wstring(L"[8-bit Pointer]\n+1 Test"), inspector.GetCurrentAddressNote());
         Assert::AreEqual(std::wstring(L"0 0 0 0 0 0 1 1"), inspector.GetCurrentAddressBits());
+        Assert::IsFalse(inspector.IsCurrentAddressNoteReadOnly());
         Assert::IsTrue(inspector.CanEditCurrentAddressNote());
         Assert::IsFalse(inspector.CanPublishCurrentAddressNote());
         Assert::IsFalse(inspector.CanRevertCurrentAddressNote());
@@ -218,7 +223,8 @@ public:
         Assert::AreEqual(std::wstring(L"0x0004"), inspector.GetCurrentAddressText());
         Assert::AreEqual(std::wstring(L"[Indirect from 0x0003]\r\nTest"), inspector.GetCurrentAddressNote());
         Assert::AreEqual(std::wstring(L"0 0 0 0 0 1 0 0"), inspector.GetCurrentAddressBits());
-        Assert::IsFalse(inspector.CanEditCurrentAddressNote());
+        Assert::IsTrue(inspector.IsCurrentAddressNoteReadOnly());
+        Assert::IsTrue(inspector.CanEditCurrentAddressNote());
         Assert::IsFalse(inspector.CanPublishCurrentAddressNote());
         Assert::IsFalse(inspector.CanRevertCurrentAddressNote());
 
@@ -229,6 +235,7 @@ public:
         Assert::AreEqual(std::wstring(L"0x0004"), inspector.GetCurrentAddressText());
         Assert::AreEqual(std::wstring(), inspector.GetCurrentAddressNote());
         Assert::AreEqual(std::wstring(L"0 0 0 0 0 1 0 0"), inspector.GetCurrentAddressBits());
+        Assert::IsFalse(inspector.IsCurrentAddressNoteReadOnly());
         Assert::IsTrue(inspector.CanEditCurrentAddressNote());
         Assert::IsFalse(inspector.CanPublishCurrentAddressNote());
         Assert::IsFalse(inspector.CanRevertCurrentAddressNote());
@@ -241,6 +248,7 @@ public:
         Assert::AreEqual(std::wstring(L"0x0003"), inspector.GetCurrentAddressText());
         Assert::AreEqual(std::wstring(L"[8-bit Pointer]\n+1 Test"), inspector.GetCurrentAddressNote());
         Assert::AreEqual(std::wstring(L"0 0 0 0 0 0 1 0"), inspector.GetCurrentAddressBits());
+        Assert::IsFalse(inspector.IsCurrentAddressNoteReadOnly());
         Assert::IsTrue(inspector.CanEditCurrentAddressNote());
         Assert::IsFalse(inspector.CanPublishCurrentAddressNote());
         Assert::IsFalse(inspector.CanRevertCurrentAddressNote());
diff --git a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
index d5cb8b4f..1960eca8 100644
--- a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
@@ -1334,6 +1334,38 @@ public:
         Assert::AreEqual(62U, vmTrigger.Conditions().GetItemAt(6)->GetTotalHits()); // end of hit-chain (2+12+48)
     }
 
+    TEST_METHOD(TestDoFrameHitsChainReset)
+    {
+        TriggerViewModelHarness vmTrigger;
+        Parse(vmTrigger, "0=0.5._C:0=0.10._0=0.20._R:1=1");
+        Assert::AreEqual({ 1U }, vmTrigger.Groups().Count());
+
+        auto* cond = vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions;
+        cond->current_hits = 2; cond = cond->next;  //         0=0 (5)
+        cond->current_hits = 3; cond = cond->next;  // AddHits 0=0 (10)
+        cond->current_hits = 6; cond = cond->next;  //         0=0 (20)
+        vmTrigger.DoFrame();
+        Assert::AreEqual(2U, vmTrigger.Conditions().GetItemAt(0)->GetCurrentHits());
+        Assert::AreEqual(3U, vmTrigger.Conditions().GetItemAt(1)->GetCurrentHits());
+        Assert::AreEqual(6U, vmTrigger.Conditions().GetItemAt(2)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetTotalHits()); // non hit-chain
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(1)->GetTotalHits()); // middle of hit-chain
+        Assert::AreEqual(9U, vmTrigger.Conditions().GetItemAt(2)->GetTotalHits()); // end of hit-chain (3+6)
+
+        // mimic a ResetIf clearing all hits
+        cond = vmTrigger.Groups().GetItemAt(0)->m_pConditionSet->conditions;
+        cond->current_hits = 0; cond = cond->next;  //         0=0 (5)
+        cond->current_hits = 0; cond = cond->next;  // AddHits 0=0 (10)
+        cond->current_hits = 0; cond = cond->next;  //         0=0 (20)
+        vmTrigger.DoFrame();
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(1)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(2)->GetCurrentHits());
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(0)->GetTotalHits()); // non hit-chain
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(1)->GetTotalHits()); // middle of hit-chain
+        Assert::AreEqual(0U, vmTrigger.Conditions().GetItemAt(2)->GetTotalHits()); // end of hit-chain (3+6)
+    }
+
     TEST_METHOD(TestBuildHitChainTooltip)
     {
         TriggerViewModelHarness vmTrigger;