Repository: RetroAchievements/RAIntegration
Pull request #628 (2020-11-08 02:46:03)

implement new condition for trigger view model


Patch:
diff --git a/src/data/GameContext.hh b/src/data/GameContext.hh
index 2b0e6ce0..4f67443f 100644
--- a/src/data/GameContext.hh
+++ b/src/data/GameContext.hh
@@ -236,6 +236,18 @@ public:
         return &pIter->second.Note;
     }
 
+    /// <summary>
+    /// Returns the number of bytes associated to the code note at the specified address.
+    /// </summary>
+    /// <param name="nAddress">Address to query.</param>
+    /// <returns>Number of bytes associated to the code note, or 0 if no note exists at the address.</returns>
+    /// <remarks>Only works for the first byte of a multi-byte address.</remarks>
+    unsigned FindCodeNoteSize(ra::ByteAddress nAddress) const
+    {
+        const auto pIter = m_mCodeNotes.find(nAddress);
+        return (pIter == m_mCodeNotes.end()) ? 0 : pIter->second.Bytes;
+    }
+
     /// <summary>
     /// Enumerates the code notes
     /// </summary>
diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp
index 5241d742..7a5c7d79 100644
--- a/src/ui/viewmodels/TriggerViewModel.cpp
+++ b/src/ui/viewmodels/TriggerViewModel.cpp
@@ -7,6 +7,7 @@
 #include "services\ServiceLocator.hh"
 
 #include "ui\viewmodels\MessageBoxViewModel.hh"
+#include "ui\viewmodels\WindowManager.hh"
 
 namespace ra {
 namespace ui {
@@ -168,6 +169,12 @@ void TriggerViewModel::CopySelectedConditionsToClipboard()
     ra::services::ServiceLocator::Get<ra::services::IClipboard>().SetText(ra::Widen(sSerialized));
 }
 
+void TriggerViewModel::DeselectAllConditions()
+{
+    for (gsl::index nIndex = 0; nIndex < ra::to_signed(m_vConditions.Count()); ++nIndex)
+        m_vConditions.SetItemValue(nIndex, TriggerConditionViewModel::IsSelectedProperty, false);
+}
+
 void TriggerViewModel::PasteFromClipboard()
 {
     const std::wstring sClipboardText = ra::services::ServiceLocator::Get<ra::services::IClipboard>().GetText();
@@ -196,8 +203,7 @@ void TriggerViewModel::PasteFromClipboard()
 
     m_vConditions.BeginUpdate();
 
-    for (gsl::index nIndex = 0; nIndex < ra::to_signed(m_vConditions.Count()); ++nIndex)
-        m_vConditions.SetItemValue(nIndex, TriggerConditionViewModel::IsSelectedProperty, false);
+    DeselectAllConditions();
 
     for (const rc_condition_t* pCondition = pTrigger->requirement->conditions;
         pCondition != nullptr; pCondition = pCondition->next)
@@ -256,6 +262,60 @@ void TriggerViewModel::RemoveSelectedConditions()
     }
 }
 
