Refactor emulator status reporting (and fix the statusbar doesn't update bug)
[lsnes.git] / src / platform / wxwidgets / branchesmenu.cpp
blob260085b3236638709d8902c715ebf45459b1f704
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/project.hpp"
9 #include "core/moviedata.hpp"
11 void update_movie_state();
13 namespace
15 void fill_namemap(project_info& p, uint64_t id, std::map<uint64_t, std::string>& namemap,
16 std::map<uint64_t, std::set<uint64_t>>& childmap)
18 namemap[id] = p.get_branch_name(id);
19 auto s = p.branch_children(id);
20 for(auto i : s)
21 fill_namemap(p, i, namemap, childmap);
22 childmap[id] = s;
25 //Tree of branches.
26 class branches_tree : public wxTreeCtrl
28 public:
29 branches_tree(wxWindow* parent, int id, bool _nosels)
30 : wxTreeCtrl(parent, id), nosels(_nosels)
32 SetMinSize(wxSize(400, 300));
33 branchchange.set(notify_branch_change, [this]() { runuifun([this]() { this->update(); }); });
34 update();
36 struct selection
38 wxTreeItemId item;
39 bool isroot;
40 bool haschildren;
41 bool iscurrent;
42 uint64_t id;
43 std::string name;
45 selection get_selection()
47 selection s;
48 s.item = GetSelection();
49 for(auto i : ids) {
50 if(s.item.IsOk() && i.second == s.item) {
51 s.isroot = (i.first == 0);
52 s.haschildren = with_children.count(i.first);
53 s.id = i.first;
54 s.iscurrent = (i.first == current);
55 return s;
58 s.isroot = false;
59 s.haschildren = false;
60 s.iscurrent = false;
61 s.id = 0xFFFFFFFFFFFFFFFFULL;
62 return s;
64 void update()
66 std::map<uint64_t, std::string> namemap;
67 std::map<uint64_t, std::set<uint64_t>> childmap;
68 uint64_t cur = 0;
69 runemufn([&cur, &namemap, &childmap]() {
70 auto p = project_get();
71 if(!p) return;
72 fill_namemap(*p, 0, namemap, childmap);
73 cur = p->get_current_branch();
74 });
75 current = cur;
76 selection cursel = get_selection();
77 std::set<uint64_t> expanded;
78 for(auto i : ids)
79 if(IsExpanded(i.second))
80 expanded.insert(i.first);
81 DeleteAllItems();
82 ids.clear();
83 with_children.clear();
84 if(namemap.empty()) return;
85 //Create ROOT.
86 names = namemap;
87 ids[0] = AddRoot(towxstring(namemap[0] + ((!nosels && current == 0) ? " <selected>" : "")));
88 build_tree(0, ids[0], childmap, namemap);
89 for(auto i : expanded)
90 if(ids.count(i))
91 Expand(ids[i]);
92 for(auto i : ids) {
93 if(i.first == cursel.id) {
94 SelectItem(i.second);
98 std::string get_name(uint64_t id)
100 if(names.count(id))
101 return names[id];
102 return "";
104 void call_project_flush()
106 runemufn_async([] {
107 auto p = project_get();
108 if(p) p->flush();
111 private:
112 void build_tree(uint64_t id, wxTreeItemId parent, std::map<uint64_t, std::set<uint64_t>>& childmap,
113 std::map<uint64_t, std::string>& namemap)
115 if(!childmap.count(id) || childmap[id].empty())
116 return;
117 for(auto i : childmap[id]) {
118 ids[i] = AppendItem(ids[id], towxstring(namemap[i] + ((!nosels && current == i) ?
119 " <selected>" : "")));
120 build_tree(i, ids[i], childmap, namemap);
123 uint64_t current;
124 std::map<uint64_t, std::string> names;
125 std::map<uint64_t, wxTreeItemId> ids;
126 std::set<uint64_t> with_children;
127 struct dispatch::target<> branchchange;
128 bool nosels;
131 class branch_select : public wxDialog
133 public:
134 branch_select(wxWindow* parent)
135 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Select new parent branch"))
137 Centre();
138 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
139 SetSizer(top_s);
140 Center();
142 top_s->Add(branches = new branches_tree(this, wxID_ANY, true), 1, wxGROW);
143 branches->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED,
144 wxCommandEventHandler(branch_select::on_change), NULL, this);
146 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
147 pbutton_s->AddStretchSpacer();
148 pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
149 pbutton_s->Add(cancelbutton = new wxButton(this, wxID_OK, wxT("Cancel")), 0, wxGROW);
150 okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
151 wxCommandEventHandler(branch_select::on_ok), NULL, this);
152 cancelbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
153 wxCommandEventHandler(branch_select::on_cancel), NULL, this);
154 top_s->Add(pbutton_s, 0, wxGROW);
156 top_s->SetSizeHints(this);
157 Fit();
159 wxCommandEvent e;
160 on_change(e);
162 void on_ok(wxCommandEvent& e)
164 EndModal(wxID_OK);
166 void on_cancel(wxCommandEvent& e)
168 EndModal(wxID_CANCEL);
170 uint64_t get_selection()
172 return branches->get_selection().id;
174 void on_change(wxCommandEvent& e)
176 okbutton->Enable(get_selection() != 0xFFFFFFFFFFFFFFFFULL);
178 private:
179 wxButton* okbutton;
180 wxButton* cancelbutton;
181 branches_tree* branches;
184 class branch_config : public wxDialog
186 public:
187 branch_config(wxWindow* parent)
188 : wxDialog(parent, wxID_ANY, towxstring("lsnes: Edit slot branches"))
190 Centre();
191 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
192 SetSizer(top_s);
193 Center();
195 top_s->Add(branches = new branches_tree(this, wxID_ANY, false), 1, wxGROW);
196 branches->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED,
197 wxCommandEventHandler(branch_config::on_change), NULL, this);
199 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
200 pbutton_s->Add(createbutton = new wxButton(this, wxID_OK, wxT("Create")), 0, wxGROW);
201 pbutton_s->Add(selectbutton = new wxButton(this, wxID_OK, wxT("Select")), 0, wxGROW);
202 pbutton_s->Add(renamebutton = new wxButton(this, wxID_OK, wxT("Rename")), 0, wxGROW);
203 pbutton_s->Add(reparentbutton = new wxButton(this, wxID_OK, wxT("Reparent")), 0, wxGROW);
204 pbutton_s->Add(deletebutton = new wxButton(this, wxID_OK, wxT("Delete")), 0, wxGROW);
205 pbutton_s->AddStretchSpacer();
206 pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
207 createbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
208 wxCommandEventHandler(branch_config::on_create), NULL, this);
209 selectbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
210 wxCommandEventHandler(branch_config::on_select), NULL, this);
211 renamebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
212 wxCommandEventHandler(branch_config::on_rename), NULL, this);
213 reparentbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
214 wxCommandEventHandler(branch_config::on_reparent), NULL, this);
215 deletebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
216 wxCommandEventHandler(branch_config::on_delete), NULL, this);
217 okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
218 wxCommandEventHandler(branch_config::on_close), NULL, this);
219 top_s->Add(pbutton_s, 0, wxGROW);
221 top_s->SetSizeHints(this);
222 Fit();
223 wxCommandEvent e;
224 on_change(e);
226 void on_create(wxCommandEvent& e)
228 uint64_t id = get_selected_id();
229 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
230 std::string newname;
231 try {
232 newname = pick_text(this, "Enter new branch name", "Enter name for new branch:",
233 newname, false);
234 } catch(canceled_exception& e) {
235 return;
237 runemufn([this, id, newname]() {
238 try {
239 auto p = project_get();
240 if(p) p->create_branch(id, newname);
241 } catch(std::exception& e) {
242 std::string err = e.what();
243 runuifun([this, err]() {
244 show_message_ok(this, "Error creating branch",
245 "Can't create branch: " + err, wxICON_EXCLAMATION);
249 branches->call_project_flush();
251 void on_select(wxCommandEvent& e)
253 uint64_t id = get_selected_id();
254 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
255 runemufn([this, id]() {
256 try {
257 auto p = project_get();
258 if(p) p->set_current_branch(id);
259 } catch(std::exception& e) {
260 std::string err = e.what();
261 runuifun([this, err]() {
262 show_message_ok(this, "Error setting branch",
263 "Can't set branch: " + err, wxICON_EXCLAMATION);
267 branches->call_project_flush();
268 update_movie_state();
270 void on_rename(wxCommandEvent& e)
272 uint64_t id = get_selected_id();
273 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
274 std::string newname = branches->get_name(id);
275 try {
276 newname = pick_text(this, "Enter new branch name", "Rename this branch to:",
277 newname, false);
278 } catch(canceled_exception& e) {
279 return;
281 runemufn([this, id, newname]() {
282 try {
283 auto p = project_get();
284 if(p) p->set_branch_name(id, newname);
285 } catch(std::exception& e) {
286 std::string err = e.what();
287 runuifun([this, err]() {
288 show_message_ok(this, "Error renaming branch",
289 "Can't rename branch: " + err, wxICON_EXCLAMATION);
293 branches->call_project_flush();
294 update_movie_state();
296 void on_reparent(wxCommandEvent& e)
298 uint64_t id = get_selected_id();
299 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
300 uint64_t pid;
301 branch_select* bsel = new branch_select(this);
302 int r = bsel->ShowModal();
303 if(r != wxID_OK) {
304 bsel->Destroy();
305 return;
307 pid = bsel->get_selection();
308 if(pid == 0xFFFFFFFFFFFFFFFFULL) return;
309 bsel->Destroy();
310 runemufn([this, id, pid]() {
311 try {
312 auto p = project_get();
313 if(p) p->set_parent_branch(id, pid);
314 } catch(std::exception& e) {
315 std::string err = e.what();
316 runuifun([this, err]() {
317 show_message_ok(this, "Error reparenting branch",
318 "Can't reparent branch: " + err, wxICON_EXCLAMATION);
322 branches->call_project_flush();
323 update_movie_state();
325 void on_delete(wxCommandEvent& e)
327 uint64_t id = get_selected_id();
328 if(id == 0xFFFFFFFFFFFFFFFFULL) return;
329 runemufn([this, id]() {
330 try {
331 auto p = project_get();
332 if(p) p->delete_branch(id);
333 } catch(std::exception& e) {
334 std::string err = e.what();
335 runuifun([this, err]() {
336 show_message_ok(this, "Error deleting branch",
337 "Can't delete branch: " + err, wxICON_EXCLAMATION);
341 branches->call_project_flush();
343 void on_close(wxCommandEvent& e)
345 EndModal(wxID_OK);
347 void on_change(wxCommandEvent& e)
349 set_enabled(branches->get_selection());
351 private:
352 uint64_t get_selected_id()
354 return branches->get_selection().id;
356 void set_enabled(branches_tree::selection id)
358 createbutton->Enable(id.item.IsOk());
359 selectbutton->Enable(id.item.IsOk());
360 renamebutton->Enable(id.item.IsOk() && !id.isroot);
361 reparentbutton->Enable(id.item.IsOk() && !id.isroot);
362 deletebutton->Enable(id.item.IsOk() && !id.isroot && !id.haschildren && !id.iscurrent);
364 wxButton* createbutton;
365 wxButton* selectbutton;
366 wxButton* renamebutton;
367 wxButton* reparentbutton;
368 wxButton* deletebutton;
369 wxButton* okbutton;
370 branches_tree* branches;
373 void build_menus(wxMenu* root, uint64_t id, std::list<branches_menu::miteminfo>& otheritems,
374 std::list<wxMenu*>& menus, std::map<uint64_t, std::string>& namemap,
375 std::map<uint64_t, std::set<uint64_t>>& childmap, std::map<int, uint64_t>& branch_ids, int& nextid,
376 uint64_t curbranch)
378 auto& children = childmap[id];
379 int mid = nextid++;
380 otheritems.push_back(branches_menu::miteminfo(root->AppendCheckItem(mid, towxstring(namemap[id])),
381 false, root));
382 branch_ids[mid] = id;
383 root->FindItem(mid)->Check(id == curbranch);
384 if(!children.empty())
385 otheritems.push_back(branches_menu::miteminfo(root->AppendSeparator(), false, root));
386 for(auto i : children) {
387 bool has_children = !childmap[i].empty();
388 if(!has_children) {
389 //No children, just put the item there.
390 int mid2 = nextid++;
391 otheritems.push_back(branches_menu::miteminfo(root->AppendCheckItem(mid2,
392 towxstring(namemap[i])), false, root));
393 branch_ids[mid2] = i;
394 root->FindItem(mid2)->Check(i == curbranch);
395 } else {
396 //Has children. Make a menu.
397 wxMenu* m = new wxMenu();
398 otheritems.push_back(branches_menu::miteminfo(root->AppendSubMenu(m,
399 towxstring(namemap[i])), true, root));
400 menus.push_back(m);
401 build_menus(m, i, otheritems, menus, namemap, childmap, branch_ids, nextid,
402 curbranch);
408 branches_menu::branches_menu(wxWindow* win, int wxid_low, int wxid_high)
410 pwin = win;
411 wxid_range_low = wxid_low;
412 wxid_range_high = wxid_high;
413 win->Connect(wxid_low, wxid_high, wxEVT_COMMAND_MENU_SELECTED,
414 wxCommandEventHandler(branches_menu::on_select), NULL, this);
415 branchchange.set(notify_branch_change, [this]() { runuifun([this]() { this->update(); }); });
418 branches_menu::~branches_menu()
422 void branches_menu::on_select(wxCommandEvent& e)
424 int id = e.GetId();
425 if(id < wxid_range_low || id > wxid_range_high) return;
426 if(id == wxid_range_low) {
427 //Configure.
428 branch_config* bcfg = new branch_config(pwin);
429 bcfg->ShowModal();
430 bcfg->Destroy();
431 return;
433 if(!branch_ids.count(id)) return;
434 uint64_t bid = branch_ids[id];
435 std::string err;
436 runemufn_async([this, bid]() {
437 auto p = project_get();
438 try {
439 if(p) p->set_current_branch(bid);
440 } catch(std::exception& e) {
441 std::string err = e.what();
442 runuifun([this, err]() {
443 show_message_ok(this->pwin, "Error changing branch", "Can't change branch: " +
444 err, wxICON_EXCLAMATION);
447 if(p) p->flush();
448 update_movie_state();
452 void branches_menu::update()
454 std::map<uint64_t, std::string> namemap;
455 std::map<uint64_t, std::set<uint64_t>> childmap;
456 runemufn([&namemap, &childmap]() {
457 auto p = project_get();
458 if(!p) return;
459 fill_namemap(*p, 0, namemap, childmap);
461 //First destroy everything that isn't a menu.
462 for(auto i : otheritems)
463 i.parent->Delete(i.item);
464 //Then kill all menus.
465 for(auto i : menus)
466 delete i;
467 otheritems.clear();
468 menus.clear();
469 branch_ids.clear();
470 if(namemap.empty()) {
471 if(disabler_fn) disabler_fn(false);
472 return;
474 //Okay, cleared. Rebuild things.
475 otheritems.push_back(miteminfo(Append(wxid_range_low, towxstring("Edit branches")), false, this));
476 otheritems.push_back(miteminfo(AppendSeparator(), false, this));
477 int ass_id = wxid_range_low + 1;
478 build_menus(this, 0, otheritems, menus, namemap, childmap, branch_ids, ass_id,
479 project_get()->get_current_branch());
480 if(disabler_fn) disabler_fn(true);
483 bool branches_menu::any_enabled()
485 return project_get();