If initsram/initstate points to LSS file, pull the matching member
[lsnes.git] / src / platform / wxwidgets / branchesmenu.cpp
blob815dde247733e08111c5d64703cf71c357ae3c3f
1 #include "platform/wxwidgets/settings-common.hpp"
2 #include "platform/wxwidgets/settings-keyentry.hpp"
3 #include "platform/wxwidgets/menu_branches.hpp"
4 #include "platform/wxwidgets/platform.hpp"
5 #include "platform/wxwidgets/loadsave.hpp"
6 #include "core/debug.hpp"
7 #include "core/dispatch.hpp"
8 #include "core/instance.hpp"
9 #include "core/project.hpp"
10 #include "core/moviedata.hpp"
11 #include "core/ui-services.hpp"
13 namespace
15 //Tree of branches.
16 class branches_tree : public wxTreeCtrl
18 public:
19 branches_tree(wxWindow* parent, emulator_instance& _inst, int id, bool _nosels)
20 : wxTreeCtrl(parent, id), inst(_inst), nosels(_nosels)
22 CHECK_UI_THREAD;
23 SetMinSize(wxSize(400, 300));
24 branchchange.set(inst.dispatch->branch_change, [this]() { runuifun([this]() {
25 this->update(); }); });
26 update();
28 struct selection
30 wxTreeItemId item;
31 bool isroot;
32 bool haschildren;
33 bool iscurrent;
34 uint64_t id;
35 std::string name;
37 selection get_selection()
39 CHECK_UI_THREAD;
40 selection s;
41 s.item = GetSelection();
42 for(auto i : ids) {
43 if(s.item.IsOk() && i.second == s.item) {
44 s.isroot = (i.first == 0);
45 s.haschildren = with_children.count(i.first);
46 s.id = i.first;
47 s.iscurrent = (i.first == current);
48 return s;
51 s.isroot = false;
52 s.haschildren = false;
53 s.iscurrent = false;
54 s.id = 0xFFFFFFFFFFFFFFFFULL;
55 return s;
57 void update()
59 CHECK_UI_THREAD;
60 std::map<uint64_t, std::string> namemap;
61 std::map<uint64_t, std::set<uint64_t>> childmap;
62 uint64_t cur = 0;
63 UI_get_branch_map(inst, cur, namemap, childmap);
64 current = cur;
65 selection cursel = get_selection();
66 std::set<uint64_t> expanded;
67 for(auto i : ids)
68 if(IsExpanded(i.second))
69 expanded.insert(i.first);
70 DeleteAllItems();
71 ids.clear();
72 with_children.clear();
73 if(namemap.empty()) return;
74 //Create ROOT.
75 names = namemap;
76 ids[0] = AddRoot(towxstring(namemap[0] + ((!nosels && current == 0) ? " <selected>" : "")));
77 build_tree(0, ids[0], childmap, namemap);
78 for(auto i : expanded)
79 if(ids.count(i))
80 Expand(ids[i]);
81 for(auto i : ids) {
82 if(i.first == cursel.id) {
83 SelectItem(i.second);
87 std::string get_name(uint64_t id)
89 if(names.count(id))
90 return names[id];
91 return "";
93 private:
94 void build_tree(uint64_t id, wxTreeItemId parent, std::map<uint64_t, std::set<uint64_t>>& childmap,
95 std::map<uint64_t, std::string>& namemap)
97 CHECK_UI_THREAD;
98 if(!childmap.count(id) || childmap[id].empty())
99 return;
100 for(auto i : childmap[id]) {
101 ids[i] = AppendItem(ids[id], towxstring(namemap[i] + ((!nosels && current == i) ?
102 " <selected>" : "")));
103 build_tree(i, ids[i], childmap, namemap);
106 emulator_instance& inst;
107 uint64_t current;
108 std::map<uint64_t, std::string> names;
109 std::map<uint64_t, wxTreeItemId> ids;
110 std::set<uint64_t> with_children;
111 struct dispatch::target<> branchchange;
112 bool nosels;
115 class branch_select : public wxDialog
117 public:
118 branch_select(wxWindow* parent, emulator_instance& _inst)
119 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Select new parent branch"))
121 CHECK_UI_THREAD;
122 Centre();
123 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
124 SetSizer(top_s);
125 Center();
127 top_s->Add(branches = new branches_tree(this, _inst, wxID_ANY, true), 1, wxGROW);
128 branches->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED,
129 wxCommandEventHandler(branch_select::on_change), NULL, this);
131 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
132 pbutton_s->AddStretchSpacer();
133 pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
134 pbutton_s->Add(cancelbutton = new wxButton(this, wxID_OK, wxT("Cancel")), 0, wxGROW);
135 okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
136 wxCommandEventHandler(branch_select::on_ok), NULL, this);
137 cancelbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
138 wxCommandEventHandler(branch_select::on_cancel), NULL, this);
139 top_s->Add(pbutton_s, 0, wxGROW);
141 top_s->SetSizeHints(this);
142 Fit();
144 wxCommandEvent e;
145 on_change(e);
147 void on_ok(wxCommandEvent& e)
149 CHECK_UI_THREAD;
150 EndModal(wxID_OK);
152 void on_cancel(wxCommandEvent& e)
154 CHECK_UI_THREAD;
155 EndModal(wxID_CANCEL);
157 uint64_t get_selection()
159 return branches->get_selection().id;
161 void on_change(wxCommandEvent& e)
163 CHECK_UI_THREAD;
164 okbutton->Enable(get_selection() != 0xFFFFFFFFFFFFFFFFULL);
166 private:
167 wxButton* okbutton;
168 wxButton* cancelbutton;
169 branches_tree* branches;
172 class branch_config : public wxDialog
174 public:
175 branch_config(wxWindow* parent, emulator_instance& _inst)
176 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Edit slot branches")), inst(_inst)
178 CHECK_UI_THREAD;
179 Centre();
180 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
181 SetSizer(top_s);
182 Center();
184 top_s->Add(branches = new branches_tree(this, inst, wxID_ANY, false), 1, wxGROW);
185 branches->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED,
186 wxCommandEventHandler(branch_config::on_change), NULL, this);
188 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
189 pbutton_s->Add(createbutton = new wxButton(this, wxID_OK, wxT("Create")), 0, wxGROW);
190 pbutton_s->Add(selectbutton = new wxButton(this, wxID_OK, wxT("Select")), 0, wxGROW);
191 pbutton_s->Add(renamebutton = new wxButton(this, wxID_OK, wxT("Rename")), 0, wxGROW);
192 pbutton_s->Add(reparentbutton = new wxButton(this, wxID_OK, wxT("Reparent")), 0, wxGROW);
193 pbutton_s->Add(deletebutton = new wxButton(this, wxID_OK, wxT("Delete")), 0, wxGROW);
194 pbutton_s->AddStretchSpacer();
195 pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
196 createbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
197 wxCommandEventHandler(branch_config::on_create), NULL, this);
198 selectbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
199 wxCommandEventHandler(branch_config::on_select), NULL, this);
200 renamebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
201 wxCommandEventHandler(branch_config::on_rename), NULL, this);
202 reparentbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
203 wxCommandEventHandler(branch_config::on_reparent), NULL, this);
204 deletebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
205 wxCommandEventHandler(branch_config::on_delete), NULL, this);
206 okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
207 wxCommandEventHandler(branch_config::on_close), NULL, this);
208 top_s->Add(pbutton_s, 0, wxGROW);
210 top_s->SetSizeHints(this);
211 Fit();
212 wxCommandEvent e;
213 on_change(e);
215 void on_create(wxCommandEvent& e)
217 CHECK_UI_THREAD;
218 uint64_t id = get_selected_id();
219 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
220 std::string newname;
221 try {
222 newname = pick_text(this, "Enter new branch name", "Enter name for new branch:",
223 newname, false);
224 } catch(canceled_exception& e) {
225 return;
227 UI_create_branch(inst, id, newname, [this](std::exception& e) {
228 show_exception_any(this, "Error creating branch", "Can't create branch", e);
231 void on_select(wxCommandEvent& e)
233 CHECK_UI_THREAD;
234 uint64_t id = get_selected_id();
235 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
236 UI_switch_branch(inst, id, [this](std::exception& e) {
237 show_exception_any(this, "Error setting branch", "Can't set branch", e);
240 void on_rename(wxCommandEvent& e)
242 CHECK_UI_THREAD;
243 uint64_t id = get_selected_id();
244 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
245 std::string newname = branches->get_name(id);
246 try {
247 newname = pick_text(this, "Enter new branch name", "Rename this branch to:",
248 newname, false);
249 } catch(canceled_exception& e) {
250 return;
252 UI_rename_branch(inst, id, newname, [this](std::exception& e) {
253 show_exception_any(this, "Error renaming branch", "Can't rename branch", e);
256 void on_reparent(wxCommandEvent& e)
258 CHECK_UI_THREAD;
259 uint64_t id = get_selected_id();
260 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
261 uint64_t pid;
262 branch_select* bsel = new branch_select(this, inst);
263 int r = bsel->ShowModal();
264 if(r != wxID_OK) {
265 bsel->Destroy();
266 return;
268 pid = bsel->get_selection();
269 if(pid == 0xFFFFFFFFFFFFFFFFULL) return;
270 bsel->Destroy();
271 UI_reparent_branch(inst, id, pid, [this](std::exception& e) {
272 show_exception_any(this, "Error reparenting branch", "Can't reparent branch", e);
275 void on_delete(wxCommandEvent& e)
277 CHECK_UI_THREAD;
278 uint64_t id = get_selected_id();
279 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
280 UI_delete_branch(inst, id, [this](std::exception& e) {
281 show_exception_any(this, "Error deleting branch", "Can't delete branch", e);
284 void on_close(wxCommandEvent& e)
286 CHECK_UI_THREAD;
287 EndModal(wxID_OK);
289 void on_change(wxCommandEvent& e)
291 set_enabled(branches->get_selection());
293 private:
294 uint64_t get_selected_id()
296 return branches->get_selection().id;
298 void set_enabled(branches_tree::selection id)
300 CHECK_UI_THREAD;
301 createbutton->Enable(id.item.IsOk());
302 selectbutton->Enable(id.item.IsOk());
303 renamebutton->Enable(id.item.IsOk() && !id.isroot);
304 reparentbutton->Enable(id.item.IsOk() && !id.isroot);
305 deletebutton->Enable(id.item.IsOk() && !id.isroot && !id.haschildren && !id.iscurrent);
307 wxButton* createbutton;
308 wxButton* selectbutton;
309 wxButton* renamebutton;
310 wxButton* reparentbutton;
311 wxButton* deletebutton;
312 wxButton* okbutton;
313 branches_tree* branches;
314 emulator_instance& inst;
317 void build_menus(wxMenu* root, uint64_t id, std::list<branches_menu::miteminfo>& otheritems,
318 std::list<wxMenu*>& menus, std::map<uint64_t, std::string>& namemap,
319 std::map<uint64_t, std::set<uint64_t>>& childmap, std::map<int, uint64_t>& branch_ids, int& nextid,
320 uint64_t curbranch)
322 CHECK_UI_THREAD;
323 auto& children = childmap[id];
324 int mid = nextid++;
325 otheritems.push_back(branches_menu::miteminfo(root->AppendCheckItem(mid, towxstring(namemap[id])),
326 false, root));
327 branch_ids[mid] = id;
328 root->FindItem(mid)->Check(id == curbranch);
329 if(!children.empty())
330 otheritems.push_back(branches_menu::miteminfo(root->AppendSeparator(), false, root));
331 for(auto i : children) {
332 bool has_children = !childmap[i].empty();
333 if(!has_children) {
334 //No children, just put the item there.
335 int mid2 = nextid++;
336 otheritems.push_back(branches_menu::miteminfo(root->AppendCheckItem(mid2,
337 towxstring(namemap[i])), false, root));
338 branch_ids[mid2] = i;
339 root->FindItem(mid2)->Check(i == curbranch);
340 } else {
341 //Has children. Make a menu.
342 wxMenu* m = new wxMenu();
343 otheritems.push_back(branches_menu::miteminfo(root->AppendSubMenu(m,
344 towxstring(namemap[i])), true, root));
345 menus.push_back(m);
346 build_menus(m, i, otheritems, menus, namemap, childmap, branch_ids, nextid,
347 curbranch);
353 branches_menu::branches_menu(wxWindow* win, emulator_instance& _inst, int wxid_low, int wxid_high)
354 : inst(_inst)
356 CHECK_UI_THREAD;
357 pwin = win;
358 wxid_range_low = wxid_low;
359 wxid_range_high = wxid_high;
360 win->Connect(wxid_low, wxid_high, wxEVT_COMMAND_MENU_SELECTED,
361 wxCommandEventHandler(branches_menu::on_select), NULL, this);
362 branchchange.set(inst.dispatch->branch_change, [this]() { runuifun([this]() { this->update(); }); });
365 branches_menu::~branches_menu()
369 void branches_menu::on_select(wxCommandEvent& e)
371 CHECK_UI_THREAD;
372 int id = e.GetId();
373 if(id < wxid_range_low || id > wxid_range_high) return;
374 if(id == wxid_range_low) {
375 //Configure.
376 branch_config* bcfg = new branch_config(pwin, inst);
377 bcfg->ShowModal();
378 bcfg->Destroy();
379 return;
381 if(!branch_ids.count(id)) return;
382 uint64_t bid = branch_ids[id];
383 std::string err;
384 UI_switch_branch(inst, bid, [this](std::exception& e) {
385 show_exception_any(this->pwin, "Error changing branch", "Can't change branch", e);
389 void branches_menu::update()
391 CHECK_UI_THREAD;
392 std::map<uint64_t, std::string> namemap;
393 std::map<uint64_t, std::set<uint64_t>> childmap;
394 uint64_t cur;
395 UI_get_branch_map(inst, cur, namemap, childmap);
396 //First destroy everything that isn't a menu.
397 for(auto i : otheritems)
398 i.parent->Delete(i.item);
399 //Then kill all menus.
400 for(auto i : menus)
401 delete i;
402 otheritems.clear();
403 menus.clear();
404 branch_ids.clear();
405 if(namemap.empty()) {
406 if(disabler_fn) disabler_fn(false);
407 return;
409 //Okay, cleared. Rebuild things.
410 otheritems.push_back(miteminfo(Append(wxid_range_low, towxstring("Edit branches")), false, this));
411 otheritems.push_back(miteminfo(AppendSeparator(), false, this));
412 int ass_id = wxid_range_low + 1;
413 build_menus(this, 0, otheritems, menus, namemap, childmap, branch_ids, ass_id, cur);
414 if(disabler_fn) disabler_fn(true);
417 bool branches_menu::any_enabled()
419 return UI_in_project_context(inst);