Skip to content

Commit 0464fb8

Browse files
authored
Merge pull request moneymanagerex#8318 from KWich/Feature-Custom-GRM-2026-04-13
Feature: Custom icons for GRM reports
2 parents 12372ee + 6a59e61 commit 0464fb8

File tree

15 files changed

+408
-36
lines changed

15 files changed

+408
-36
lines changed

docs/en_US/grm.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,13 @@ <h5>Generated report example:</h5><p>
561561
<img class="shadow wide" alt="selection example output" src="../en_US/grm/ExampleOutputSelection.png"></td><br>
562562

563563

564+
<!-- ====================== Section ====================== -->
565+
<!-- Exporting report definition -->
566+
<!-- ======================================================= -->
567+
568+
<h2>Customizing the report</h2>
569+
<p>Within the context menu of an report, there are options to rename the report, to add a custom icon (see also <a href="../icon.html?lang=en_US">Custom account and navigator icons</a> for details)
570+
and to assign the report to a new/different group.</p>
564571

565572
<!-- ====================== Section ====================== -->
566573
<!-- Exporting report definition -->
16.4 KB
Loading
44 KB
Loading

docs/en_US/settings/icons.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,24 @@ <h3>Directly adding a favicon</h3>
7676
<img class="shadow" alt='Icon manager dialog' src='../../en_US/settings/fav_icon_from_account_dialog.png' height="600">
7777
</div>
7878

79+
<h2>Adding a custom icon to a navigator section</h2>
80+
<p>All configured custom icons are available in the navigator configuration dialog and can be selected during editing of the navigator entry
81+
(see <a href="../../navigator.html?lang=en_US">Navigator configuration and custom account types - 2.4</a> for details):</p>
82+
83+
<h2>Adding a custom icon to a GRM report</h2>
84+
<p>Within the GRM manager report tree, with a right click on a selected report, the context menu for the report has an <kbd><samp>Set icon </samp></kbd> option:</p>
85+
<div>
86+
<img class="shadow" alt='Icon selection dialog' src='../../en_US/settings/grm_selection.png' height="200">
87+
</div>
88+
<p> This opens an icon selection dialog, to assign an icon to an report:</p>
89+
<div>
90+
<img class="shadow" alt='Icon selection dialog' src='../../en_US/settings/iconSelectionDialog.png' height="400">
91+
</div>
92+
<p>An icon can be directly selected with a double click, or by marking it in the list and applying it with the <kbd><samp>OK</samp></kbd> button. The <kbd><samp>Default</samp></kbd> button
93+
removes a custom icon from the report.</p>
94+
<p>The icon is used in the GRM manager tree and the navigator tree.</p>
95+
96+
7997
<h2>Notes</h2>
8098
<ul>
8199
<li>All icons are stored globally for the MMEX installation beside the global settings database in the sub directory "icons" and are available for all mmex databases on the PC.</li>

src/app/mmFrame.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,6 +2637,7 @@ bool mmFrame::openFile(const wxString& fileName, bool openingNew, const wxString
26372637
InfoModel::instance().saveBool("ISUSED", true);
26382638
db_lockInPlace = false;
26392639
mmNavigatorList::instance().LoadFromDB();
2640+
loadGrmIconMapping();
26402641
autoRepeatTransactionsTimer_.Start(REPEAT_FREQ_TRANS_DELAY_TIME, wxTIMER_ONE_SHOT);
26412642
}
26422643
else
@@ -2667,6 +2668,7 @@ void mmFrame::OnNew(wxCommandEvent& /*event*/)
26672668

26682669
SetDatabaseFile(fileName, true);
26692670
SettingModel::instance().saveString("LASTFILENAME", fileName);
2671+
loadGrmIconMapping();
26702672
}
26712673
//----------------------------------------------------------------------------
26722674

