From d0c24dc0480c012e70792833aba6d2357706206d Mon Sep 17 00:00:00 2001 From: yuygfgg <140488233+yuygfgg@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:11:25 +0800 Subject: [PATCH 1/3] Fix crash on macOS when deleting a hotkey Commit message modified by arch1t3cht --- src/hotkey_data_view_model.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/hotkey_data_view_model.cpp b/src/hotkey_data_view_model.cpp index 9dc25f901e..838624ddd3 100644 --- a/src/hotkey_data_view_model.cpp +++ b/src/hotkey_data_view_model.cpp @@ -147,7 +147,14 @@ class HotkeyModelCategory final : public HotkeyModelItem { void Delete(wxDataViewItem const& item) { for (auto it = children.begin(); it != children.end(); ++it) { if (&*it == item.GetID()) { - model->ItemDeleted(wxDataViewItem(this), wxDataViewItem((void*)&*it)); + wxDataViewItem deleted_item((void*)&*it); + for (auto visible_it = visible_items.begin(); visible_it != visible_items.end(); ++visible_it) { + if (visible_it->GetID() == deleted_item.GetID()) { + visible_items.erase(visible_it); + break; + } + } + model->ItemDeleted(wxDataViewItem(this), deleted_item); children.erase(it); return; } From d322b603b2cc977b6b764c5daa4b87fb1cfd350d Mon Sep 17 00:00:00 2001 From: yuygfgg <140488233+yuygfgg@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:12:41 +0800 Subject: [PATCH 2/3] Move icon logic from HotkeyDataViewModel to the CommandRenderer This is in preparation for using a choice renderer for the command column on macOS. Commit message modified by arch1t3cht --- src/hotkey_data_view_model.cpp | 30 +++++++++++------------- src/preferences.cpp | 43 +++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/hotkey_data_view_model.cpp b/src/hotkey_data_view_model.cpp index 838624ddd3..5187cc0807 100644 --- a/src/hotkey_data_view_model.cpp +++ b/src/hotkey_data_view_model.cpp @@ -84,14 +84,7 @@ class HotkeyModelCombo final : public HotkeyModelItem { if (col == 0) variant = to_wx(combo.Str()); else if (col == 1) { - wxBitmapBundle icon; - try { - icon = cmd::get(combo.CmdName())->Icon(); - } - catch (agi::Exception const&) { - // Just use no icon; error is reported in the description column - } - variant << wxDataViewIconText(to_wx(combo.CmdName()), icon); + variant = to_wx(combo.CmdName()); } else if (col == 2) { try { @@ -112,9 +105,17 @@ class HotkeyModelCombo final : public HotkeyModelItem { return true; } else if (col == 1) { - wxDataViewIconText text; - text << variant; - cmd_name = from_wx(text.GetText()); + if (variant.GetType() == "wxDataViewIconText") { + wxDataViewIconText text; + text << variant; + cmd_name = from_wx(text.GetText()); + } + else if (variant.GetType() == "string") { + cmd_name = from_wx(variant.GetString()); + } + else { + return false; + } combo = Combo(combo.Context(), cmd_name, combo.Str()); return true; } @@ -200,11 +201,8 @@ class HotkeyModelCategory final : public HotkeyModelItem { wxDataViewItem GetParent() const override { return wxDataViewItem(nullptr); } bool IsContainer() const override { return true; } bool SetValue(wxVariant const&, unsigned int) override { return false; } - void GetValue(wxVariant &variant, unsigned int col) const override { - if (col == 1) - variant << wxDataViewIconText(translated_name); - else - variant = translated_name; + void GetValue(wxVariant &variant, unsigned int) const override { + variant = translated_name; } unsigned int GetChildren(wxDataViewItemArray &out) const override { diff --git a/src/preferences.cpp b/src/preferences.cpp index e92e750f02..996efd7fb6 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -59,6 +59,12 @@ #include namespace { +wxArrayString get_registered_commands() { + wxArrayString commands = to_wx(cmd::get_registered_commands()); + commands.Sort(); + return commands; +} + /// General preferences page void General(wxTreebook *book, Preferences *parent) { auto p = new OptionPage(book, parent, _("General")); @@ -481,18 +487,34 @@ class CommandRenderer final : public wxDataViewCustomRenderer { wxDataViewIconText value; static const int icon_width = 20; + wxDataViewIconText MakeValue(wxString const& text) const { + wxBitmapBundle icon; + try { + icon = cmd::get(from_wx(text))->Icon(); + } + catch (agi::Exception const&) { + // Invalid command names are reported in the description column. + } + return wxDataViewIconText(text, icon); + } + public: CommandRenderer() - : wxDataViewCustomRenderer("wxDataViewIconText", wxDATAVIEW_CELL_EDITABLE) - , autocomplete(to_wx(cmd::get_registered_commands())) + : wxDataViewCustomRenderer("string", wxDATAVIEW_CELL_EDITABLE) + , autocomplete(get_registered_commands()) { } wxWindow *CreateEditorCtrl(wxWindow *parent, wxRect label_rect, wxVariant const& value) override { - wxDataViewIconText iconText; - iconText << value; - - wxString text = iconText.GetText(); + wxString text; + if (value.GetType() == "wxDataViewIconText") { + wxDataViewIconText iconText; + iconText << value; + text = iconText.GetText(); + } + else { + text = value.GetString(); + } // adjust the label rect to take the width of the icon into account label_rect.x += parent->FromDIP(icon_width); @@ -510,6 +532,10 @@ class CommandRenderer final : public wxDataViewCustomRenderer { value << var; return true; } + if (var.GetType() == "string") { + value = MakeValue(var.GetString()); + return true; + } return false; } @@ -537,8 +563,7 @@ class CommandRenderer final : public wxDataViewCustomRenderer { bool GetValueFromEditorCtrl(wxWindow* editor, wxVariant &var) override { wxTextCtrl *text = static_cast(editor); - wxDataViewIconText iconText(text->GetValue(), value.GetIcon()); - var << iconText; + var = text->GetValue(); return true; } @@ -630,7 +655,7 @@ Interface_Hotkeys::Interface_Hotkeys(wxTreebook *book, Preferences *parent) auto col = new wxDataViewColumn(_("Hotkey"), new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE), 0, 150, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE); col->SetMinWidth(150); dvc->AppendColumn(col); - dvc->AppendColumn(new wxDataViewColumn(_("Command"), new wxDataViewIconTextRenderer("wxDataViewIconText", wxDATAVIEW_CELL_EDITABLE), 1, 250, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE)); + dvc->AppendColumn(new wxDataViewColumn(_("Command"), new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE), 1, 250, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE)); #endif dvc->AppendTextColumn(_("Description"), 2, wxDATAVIEW_CELL_INERT, 300, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE); From 54108bc29e723d32eecfd619831a952d4f169a5e Mon Sep 17 00:00:00 2001 From: yuygfgg <140488233+yuygfgg@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:13:06 +0800 Subject: [PATCH 3/3] Use wxDataViewChoiceRenderer for hotkey on macOS wxCocoa does not support custom data view renderers. Previously a simple text renderer was used instead, but that resulted in no autocomplete for commands. Replace this with a dropdown to make entering commands on macOS more user-friendly. Commit message modified by arch1t3cht --- src/preferences.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/preferences.cpp b/src/preferences.cpp index 996efd7fb6..01e8221621 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -65,6 +65,34 @@ wxArrayString get_registered_commands() { return commands; } +#ifdef __APPLE__ +void add_current_hotkey_commands(wxArrayString& commands, HotkeyDataViewModel *model, wxDataViewItem const& parent) { + wxDataViewItemArray children; + model->GetChildren(parent, children); + + for (auto const& child : children) { + wxVariant value; + model->GetValue(value, child, 1); + wxString command = value.GetString(); + if (commands.Index(command) == wxNOT_FOUND) + commands.Add(command); + + if (model->IsContainer(child)) + add_current_hotkey_commands(commands, model, child); + } +} + +wxArrayString get_hotkey_command_choices(HotkeyDataViewModel *model) { + wxArrayString commands = get_registered_commands(); + if (commands.Index("") == wxNOT_FOUND) + commands.Add(""); + + add_current_hotkey_commands(commands, model, wxDataViewItem(nullptr)); + commands.Sort(); + return commands; +} +#endif + /// General preferences page void General(wxTreebook *book, Preferences *parent) { auto p = new OptionPage(book, parent, _("General")); @@ -655,7 +683,7 @@ Interface_Hotkeys::Interface_Hotkeys(wxTreebook *book, Preferences *parent) auto col = new wxDataViewColumn(_("Hotkey"), new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE), 0, 150, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE); col->SetMinWidth(150); dvc->AppendColumn(col); - dvc->AppendColumn(new wxDataViewColumn(_("Command"), new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE), 1, 250, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE)); + dvc->AppendColumn(new wxDataViewColumn(_("Command"), new wxDataViewChoiceRenderer(get_hotkey_command_choices(model.get()), wxDATAVIEW_CELL_EDITABLE), 1, 250, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE)); #endif dvc->AppendTextColumn(_("Description"), 2, wxDATAVIEW_CELL_INERT, 300, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE);