Skip to content

Commit 65df428

Browse files
committed
add Copy Chain button
1 parent 75bed8b commit 65df428

5 files changed

Lines changed: 204 additions & 15 deletions

File tree

src/RA_Shared.rc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ BEGIN
387387
COMBOBOX IDC_RA_SEARCHTYPE,7,19,71,13,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
388388
PUSHBUTTON "&Find",IDC_RA_RESET_FILTER,6,33,72,13
389389
PUSHBUTTON "Book&mark Selected",IDC_RA_RESULTS_BOOKMARK,6,65,72,13
390-
PUSHBUTTON "E&xport",IDC_RA_RESULTS_EXPORT,6,78,72,13
390+
PUSHBUTTON "&Copy Chain",IDC_RA_COPY_ALL,6,78,72,13
391391
CONTROL "",IDC_RA_RESULTS,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | WS_BORDER | WS_VSCROLL | WS_TABSTOP,80,9,312,81
392392
GROUPBOX "State 1",IDC_RA_GBX_STATE_1,4,94,392,59
393393
LTEXT "Address:",IDC_RA_LBL_ADDRESS_1,8,103,32,9

src/ui/viewmodels/PointerFinderViewModel.cpp

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "data/context/EmulatorContext.hh"
99
#include "data/context/GameContext.hh"
1010

11+
#include "services/IClipboard.hh"
1112
#include "services/IFileSystem.hh"
1213
#include "services/ServiceLocator.hh"
1314

@@ -204,7 +205,7 @@ void PointerFinderViewModel::Find()
204205
return false;
205206

206207
static const std::array<const wchar_t*, 6> sProgressTicker = { L"", L".", L"..", L"...", L" ..", L" ." };
207-
nCount = (nCount + 1) % sProgressTicker.size();
208+
nCount = (nCount + 1) % gsl::narrow_cast<uint32_t>(sProgressTicker.size());
208209
vmProgress.SetMessage(sMessage + sProgressTicker.at(nCount));
209210
vmProgress.SetProgress(gsl::narrow_cast<int>(nProgress * 100 / nTotal));
210211
return true;
@@ -304,13 +305,47 @@ ra::data::Memory::Size PointerFinderViewModel::GetSearchSize() const
304305
return nSize;
305306
}
306307

307-
void PointerFinderViewModel::BookmarkSelected()
308+
void PointerFinderViewModel::BookmarkSelected() const
308309
{
309310
auto& vmBookmarks = ra::services::ServiceLocator::GetMutable<ra::ui::viewmodels::WindowManager>().MemoryBookmarks;
310311
if (!vmBookmarks.IsVisible())
311312
vmBookmarks.Show();
312313

313314
std::vector<gsl::index> vSelectedItems;
315+
GetSelectedItems(vSelectedItems);
316+
if (vSelectedItems.empty())
317+
return;
318+
319+
ConvertResultsToAchievementLogic(vSelectedItems,
320+
[&vmBookmarks](const std::string& sSerialized)
321+
{
322+
vmBookmarks.AddBookmark(sSerialized);
323+
});
324+
}
325+
326+
void PointerFinderViewModel::CopySelectedToClipboard() const
327+
{
328+
std::vector<gsl::index> vSelectedItems;
329+
GetSelectedItems(vSelectedItems);
330+
if (vSelectedItems.empty())
331+
return;
332+
333+
if (vSelectedItems.size() > 1)
334+
{
335+
MessageBoxViewModel::ShowErrorMessage(L"Multiple items selected", L"Only one item can be copied at a time.");
336+
return;
337+
}
338+
339+
ConvertResultsToAchievementLogic(vSelectedItems,
340+
[](const std::string& sSerialized)
341+
{
342+
auto& pClipboard = ra::services::ServiceLocator::Get<ra::services::IClipboard>();
343+
pClipboard.SetText(ra::util::String::Widen(sSerialized));
344+
});
345+
}
346+
347+
void PointerFinderViewModel::GetSelectedItems(std::vector<gsl::index>& nIndices) const
348+
{
314349
gsl::index nStartIndex = 0;
315350
for (gsl::index nIndex = 0; nIndex < gsl::narrow_cast<gsl::index>(m_vResults.Count()); ++nIndex)
316351
{
@@ -322,14 +357,14 @@ void PointerFinderViewModel::BookmarkSelected()
322357

323358
if (pItem->IsSelected())
324359
{
325-
if (vSelectedItems.empty() || vSelectedItems.back() != nStartIndex)
326-
vSelectedItems.push_back(nStartIndex);
360+
if (nIndices.empty() || nIndices.back() != nStartIndex)
361+
nIndices.push_back(nStartIndex);
327362
}
328363
}
364+
}
329365