@@ -3306,6 +3308,7 @@ void mmFrame::OnGeneralReportManager(wxCommandEvent& WXUNUSED(event))
33063308
wxTreeItemId selectedItem = m_nav_tree_ctrl->GetSelection();
33073309
GeneralReportManager dlg(this, m_db.get(), selectedItem.IsOk() ? m_nav_tree_ctrl->GetItemText(selectedItem) : "");
33083310
dlg.ShowModal();
3311+
loadGrmIconMapping();
33093312
RefreshNavigationTree();
33103313
}
33113314
}
@@ -4605,6 +4608,8 @@ void mmFrame::DoUpdateGRMNavigation(wxTreeItemId& parent_item)
46054608
));
46064609
}
46074610

4611+
// Update icons:
4612+
applyGrmIconMapping(parent_item);
46084613
}
46094614

46104615
void mmFrame::DoUpdateFilterNavigation(wxTreeItemId& parent_item)
@@ -4665,3 +4670,48 @@ void mmFrame::mmDoHideReportsDialog()
46654670
}
46664671
DoRecreateNavTreeControl();
46674672
}
4673+
4674+
void mmFrame::loadGrmIconMapping()
4675+
{
4676+
m_grm_icons_map.clear();
4677+
4678+
rapidjson::Document doc;
4679+
const wxString& json = InfoModel::instance().getString("GRM_REPORT_IMAGE_STATUS", "");
4680+
doc.Parse(json.c_str());
4681+
if (!doc.IsObject())
4682+
return;
4683+
4684+
const int prefix_len = static_cast<int>(_t("Reports").Len()) + 1;
4685+
4686+
for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) {
4687+
std::string p = it->name.GetString();
4688+
wxString path(p.substr(prefix_len).c_str(), wxConvUTF8);
4689+
wxString timg = wxString::FromUTF8(it->value.GetString());
4690+
m_grm_icons_map[path] = NavTreeIconImages::instance().getImgIndexFromStorageString(timg);
4691+
}
4692+
}
4693+
4694+
void mmFrame::applyGrmIconMapping(wxTreeItemId& parent_item)
4695+
{
4696+
for (auto const& x : m_grm_icons_map) {
4697+
wxLogDebug ("Apply icon '%d' to path '%s'", x.second, x.first);
4698+
4699+
std::vector<std::string> parts = GeneralReportManager::splitPath(x.first.ToStdString());
4700+
wxTreeItemId current = parent_item;
4701+
if (!parts.empty()) {
4702+
for (size_t i = 0; i < parts.size(); ++i) {
4703+
wxTreeItemId next = GeneralReportManager::findChild(m_nav_tree_ctrl, current, parts[i]);
4704+
if (!next.IsOk()) {
4705+
current = wxTreeItemId();
4706+
break;
4707+
}
4708+
current = next;
4709+
}
4710+
}
4711+
4712+
if (current.IsOk()) {
4713+
m_nav_tree_ctrl->SetItemImage(current, x.second);
4714+
m_nav_tree_ctrl->SetItemImage(current, x.second, wxTreeItemIcon_Selected);
4715+
}
4716+
}
4717+
}

src/app/mmFrame.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ class mmFrame : public wxFrame
196196

197197
private:
198198
static const std::vector<std::pair<mmNavigatorItem::TYPE_ID, wxString>> ACCOUNT_SECTION_TABLE;
199-
//static wxArrayString account_section_all();
200199

201200
// -- state
202201

@@ -207,6 +206,7 @@ class mmFrame : public wxFrame
207206
private:
208207
std::vector<WebsiteNews> websiteNewsArray_;
209208
std::vector<TableBase*> m_all_models;
209+
std::map<wxString, int> m_grm_icons_map;
210210

211211
// handles to SQLite Database
212212
wxSharedPtr<wxSQLite3Database> m_db;
@@ -299,6 +299,8 @@ class mmFrame : public wxFrame
299299
wxSizer* cleanupHomePanel(bool new_sizer = true);
300300
void updateHomePagePanel(PanelBase* panel);
301301
bool openFile(const wxString& fileName, bool openingNew, const wxString &password = "");
302+
void loadGrmIconMapping();
303+
void applyGrmIconMapping(wxTreeItemId& parent_item);
302304
void InitializeModelTables();
303305
bool createDataStore(const wxString& fileName, const wxString &passwd, bool openingNew);
304306
void createMenu();