+void TriggerViewModel::NewCondition()
+{
+    m_vConditions.BeginUpdate();
+
+    DeselectAllConditions();
+
+    auto& vmCondition = m_vConditions.Add();
+    vmCondition.SetIndex(gsl::narrow_cast<int>(m_vConditions.Count()));
+
+    // assume the user wants to create a condition for the currently watched memory address.
+    const auto& pMemoryInspector = ra::services::ServiceLocator::Get<ra::ui::viewmodels::WindowManager>().MemoryInspector;
+    const auto nAddress = pMemoryInspector.Viewer().GetAddress();
+    MemSize nSize = MemSize::EightBit;
+
+    // if the code note specifies an explicit size, use it. otherwise, use the selected viewer mode size.
+    const auto& pGameContext = ra::services::ServiceLocator::Get<ra::data::GameContext>();
+    switch (pGameContext.FindCodeNoteSize(nAddress))
+    {
+        default: // no code note, or code note doesn't specify a size
+            nSize = pMemoryInspector.Viewer().GetSize();
+            break;
+
+        case 2:
+            nSize = MemSize::SixteenBit;
+            break;
+
+        case 3:
+            nSize = MemSize::TwentyFourBit;
+            break;
+
+        case 4:
+            nSize = MemSize::ThirtyTwoBit;
+            break;
+    }
+
+    vmCondition.SetSourceType(TriggerOperandType::Address);
+    vmCondition.SetSourceSize(nSize);
+    vmCondition.SetSourceValue(nAddress);
+
+    // assume the user wants to compare to the current value of the watched memory address
+    vmCondition.SetOperator(TriggerOperatorType::Equals);
+
+    const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::EmulatorContext>();
+    vmCondition.SetTargetSize(nSize);
+    vmCondition.SetTargetType(TriggerOperandType::Value);
+    vmCondition.SetTargetValue(pEmulatorContext.ReadMemory(nAddress, nSize));
+
+    vmCondition.SetSelected(true);
+
+    m_vConditions.EndUpdate();
+
+    SetValue(EnsureVisibleConditionIndexProperty, gsl::narrow_cast<int>(m_vConditions.Count()) - 1);
+}
+
 static void AddAltGroup(ViewModelCollection<TriggerViewModel::GroupViewModel>& vGroups,
     int nId, rc_condset_t* pConditionSet)
 {
diff --git a/src/ui/viewmodels/TriggerViewModel.hh b/src/ui/viewmodels/TriggerViewModel.hh
index a434cbcb..fcfaad15 100644
--- a/src/ui/viewmodels/TriggerViewModel.hh
+++ b/src/ui/viewmodels/TriggerViewModel.hh
@@ -81,6 +81,8 @@ public:
     void CopySelectedConditionsToClipboard();
     void PasteFromClipboard();
 
+    void NewCondition();
+
     /// <summary>
     /// Gets the list of condition types.
     /// </summary>
@@ -125,6 +127,8 @@ private:
     void UpdateVersion();
     void UpdateConditions(const GroupViewModel* pGroup);
 
+    void DeselectAllConditions();
+
     ViewModelCollection<GroupViewModel> m_vGroups;
     ViewModelCollection<TriggerConditionViewModel> m_vConditions;
 
diff --git a/src/ui/win32/AssetEditorDialog.cpp b/src/ui/win32/AssetEditorDialog.cpp
index 9aea0f5b..5ac1b322 100644
--- a/src/ui/win32/AssetEditorDialog.cpp
+++ b/src/ui/win32/AssetEditorDialog.cpp
@@ -368,6 +368,15 @@ BOOL AssetEditorDialog::OnCommand(WORD nCommand)
 {
     switch (nCommand)
     {
+        case IDC_RA_ADDCOND:
+        {
+            auto* vmAssetEditor = dynamic_cast<AssetEditorViewModel*>(&m_vmWindow);
+            if (vmAssetEditor)
+                vmAssetEditor->Trigger().NewCondition();
+
+            return TRUE;
+        }
+
         case IDC_RA_COPYCOND:
         {
             auto* vmAssetEditor = dynamic_cast<AssetEditorViewModel*>(&m_vmWindow);


Test patch:
diff --git a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
index 96e635d8..58da401c 100644
--- a/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/TriggerViewModel_Tests.cpp
@@ -5,6 +5,9 @@
 #include "tests\RA_UnitTestHelpers.h"
 #include "tests\mocks\MockClipboard.hh"
 #include "tests\mocks\MockDesktop.hh"
+#include "tests\mocks\MockGameContext.hh"
+#include "tests\mocks\MockEmulatorContext.hh"
+#include "tests\mocks\MockWindowManager.hh"
 
 using namespace Microsoft::VisualStudio::CppUnitTestFramework;
 
@@ -19,8 +22,18 @@ private:
     class TriggerViewModelHarness : public TriggerViewModel
     {
     public:
+        ra::data::mocks::MockEmulatorContext mockEmulatorContext;
+        ra::data::mocks::MockGameContext mockGameContext;
         ra::services::mocks::MockClipboard mockClipboard;
         ra::ui::mocks::MockDesktop mockDesktop;
+        ra::ui::viewmodels::mocks::MockWindowManager mockWindowManager;
+
+        void InitializeMemory(uint8_t* pMemory, size_t nMemorySize)
+        {
+            // make sure to hook up the memory inspector first or we won't be able to set an address
+            mockWindowManager.MemoryInspector.Viewer().InitializeNotifyTargets();
+            mockEmulatorContext.MockMemory(pMemory, nMemorySize);
+        }
     };
 
     void Parse(TriggerViewModel& vmTrigger, const std::string& sInput)
@@ -450,6 +463,75 @@ public:
 
         Assert::AreEqual(std::string("0xH1234=16_0xL65ff=11.1._R:0xT3333=1_0xW5555=16"), vmTrigger.Serialize());
     }
+
+    TEST_METHOD(TestNewCondition)
+    {
+        std::array<uint8_t, 10> pMemory = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+        TriggerViewModelHarness vmTrigger;
+        Parse(vmTrigger, "0xH1234=16");
+        Assert::AreEqual({ 1U }, vmTrigger.Conditions().Count());
+        vmTrigger.Conditions().GetItemAt(0)->SetSelected(true);
+
+        vmTrigger.InitializeMemory(&pMemory.at(0), pMemory.size());
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(8);
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetSize(MemSize::EightBit);
+        vmTrigger.NewCondition();
+
+        Assert::AreEqual({ 2U }, vmTrigger.Conditions().Count());
+
+        // new condition should be selected, any previously selected items should be deselected
+        Assert::IsFalse(vmTrigger.Conditions().GetItemAt(0)->IsSelected());
+        Assert::IsTrue(vmTrigger.Conditions().GetItemAt(1)->IsSelected());
+
+        Assert::AreEqual(std::string("0xH1234=16_0xH0008=8"), vmTrigger.Serialize());
+    }
+
+    TEST_METHOD(TestNewConditionViewerSize)
+    {
+        std::array<uint8_t, 10> pMemory = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+        TriggerViewModelHarness vmTrigger;
+        Parse(vmTrigger, "0xH1234=16");
+        Assert::AreEqual({ 1U }, vmTrigger.Conditions().Count());
+
+        vmTrigger.InitializeMemory(&pMemory.at(0), pMemory.size());
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(8);
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetSize(MemSize::SixteenBit);
+        vmTrigger.NewCondition();
+
+        Assert::AreEqual({ 2U }, vmTrigger.Conditions().Count());
+        Assert::AreEqual(std::string("0xH1234=16_0x 0008=2312"), vmTrigger.Serialize());
+
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(4);
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetSize(MemSize::ThirtyTwoBit);
+        vmTrigger.NewCondition();
+
+        Assert::AreEqual({ 3U }, vmTrigger.Conditions().Count());
+        Assert::AreEqual(std::string("0xH1234=16_0x 0008=2312_0xX0004=117835012"), vmTrigger.Serialize());
+    }
+
+    TEST_METHOD(TestNewConditionCodeNoteSize)
+    {
+        std::array<uint8_t, 10> pMemory = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+        TriggerViewModelHarness vmTrigger;
+        Parse(vmTrigger, "0xH1234=16");
+        Assert::AreEqual({ 1U }, vmTrigger.Conditions().Count());
+        vmTrigger.mockGameContext.SetCodeNote({ 8U }, L"[16-bit] test");
+        vmTrigger.mockGameContext.SetCodeNote({ 4U }, L"[4 byte] test");
+
+        vmTrigger.InitializeMemory(&pMemory.at(0), pMemory.size());
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(8);
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetSize(MemSize::EightBit);
+        vmTrigger.NewCondition();
+
+        Assert::AreEqual({ 2U }, vmTrigger.Conditions().Count());
+        Assert::AreEqual(std::string("0xH1234=16_0x 0008=2312"), vmTrigger.Serialize());
+
+        vmTrigger.mockWindowManager.MemoryInspector.Viewer().SetAddress(4);
+        vmTrigger.NewCondition();
+
+        Assert::AreEqual({ 3U }, vmTrigger.Conditions().Count());
+        Assert::AreEqual(std::string("0xH1234=16_0x 0008=2312_0xX0004=117835012"), vmTrigger.Serialize());
+    }
 };
 
 } // namespace tests