330-
if (vSelectedItems.empty())
331-
return;
332-
366+
void PointerFinderViewModel::ConvertResultsToAchievementLogic(const std::vector<gsl::index>& nIndices, std::function<void(const std::string& sSerialized)> fCallback) const
367+
{
333368
auto nSize = ra::data::Memory::Size::ThirtyTwoBit;
334369
uint32_t nMask = 0xFFFFFFFF;
335370
uint32_t nOffset = 0;
@@ -350,7 +385,7 @@ void PointerFinderViewModel::BookmarkSelected()
350385
nMask = 0xFFFFFFFF; // indicate masking is not needed
351386
}
352387

353-
for (auto nIndex : vSelectedItems)
388+
for (auto nIndex : nIndices)
354389
{
355390
const auto* pItem = m_vResults.GetItemAt(nIndex);
356391
Expects(pItem != nullptr);
@@ -360,7 +395,7 @@ void PointerFinderViewModel::BookmarkSelected()
360395
do
361396
{
362397
ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, ra::services::TriggerConditionType::AddAddress);
363-
ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, nSize, nAddress);
398+
ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, nSize, nAddress);
364399

365400
if (nOffset != 0)
366401
{
@@ -380,15 +415,15 @@ void PointerFinderViewModel::BookmarkSelected()
380415
{
381416
ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, ra::services::TriggerConditionType::Measured);
382417
ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address,
383-
nSize, ra::to_unsigned(pItem->m_nOffset));
418+
nSize, ra::to_unsigned(pItem->m_nOffset));
384419
break;
385420
}
386421

387422
nAddress = pItem->m_nOffset;
388423
pItem = pNextItem;
389424
} while (true);
390425

391-
vmBookmarks.AddBookmark(sBuffer);
426+
fCallback(sBuffer);
392427
}
393428
}
394429

src/ui/viewmodels/PointerFinderViewModel.hh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ public:
6666
/// <summary>
6767
/// Bookmarks the currently item from the search results.
6868
/// </summary>
69-
void BookmarkSelected();
69+
void BookmarkSelected() const;
70+
71+
/// <summary>
72+
/// Copies the currently item from the search results as achievemnet logic.
73+
/// </summary>
74+
void CopySelectedToClipboard() const;
7075

7176
class StateViewModel : public ViewModelBase,
7277
protected ra::data::context::EmulatorContext::DispatchesReadMemory
@@ -289,6 +294,9 @@ protected:
289294
void AddPotentialPointer(const ra::services::PointerFinder::PotentialPointer& pPotentialPointer, ra::data::Memory::Size nSize);
290295
void OnValueChanged(const IntModelProperty::ChangeArgs& args) override;
291296

297+
void GetSelectedItems(std::vector<gsl::index>& nIndices) const;
298+
void ConvertResultsToAchievementLogic(const std::vector<gsl::index>& nIndices, std::function<void(const std::string& sSerialized)> fCallback) const;
299+
292300
ra::data::Memory::Size GetSearchSize() const;
293301

294302
private:

