Slot branches

This commit is contained in:
Ilari Liusvaara 2014-01-18 11:50:19 +02:00
parent 4bb75bc30e
commit 91c765a0a7
19 changed files with 1170 additions and 163 deletions

View file

@ -266,6 +266,7 @@ extern struct dispatch::source<bool> notify_sound_unmute;
extern struct dispatch::source<bool> notify_mode_change;
extern struct dispatch::source<> notify_core_change;
extern struct dispatch::source<> notify_title_change;
extern struct dispatch::source<> notify_branch_change;
extern struct dispatch::source<bool> notify_core_changed;
extern struct dispatch::source<> notify_new_core;
extern struct dispatch::source<> notify_voice_stream_change;

View file

@ -31,7 +31,7 @@ void do_save_movie(const std::string& filename, int binary) throw(std::bad_alloc
void do_load_beginning(bool reloading = false) throw(std::bad_alloc, std::runtime_error);
void do_load_state(struct moviefile& _movie, int lmode);
bool do_load_state(const std::string& filename, int lmode);
std::string translate_name_mprefix(std::string original, int& binary, bool save);
std::string translate_name_mprefix(std::string original, int& binary, int save);
extern std::string last_save;
extern movie_logic movb;

View file

@ -9,6 +9,15 @@
#include "core/controller.hpp"
#include "core/rom.hpp"
//A branch.
struct project_branch_info
{
//Parent branch ID.
uint64_t pbid;
//Name.
std::string name;
};
//Information about project.
struct project_info
{
@ -32,6 +41,10 @@ struct project_info
std::map<std::string, std::string> watches;
//Macros.
std::map<std::string, JSON::node> macros;
//Branches.
std::map<uint64_t, project_branch_info> branches;
uint64_t active_branch;
uint64_t next_branch;
//Stub movie data.
std::string gametype;
std::map<std::string, std::string> settings;
@ -46,6 +59,89 @@ struct project_info
std::vector<char> anchor_savestate;
int64_t movie_rtc_second;
int64_t movie_rtc_subsecond;
/**
* Obtain parent of branch.
*
* Parameter bid: The branch ID.
* Returns: The parent branch ID.
* Throws std::runtime_error: Invalid branch ID.
*
* Note: bid 0 is root. Calling this on root always gives 0.
*/
uint64_t get_parent_branch(uint64_t bid);
/**
* Get current branch id.
*
* Returns: The branch id.
*/
uint64_t get_current_branch() { return active_branch; }
/**
* Set current branch.
*
* Parameter bid: The branch id.
* Throws std::runtime_error: Invalid branch ID.
*/
void set_current_branch(uint64_t bid);
/**
* Get name of branch.
*
* Parameter bid: The branch id.
* Throws std::runtime_error: Invalid branch ID.
* Note: The name of ROOT branch is always empty string.
*/
const std::string& get_branch_name(uint64_t bid);
/**
* Set name of branch.
*
* Parameter bid: The branch id.
* Parameter name: The new name
* Throws std::runtime_error: Invalid branch ID.
* Note: The name of ROOT branch can't be changed.
*/
void set_branch_name(uint64_t bid, const std::string& name);
/**
* Set parent branch of branch.
*
* Parameter bid: The branch id.
* Parameter pbid: The new parent branch id.
* Throws std::runtime_error: Invalid branch ID, or cyclic dependency.
* Note: The parent of ROOT branch can't be set.
*/
void set_parent_branch(uint64_t bid, uint64_t pbid);
/**
* Enumerate child branches of specified branch.
*
* Parameter bid: The branch id.
* Returns: The set of chilid branch IDs.
* Throws std::runtime_error: Invalid branch ID.
*/
std::set<uint64_t> branch_children(uint64_t bid);
/**
* Create a new branch.
*
* Parameter pbid: Parent of the new branch.
* Parameter name: Name of new branch.
* Returns: Id of new branch.
* Throws std::runtime_error: Invalid branch ID.
*/
uint64_t create_branch(uint64_t pbid, const std::string& name);
/**
* Delete a branch.
*
* Parameter bid: The branch id.
* Throws std::runtime_error: Invalid branch ID or branch has children.
*/
void delete_branch(uint64_t bid);
/**
* Get name of current branch as string.
*/
std::string get_branch_string();
/**
* Flush the project to disk.
*/
void flush();
private:
void write(std::ostream& s);
};
/**
@ -74,12 +170,6 @@ std::map<std::string, std::string> project_enumerate();
* Returns: The project information.
*/
project_info& project_load(const std::string& id);
/**
* Flush any changes to project to disk.
*
* Parameter: The project to flush (NULL is no-op).
*/
void project_flush(project_info* p);
/**
* Get project movie path.
*

View file

@ -0,0 +1,41 @@
#ifndef _plat_wxwidgets__menu_branches__hpp__included__
#define _plat_wxwidgets__menu_branches__hpp__included__
#include "core/dispatch.hpp"
#include <wx/string.h>
#include <wx/wx.h>
#include <map>
#include <set>
#include <vector>
class branches_menu : public wxMenu
{
public:
branches_menu(wxWindow* win, int wxid_low, int wxid_high);
~branches_menu();
void on_select(wxCommandEvent& e);
void update();
bool any_enabled();
struct miteminfo
{
miteminfo(wxMenuItem* it, bool ismenu, wxMenu* p)
: item(it), is_menu(ismenu), parent(p)
{
}
wxMenuItem* item;
bool is_menu;
wxMenu* parent;
};
void set_disabler(std::function<void(bool enabled)> fn) { disabler_fn = fn; }
private:
struct dispatch::target<> branchchange;
wxWindow* pwin;
int wxid_range_low;
int wxid_range_high;
std::map<int, uint64_t> branch_ids;
std::list<wxMenu*> menus;
std::list<miteminfo> otheritems;
std::function<void(bool enabled)> disabler_fn;
};
#endif

View file

@ -16,12 +16,14 @@ public:
void on_select(wxCommandEvent& e);
void update();
bool any_enabled();
void set_disabler(std::function<void(bool enabled)> fn) { disabler_fn = fn; }
private:
struct dispatch::target<> corechange;
wxWindow* pwin;
int wxid_range_low;
int wxid_range_high;
std::vector<wxMenuItem*> items;
std::function<void(bool enabled)> disabler_fn;
};
#endif

View file

@ -38,7 +38,7 @@ public:
void on_close(wxCloseEvent& e);
void menu_start(wxString name);
void menu_special(wxString name, wxMenu* menu);
void menu_special_sub(wxString name, wxMenu* menu);
wxMenuItem* menu_special_sub(wxString name, wxMenu* menu);
void menu_entry(int id, wxString name);
void menu_entry_check(int id, wxString name);
void menu_start_sub(wxString name);

View file

@ -812,6 +812,62 @@ save-jukebox
Do state save to jukebox.
\end_layout
\begin_layout Subsection
Slot branches
\end_layout
\begin_layout Subsubsection
list-branches
\end_layout
\begin_layout Standard
List all branches
\end_layout
\begin_layout Subsubsection
create-branch <pid> <name>
\end_layout
\begin_layout Standard
Create a new branch, with <pid> as parent and <name> as name.
\end_layout
\begin_layout Subsubsection
rename-branch <id> <name>
\end_layout
\begin_layout Standard
Rename branch <id> to <name>.
\end_layout
\begin_layout Subsubsection
reparent-branch <id> <pid>
\end_layout
\begin_layout Standard
Set parent of branch <id> to <pid>.
\end_layout
\begin_layout Subsubsection
set-branch <id>
\end_layout
\begin_layout Standard
Set current branch to <id>.
\end_layout
\begin_layout Subsubsection
delete-branch <id>
\end_layout
\begin_layout Standard
Delete branch <id>.
\end_layout
\begin_layout Standard
\end_layout
\begin_layout Subsection
Lua
\end_layout
@ -848,26 +904,6 @@ reset-lua
Clear the Lua VM state and restore to factory defaults.
\end_layout
\begin_layout Subsection
Memory watch
\end_layout
\begin_layout Subsubsection
add-watch <name> <expression>
\end_layout
\begin_layout Standard
Adds new watch (or modifies old one).
\end_layout
\begin_layout Subsubsection
remove-watch <name>
\end_layout
\begin_layout Standard
Remove a watch.
\end_layout
\begin_layout Subsection
Sound
\end_layout

View file

@ -387,34 +387,52 @@ Do load from jukebox (current mode).
Do state save to jukebox.
5.5 Lua
5.5 Slot branches
5.5.1 evaluate-lua <luacode>
5.5.1 list-branches
List all branches
5.5.2 create-branch <pid> <name>
Create a new branch, with <pid> as parent and <name> as name.
5.5.3 rename-branch <id> <name>
Rename branch <id> to <name>.
5.5.4 reparent-branch <id> <pid>
Set parent of branch <id> to <pid>.
5.5.5 set-branch <id>
Set current branch to <id>.
5.5.6 delete-branch <id>
Delete branch <id>.
5.6 Lua
5.6.1 evaluate-lua <luacode>
Run Lua code <luacode> using built-in Lua interpretter.
5.5.2 L <luacode>
5.6.2 L <luacode>
Synonym for evaluate-lua.
5.5.3 run-lua <script>
5.6.3 run-lua <script>
Run specified lua file using built-in Lua interpretter.
5.5.4 reset-lua
5.6.4 reset-lua
Clear the Lua VM state and restore to factory defaults.
5.6 Memory watch
5.6.1 add-watch <name> <expression>
Adds new watch (or modifies old one).
5.6.2 remove-watch <name>
Remove a watch.
5.7 Sound
5.7.1 enable-sound <on/off>

View file

@ -230,7 +230,7 @@ void controller_state::erase_macro(const std::string& macro)
project_info* p = project_get();
if(p) {
p->macros.erase(macro);
project_flush(p);
p->flush();
}
}
load_macros(*this);
@ -270,7 +270,7 @@ void controller_state::set_macro(const std::string& macro, const controller_macr
project_info* p = project_get();
if(p) {
p->macros[macro] = all_macros[macro].serialize();
project_flush(p);
p->flush();
}
}
load_macros(*this);

View file

@ -341,6 +341,7 @@ void dispatch_set_error_streams(std::ostream* stream)
notify_core_changed.errors_to(stream);
notify_multitrack_change.errors_to(stream);
notify_title_change.errors_to(stream);
notify_branch_change.errors_to(stream);
}
struct dispatch::source<> notify_autohold_reconfigure("autohold_reconfigure");
@ -361,3 +362,4 @@ struct dispatch::source<> notify_vu_change("vu_change");
struct dispatch::source<> notify_subtitle_change("subtitle_change");
struct dispatch::source<unsigned, unsigned, int> notify_multitrack_change("multitrack_change");
struct dispatch::source<> notify_title_change("title_change");
struct dispatch::source<> notify_branch_change("branch_change");

View file

@ -281,13 +281,13 @@ namespace
if(smode == SAVE_MOVIE) {
//Just do this immediately.
do_save_movie(filename, binary);
flush_slotinfo(translate_name_mprefix(filename, tmp, false));
flush_slotinfo(translate_name_mprefix(filename, tmp, -1));
return;
}
if(location_special == SPECIAL_SAVEPOINT) {
//We can save immediately here.
do_save_state(filename, binary);
flush_slotinfo(translate_name_mprefix(filename, tmp, false));
flush_slotinfo(translate_name_mprefix(filename, tmp, -1));
return;
}
queued_saves.insert(std::make_pair(filename, binary));
@ -311,6 +311,7 @@ namespace
void update_movie_state()
{
auto p = project_get();
bool readonly = false;
static unsigned last_controllers = 0;
{
@ -352,13 +353,18 @@ void update_movie_state()
}
if(jukebox_size > 0) {
int tmp = -1;
std::string sfilen = translate_name_mprefix(save_jukebox_name(save_jukebox_pointer), tmp, false);
std::string sfilen = translate_name_mprefix(save_jukebox_name(save_jukebox_pointer), tmp, -1);
_status.set("!saveslot", (stringfmt() << (save_jukebox_pointer + 1)).str());
_status.set("!saveslotinfo", get_slotinfo(sfilen));
} else {
_status.erase("!saveslot");
_status.erase("!saveslotinfo");
}
if(p) {
_status.set("!branch", p->get_branch_string());
} else {
_status.erase("!branch");
}
_status.set("!speed", (stringfmt() << (unsigned)(100 * get_realized_multiplier() + 0.5)).str());
if(!system_corrupt) {
@ -1105,7 +1111,7 @@ nothing_to_do:
for(auto i : queued_saves) {
do_save_state(i.first, i.second);
int tmp = -1;
flush_slotinfo(translate_name_mprefix(i.first, tmp, false));
flush_slotinfo(translate_name_mprefix(i.first, tmp, -1));
}
if(do_unsafe_rewind && !unsafe_rewind_obj) {
uint64_t t = get_utime();

View file

@ -313,7 +313,7 @@ void lsnes_memorywatch_set::clear(const std::string& name)
auto pr = project_get();
if(pr) {
pr->watches.erase(name);
project_flush(pr);
pr->flush();
}
redraw_framebuffer();
}
@ -377,7 +377,7 @@ void lsnes_memorywatch_set::set(const std::string& name, lsnes_memorywatch_item&
auto pr = project_get();
if(pr) {
pr->watches[name] = get_string(name);
project_flush(pr);
pr->flush();
}
redraw_framebuffer();
update_movie_state();
@ -399,7 +399,7 @@ void lsnes_memorywatch_set::set_multi(std::list<std::pair<std::string, lsnes_mem
if(pr) {
for(auto& i : list)
pr->watches[i.first] = get_string(i.first);
project_flush(pr);
pr->flush();
}
redraw_framebuffer();
update_movie_state();
@ -427,7 +427,7 @@ void lsnes_memorywatch_set::clear_multi(const std::set<std::string>& names)
if(pr) {
for(auto& i : names)
pr->watches.erase(i);
project_flush(pr);
pr->flush();
}
redraw_framebuffer();
update_movie_state();

View file

@ -138,16 +138,28 @@ void set_mprefix_for_project(const std::string& pfx)
set_mprefix(pfx);
}
std::string translate_name_mprefix(std::string original, int& binary, bool save)
std::string translate_name_mprefix(std::string original, int& binary, int save)
{
auto p = project_get();
regex_results r = regex("\\$SLOT:(.*)", original);
if(r) {
if(binary < 0)
binary = jukebox_dflt_binary ? 1 : 0;
if(p)
return p->directory + "/" + p->prefix + "-" + r[1] + ".lss";
else {
if(p) {
uint64_t branch = p->get_current_branch();
std::string branch_str;
std::string filename;
if(branch) branch_str = (stringfmt() << "--" << branch).str();
filename = p->directory + "/" + p->prefix + "-" + r[1] + branch_str + ".lss";
while(save < 0 && branch) {
if(zip::file_exists(filename))
break;
branch = p->get_parent_branch(branch);
branch_str = branch ? ((stringfmt() << "--" << branch).str()) : "";
filename = p->directory + "/" + p->prefix + "-" + r[1] + branch_str + ".lss";
}
return filename;
} else {
std::string pprf = lsnes_vset["slotpath"].str() + "/";
return pprf + get_mprefix() + r[1] + ".lsmv";
}
@ -195,7 +207,7 @@ void do_save_state(const std::string& filename, int binary) throw(std::bad_alloc
messages << "Can't save movie without a ROM" << std::endl;
return;
}
std::string filename2 = translate_name_mprefix(filename, binary, true);
std::string filename2 = translate_name_mprefix(filename, binary, 1);
lua_callback_pre_save(filename2, true);
try {
uint64_t origtime = get_utime();
@ -235,7 +247,7 @@ void do_save_state(const std::string& filename, int binary) throw(std::bad_alloc
auto p = project_get();
if(p) {
p->last_save = last_save;
project_flush(p);
p->flush();
}
}
@ -247,7 +259,7 @@ void do_save_movie(const std::string& filename, int binary) throw(std::bad_alloc
messages << "Can't save movie without a ROM" << std::endl;
return;
}
std::string filename2 = translate_name_mprefix(filename, binary, false);
std::string filename2 = translate_name_mprefix(filename, binary, 0);
lua_callback_pre_save(filename2, false);
try {
uint64_t origtime = get_utime();
@ -276,7 +288,7 @@ void do_save_movie(const std::string& filename, int binary) throw(std::bad_alloc
auto p = project_get();
if(p) {
p->last_save = last_save;
project_flush(p);
p->flush();
}
}
@ -614,7 +626,7 @@ void try_request_rom(const std::string& moviefile)
bool do_load_state(const std::string& filename, int lmode)
{
int tmp = -1;
std::string filename2 = translate_name_mprefix(filename, tmp, false);
std::string filename2 = translate_name_mprefix(filename, tmp, -1);
uint64_t origtime = get_utime();
lua_callback_pre_load(filename2);
struct moviefile mfile;

View file

@ -11,6 +11,7 @@
#include "core/settings.hpp"
#include "core/window.hpp"
#include "library/directory.hpp"
#include "library/minmax.hpp"
#include "library/string.hpp"
#include <fstream>
#include <dirent.h>
@ -140,66 +141,6 @@ namespace
}
}
void project_write(std::ostream& s, project_info& p)
{
s << p.name << std::endl;
s << "rom=" << p.rom << std::endl;
if(p.last_save != "")
s << "last-save=" << p.last_save << std::endl;
s << "directory=" << p.directory << std::endl;
s << "prefix=" << p.prefix << std::endl;
for(auto i : p.luascripts)
s << "luascript=" << i << std::endl;
s << "gametype=" << p.gametype << std::endl;
s << "coreversion=" << p.coreversion << std::endl;
if(p.gamename != "")
s << "gamename=" << p.gamename << std::endl;
s << "projectid=" << p.projectid << std::endl;
s << "time=" << p.movie_rtc_second << ":" << p.movie_rtc_subsecond << std::endl;
for(auto i : p.authors)
s << "author=" << i.first << "|" << i.second << std::endl;
for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
if(p.romimg_sha256[i] != "") {
if(i)
s << "slotsha" << static_cast<char>(96 + i) << "=" << p.romimg_sha256[i]
<< std::endl;
else
s << "romsha=" << p.romimg_sha256[i] << std::endl;
}
if(p.romxml_sha256[i] != "") {
if(i)
s << "slotxml" << static_cast<char>(96 + i) << "=" << p.romxml_sha256[i]
<< std::endl;
else
s << "romxml=" << p.romxml_sha256[i] << std::endl;
}
if(p.namehint[i] != "") {
if(i)
s << "slothint" << static_cast<char>(96 + i) << "=" << p.namehint[i]
<< std::endl;
else
s << "romhint=" << p.namehint[i] << std::endl;
}
if(p.roms[i] != "") {
if(i)
s << "slotrom" << static_cast<char>(96 + i) << "=" << p.roms[i]
<< std::endl;
else
s << "romrom=" << p.roms[i] << std::endl;
}
}
for(auto i : p.settings)
s << "setting." << i.first << "=" << i.second << std::endl;
for(auto i : p.watches)
s << "watch." << eq_escape(i.first) << "=" << i.second << std::endl;
for(auto i : p.macros)
s << "macro." + i.first << "=" << i.second.serialize() << std::endl;
for(auto i : p.movie_sram)
save_binary(s, "sram." + i.first, i.second);
if(p.anchor_savestate.size())
save_binary(s, "anchor", p.anchor_savestate);
}
void fill_stub_movie(struct moviefile& m, struct project_info& p, struct core_type& coretype)
{
//Create a dummy movie.
@ -255,6 +196,10 @@ project_info& project_load(const std::string& id)
throw std::runtime_error("Can't open project file");
project_info& pi = *new project_info();
pi.id = id;
pi.movie_rtc_second = 1000000000;
pi.movie_rtc_subsecond = 0;
pi.active_branch = 0;
pi.next_branch = 0;
//First line is always project name.
std::getline(f, pi.name);
if(!f || pi.name == "") {
@ -323,47 +268,45 @@ project_info& project_load(const std::string& id)
else if(r = regex("time=([0-9]+):([0-9]+)", tmp)) {
pi.movie_rtc_second = parse_value<int64_t>(r[1]);
pi.movie_rtc_subsecond = parse_value<int64_t>(r[2]);
} else if(r = regex("branch([1-9][0-9]*)parent=([0-9]+)", tmp)) {
uint64_t bid = parse_value<int64_t>(r[1]);
uint64_t pbid = parse_value<int64_t>(r[2]);
if(!pi.branches.count(bid))
pi.branches[bid].name = "(Unnamed branch)";
pi.branches[bid].pbid = pbid;
} else if(r = regex("branch([1-9][0-9]*)name=(.*)", tmp)) {
uint64_t bid = parse_value<int64_t>(r[1]);
if(!pi.branches.count(bid))
pi.branches[bid].pbid = 0;
pi.branches[bid].name = r[2];
} else if(r = regex("branchcurrent=([0-9]+)", tmp)) {
pi.active_branch = parse_value<int64_t>(r[1]);
} else if(r = regex("branchnext=([0-9]+)", tmp)) {
pi.next_branch = parse_value<int64_t>(r[1]);
}
}
for(auto& i : pi.branches) {
uint64_t j = i.first;
uint64_t m = j;
while(j) {
j = pi.branches[j].pbid;
m = min(m, j);
if(j == i.first) {
//Cyclic dependency!
messages << "Warning: Cyclic slot branch dependency, reparenting '" <<
pi.branches[m].name << "' to be child of root..." << std::endl;
pi.branches[j].pbid = 0;
break;
}
}
}
if(pi.active_branch && !pi.branches.count(pi.active_branch)) {
messages << "Warning: Current slot branch does not exist, using root..." << std::endl;
pi.active_branch = 0;
}
return pi;
}
void project_flush(project_info* p)
{
if(!p)
return;
std::string file = get_config_path() + "/" + p->id + ".prj";
std::string tmpfile = get_config_path() + "/" + p->id + ".prj.tmp";
std::string bakfile = get_config_path() + "/" + p->id + ".prj.bak";
std::ofstream f(tmpfile);
if(!f)
throw std::runtime_error("Can't write project file");
project_write(f, *p);
if(!f)
throw std::runtime_error("Can't write project file");
f.close();
std::ifstream f2(file);
if(f2) {
std::ofstream f3(bakfile);
if(!f3)
throw std::runtime_error("Can't backup project file");
while(f2) {
std::string tmp;
std::getline(f2, tmp);
f3 << tmp << std::endl;
}
f2.close();
f3.close();
}
#if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
if(MoveFileEx(tmpfile.c_str(), file.c_str(), MOVEFILE_REPLACE_EXISTING) < 0)
#else
if(rename(tmpfile.c_str(), file.c_str()) < 0)
#endif
throw std::runtime_error("Can't replace project file");
}
project_info* project_get()
{
return active_project;
@ -376,6 +319,7 @@ bool project_set(project_info* p, bool current)
voicesub_unload_collection();
active_project = p;
notify_core_change();
notify_branch_change();
return true;
}
@ -445,6 +389,7 @@ skip_rom_movie:
do_flush_slotinfo();
update_movie_state();
notify_core_change();
notify_branch_change();
}
return switched;
}
@ -512,3 +457,351 @@ void project_copy_macros(project_info& p, controller_state& s)
for(auto i : s.enumerate_macro())
p.macros[i] = s.get_macro(i).serialize();
}
uint64_t project_info::get_parent_branch(uint64_t bid)
{
if(!bid)
return 0;
if(!branches.count(bid))
throw std::runtime_error("Invalid branch ID");
return branches[bid].pbid;
}
void project_info::set_current_branch(uint64_t bid)
{
if(bid && !branches.count(bid))
throw std::runtime_error("Invalid branch ID");
active_branch = bid;
notify_branch_change();
messages << "Set current slot branch to " << get_branch_string() << std::endl;
}
const std::string& project_info::get_branch_name(uint64_t bid)
{
static std::string rootname = "(root)";
if(!bid)
return rootname;
if(!branches.count(bid))
throw std::runtime_error("Invalid branch ID");
return branches[bid].name;
}
void project_info::set_branch_name(uint64_t bid, const std::string& name)
{
if(!bid)
throw std::runtime_error("Root branch name can't be set");
if(!branches.count(bid))
throw std::runtime_error("Invalid branch ID");
branches[bid].name = name;
notify_branch_change();
}
void project_info::set_parent_branch(uint64_t bid, uint64_t pbid)
{
if(!bid)
throw std::runtime_error("Root branch never has parent");
if(!branches.count(bid))
throw std::runtime_error("Invalid branch ID");
if(pbid && !branches.count(pbid))
throw std::runtime_error("Invalid parent branch ID");
for(auto& i : branches) {
uint64_t j = i.first;
while(j) {
j = (j == bid) ? pbid : branches[j].pbid;
if(j == i.first)
throw std::runtime_error("Reparenting would create a circular dependency");
}
}
branches[bid].pbid = pbid;
notify_branch_change();
}
std::set<uint64_t> project_info::branch_children(uint64_t bid)
{
if(bid && !branches.count(bid))
throw std::runtime_error("Invalid branch ID");
std::set<uint64_t> r;
for(auto& i : branches)
if(i.second.pbid == bid)
r.insert(i.first);
return r;
}
uint64_t project_info::create_branch(uint64_t pbid, const std::string& name)
{
if(pbid && !branches.count(pbid))
throw std::runtime_error("Invalid parent branch ID");
uint64_t assign_bid = next_branch;
uint64_t last_bid = (branches.empty() ? 1 : branches.rbegin()->first + 1);
assign_bid = max(assign_bid, last_bid);
branches[assign_bid].name = name;
branches[assign_bid].pbid = pbid;
next_branch = assign_bid + 1;
notify_branch_change();
return assign_bid;
}
void project_info::delete_branch(uint64_t bid)
{
if(!bid)
throw std::runtime_error("Root branch can not be deleted");
if(bid == active_branch)
throw std::runtime_error("Current branch can't be deleted");
if(!branches.count(bid))
throw std::runtime_error("Invalid branch ID");
for(auto& i : branches)
if(i.second.pbid == bid)
throw std::runtime_error("Can't delete branch with children");
branches.erase(bid);
notify_branch_change();
}
std::string project_info::get_branch_string()
{
std::string r;
uint64_t j = active_branch;
if(!j)
return "(root)";
while(j) {
if(r == "")
r = get_branch_name(j);
else
r = get_branch_name(j) + "" + r;
j = get_parent_branch(j);
}
return r;
}
void project_info::flush()
{
std::string file = get_config_path() + "/" + id + ".prj";
std::string tmpfile = get_config_path() + "/" + id + ".prj.tmp";
std::string bakfile = get_config_path() + "/" + id + ".prj.bak";
std::ofstream f(tmpfile);
if(!f)
throw std::runtime_error("Can't write project file");
write(f);
if(!f)
throw std::runtime_error("Can't write project file");
f.close();
std::ifstream f2(file);
if(f2) {
std::ofstream f3(bakfile);
if(!f3)
throw std::runtime_error("Can't backup project file");
while(f2) {
std::string tmp;
std::getline(f2, tmp);
f3 << tmp << std::endl;
}
f2.close();
f3.close();
}
#if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
if(MoveFileEx(tmpfile.c_str(), file.c_str(), MOVEFILE_REPLACE_EXISTING) < 0)
#else
if(rename(tmpfile.c_str(), file.c_str()) < 0)
#endif
throw std::runtime_error("Can't replace project file");
}
void project_info::write(std::ostream& s)
{
s << name << std::endl;
s << "rom=" << rom << std::endl;
if(last_save != "")
s << "last-save=" << last_save << std::endl;
s << "directory=" << directory << std::endl;
s << "prefix=" << prefix << std::endl;
for(auto i : luascripts)
s << "luascript=" << i << std::endl;
s << "gametype=" << gametype << std::endl;
s << "coreversion=" << coreversion << std::endl;
if(gamename != "")
s << "gamename=" << gamename << std::endl;
s << "projectid=" << projectid << std::endl;
s << "time=" << movie_rtc_second << ":" << movie_rtc_subsecond << std::endl;
for(auto i : authors)
s << "author=" << i.first << "|" << i.second << std::endl;
for(unsigned i = 0; i < ROM_SLOT_COUNT; i++) {
if(romimg_sha256[i] != "") {
if(i)
s << "slotsha" << static_cast<char>(96 + i) << "=" << romimg_sha256[i] << std::endl;
else
s << "romsha=" << romimg_sha256[i] << std::endl;
}
if(romxml_sha256[i] != "") {
if(i)
s << "slotxml" << static_cast<char>(96 + i) << "=" << romxml_sha256[i] << std::endl;
else
s << "romxml=" << romxml_sha256[i] << std::endl;
}
if(namehint[i] != "") {
if(i)
s << "slothint" << static_cast<char>(96 + i) << "=" << namehint[i] << std::endl;
else
s << "romhint=" << namehint[i] << std::endl;
}
if(roms[i] != "") {
if(i)
s << "slotrom" << static_cast<char>(96 + i) << "=" << roms[i] << std::endl;
else
s << "romrom=" << roms[i] << std::endl;
}
}
for(auto i : settings)
s << "setting." << i.first << "=" << i.second << std::endl;
for(auto i : watches)
s << "watch." << eq_escape(i.first) << "=" << i.second << std::endl;
for(auto i : macros)
s << "macro." + i.first << "=" << i.second.serialize() << std::endl;
for(auto i : movie_sram)
save_binary(s, "sram." + i.first, i.second);
if(anchor_savestate.size())
save_binary(s, "anchor", anchor_savestate);
for(auto& i : branches) {
s << "branch" << i.first << "parent=" << i.second.pbid << std::endl;
s << "branch" << i.first << "name=" << i.second.name << std::endl;
}
s << "branchcurrent=" << active_branch << std::endl;
s << "branchnext=" << next_branch << std::endl;
}
namespace
{
void recursive_list_branch(uint64_t bid, std::set<unsigned>& dset, unsigned depth, bool last_of)
{
if(!active_project) {
messages << "Not in project context." << std::endl;
return;
}
std::set<uint64_t> children = active_project->branch_children(bid);
std::string prefix;
for(unsigned i = 0; i + 1 < depth; i++)
prefix += (dset.count(i) ? "\u2502" : " ");
prefix += (dset.count(depth - 1) ? (last_of ? "\u2514" : "\u251c") : " ");
if(last_of) dset.erase(depth - 1);
messages << prefix
<< ((bid == active_project->get_current_branch()) ? "*" : "")
<< bid << ":" << active_project->get_branch_name(bid) << std::endl;
dset.insert(depth);
size_t c = 0;
for(auto i : children) {
bool last = (++c == children.size());
recursive_list_branch(i, dset, depth + 1, last);
}
dset.erase(depth);
}
command::fnptr<> list_branches(lsnes_cmd, "list-branches", "List all slot branches",
"Syntax: list-branches\nList all slot branches.\n",
[]() throw(std::bad_alloc, std::runtime_error) {
std::set<unsigned> dset;
recursive_list_branch(0, dset, 0, false);
});
command::fnptr<const std::string&> create_branch(lsnes_cmd, "create-branch", "Create a new slot branch",
"Syntax: create-branch <parentid> <name>\nCreate new branch named <name> under <parentid>.\n",
[](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
regex_results r = regex("([0-9]+)[ \t]+(.*)", args);
if(!r) {
messages << "Syntax: create-branch <parentid> <name>" << std::endl;
return;
}
try {
uint64_t pbid = parse_value<uint64_t>(r[1]);
if(!active_project)
throw std::runtime_error("Not in project context");
uint64_t bid = active_project->create_branch(pbid, r[2]);
messages << "Created branch #" << bid << std::endl;
active_project->flush();
} catch(std::exception& e) {
messages << "Can't create new branch: " << e.what() << std::endl;
}
});
command::fnptr<const std::string&> delete_branch(lsnes_cmd, "delete-branch", "Delete a slot branch",
"Syntax: delete-branch <id>\nDelete slot branch with id <id>.\n",
[](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
regex_results r = regex("([0-9]+)[ \t]*", args);
if(!r) {
messages << "Syntax: delete-branch <id>" << std::endl;
return;
}
try {
uint64_t bid = parse_value<uint64_t>(r[1]);
if(!active_project)
throw std::runtime_error("Not in project context");
active_project->delete_branch(bid);
messages << "Deleted branch #" << bid << std::endl;
active_project->flush();
} catch(std::exception& e) {
messages << "Can't delete branch: " << e.what() << std::endl;
}
});
command::fnptr<const std::string&> set_branch(lsnes_cmd, "set-branch", "Set current slot branch",
"Syntax: set-branch <id>\nSet current branch to <id>.\n",
[](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
regex_results r = regex("([0-9]+)[ \t]*", args);
if(!r) {
messages << "Syntax: set-branch <id>" << std::endl;
return;
}
try {
uint64_t bid = parse_value<uint64_t>(r[1]);
if(!active_project)
throw std::runtime_error("Not in project context");
active_project->set_current_branch(bid);
messages << "Set current branch to #" << bid << std::endl;
active_project->flush();
update_movie_state();
} catch(std::exception& e) {
messages << "Can't set branch: " << e.what() << std::endl;
}
});
command::fnptr<const std::string&> reparent_branch(lsnes_cmd, "reparent-branch", "Reparent a slot branch",
"Syntax: reparent-branch <id> <newpid>\nReparent branch <id> to be child of <newpid>.\n",
[](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
regex_results r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]*", args);
if(!r) {
messages << "Syntax: reparent-branch <id> <newpid>" << std::endl;
return;
}
try {
uint64_t bid = parse_value<uint64_t>(r[1]);
uint64_t pbid = parse_value<uint64_t>(r[2]);
if(!active_project)
throw std::runtime_error("Not in project context");
active_project->set_parent_branch(bid, pbid);
messages << "Reparented branch #" << bid << std::endl;
active_project->flush();
update_movie_state();
} catch(std::exception& e) {
messages << "Can't reparent branch: " << e.what() << std::endl;
}
});
command::fnptr<const std::string&> rename_branch(lsnes_cmd, "rename-branch", "Rename a slot branch",
"Syntax: rename-branch <id> <name>\nRename branch <id> to <name>.\n",
[](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
regex_results r = regex("([0-9]+)[ \t]+(.*)", args);
if(!r) {
messages << "Syntax: rename-branch <id> <name>" << std::endl;
return;
}
try {
uint64_t bid = parse_value<uint64_t>(r[1]);
if(!active_project)
throw std::runtime_error("Not in project context");
active_project->set_branch_name(bid, r[2]);
messages << "Renamed branch #" << bid << std::endl;
active_project->flush();
update_movie_state();
} catch(std::exception& e) {
messages << "Can't rename branch: " << e.what() << std::endl;
}
});
}

View file

@ -0,0 +1,492 @@
#include "platform/wxwidgets/settings-common.hpp"
#include "platform/wxwidgets/settings-keyentry.hpp"
#include "platform/wxwidgets/menu_branches.hpp"
#include "platform/wxwidgets/platform.hpp"
#include "platform/wxwidgets/loadsave.hpp"
#include "core/debug.hpp"
#include "core/dispatch.hpp"
#include "core/project.hpp"
#include "core/moviedata.hpp"
void update_movie_state();
namespace
{
void fill_namemap(project_info& p, uint64_t id, std::map<uint64_t, std::string>& namemap,
std::map<uint64_t, std::set<uint64_t>>& childmap)
{
namemap[id] = p.get_branch_name(id);
auto s = p.branch_children(id);
for(auto i : s)
fill_namemap(p, i, namemap, childmap);
childmap[id] = s;
}
template<typename T> void runemufn_async(T fn)
{
platform::queue(functor_call_helper2<T>, new T(fn), false);
}
//Tree of branches.
class branches_tree : public wxTreeCtrl
{
public:
branches_tree(wxWindow* parent, int id, bool _nosels)
: wxTreeCtrl(parent, id), nosels(_nosels)
{
SetMinSize(wxSize(400, 300));
branchchange.set(notify_branch_change, [this]() { runuifun([this]() { this->update(); }); });
update();
}
struct selection
{
wxTreeItemId item;
bool isroot;
bool haschildren;
bool iscurrent;
uint64_t id;
std::string name;
};
selection get_selection()
{
selection s;
s.item = GetSelection();
for(auto i : ids) {
if(s.item.IsOk() && i.second == s.item) {
s.isroot = (i.first == 0);
s.haschildren = with_children.count(i.first);
s.id = i.first;
s.iscurrent = (i.first == current);
return s;
}
}
s.isroot = false;
s.haschildren = false;
s.iscurrent = false;
s.id = 0xFFFFFFFFFFFFFFFFULL;
return s;
}
void update()
{
std::map<uint64_t, std::string> namemap;
std::map<uint64_t, std::set<uint64_t>> childmap;
uint64_t cur = 0;
runemufn([&cur, &namemap, &childmap]() {
auto p = project_get();
if(!p) return;
fill_namemap(*p, 0, namemap, childmap);
cur = p->get_current_branch();
});
current = cur;
selection cursel = get_selection();
std::set<uint64_t> expanded;
for(auto i : ids)
if(IsExpanded(i.second))
expanded.insert(i.first);
DeleteAllItems();
ids.clear();
with_children.clear();
if(namemap.empty()) return;
//Create ROOT.
names = namemap;
ids[0] = AddRoot(towxstring(namemap[0] + ((!nosels && current == 0) ? " <selected>" : "")));
build_tree(0, ids[0], childmap, namemap);
for(auto i : expanded)
if(ids.count(i))
Expand(ids[i]);
for(auto i : ids) {
if(i.first == cursel.id) {
SelectItem(i.second);
}
}
}
std::string get_name(uint64_t id)
{
if(names.count(id))
return names[id];
return "";
}
void call_project_flush()
{
runemufn_async([] {
auto p = project_get();
if(p) p->flush();
});
}
private:
void build_tree(uint64_t id, wxTreeItemId parent, std::map<uint64_t, std::set<uint64_t>>& childmap,
std::map<uint64_t, std::string>& namemap)
{
if(!childmap.count(id) || childmap[id].empty())
return;
for(auto i : childmap[id]) {
ids[i] = AppendItem(ids[id], towxstring(namemap[i] + ((!nosels && current == i) ?
" <selected>" : "")));
build_tree(i, ids[i], childmap, namemap);
}
}
uint64_t current;
std::map<uint64_t, std::string> names;
std::map<uint64_t, wxTreeItemId> ids;
std::set<uint64_t> with_children;
struct dispatch::target<> branchchange;
bool nosels;
};
class branch_select : public wxDialog
{
public:
branch_select(wxWindow* parent)
: wxDialog(parent, wxID_ANY, towxstring("lsnes: Select new parent branch"))
{
Centre();
wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
SetSizer(top_s);
Center();
top_s->Add(branches = new branches_tree(this, wxID_ANY, true), 1, wxGROW);
branches->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED,
wxCommandEventHandler(branch_select::on_change), NULL, this);
wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
pbutton_s->AddStretchSpacer();
pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW);
pbutton_s->Add(cancelbutton = new wxButton(this, wxID_OK, wxT("Cancel")), 0, wxGROW);
okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(branch_select::on_ok), NULL, this);
cancelbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(branch_select::on_cancel), NULL, this);
top_s->Add(pbutton_s, 0, wxGROW);
top_s->SetSizeHints(this);
Fit();
wxCommandEvent e;
on_change(e);
}
void on_ok(wxCommandEvent& e)
{
EndModal(wxID_OK);
}
void on_cancel(wxCommandEvent& e)
{
EndModal(wxID_CANCEL);
}
uint64_t get_selection()
{
return branches->get_selection().id;
}
void on_change(wxCommandEvent& e)
{
okbutton->Enable(get_selection() != 0xFFFFFFFFFFFFFFFFULL);
}
private:
wxButton* okbutton;
wxButton* cancelbutton;
branches_tree* branches;
};
class branch_config : public wxDialog
{
public:
branch_config(wxWindow* parent)
: wxDialog(parent, wxID_ANY, towxstring("lsnes: Edit slot branches"))
{
Centre();
wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
SetSizer(top_s);
Center();
top_s->Add(branches = new branches_tree(this, wxID_ANY, false), 1, wxGROW);
branches->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED,
wxCommandEventHandler(branch_config::on_change), NULL, this);
wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
pbutton_s->Add(createbutton = new wxButton(this, wxID_OK, wxT("Create")), 0, wxGROW);
pbutton_s->Add(selectbutton = new wxButton(this, wxID_OK, wxT("Select")), 0, wxGROW);
pbutton_s->Add(renamebutton = new wxButton(this, wxID_OK, wxT("Rename")), 0, wxGROW);
pbutton_s->Add(reparentbutton = new wxButton(this, wxID_OK, wxT("Reparent")), 0, wxGROW);
pbutton_s->Add(deletebutton = new wxButton(this, wxID_OK, wxT("Delete")), 0, wxGROW);
pbutton_s->AddStretchSpacer();
pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
createbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(branch_config::on_create), NULL, this);
selectbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(branch_config::on_select), NULL, this);
renamebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(branch_config::on_rename), NULL, this);
reparentbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(branch_config::on_reparent), NULL, this);
deletebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(branch_config::on_delete), NULL, this);
okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(branch_config::on_close), NULL, this);
top_s->Add(pbutton_s, 0, wxGROW);
top_s->SetSizeHints(this);
Fit();
wxCommandEvent e;
on_change(e);
}
void on_create(wxCommandEvent& e)
{
uint64_t id = get_selected_id();
if(id == 0xFFFFFFFFFFFFFFFFULL) return;
std::string newname;
try {
newname = pick_text(this, "Enter new branch name", "Enter name for new branch:",
newname, false);
} catch(canceled_exception& e) {
return;
}
runemufn([this, id, newname]() {
try {
auto p = project_get();
if(p) p->create_branch(id, newname);
} catch(std::exception& e) {
std::string err = e.what();
runuifun([this, err]() {
show_message_ok(this, "Error creating branch",
"Can't create branch: " + err, wxICON_EXCLAMATION);
});
}
});
branches->call_project_flush();
}
void on_select(wxCommandEvent& e)
{
uint64_t id = get_selected_id();
if(id == 0xFFFFFFFFFFFFFFFFULL) return;
runemufn([this, id]() {
try {
auto p = project_get();
if(p) p->set_current_branch(id);
} catch(std::exception& e) {
std::string err = e.what();
runuifun([this, err]() {
show_message_ok(this, "Error setting branch",
"Can't set branch: " + err, wxICON_EXCLAMATION);
});
}
});
branches->call_project_flush();
update_movie_state();
}
void on_rename(wxCommandEvent& e)
{
uint64_t id = get_selected_id();
if(id == 0xFFFFFFFFFFFFFFFFULL) return;
std::string newname = branches->get_name(id);
try {
newname = pick_text(this, "Enter new branch name", "Rename this branch to:",
newname, false);
} catch(canceled_exception& e) {
return;
}
runemufn([this, id, newname]() {
try {
auto p = project_get();
if(p) p->set_branch_name(id, newname);
} catch(std::exception& e) {
std::string err = e.what();
runuifun([this, err]() {
show_message_ok(this, "Error renaming branch",
"Can't rename branch: " + err, wxICON_EXCLAMATION);
});
}
});
branches->call_project_flush();
update_movie_state();
}
void on_reparent(wxCommandEvent& e)
{
uint64_t id = get_selected_id();
if(id == 0xFFFFFFFFFFFFFFFFULL) return;
uint64_t pid;
branch_select* bsel = new branch_select(this);
int r = bsel->ShowModal();
if(r != wxID_OK) {
bsel->Destroy();
return;
}
pid = bsel->get_selection();
if(pid == 0xFFFFFFFFFFFFFFFFULL) return;
bsel->Destroy();
runemufn([this, id, pid]() {
try {
auto p = project_get();
if(p) p->set_parent_branch(id, pid);
} catch(std::exception& e) {
std::string err = e.what();
runuifun([this, err]() {
show_message_ok(this, "Error reparenting branch",
"Can't reparent branch: " + err, wxICON_EXCLAMATION);
});
}
});
branches->call_project_flush();
update_movie_state();
}
void on_delete(wxCommandEvent& e)
{
uint64_t id = get_selected_id();
if(id == 0xFFFFFFFFFFFFFFFFULL) return;
runemufn([this, id]() {
try {
auto p = project_get();
if(p) p->delete_branch(id);
} catch(std::exception& e) {
std::string err = e.what();
runuifun([this, err]() {
show_message_ok(this, "Error deleting branch",
"Can't delete branch: " + err, wxICON_EXCLAMATION);
});
}
});
branches->call_project_flush();
}
void on_close(wxCommandEvent& e)
{
EndModal(wxID_OK);
}
void on_change(wxCommandEvent& e)
{
set_enabled(branches->get_selection());
}
private:
uint64_t get_selected_id()
{
return branches->get_selection().id;
}
void set_enabled(branches_tree::selection id)
{
createbutton->Enable(id.item.IsOk());
selectbutton->Enable(id.item.IsOk());
renamebutton->Enable(id.item.IsOk() && !id.isroot);
reparentbutton->Enable(id.item.IsOk() && !id.isroot);
deletebutton->Enable(id.item.IsOk() && !id.isroot && !id.haschildren && !id.iscurrent);
}
wxButton* createbutton;
wxButton* selectbutton;
wxButton* renamebutton;
wxButton* reparentbutton;
wxButton* deletebutton;
wxButton* okbutton;
branches_tree* branches;
};
void build_menus(wxMenu* root, uint64_t id, std::list<branches_menu::miteminfo>& otheritems,
std::list<wxMenu*>& menus, std::map<uint64_t, std::string>& namemap,
std::map<uint64_t, std::set<uint64_t>>& childmap, std::map<int, uint64_t>& branch_ids, int& nextid,
uint64_t curbranch)
{
auto& children = childmap[id];
int mid = nextid++;
otheritems.push_back(branches_menu::miteminfo(root->AppendCheckItem(mid, towxstring(namemap[id])),
false, root));
branch_ids[mid] = id;
root->FindItem(mid)->Check(id == curbranch);
if(!children.empty())
otheritems.push_back(branches_menu::miteminfo(root->AppendSeparator(), false, root));
for(auto i : children) {
bool has_children = !childmap[i].empty();
if(!has_children) {
//No children, just put the item there.
int mid2 = nextid++;
otheritems.push_back(branches_menu::miteminfo(root->AppendCheckItem(mid2,
towxstring(namemap[i])), false, root));
branch_ids[mid2] = i;
root->FindItem(mid2)->Check(i == curbranch);
} else {
//Has children. Make a menu.
wxMenu* m = new wxMenu();
otheritems.push_back(branches_menu::miteminfo(root->AppendSubMenu(m,
towxstring(namemap[i])), true, root));
menus.push_back(m);
build_menus(m, i, otheritems, menus, namemap, childmap, branch_ids, nextid,
curbranch);
}
}
}
}
branches_menu::branches_menu(wxWindow* win, int wxid_low, int wxid_high)
{
pwin = win;
wxid_range_low = wxid_low;
wxid_range_high = wxid_high;
win->Connect(wxid_low, wxid_high, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(branches_menu::on_select), NULL, this);
branchchange.set(notify_branch_change, [this]() { runuifun([this]() { this->update(); }); });
}
branches_menu::~branches_menu()
{
}
void branches_menu::on_select(wxCommandEvent& e)
{
int id = e.GetId();
if(id < wxid_range_low || id > wxid_range_high) return;
if(id == wxid_range_low) {
//Configure.
branch_config* bcfg = new branch_config(pwin);
bcfg->ShowModal();
bcfg->Destroy();
return;
}
if(!branch_ids.count(id)) return;
uint64_t bid = branch_ids[id];
std::string err;
runemufn_async([this, bid]() {
auto p = project_get();
try {
if(p) p->set_current_branch(bid);
} catch(std::exception& e) {
std::string err = e.what();
runuifun([this, err]() {
show_message_ok(this->pwin, "Error changing branch", "Can't change branch: " +
err, wxICON_EXCLAMATION);
});
}
if(p) p->flush();
update_movie_state();
});
}
void branches_menu::update()
{
std::map<uint64_t, std::string> namemap;
std::map<uint64_t, std::set<uint64_t>> childmap;
runemufn([&namemap, &childmap]() {
auto p = project_get();
if(!p) return;
fill_namemap(*p, 0, namemap, childmap);
});
//First destroy everything that isn't a menu.
for(auto i : otheritems)
i.parent->Delete(i.item);
//Then kill all menus.
for(auto i : menus)
delete i;
otheritems.clear();
menus.clear();
branch_ids.clear();
if(namemap.empty()) {
if(disabler_fn) disabler_fn(false);
return;
}
//Okay, cleared. Rebuild things.
otheritems.push_back(miteminfo(Append(wxid_range_low, towxstring("Edit branches")), false, this));
otheritems.push_back(miteminfo(AppendSeparator(), false, this));
int ass_id = wxid_range_low + 1;
build_menus(this, 0, otheritems, menus, namemap, childmap, branch_ids, ass_id,
project_get()->get_current_branch());
if(disabler_fn) disabler_fn(true);
}
bool branches_menu::any_enabled()
{
return project_get();
}

View file

@ -227,7 +227,7 @@ void wxeditor_authors::on_ok(wxCommandEvent& e)
proj->directory = dir;
proj->name = prjname;
proj->luascripts = luascriptlist;
project_flush(proj);
proj->flush();
//For save status to immediately update.
do_flush_slotinfo();
update_movie_state();

View file

@ -11,6 +11,7 @@
#include "platform/wxwidgets/window-romload.hpp"
#include "platform/wxwidgets/settings-common.hpp"
#include "platform/wxwidgets/menu_tracelog.hpp"
#include "platform/wxwidgets/menu_branches.hpp"
#include "core/audioapi.hpp"
#include "core/command.hpp"
@ -134,6 +135,8 @@ enum
wxID_TRACELOG_FIRST,
wxID_TRACELOG_LAST = wxID_TRACELOG_FIRST + 256,
wxID_PLUGIN_MANAGER,
wxID_BRANCH_FIRST,
wxID_BRANCH_LAST = wxID_BRANCH_FIRST + 10240,
};
@ -815,9 +818,9 @@ void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
current_menu = NULL;
}
void wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
wxMenuItem* wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
{
current_menu->AppendSubMenu(menu, name);
return current_menu->AppendSubMenu(menu, name);
}
void wxwin_mainwindow::menu_entry(int id, wxString name)
@ -1062,6 +1065,12 @@ wxwin_mainwindow::wxwin_mainwindow()
recent_script_selected));
menu_separator();
menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
menu_separator();
branches_menu* brlist;
auto brlist_item = menu_special_sub(wxT("Branches"), brlist = new branches_menu(this, wxID_BRANCH_FIRST,
wxID_BRANCH_LAST));
brlist->set_disabler([brlist_item](bool enabled) { brlist_item->Enable(enabled); });
brlist->update();
menu_end_sub();
menu_start_sub(wxT("Save"));
menu_entry(wxID_SAVE_STATE, wxT("State..."));
@ -1132,7 +1141,9 @@ wxwin_mainwindow::wxwin_mainwindow()
menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
menu_entry(wxID_HEXEDITOR, wxT("Memory editor..."));
tracelog_menu* trlog;
menu_special_sub(wxT("Trace log"), trlog = new tracelog_menu(this, wxID_TRACELOG_FIRST, wxID_TRACELOG_LAST));
auto trlog_item = menu_special_sub(wxT("Trace log"), trlog = new tracelog_menu(this, wxID_TRACELOG_FIRST,
wxID_TRACELOG_LAST));
trlog->set_disabler([trlog_item](bool enabled) { trlog_item->Enable(enabled); });
trlog->update();
menu_separator();
menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
@ -1270,6 +1281,8 @@ void wxwin_mainwindow::update_statusbar(const std::map<std::string, std::u32stri
std::u32string macros = read_variable_map(vars, "!macros");
if(macros.length())
s << U" Macros: " << macros;
if(vars.count("!branch"))
s << U" Branch: " << read_variable_map(vars, "!branch");
statusbar->SetStatusText(towxstring(s.str()));
} catch(std::exception& e) {

View file

@ -280,7 +280,7 @@ namespace
}
no_watch:
project_info* pinfo2 = new project_info(pinfo);
project_flush(pinfo2);
pinfo2->flush();
project_info* old_proj = project_get();
project_set(pinfo2, true);
if(old_proj)

View file

@ -51,6 +51,7 @@ void tracelog_menu::update()
items[id]->Check(debug_tracelogging(id));
id++;
}
if(disabler_fn) disabler_fn(!_items.empty());
}
bool tracelog_menu::any_enabled()