src/dialog/IconManagerDialog.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,3 +640,117 @@ void IconManagerDialog::OnRightClick(wxMouseEvent& WXUNUSED(event), const wxStri
640640

641641
PopupMenu(&menu);
642642
}
643+
644+
645+
// =============== Icon Selection Dialog =============================
646+
IconSelectionDialog::IconSelectionDialog(wxWindow* parent, wxVector<wxBitmapBundle>& images, int iconSize)
647+
: wxDialog(parent, wxID_ANY, _t("Select icon"),
648+
wxDefaultPosition, wxDefaultSize,
649+
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
650+
m_icons(images)
651+
{
652+
wxBoxSizer* dialogSizer = new wxBoxSizer(wxVERTICAL);
653+
654+
m_scrollWin = new wxScrolledWindow(this, wxID_ANY,
655+
wxDefaultPosition, wxDefaultSize,
656+
wxVSCROLL);
657+
m_scrollWin->SetScrollRate(5, 5);
658+
m_scrollWin->SetMinSize(wxSize(300, 200));
659+
660+
int cols = 6;
661+
m_gridSizer = new wxGridSizer(cols, 5, 5);
662+
663+
for (size_t i = 0; i < m_icons.size(); ++i) {
664+
wxBitmap bmp = m_icons[i].GetBitmap(wxSize(iconSize, iconSize));
665+
666+
wxStaticBitmap* iconCtrl = new wxStaticBitmap(
667+
m_scrollWin, wxID_ANY, bmp,
668+
wxDefaultPosition,
669+
wxSize(iconSize + 10, iconSize + 10),
670+
wxBORDER_SIMPLE
671+
);
672+
673+
iconCtrl->SetBackgroundColour(*wxWHITE);
674+
iconCtrl->SetClientData(reinterpret_cast<void*>(i));
675+
676+
iconCtrl->Bind(wxEVT_LEFT_DOWN, &IconSelectionDialog::OnIconClicked, this);
677+
iconCtrl->Bind(wxEVT_LEFT_DCLICK, [this, i](wxMouseEvent&) {
678+
m_selectedIndex = i;
679+
EndModal(wxID_OK);
680+
});
681+
iconCtrl->Bind(wxEVT_ENTER_WINDOW, [](wxMouseEvent& e) {
682+
auto w = static_cast<wxWindow*>(e.GetEventObject());
683+
w->SetBackgroundColour(wxColour(220,220,220));
684+
w->Refresh();
685+
});
686+
687+
iconCtrl->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) {
688+
auto w = static_cast<wxWindow*>(e.GetEventObject());
689+
if (reinterpret_cast<intptr_t>(w->GetClientData()) != m_selectedIndex)
690+
w->SetBackgroundColour(*wxWHITE);
691+
w->Refresh();
692+
});
693+
694+
m_gridSizer->Add(iconCtrl, 0, wxALIGN_CENTER | wxALL, 2);
695+
m_iconWidgets.push_back(iconCtrl);
696+
}
697+
698+
m_scrollWin->SetSizer(m_gridSizer);
699+
m_gridSizer->FitInside(m_scrollWin);
700+
701+
dialogSizer->Add(m_scrollWin, 1, wxEXPAND | wxALL, 5);
702+
703+
// bottom area
704+
wxStaticLine* panelSeparatorLine = new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
705+
dialogSizer->Add(panelSeparatorLine, 0, wxGROW | wxLEFT | wxRIGHT, 10);
706+
707+
// Buttons
708+
wxBoxSizer* btnSizer = new wxBoxSizer(wxHORIZONTAL);
709+
btnSizer->AddStretchSpacer(1);
710+
btnSizer->Add(new wxButton(this, wxID_CANCEL), 0, wxALL | wxALIGN_CENTER, 10);
711+
712+
wxButton* rstBtn = new wxButton(this, wxID_RESET, "&Default");
713+
rstBtn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
714+
m_selectedIndex = -1;
715+
EndModal(wxID_OK);
716+
});
717+
btnSizer->Add(rstBtn, 0, wxALL | wxALIGN_CENTER, 10);
718+
719+
btnSizer->Add(new wxButton(this, wxID_OK), 0, wxALL | wxALIGN_CENTER, 10);
720+
btnSizer->AddStretchSpacer(1);
721+
722+
dialogSizer->Add(btnSizer, 0, wxEXPAND | wxALL, 5);
723+
724+
SetSizer(dialogSizer);
725+
726+
// Dialog window
727+
SetMinSize(wxSize(400, 300));
728+
SetSize(wxSize(400, 600));
729+
Layout();
730+
Centre();
731+
}
732+
733+
void IconSelectionDialog::OnIconClicked(wxMouseEvent& event)
734+
{
735+
wxStaticBitmap* clicked = dynamic_cast<wxStaticBitmap*>(event.GetEventObject());
736+
if (!clicked) return;
737+
738+
int index = reinterpret_cast<intptr_t>(clicked->GetClientData());
739+
m_selectedIndex = index;
740+
741+
for (auto* icon : m_iconWidgets)
742+
{
743+
icon->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
744+
icon->Refresh();
745+
}
746+
747+
clicked->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
748+
clicked->Refresh();
749+
750+
event.Skip();
751+
}
752+
753+
int IconSelectionDialog::GetSelectedIndex() const
754+
{
755+
return m_selectedIndex;
756+
}