src/ui/win32/PointerFinderDialog.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ PointerFinderDialog::PointerFinderDialog(PointerFinderViewModel& vmPointerFinder
164164
SetAnchor(IDC_RA_RESET_FILTER, Anchor::Top | Anchor::Left);
165165
SetAnchor(IDC_RA_APPLY_FILTER, Anchor::Top | Anchor::Left);
166166
SetAnchor(IDC_RA_RESULTS_BOOKMARK, Anchor::Left | Anchor::Bottom);
167-
SetAnchor(IDC_RA_RESULTS_EXPORT, Anchor::Left | Anchor::Bottom);
167+
SetAnchor(IDC_RA_COPY_ALL, Anchor::Left | Anchor::Bottom);
168168
SetAnchor(IDC_RA_RESULTS, Anchor::Top | Anchor::Left | Anchor::Bottom | Anchor::Right);
169169

170170
SetAnchor(IDC_RA_GBX_STATE_1, Anchor::Left | Anchor::Bottom | Anchor::Right);
@@ -226,13 +226,21 @@ BOOL PointerFinderDialog::OnCommand(WORD nCommand)
226226
}
227227

228228
case IDC_RA_RESULTS_BOOKMARK: {
229-
auto* vmPointerFinder = dynamic_cast<PointerFinderViewModel*>(&m_vmWindow);
229+
const auto* vmPointerFinder = dynamic_cast<PointerFinderViewModel*>(&m_vmWindow);
230230
if (vmPointerFinder)
231231
vmPointerFinder->BookmarkSelected();
232232

233233
return TRUE;
234234
}
235235

236+
case IDC_RA_COPY_ALL: {
237+
const auto* vmPointerFinder = dynamic_cast<PointerFinderViewModel*>(&m_vmWindow);
238+
if (vmPointerFinder)
239+
vmPointerFinder->CopySelectedToClipboard();
240+
241+
return TRUE;
242+
}
243+
236244
case IDC_RA_RESULTS_EXPORT: {
237245
const auto* vmPointerFinder = dynamic_cast<PointerFinderViewModel*>(&m_vmWindow);
238246
if (vmPointerFinder)

tests/ui/viewmodels/PointerFinderViewModel_Tests.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "tests\devkit\context\mocks\MockEmulatorMemoryContext.hh"
1212
#include "tests\devkit\context\mocks\MockRcClient.hh"
1313
#include "tests\devkit\services\mocks\MockFileSystem.hh"
14+
15+
#include "tests\mocks\MockClipboard.hh"
1416
#include "tests\mocks\MockConfiguration.hh"
1517
#include "tests\mocks\MockGameContext.hh"
1618
#include "tests\mocks\MockDesktop.hh"
@@ -574,6 +576,142 @@ TEST_CLASS(PointerFinderViewModel_Tests)
574576
Assert::AreEqual(std::string("I:0x 0020_I:0x 0008_I:0x 0000_M:0x 0004"), pBookmarks.Items().GetItemAt(4)->GetIndirectAddress());
575577
}
576578

