Fix scaling-related crashes
[lsnes.git] / src / platform / wxwidgets / editor-voicesub.cpp
blob3ca3d2dbb08bd70d419cea0dfaefb2b8212d92b8
1 #include <wx/wx.h>
2 #include <wx/event.h>
3 #include <wx/control.h>
4 #include <wx/combobox.h>
6 #include "core/dispatch.hpp"
7 #include "core/instance.hpp"
8 #include "core/inthread.hpp"
9 #include "core/project.hpp"
10 #include "core/ui-services.hpp"
11 #include "library/string.hpp"
13 #include "platform/wxwidgets/platform.hpp"
14 #include "platform/wxwidgets/loadsave.hpp"
16 #include <stdexcept>
17 #include <sstream>
18 #include <set>
20 #define NOTHING 0xFFFFFFFFFFFFFFFFULL
22 namespace
24 std::set<emulator_instance*> voicesub_open;
27 class wxeditor_voicesub : public wxDialog
29 public:
30 wxeditor_voicesub(wxWindow* parent, emulator_instance& _inst);
31 ~wxeditor_voicesub() throw();
32 bool ShouldPreventAppExit() const;
33 void on_select(wxCommandEvent& e);
34 void on_play(wxCommandEvent& e);
35 void on_delete(wxCommandEvent& e);
36 void on_export(wxCommandEvent& e);
37 void on_export_s(wxCommandEvent& e);
38 void on_import(wxCommandEvent& e);
39 void on_change_ts(wxCommandEvent& e);
40 void on_change_gain(wxCommandEvent& e);
41 void on_load(wxCommandEvent& e);
42 void on_unload(wxCommandEvent& e);
43 void on_refresh(wxCommandEvent& e);
44 void on_close(wxCommandEvent& e);
45 void on_wclose(wxCloseEvent& e);
46 void refresh();
47 private:
48 emulator_instance& inst;
49 bool closing;
50 uint64_t get_id();
51 std::map<int, uint64_t> smap;
52 wxListBox* subtitles;
53 wxButton* playbutton;
54 wxButton* deletebutton;
55 wxButton* exportpbutton;
56 wxButton* exportsbutton;
57 wxButton* importpbutton;
58 wxButton* changetsbutton;
59 wxButton* changegainbutton;
60 wxButton* loadbutton;
61 wxButton* unloadbutton;
62 wxButton* refreshbutton;
63 wxButton* closebutton;
64 struct dispatch::target<> corechange;
65 struct dispatch::target<> vstreamchange;
68 wxeditor_voicesub::wxeditor_voicesub(wxWindow* parent, emulator_instance& _inst)
69 : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit commentary track"), wxDefaultPosition, wxSize(-1, -1)),
70 inst(_inst)
72 CHECK_UI_THREAD;
73 closing = false;
74 Centre();
75 wxFlexGridSizer* top_s = new wxFlexGridSizer(6, 1, 0, 0);
76 SetSizer(top_s);
78 top_s->Add(subtitles = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(300, 400), 0, NULL,
79 wxLB_SINGLE), 1, wxGROW);
81 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
82 pbutton_s->Add(new wxStaticText(this, wxID_ANY, wxT("Stream")), 0, wxGROW);
83 pbutton_s->Add(playbutton = new wxButton(this, wxID_ANY, wxT("Play")), 0, wxGROW);
84 pbutton_s->Add(deletebutton = new wxButton(this, wxID_ANY, wxT("Delete")), 0, wxGROW);
85 top_s->Add(pbutton_s, 1, wxGROW);
86 pbutton_s->SetSizeHints(this);
88 pbutton_s = new wxBoxSizer(wxHORIZONTAL);
89 pbutton_s->Add(new wxStaticText(this, wxID_ANY, wxT("I/O")), 0, wxGROW);
90 pbutton_s->Add(importpbutton = new wxButton(this, wxID_ANY, wxT("Import")), 0, wxGROW);
91 pbutton_s->Add(exportpbutton = new wxButton(this, wxID_ANY, wxT("Export")), 0, wxGROW);
92 pbutton_s->Add(exportsbutton = new wxButton(this, wxID_ANY, wxT("Export all")), 0, wxGROW);
93 top_s->Add(pbutton_s, 1, wxGROW);
94 pbutton_s->SetSizeHints(this);
96 pbutton_s = new wxBoxSizer(wxHORIZONTAL);
97 pbutton_s->Add(new wxStaticText(this, wxID_ANY, wxT("Misc.")), 0, wxGROW);
98 pbutton_s->Add(changetsbutton = new wxButton(this, wxID_ANY, wxT("Change time")), 0, wxGROW);
99 pbutton_s->Add(changegainbutton = new wxButton(this, wxID_ANY, wxT("Change gain")), 0, wxGROW);
100 top_s->Add(pbutton_s, 1, wxGROW);
101 pbutton_s->SetSizeHints(this);
103 pbutton_s = new wxBoxSizer(wxHORIZONTAL);
104 pbutton_s->Add(new wxStaticText(this, wxID_ANY, wxT("Collection")), 0, wxGROW);
105 pbutton_s->Add(loadbutton = new wxButton(this, wxID_ANY, wxT("Load")), 0, wxGROW);
106 pbutton_s->Add(unloadbutton = new wxButton(this, wxID_ANY, wxT("Unload")), 0, wxGROW);
107 top_s->Add(pbutton_s, 1, wxGROW);
108 pbutton_s->SetSizeHints(this);
110 pbutton_s = new wxBoxSizer(wxHORIZONTAL);
111 pbutton_s->AddStretchSpacer();
112 pbutton_s->Add(refreshbutton = new wxButton(this, wxID_OK, wxT("Refresh")), 0, wxGROW);
113 pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
114 top_s->Add(pbutton_s, 1, wxGROW);
115 pbutton_s->SetSizeHints(this);
117 playbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
118 wxCommandEventHandler(wxeditor_voicesub::on_play), NULL, this);
119 deletebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
120 wxCommandEventHandler(wxeditor_voicesub::on_delete), NULL, this);
121 exportpbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
122 wxCommandEventHandler(wxeditor_voicesub::on_export), NULL, this);
123 exportsbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
124 wxCommandEventHandler(wxeditor_voicesub::on_export_s), NULL, this);
125 importpbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
126 wxCommandEventHandler(wxeditor_voicesub::on_import), NULL, this);
127 changetsbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
128 wxCommandEventHandler(wxeditor_voicesub::on_change_ts), NULL, this);
129 changegainbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
130 wxCommandEventHandler(wxeditor_voicesub::on_change_gain), NULL, this);
131 loadbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
132 wxCommandEventHandler(wxeditor_voicesub::on_load), NULL, this);
133 unloadbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
134 wxCommandEventHandler(wxeditor_voicesub::on_unload), NULL, this);
135 refreshbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
136 wxCommandEventHandler(wxeditor_voicesub::on_refresh), NULL, this);
137 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
138 wxCommandEventHandler(wxeditor_voicesub::on_close), NULL, this);
139 subtitles->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
140 wxCommandEventHandler(wxeditor_voicesub::on_select), NULL, this);
141 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxeditor_voicesub::on_wclose));
143 top_s->SetSizeHints(this);
144 Fit();
145 vstreamchange.set(inst.dispatch->voice_stream_change, [this]() {
146 runuifun([this]() -> void { this->refresh(); });
148 corechange.set(inst.dispatch->core_change, [this]() {
149 runuifun([this]() -> void { this->refresh(); });
151 refresh();
154 wxeditor_voicesub::~wxeditor_voicesub() throw()
158 void wxeditor_voicesub::on_select(wxCommandEvent& e)
160 CHECK_UI_THREAD;
161 if(closing)
162 return;
163 uint64_t id = get_id();
164 bool valid = (id != NOTHING);
165 playbutton->Enable(valid);
166 deletebutton->Enable(valid);
167 exportpbutton->Enable(valid);
168 changetsbutton->Enable(valid);
169 changegainbutton->Enable(valid);
172 void wxeditor_voicesub::on_play(wxCommandEvent& e)
174 CHECK_UI_THREAD;
175 uint64_t id = get_id();
176 if(id == NOTHING)
177 return;
178 try {
179 inst.commentary->play_stream(id);
180 } catch(std::exception& e) {
181 show_message_ok(this, "Error playing", e.what(), wxICON_EXCLAMATION);
185 void wxeditor_voicesub::on_delete(wxCommandEvent& e)
187 CHECK_UI_THREAD;
188 uint64_t id = get_id();
189 if(id == NOTHING)
190 return;
191 try {
192 inst.commentary->delete_stream(id);
193 } catch(std::exception& e) {
194 show_message_ok(this, "Error deleting", e.what(), wxICON_EXCLAMATION);
198 namespace
200 class _opus_or_sox
202 public:
203 typedef std::pair<std::string, enum voice_commentary::external_stream_format> returntype;
204 _opus_or_sox() {}
205 filedialog_input_params input(bool save) const
207 filedialog_input_params p;
208 p.types.push_back(filedialog_type_entry("Opus streams", "*.opus", "opus"));
209 p.types.push_back(filedialog_type_entry("SoX files", "*.sox", "sox"));
210 p.default_type = 0;
211 return p;
213 std::pair<std::string, enum voice_commentary::external_stream_format> output(
214 const filedialog_output_params& p, bool save) const
216 return std::make_pair(p.path, (p.typechoice == 1) ? voice_commentary::EXTFMT_SOX :
217 voice_commentary::EXTFMT_OGGOPUS);
219 } filetype_opus_sox;
222 void wxeditor_voicesub::on_export(wxCommandEvent& e)
224 CHECK_UI_THREAD;
225 uint64_t id = get_id();
226 if(id == NOTHING)
227 return;
228 try {
229 auto filename = choose_file_save(this, "Select file to epxort", UI_get_project_otherpath(inst),
230 filetype_opus_sox);
231 inst.commentary->export_stream(id, filename.first, filename.second);
232 } catch(canceled_exception& e) {
233 } catch(std::exception& e) {
234 show_message_ok(this, "Error exporting", e.what(), wxICON_EXCLAMATION);
238 void wxeditor_voicesub::on_export_s(wxCommandEvent& e)
240 CHECK_UI_THREAD;
241 try {
242 std::string filename;
243 filename = choose_file_save(this, "Select file to export superstream",
244 UI_get_project_otherpath(inst), filetype_sox);
245 inst.commentary->export_superstream(filename);
246 } catch(canceled_exception& e) {
247 } catch(std::exception& e) {
248 show_message_ok(this, "Error exporting superstream", e.what(), wxICON_EXCLAMATION);
252 void wxeditor_voicesub::on_import(wxCommandEvent& e)
254 CHECK_UI_THREAD;
255 try {
256 uint64_t ts;
257 ts = inst.commentary->parse_timebase(pick_text(this, "Enter timebase",
258 "Enter position for newly imported stream"));
259 auto filename = choose_file_save(this, "Select file to import", UI_get_project_otherpath(inst),
260 filetype_opus_sox);
261 inst.commentary->import_stream(ts, filename.first, filename.second);
262 } catch(canceled_exception& e) {
263 } catch(std::exception& e) {
264 show_message_ok(this, "Error importing", e.what(), wxICON_EXCLAMATION);
268 void wxeditor_voicesub::on_change_ts(wxCommandEvent& e)
270 CHECK_UI_THREAD;
271 uint64_t id = get_id();
272 if(id == NOTHING)
273 return;
274 try {
275 uint64_t ts;
276 ts = inst.commentary->parse_timebase(pick_text(this, "Enter timebase",
277 "Enter new position for stream"));
278 inst.commentary->alter_timebase(id, ts);
279 } catch(canceled_exception& e) {
280 } catch(std::exception& e) {
281 show_message_ok(this, "Error changing timebase", e.what(), wxICON_EXCLAMATION);
285 void wxeditor_voicesub::on_change_gain(wxCommandEvent& e)
287 CHECK_UI_THREAD;
288 uint64_t id = get_id();
289 if(id == NOTHING)
290 return;
291 try {
292 float gain;
293 std::string old = (stringfmt() << inst.commentary->get_gain(id)).str();
294 gain = parse_value<float>(pick_text(this, "Enter gain", "Enter new gain (dB) for stream", old));
295 inst.commentary->set_gain(id, gain);
296 } catch(canceled_exception& e) {
297 } catch(std::exception& e) {
298 show_message_ok(this, "Error changing gain", e.what(), wxICON_EXCLAMATION);
302 void wxeditor_voicesub::on_load(wxCommandEvent& e)
304 CHECK_UI_THREAD;
305 if(UI_in_project_context(inst))
306 return;
307 try {
308 std::string filename;
309 try {
310 //Use "." here because there can't be active project.
311 filename = choose_file_load(this, "Select collection to load", ".", filetype_commentary);
312 } catch(...) {
313 return;
315 inst.commentary->load_collection(filename);
316 } catch(canceled_exception& e) {
317 } catch(std::exception& e) {
318 show_message_ok(this, "Error loading collection", e.what(), wxICON_EXCLAMATION);
322 void wxeditor_voicesub::on_unload(wxCommandEvent& e)
324 CHECK_UI_THREAD;
325 if(UI_in_project_context(inst))
326 return;
327 inst.commentary->unload_collection();
330 void wxeditor_voicesub::on_refresh(wxCommandEvent& e)
332 refresh();
335 void wxeditor_voicesub::on_close(wxCommandEvent& e)
337 CHECK_UI_THREAD;
338 voicesub_open.erase(&inst);
339 Destroy();
342 void wxeditor_voicesub::refresh()
344 CHECK_UI_THREAD;
345 if(closing)
346 return;
347 bool cflag = inst.commentary->collection_loaded();
348 bool pflag = UI_in_project_context(inst);
349 unloadbutton->Enable(cflag && !pflag);
350 loadbutton->Enable(!pflag);
351 exportsbutton->Enable(cflag);
352 importpbutton->Enable(cflag);
353 int sel = subtitles->GetSelection();
354 subtitles->Clear();
355 smap.clear();
356 int next = 0;
357 for(auto i : inst.commentary->get_stream_info()) {
358 smap[next++] = i.id;
359 std::ostringstream tmp;
360 tmp << "#" << i.id << " " << inst.commentary->ts_seconds(i.length) << "s@"
361 << inst.commentary->ts_seconds(i.base) << "s";
362 float gain = inst.commentary->get_gain(i.id);
363 if(gain < -1e-5 || gain > 1e-5)
364 tmp << " (gain " << gain << "dB)";
365 std::string text = tmp.str();
366 subtitles->Append(towxstring(text));
368 if(sel != wxNOT_FOUND && sel < (ssize_t)subtitles->GetCount())
369 subtitles->SetSelection(sel);
370 else if(subtitles->GetCount())
371 subtitles->SetSelection(0);
372 wxCommandEvent e;
373 on_select(e);
376 uint64_t wxeditor_voicesub::get_id()
378 CHECK_UI_THREAD;
379 int id = subtitles->GetSelection();
380 return smap.count(id) ? smap[id] : NOTHING;
383 void wxeditor_voicesub::on_wclose(wxCloseEvent& e)
385 CHECK_UI_THREAD;
386 bool wasc = closing;
387 closing = true;
388 if(!wasc)
389 Destroy();
390 voicesub_open.erase(&inst);
393 bool wxeditor_voicesub::ShouldPreventAppExit() const { return false; }
395 void show_wxeditor_voicesub(wxWindow* parent, emulator_instance& inst)
397 CHECK_UI_THREAD;
398 if(voicesub_open.count(&inst))
399 return;
400 wxeditor_voicesub* v = new wxeditor_voicesub(parent, inst);
401 v->Show();
402 voicesub_open.insert(&inst);