src/dialog/IconManagerDialog.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,26 @@ class IconManagerDialog : public wxDialog
7474

7575
wxDECLARE_EVENT_TABLE();
7676
};
77+
78+
79+
class IconSelectionDialog : public wxDialog
80+
{
81+
public:
82+
IconSelectionDialog(wxWindow* parent, wxVector<wxBitmapBundle>& images, int iconSize = 32);
83+
84+
int GetSelectedIndex() const;
85+
//wxBitmapBundle GetSelectedIcon() const;
86+
87+
private:
88+
void OnIconClicked(wxMouseEvent& event);
89+
90+
wxScrolledWindow* m_scrollWin;
91+
wxGridSizer* m_gridSizer;
92+
93+
wxVector<wxBitmapBundle> m_icons;
94+
int m_selectedIndex = wxNOT_FOUND;
95+
96+
std::vector<wxStaticBitmap*> m_iconWidgets;
97+
98+
//wxDECLARE_EVENT_TABLE();
99+
};

src/dialog/NavigatorEditDialog.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,6 @@ void NavigatorEditDialog::CreateControls()
104104

105105
wxStaticText* iconLabel = new wxStaticText(uiBox, wxID_ANY, _t("Symbol") + ":");
106106

107-
/* wxImageList* imageList = new wxImageList(navIconSize, navIconSize);
108-
for (const auto& bundle : navtree_images_list(navIconSize)) {
109-
wxBitmap bitmap = bundle.GetBitmap(wxSize(navIconSize, navIconSize));
110-
imageList->Add(bitmap);
111-
}
112-
wxImageList* imageList = NavTreeIconImages::instance().getImageList();*/
113-
114107
const auto navIconSize = PrefModel::instance().getNavigationIconSize();
115108
m_cbIcon = new wxBitmapComboBox(uiBox, wxID_ANY, "",
116109
wxPoint(navIconSize, navIconSize), wxDefaultSize,
@@ -122,11 +115,6 @@ void NavigatorEditDialog::CreateControls()
122115
m_cbIcon->Append("", bitmap);
123116
}
124117

125-
/*const int imageCount = imageList->GetImageCount();
126-
for (int i = 0; i < imageCount; ++i) {
127-
m_cbIcon->Append("", imageList->GetBitmap(i));
128-
}*/
129-
130118
uiStyleSizer->Add(iconLabel, g_flagsH);
131119
uiStyleSizer->Add(m_cbIcon, g_flagsH);
132120

0 commit comments

Comments
 (0)