579+
TEST_METHOD(TestCopySelectedToClipboard)
580+
{
581+
PointerFinderViewModelHarness vmPointerFinder;
582+
ra::services::mocks::MockClipboard mockClipboard;
583+
vmPointerFinder.mockGameContext.SetGameId(1U);
584+
vmPointerFinder.SetSearchType(ra::services::SearchType::SixteenBitAligned);
585+
586+
// no results - nothing copied, no dialog shown
587+
vmPointerFinder.CopySelectedToClipboard();
588+
Assert::AreEqual(std::wstring(), mockClipboard.GetText());
589+
Assert::IsFalse(vmPointerFinder.mockDesktop.WasDialogShown());
590+
vmPointerFinder.mockDesktop.ResetExpectedWindows();
591+
592+
// initialize results
593+
ra::services::PointerFinder::PotentialPointer pPointer;
594+
pPointer.nRootAddress = 0x009c;
595+
pPointer.vOffsets.at(0) = 0;
596+
pPointer.nOffsetLength = 1;
597+
vmPointerFinder.AddPotentialPointer(pPointer, ra::data::Memory::Size::SixteenBit);
598+
599+
pPointer.nRootAddress = 0x0008;
600+
pPointer.vOffsets.at(0) = 4;
601+
pPointer.nOffsetLength = 1;
602+
vmPointerFinder.AddPotentialPointer(pPointer, ra::data::Memory::Size::SixteenBit);
603+
604+
pPointer.nRootAddress = 0x0020;
605+
pPointer.vOffsets.at(0) = 8;
606+
pPointer.vOffsets.at(1) = 0;
607+
pPointer.vOffsets.at(2) = 4;
608+
pPointer.nOffsetLength = 3;
609+
vmPointerFinder.AddPotentialPointer(pPointer, ra::data::Memory::Size::SixteenBit);
610+
611+
pPointer.nRootAddress = 0x0070;
612+
pPointer.vOffsets.at(0) = 4;
613+
pPointer.nOffsetLength = 1;
614+
vmPointerFinder.AddPotentialPointer(pPointer, ra::data::Memory::Size::SixteenBit);
615+
616+
// no selection
617+
vmPointerFinder.CopySelectedToClipboard();
618+
Assert::AreEqual(std::wstring(), mockClipboard.GetText());
619+
Assert::IsFalse(vmPointerFinder.mockDesktop.WasDialogShown());
620+
vmPointerFinder.mockDesktop.ResetExpectedWindows();
621+
622+
// selection
623+
vmPointerFinder.PotentialPointers().GetItemAt(1)->SetSelected(true);
624+
vmPointerFinder.CopySelectedToClipboard();
625+
Assert::AreEqual(std::wstring(L"I:0x 0008_M:0x 0004"), mockClipboard.GetText());
626+
Assert::IsFalse(vmPointerFinder.mockDesktop.WasDialogShown());
627+
vmPointerFinder.mockDesktop.ResetExpectedWindows();
628+
vmPointerFinder.PotentialPointers().GetItemAt(1)->SetSelected(false);
629+
630+
// first part of chain
631+
vmPointerFinder.PotentialPointers().GetItemAt(2)->SetSelected(true);
632+
vmPointerFinder.CopySelectedToClipboard();
633+
Assert::AreEqual(std::wstring(L"I:0x 0020_I:0x 0008_I:0x 0000_M:0x 0004"), mockClipboard.GetText());
634+
Assert::IsFalse(vmPointerFinder.mockDesktop.WasDialogShown());
635+
vmPointerFinder.mockDesktop.ResetExpectedWindows();
636+
vmPointerFinder.PotentialPointers().GetItemAt(2)->SetSelected(false);
637+
mockClipboard.SetText(L"");
638+
639+
// middle part of chain
640+
vmPointerFinder.PotentialPointers().GetItemAt(3)->SetSelected(true);
641+
vmPointerFinder.CopySelectedToClipboard();
642+
Assert::AreEqual(std::wstring(L"I:0x 0020_I:0x 0008_I:0x 0000_M:0x 0004"), mockClipboard.GetText());
643+
Assert::IsFalse(vmPointerFinder.mockDesktop.WasDialogShown());
644+
vmPointerFinder.mockDesktop.ResetExpectedWindows();
645+
vmPointerFinder.PotentialPointers().GetItemAt(3)->SetSelected(false);
646+
mockClipboard.SetText(L"");
647+
648+
// end part of chain
649+
vmPointerFinder.PotentialPointers().GetItemAt(4)->SetSelected(true);
650+
vmPointerFinder.CopySelectedToClipboard();
651+
Assert::AreEqual(std::wstring(L"I:0x 0020_I:0x 0008_I:0x 0000_M:0x 0004"), mockClipboard.GetText());
652+
Assert::IsFalse(vmPointerFinder.mockDesktop.WasDialogShown());
653+
vmPointerFinder.mockDesktop.ResetExpectedWindows();
654+
mockClipboard.SetText(L"");
655+
656+
// entire chain
657+
vmPointerFinder.PotentialPointers().GetItemAt(3)->SetSelected(false);
658+
vmPointerFinder.PotentialPointers().GetItemAt(2)->SetSelected(false);
659+
vmPointerFinder.CopySelectedToClipboard();
660+
Assert::AreEqual(std::wstring(L"I:0x 0020_I:0x 0008_I:0x 0000_M:0x 0004"), mockClipboard.GetText());
661+
Assert::IsFalse(vmPointerFinder.mockDesktop.WasDialogShown());
662+
vmPointerFinder.mockDesktop.ResetExpectedWindows();
663+
}
664+
665+
TEST_METHOD(TestCopySelectedToClipboardMultipleSelections)
666+
{
667+
PointerFinderViewModelHarness vmPointerFinder;
668+
ra::services::mocks::MockClipboard mockClipboard;
669+
vmPointerFinder.mockGameContext.SetGameId(1U);
670+
vmPointerFinder.SetSearchType(ra::services::SearchType::SixteenBitAligned);
671+
672+
// initialize results
673+
ra::services::PointerFinder::PotentialPointer pPointer;
674+
pPointer.nRootAddress = 0x009c;
675+
pPointer.vOffsets.at(0) = 0;
676+
pPointer.nOffsetLength = 1;
677+
vmPointerFinder.AddPotentialPointer(pPointer, ra::data::Memory::Size::SixteenBit);
678+
679+
pPointer.nRootAddress = 0x0008;
680+
pPointer.vOffsets.at(0) = 4;
681+
pPointer.nOffsetLength = 1;
682+
vmPointerFinder.AddPotentialPointer(pPointer, ra::data::Memory::Size::SixteenBit);
683+
684+
pPointer.nRootAddress = 0x0020;
685+
pPointer.vOffsets.at(0) = 8;
686+
pPointer.vOffsets.at(1) = 0;
687+
pPointer.vOffsets.at(2) = 4;
688+
pPointer.nOffsetLength = 3;
689+
vmPointerFinder.AddPotentialPointer(pPointer, ra::data::Memory::Size::SixteenBit);
690+
691+
pPointer.nRootAddress = 0x0070;
692+
pPointer.vOffsets.at(0) = 4;
693+
pPointer.nOffsetLength = 1;
694+
vmPointerFinder.AddPotentialPointer(pPointer, ra::data::Memory::Size::SixteenBit);
695+
696+
// selection
697+
vmPointerFinder.PotentialPointers().GetItemAt(1)->SetSelected(true);
698+
vmPointerFinder.PotentialPointers().GetItemAt(4)->SetSelected(true);
699+
700+
bool bDialogSeen = false;
701+
vmPointerFinder.mockDesktop.ExpectWindow<ra::ui::viewmodels::MessageBoxViewModel>(
702+
[&bDialogSeen](const ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox) {
703+
Assert::AreEqual(std::wstring(L"Multiple items selected"), vmMessageBox.GetHeader());
704+
Assert::AreEqual(std::wstring(L"Only one item can be copied at a time."), vmMessageBox.GetMessage());
705+
706+
bDialogSeen = true;
707+
return ra::ui::DialogResult::OK;
708+
}
709+
);
710+
vmPointerFinder.CopySelectedToClipboard();
711+
Assert::AreEqual(std::wstring(L""), mockClipboard.GetText());
712+
Assert::IsTrue(bDialogSeen);
713+
}
714+
577715
TEST_METHOD(TestExportResults)
578716
{
579717
PointerFinderViewModelHarness vmPointerFinder;

0 commit comments

Comments
 (0)