diff --git a/VERSION b/VERSION index fd6c0240..4c578b36 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1-Δ15ε3 \ No newline at end of file +1-Δ16 \ No newline at end of file diff --git a/include/core/dispatch.hpp b/include/core/dispatch.hpp index 3d08b5ac..7b2014fa 100644 --- a/include/core/dispatch.hpp +++ b/include/core/dispatch.hpp @@ -413,6 +413,27 @@ public: * Call on_dumper_update on on all objects. */ static void do_dumper_update() throw(); +/** + * Notify about changes to voice streams. + * + * Default implementation does nothing. + */ + virtual void on_voice_stream_change(); +/** + * Call on_voice_stream_change on all objects. + */ + static void do_voice_stream_change() throw(); +/** + * Notify about changes to subtitles. + * + * Default implementation does nothing. + */ + virtual void on_subtitle_change(); +/** + * Call on_subtitle_change on all objects. + */ + static void do_subtitle_change() throw(); + protected: /** * Call to indicate this target is interested in sound sample data. diff --git a/manual.lyx b/manual.lyx index 7ad0e533..72267adf 100644 --- a/manual.lyx +++ b/manual.lyx @@ -2122,6 +2122,67 @@ Returns bitwise OR of 1 left shifted by bit1 places, 1 left shifted by bit2 As special value, nil argument is no-op. \end_layout +\begin_layout Subsubsection +bit.test_any(number a, number b) +\end_layout + +\begin_layout Standard +Is there a common set bit in a and b? +\end_layout + +\begin_layout Subsubsection +bit.test_all(number a, number b) +\end_layout + +\begin_layout Standard +Are all set bits in b also set in a? +\end_layout + +\begin_layout Subsubsection +bit.popcount(number a) +\end_layout + +\begin_layout Standard +Population count of a. +\end_layout + +\begin_layout Subsubsection +bit.clshift(number a, number b, [number amount,[number bits]]) +\end_layout + +\begin_layout Standard +Does chained left shift on a, b by amount positions, assuming numbers to + be of specified number of bits. +\end_layout + +\begin_layout Subsubsection +bit.crshift(number a, number b, [number amount,[number bits]]) +\end_layout + +\begin_layout Standard +Does chained right shift on a, b by amount positions, assuming numbers to + be of specified number of bits. +\end_layout + +\begin_layout Subsubsection +bit.flagdecode(number a, number bits, [string on, [string off]]) +\end_layout + +\begin_layout Standard +Return string of length bits where ith character is ith character of on + if bit i is on, otherwise ith character of off. + Out of range reads give last character, or '*'/'-' if empty. +\end_layout + +\begin_layout Subsubsection +bit.rflagdecode(number a, number bits, [string on, [string off]]) +\end_layout + +\begin_layout Standard +Like bit.flagdecode, but outputs the string in the opposite order (most significa +nt bit first). +\end_layout + \begin_layout Subsection Table gui: \end_layout @@ -7343,5 +7404,61 @@ Compensate for nuts bsnes superscope/justifier handling Lua: Fix bit.extract boolean handling \end_layout +\begin_layout Subsection +rr1-delta16 +\end_layout + +\begin_layout Itemize +Stop at movie end: Don't off-by-one +\end_layout + +\begin_layout Itemize +Fix crash closing lsnes with voice playback active. +\end_layout + +\begin_layout Itemize +Import/Export OggOpus for commentary tracks +\end_layout + +\begin_layout Itemize +16-button controllers. +\end_layout + +\begin_layout Itemize +Don't show nonexistent controllers in input display +\end_layout + +\begin_layout Itemize +Set voice record/playback volume from UI +\end_layout + +\begin_layout Itemize +Patches for gambatte SVN364. +\end_layout + +\begin_layout Itemize +Load markup (if exists) even without gamepack file. +\end_layout + +\begin_layout Itemize +Screen rotation & flipping +\end_layout + +\begin_layout Itemize +Lua: Some new bit functions +\end_layout + +\begin_layout Itemize +Auto-refresh voice streams on change. +\end_layout + +\begin_layout Itemize +Auto-refresh subtitles on change & new subtitle editor. +\end_layout + +\begin_layout Itemize +Fix music volume adjustment. +\end_layout + \end_body \end_document diff --git a/manual.txt b/manual.txt index 6ff912e4..294d4058 100644 --- a/manual.txt +++ b/manual.txt @@ -1047,6 +1047,43 @@ Returns bitwise OR of 1 left shifted by bit1 places, 1 left shifted by bit2 places and so on. As special value, nil argument is no-op. +8.2.12 bit.test_any(number a, number b) + +Is there a common set bit in a and b? + +8.2.13 bit.test_all(number a, number b) + +Are all set bits in b also set in a? + +8.2.14 bit.popcount(number a) + +Population count of a. + +8.2.15 bit.clshift(number a, number b, [number amount,[number + bits]]) + +Does chained left shift on a, b by amount positions, assuming +numbers to be of specified number of bits. + +8.2.16 bit.crshift(number a, number b, [number amount,[number + bits]]) + +Does chained right shift on a, b by amount positions, assuming +numbers to be of specified number of bits. + +8.2.17 bit.flagdecode(number a, number bits, [string on, [string + off]]) + +Return string of length bits where ith character is ith character +of on if bit i is on, otherwise ith character of off. Out of +range reads give last character, or '*'/'-' if empty. + +8.2.18 bit.rflagdecode(number a, number bits, [string on, [string + off]]) + +Like bit.flagdecode, but outputs the string in the opposite order +(most significant bit first). + 8.3 Table gui: Most of these functions can only be called in on_paint and @@ -3616,3 +3653,31 @@ set-axis joystick0axis19 disabled • Lua: Fix bit.extract boolean handling +18.76 rr1-delta16 + +• Stop at movie end: Don't off-by-one + +• Fix crash closing lsnes with voice playback active. + +• Import/Export OggOpus for commentary tracks + +• 16-button controllers. + +• Don't show nonexistent controllers in input display + +• Set voice record/playback volume from UI + +• Patches for gambatte SVN364. + +• Load markup (if exists) even without gamepack file. + +• Screen rotation & flipping + +• Lua: Some new bit functions + +• Auto-refresh voice streams on change. + +• Auto-refresh subtitles on change & new subtitle editor. + +• Fix music volume adjustment. + diff --git a/src/core/dispatch.cpp b/src/core/dispatch.cpp index 4672af85..cc6aef73 100644 --- a/src/core/dispatch.cpp +++ b/src/core/dispatch.cpp @@ -538,3 +538,35 @@ void information_dispatch::do_dumper_update() throw() END_EH_BLOCK(i, "on_dumper_update"); } } + +void information_dispatch::on_voice_stream_change() +{ + //Do nothing. +} + +void information_dispatch::do_voice_stream_change() throw() +{ + if(in_global_ctors()) + return; + for(auto& i : dispatch()) { + START_EH_BLOCK + i->on_voice_stream_change(); + END_EH_BLOCK(i, "on_voice_stream_change"); + } +} + +void information_dispatch::on_subtitle_change() +{ + //Do nothing. +} + +void information_dispatch::do_subtitle_change() throw() +{ + if(in_global_ctors()) + return; + for(auto& i : dispatch()) { + START_EH_BLOCK + i->on_subtitle_change(); + END_EH_BLOCK(i, "on_subtitle_change"); + } +} diff --git a/src/core/inthread.cpp b/src/core/inthread.cpp index e7136f3d..4a55b616 100644 --- a/src/core/inthread.cpp +++ b/src/core/inthread.cpp @@ -9,6 +9,7 @@ #include "library/ogg.hpp" #include "core/audioapi.hpp" #include "core/command.hpp" +#include "core/dispatch.hpp" #include "core/framerate.hpp" #include "core/inthread.hpp" #include "core/keymapper.hpp" @@ -1669,6 +1670,7 @@ out: messages << "Can't add stream: " << e.what() << std::endl; active_stream->put_ref(); } + information_dispatch::do_voice_stream_change(); } else active_stream->put_ref(); active_stream = NULL; @@ -1872,6 +1874,7 @@ namespace } s->put_ref(); current_collection->delete_stream(id); + information_dispatch::do_voice_stream_change(); messages << "Deleted stream #" << id << "." << std::endl; }); @@ -1921,6 +1924,7 @@ namespace } s->put_ref(); current_collection->alter_stream_timebase(id, tbase); + information_dispatch::do_voice_stream_change(); messages << "Timebase of stream #" << id << " is now " << (tbase / 48000.0) << "s" << std::endl; }); @@ -1953,6 +1957,7 @@ namespace throw; } st->unlock(); //Not locked. + information_dispatch::do_voice_stream_change(); messages << "Imported stream (" << st->length() / 48000.0 << "s) as ID #" << id << std::endl; } @@ -2058,6 +2063,7 @@ namespace if(current_collection) delete current_collection; current_collection = newc; + information_dispatch::do_voice_stream_change(); messages << "Loaded '" << x << "'" << std::endl; }); @@ -2068,6 +2074,7 @@ namespace if(current_collection) delete current_collection; current_collection = NULL; + information_dispatch::do_voice_stream_change(); messages << "Collection unloaded" << std::endl; }); @@ -2160,6 +2167,7 @@ uint64_t voicesub_import_stream(uint64_t ts, const std::string& filename, extern throw; } st->unlock(); //Not locked. + information_dispatch::do_voice_stream_change(); return id; } @@ -2169,6 +2177,7 @@ void voicesub_delete_stream(uint64_t id) if(!current_collection) throw std::runtime_error("No collection loaded"); current_collection->delete_stream(id); + information_dispatch::do_voice_stream_change(); } void voicesub_export_superstream(const std::string& filename) @@ -2192,6 +2201,7 @@ void voicesub_load_collection(const std::string& filename) if(current_collection) delete current_collection; current_collection = newc; + information_dispatch::do_voice_stream_change(); } void voicesub_unload_collection() @@ -2200,6 +2210,7 @@ void voicesub_unload_collection() if(current_collection) delete current_collection; current_collection = NULL; + information_dispatch::do_voice_stream_change(); } void voicesub_alter_timebase(uint64_t id, uint64_t ts) @@ -2208,6 +2219,7 @@ void voicesub_alter_timebase(uint64_t id, uint64_t ts) if(!current_collection) throw std::runtime_error("No collection loaded"); current_collection->alter_stream_timebase(id, ts); + information_dispatch::do_voice_stream_change(); } double voicesub_ts_seconds(uint64_t ts) diff --git a/src/core/subtitles.cpp b/src/core/subtitles.cpp index 29a4f678..c2d3f707 100644 --- a/src/core/subtitles.cpp +++ b/src/core/subtitles.cpp @@ -1,4 +1,6 @@ #include "core/command.hpp" +#include "core/dispatch.hpp" +#include "core/framebuffer.hpp" #include "core/moviedata.hpp" #include "core/subtitles.hpp" #include "core/window.hpp" @@ -109,6 +111,8 @@ namespace our_movie.subtitles.erase(key); else our_movie.subtitles[key] = s_unescape(text); + information_dispatch::do_subtitle_change(); + redraw_framebuffer(); }); function_ptr_command<> list_subtitle("list-subtitle", "List the subtitles", @@ -229,4 +233,6 @@ void set_subtitle_for(uint64_t f, uint64_t l, const std::string& x) our_movie.subtitles.erase(key); else our_movie.subtitles[key] = s_unescape(x); + information_dispatch::do_subtitle_change(); + redraw_framebuffer(); } diff --git a/src/core/window.cpp b/src/core/window.cpp index 48eccea4..ae8cfb02 100644 --- a/src/core/window.cpp +++ b/src/core/window.cpp @@ -639,8 +639,6 @@ void platform::queue(const std::string& c) throw(std::bad_alloc) void platform::queue(void (*f)(void* arg), void* arg, bool sync) throw(std::bad_alloc) { - if(sync && queue_synchronous_fn_warning) - std::cerr << "WARNING: Synchronous queue in callback to UI, this may deadlock!" << std::endl; init_threading(); mutex::holder h(*queue_lock); ++next_function; diff --git a/src/lua/bit.cpp b/src/lua/bit.cpp index f2e1ef29..e1f7e2b9 100644 --- a/src/lua/bit.cpp +++ b/src/lua/bit.cpp @@ -1,4 +1,5 @@ #include "lua/internal.hpp" +#include "library/minmax.hpp" #define BITWISE_BITS 48 #define BITWISE_MASK ((1ULL << (BITWISE_BITS)) - 1) @@ -133,6 +134,111 @@ namespace return 1; }); + function_ptr_luafun lua_testany("bit.test_any", [](lua_State* LS, const std::string& fname) -> int { + uint64_t a = get_numeric_argument(LS, 1, fname.c_str()); + uint64_t b = get_numeric_argument(LS, 2, fname.c_str()); + lua_pushboolean(LS, (a & b) != 0); + return 1; + }); + + function_ptr_luafun lua_testall("bit.test_all", [](lua_State* LS, const std::string& fname) -> int { + uint64_t a = get_numeric_argument(LS, 1, fname.c_str()); + uint64_t b = get_numeric_argument(LS, 2, fname.c_str()); + lua_pushboolean(LS, (a & b) == b); + return 1; + }); + + int poptable[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + + int popcount(uint64_t x) + { + int c = 0; + for(unsigned i = 0; i < 16; i++) { + c += poptable[x & 15]; + x >>= 4; + } + return c; + } + + function_ptr_luafun lua_popcount("bit.popcount", [](lua_State* LS, const std::string& fname) -> int { + uint64_t a = get_numeric_argument(LS, 1, fname.c_str()); + lua_pushnumber(LS,popcount(a)); + return 1; + }); + + function_ptr_luafun lua_clshift("bit.clshift", [](lua_State* LS, const std::string& fname) -> int { + unsigned amount = 1; + unsigned bits = 48; + uint64_t a = get_numeric_argument(LS, 1, fname.c_str()); + uint64_t b = get_numeric_argument(LS, 2, fname.c_str()); + get_numeric_argument(LS, 3, amount, fname.c_str()); + get_numeric_argument(LS, 4, bits, fname.c_str()); + uint64_t mask = ((1ULL << bits) - 1); + a &= mask; + b &= mask; + a <<= amount; + a &= mask; + a |= (b >> (bits - amount)); + b <<= amount; + b &= mask; + lua_pushnumber(LS, a); + lua_pushnumber(LS, b); + return 2; + }); + + function_ptr_luafun lua_crshift("bit.crshift", [](lua_State* LS, const std::string& fname) -> int { + unsigned amount = 1; + unsigned bits = 48; + uint64_t a = get_numeric_argument(LS, 1, fname.c_str()); + uint64_t b = get_numeric_argument(LS, 2, fname.c_str()); + get_numeric_argument(LS, 3, amount, fname.c_str()); + get_numeric_argument(LS, 4, bits, fname.c_str()); + uint64_t mask = ((1ULL << bits) - 1); + a &= mask; + b &= mask; + b >>= amount; + b |= (a << (bits - amount)); + b &= mask; + a >>= amount; + lua_pushnumber(LS, a); + lua_pushnumber(LS, b); + return 2; + }); + + int flagdecode_core(lua_State* LS, const std::string& fname, bool reverse) + { + uint64_t a = get_numeric_argument(LS, 1, fname.c_str()); + uint64_t b = get_numeric_argument(LS, 2, fname.c_str()); + std::string on, off; + if(lua_type(LS, 3) == LUA_TSTRING) + on = get_string_argument(LS, 3, fname.c_str()); + if(lua_type(LS, 4) == LUA_TSTRING) + off = get_string_argument(LS, 4, fname.c_str()); + size_t onl = on.length(); + size_t offl = off.length(); + char onc = onl ? on[onl - 1] : '*'; + char offc = offl ? off[offl - 1] : '-'; + char buffer[65]; + unsigned i; + size_t bias = min(b, (uint64_t)64) - 1; + for(i = 0; i < 64 && i < b; i++) { + char onc2 = (i < onl) ? on[i] : onc; + char offc2 = (i < offl) ? off[i] : offc; + buffer[reverse ? (bias - i) : i] = ((a >> i) & 1) ? onc2 : offc2; + } + buffer[i] = '\0'; + lua_pushstring(LS, buffer); + return 1; + } + + function_ptr_luafun lua_flagdecode("bit.flagdecode", [](lua_State* LS, const std::string& fname) -> int { + return flagdecode_core(LS, fname, false); + }); + + function_ptr_luafun lua_rflagdecode("bit.rflagdecode", [](lua_State* LS, const std::string& fname) -> int { + return flagdecode_core(LS, fname, true); + }); + lua_symmetric_bitwise bit_none("bit.none"); lua_symmetric_bitwise bit_bnot("bit.bnot"); lua_symmetric_bitwise bit_any("bit.any"); diff --git a/src/platform/wxwidgets/editor-subtitles.cpp b/src/platform/wxwidgets/editor-subtitles.cpp index 37e0b6f0..3c62cffd 100644 --- a/src/platform/wxwidgets/editor-subtitles.cpp +++ b/src/platform/wxwidgets/editor-subtitles.cpp @@ -10,57 +10,204 @@ #include #include "library/string.hpp" +#include "core/dispatch.hpp" #include "core/emucore.hpp" +#include "core/subtitles.hpp" -class wxeditor_subtitles : public wxDialog +namespace +{ + struct subdata + { + uint64_t first; + uint64_t last; + std::string text; + }; +} + +class wxeditor_subtitles : public wxFrame { public: wxeditor_subtitles(wxWindow* parent); + ~wxeditor_subtitles() throw(); bool ShouldPreventAppExit() const; - void on_subtitles_change(wxCommandEvent& e); - void on_ok(wxCommandEvent& e); - void on_cancel(wxCommandEvent& e); + void on_change(wxCommandEvent& e); + void on_add(wxCommandEvent& e); + void on_edit(wxCommandEvent& e); + void on_delete(wxCommandEvent& e); + void on_close(wxCommandEvent& e); + void on_wclose(wxCloseEvent& e); + void refresh(); private: - wxTextCtrl* subs; - wxButton* ok; - wxButton* cancel; + struct refresh_listener : public information_dispatch + { + refresh_listener(wxeditor_subtitles* v) + : information_dispatch("subtitle-editor-change-listener") + { + obj = v; + } + void on_subtitle_change() + { + wxeditor_subtitles* _obj = obj; + runuifun([_obj]() -> void { _obj->refresh(); }); + } + wxeditor_subtitles* obj; + }; + bool closing; + wxListBox* subs; + wxTextCtrl* subtext; + wxButton* add; + wxButton* edit; + wxButton* _delete; + wxButton* close; + std::map subtexts; + refresh_listener* rlistener; }; +namespace +{ + + class wxeditor_subtitles_subtitle : public wxDialog + { + public: + wxeditor_subtitles_subtitle(wxWindow* parent, subdata d); + void on_change(wxCommandEvent& e); + void on_cancel(wxCommandEvent& e); + void on_ok(wxCommandEvent& e); + subdata get_result(); + private: + wxTextCtrl* first; + wxTextCtrl* last; + wxTextCtrl* text; + wxButton* ok; + wxButton* cancel; + }; + + wxeditor_subtitles_subtitle::wxeditor_subtitles_subtitle(wxWindow* parent, subdata d) + : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit subtitle"), wxDefaultPosition, wxSize(-1, -1)) + { + Centre(); + wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0); + SetSizer(top_s); + + wxFlexGridSizer* data_s = new wxFlexGridSizer(3, 2, 0, 0); + data_s->Add(new wxStaticText(this, wxID_ANY, wxT("First frame:"))); + data_s->Add(first = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(200, -1))); + data_s->Add(new wxStaticText(this, wxID_ANY, wxT("Last frame:"))); + data_s->Add(last = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(200, -1))); + data_s->Add(new wxStaticText(this, wxID_ANY, wxT("Text:"))); + data_s->Add(text = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(400, -1))); + top_s->Add(data_s, 1, wxGROW); + + first->Connect(wxEVT_COMMAND_TEXT_UPDATED, + wxCommandEventHandler(wxeditor_subtitles_subtitle::on_change), NULL, this); + last->Connect(wxEVT_COMMAND_TEXT_UPDATED, + wxCommandEventHandler(wxeditor_subtitles_subtitle::on_change), NULL, this); + text->Connect(wxEVT_COMMAND_TEXT_UPDATED, + wxCommandEventHandler(wxeditor_subtitles_subtitle::on_change), NULL, this); + + wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); + pbutton_s->AddStretchSpacer(); + pbutton_s->Add(ok = new wxButton(this, wxID_ANY, wxT("OK")), 0, wxGROW); + pbutton_s->Add(cancel = new wxButton(this, wxID_ANY, wxT("Cancel")), 0, wxGROW); + ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_subtitles_subtitle::on_ok), + NULL, this); + cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxeditor_subtitles_subtitle::on_cancel), NULL, this); + top_s->Add(pbutton_s, 0, wxGROW); + + pbutton_s->SetSizeHints(this); + top_s->SetSizeHints(this); + + first->SetValue(towxstring((stringfmt() << d.first).str())); + last->SetValue(towxstring((stringfmt() << d.last).str())); + text->SetValue(towxstring(d.text)); + Fit(); + } + + void wxeditor_subtitles_subtitle::on_change(wxCommandEvent& e) + { + bool valid = true; + std::string _first = tostdstring(first->GetValue()); + std::string _last = tostdstring(last->GetValue()); + std::string _text = tostdstring(text->GetValue()); + valid = valid && regex_match("[0-9]{1,19}", _first); + valid = valid && regex_match("[0-9]{1,19}", _last); + valid = valid && (_text != ""); + ok->Enable(valid); + } + + void wxeditor_subtitles_subtitle::on_cancel(wxCommandEvent& e) + { + EndModal(wxID_CANCEL); + } + + void wxeditor_subtitles_subtitle::on_ok(wxCommandEvent& e) + { + EndModal(wxID_OK); + } + + subdata wxeditor_subtitles_subtitle::get_result() + { + subdata d; + d.first = parse_value(tostdstring(first->GetValue())); + d.last = parse_value(tostdstring(last->GetValue())); + d.text = tostdstring(text->GetValue()); + return d; + } + + bool edit_subtext(wxWindow* w, struct subdata& d) + { + wxeditor_subtitles_subtitle* editor = NULL; + try { + editor = new wxeditor_subtitles_subtitle(w, d); + int ret = editor->ShowModal(); + if(ret == wxID_OK) + d = editor->get_result(); + } catch(...) { + } + if(editor) + editor->Destroy(); + } +} + wxeditor_subtitles::wxeditor_subtitles(wxWindow* parent) - : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit subtitles"), wxDefaultPosition, wxSize(-1, -1)) + : wxFrame(NULL, wxID_ANY, wxT("lsnes: Edit subtitles"), wxDefaultPosition, wxSize(-1, -1)) { + closing = false; Centre(); wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0); SetSizer(top_s); - top_s->Add(subs = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(400, 300), - wxTE_MULTILINE), 1, wxGROW); - subs->Connect(wxEVT_COMMAND_TEXT_UPDATED, - wxCommandEventHandler(wxeditor_subtitles::on_subtitles_change), NULL, this); + //TODO: Apppropriate controls. + top_s->Add(subs = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(300, 400), 0, NULL, + wxLB_SINGLE), 1, wxGROW); + subs->Connect(wxEVT_COMMAND_LISTBOX_SELECTED, + wxCommandEventHandler(wxeditor_subtitles::on_change), NULL, this); wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); pbutton_s->AddStretchSpacer(); - pbutton_s->Add(ok = new wxButton(this, wxID_ANY, wxT("OK")), 0, wxGROW); - pbutton_s->Add(cancel = new wxButton(this, wxID_ANY, wxT("Cancel")), 0, wxGROW); - ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_subtitles::on_ok), NULL, this); - cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_subtitles::on_cancel), NULL, + pbutton_s->Add(add = new wxButton(this, wxID_ANY, wxT("Add")), 0, wxGROW); + pbutton_s->Add(edit = new wxButton(this, wxID_ANY, wxT("Edit")), 0, wxGROW); + pbutton_s->Add(_delete = new wxButton(this, wxID_ANY, wxT("Delete")), 0, wxGROW); + pbutton_s->Add(close = new wxButton(this, wxID_ANY, wxT("Close")), 0, wxGROW); + add->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_subtitles::on_add), NULL, this); + edit->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_subtitles::on_edit), NULL, this); + _delete->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_subtitles::on_delete), NULL, this); + close->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_subtitles::on_close), NULL, this); top_s->Add(pbutton_s, 0, wxGROW); pbutton_s->SetSizeHints(this); top_s->SetSizeHints(this); Fit(); + rlistener = new refresh_listener(this); + refresh(); +} - std::string txt = ""; - runemufn([&txt]() { - for(auto i : get_subtitles()) { - std::ostringstream line; - line << i.first << " " << i.second << " " << get_subtitle_for(i.first, i.second) << std::endl; - txt = txt + line.str(); - } - }); - subs->SetValue(towxstring(txt)); +wxeditor_subtitles::~wxeditor_subtitles() throw() +{ + delete rlistener; } bool wxeditor_subtitles::ShouldPreventAppExit() const @@ -68,76 +215,107 @@ bool wxeditor_subtitles::ShouldPreventAppExit() const return false; } -void wxeditor_subtitles::on_subtitles_change(wxCommandEvent& e) +void wxeditor_subtitles::on_close(wxCommandEvent& e) { - std::string txt = tostdstring(subs->GetValue()); - std::string line; - while(txt != "") { - extract_token(txt, line, "\n"); - istrip_CR(line); - if(line == "") - continue; - auto r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.+)", line); - if(!r) { - ok->Disable(); - return; - } - try { - parse_value(r[1]); - parse_value(r[2]); - } catch(...) { - ok->Disable(); - return; - } - } - ok->Enable(); + closing = true; + Destroy(); } -void wxeditor_subtitles::on_ok(wxCommandEvent& e) +void wxeditor_subtitles::on_wclose(wxCloseEvent& e) { - std::map, std::string> data; - runemufn([&data]() { - for(auto i : get_subtitles()) - data[std::make_pair(i.first, i.second)] = ""; - }); - std::string txt = tostdstring(subs->GetValue()); - std::string line; - while(txt != "") { - extract_token(txt, line, "\n"); - istrip_CR(line); - if(line == "") - continue; - auto r = regex("([0-9]+)[ \t]+([0-9]+)[ \t]+(.+)", line); - if(!r) - return; - try { - uint64_t f = parse_value(r[1]); - uint64_t l = parse_value(r[2]); - data[std::make_pair(f, l)] = r[3]; - } catch(...) { - return; - } - } - runemufn([&data]() { - for(auto i : data) - set_subtitle_for(i.first.first, i.first.second, i.second); - }); - EndModal(wxID_OK); + closing = true; } -void wxeditor_subtitles::on_cancel(wxCommandEvent& e) +void wxeditor_subtitles::refresh() { - EndModal(wxID_CANCEL); + if(closing) + return; + std::map, std::string> _subtitles; + runemufn([&_subtitles]() -> void { + auto keys = get_subtitles(); + for(auto i : keys) + _subtitles[i] = get_subtitle_for(i.first, i.second); + }); + int sel = subs->GetSelection(); + bool found = (subtexts.count(sel) != 0); + subdata matching = subtexts[sel]; + subs->Clear(); + unsigned num = 0; + subtexts.clear(); + for(auto i : _subtitles) { + subdata newdata; + newdata.first = i.first.first; + newdata.last = i.first.second; + newdata.text = i.second; + subtexts[num++] = newdata; + std::string s = (stringfmt() << i.first.first << "-" << i.first.second << ": " << i.second).str(); + subs->Append(towxstring(s)); + } + for(int i = 0; i < subs->GetCount(); i++) + if(subtexts[i].first == matching.first && subtexts[i].last == matching.last) + subs->SetSelection(i); + if(subs->GetSelection() == wxNOT_FOUND && sel < subs->GetCount()) + subs->SetSelection(sel); + sel = subs->GetSelection(); + found = (subtexts.count(sel) != 0); + edit->Enable(found); + _delete->Enable(found); +} + +void wxeditor_subtitles::on_change(wxCommandEvent& e) +{ + if(closing) + return; + int sel = subs->GetSelection(); + bool found = (subtexts.count(sel) != 0); + edit->Enable(found); + _delete->Enable(found); +} + +void wxeditor_subtitles::on_add(wxCommandEvent& e) +{ + if(closing) + return; + subdata t; + t.first = 0; + t.last = 0; + t.text = ""; + if(edit_subtext(this, t)) + set_subtitle_for(t.first, t.last, t.text); +} + +void wxeditor_subtitles::on_edit(wxCommandEvent& e) +{ + if(closing) + return; + int sel = subs->GetSelection(); + if(!subtexts.count(sel)) + return; + auto t = subtexts[sel]; + auto old = t; + if(edit_subtext(this, t)) { + set_subtitle_for(old.first, old.last, ""); + set_subtitle_for(t.first, t.last, t.text); + } +} + +void wxeditor_subtitles::on_delete(wxCommandEvent& e) +{ + if(closing) + return; + int sel = subs->GetSelection(); + if(!subtexts.count(sel)) + return; + auto t = subtexts[sel]; + set_subtitle_for(t.first, t.last, ""); } void wxeditor_subtitles_display(wxWindow* parent) { - modal_pause_holder hld; - wxDialog* editor; + wxFrame* editor; try { editor = new wxeditor_subtitles(parent); - editor->ShowModal(); + editor->Show(); } catch(...) { } - editor->Destroy(); } diff --git a/src/platform/wxwidgets/editor-voicesub.cpp b/src/platform/wxwidgets/editor-voicesub.cpp index 58a48b3f..3bc9ecc4 100644 --- a/src/platform/wxwidgets/editor-voicesub.cpp +++ b/src/platform/wxwidgets/editor-voicesub.cpp @@ -9,6 +9,7 @@ #include #include +#include "core/dispatch.hpp" #include "library/string.hpp" #define NOTHING 0xFFFFFFFFFFFFFFFFULL @@ -22,6 +23,7 @@ class wxeditor_voicesub : public wxDialog { public: wxeditor_voicesub(wxWindow* parent); + ~wxeditor_voicesub() throw(); bool ShouldPreventAppExit() const; void on_select(wxCommandEvent& e); void on_play(wxCommandEvent& e); @@ -39,9 +41,23 @@ public: void on_refresh(wxCommandEvent& e); void on_close(wxCommandEvent& e); void on_wclose(wxCloseEvent& e); -private: - bool closing; void refresh(); +private: + struct refresh_listener : public information_dispatch + { + refresh_listener(wxeditor_voicesub* v) + : information_dispatch("voicesub-editor-change-listner") + { + obj = v; + } + void on_voice_stream_change() + { + wxeditor_voicesub* _obj = obj; + runuifun([_obj]() -> void { _obj->refresh(); }); + } + wxeditor_voicesub* obj; + }; + bool closing; uint64_t get_id(); std::map smap; wxListBox* subtitles; @@ -59,6 +75,8 @@ private: wxButton* unloadbutton; wxButton* refreshbutton; wxButton* closebutton; + refresh_listener* rlistener; + }; wxeditor_voicesub::wxeditor_voicesub(wxWindow* parent) @@ -150,9 +168,15 @@ wxeditor_voicesub::wxeditor_voicesub(wxWindow* parent) top_s->SetSizeHints(this); Fit(); + rlistener = new refresh_listener(this); refresh(); } +wxeditor_voicesub::~wxeditor_voicesub() throw() +{ + delete rlistener; +} + void wxeditor_voicesub::on_select(wxCommandEvent& e) { if(closing) @@ -177,7 +201,6 @@ void wxeditor_voicesub::on_play(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error playing", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_delete(wxCommandEvent& e) @@ -190,7 +213,6 @@ void wxeditor_voicesub::on_delete(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error deleting", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_export_o(wxCommandEvent& e) @@ -209,7 +231,6 @@ void wxeditor_voicesub::on_export_o(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error exporting", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_export_p(wxCommandEvent& e) @@ -228,7 +249,6 @@ void wxeditor_voicesub::on_export_p(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error exporting", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_export_q(wxCommandEvent& e) @@ -247,7 +267,6 @@ void wxeditor_voicesub::on_export_q(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error exporting", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_export_s(wxCommandEvent& e) @@ -263,7 +282,6 @@ void wxeditor_voicesub::on_export_s(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error exporting superstream", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_import_o(wxCommandEvent& e) @@ -282,7 +300,6 @@ void wxeditor_voicesub::on_import_o(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error importing", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_import_p(wxCommandEvent& e) @@ -301,7 +318,6 @@ void wxeditor_voicesub::on_import_p(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error importing", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_import_q(wxCommandEvent& e) @@ -320,7 +336,6 @@ void wxeditor_voicesub::on_import_q(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error importing", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_change_ts(wxCommandEvent& e) @@ -340,7 +355,6 @@ void wxeditor_voicesub::on_change_ts(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error changing timebase", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_load(wxCommandEvent& e) @@ -356,13 +370,11 @@ void wxeditor_voicesub::on_load(wxCommandEvent& e) } catch(std::exception& e) { show_message_ok(this, "Error loading collection", e.what(), wxICON_EXCLAMATION); } - refresh(); } void wxeditor_voicesub::on_unload(wxCommandEvent& e) { voicesub_unload_collection(); - refresh(); } void wxeditor_voicesub::on_refresh(wxCommandEvent& e) @@ -378,6 +390,8 @@ void wxeditor_voicesub::on_close(wxCommandEvent& e) void wxeditor_voicesub::refresh() { + if(closing) + return; bool cflag = voicesub_collection_loaded(); unloadbutton->Enable(cflag); exportsbutton->Enable(cflag); diff --git a/src/platform/wxwidgets/mainwindow.cpp b/src/platform/wxwidgets/mainwindow.cpp index 7f4be685..c187770b 100644 --- a/src/platform/wxwidgets/mainwindow.cpp +++ b/src/platform/wxwidgets/mainwindow.cpp @@ -1229,7 +1229,7 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) case wxID_SET_VOLUME: parsed = pick_volume(this, "Set volume", last_volume); if(parsed >= -1e-10) - runemufn([parsed]() { platform::global_volume = parsed; }); + runemufn([parsed]() { audioapi_music_volume(parsed); }); return; case wxID_SET_VOLUME_RECORD: parsed = pick_volume(this, "Set recording volume", last_volume_record);