1 // Copyright (c) 2010, Amar Takhar <verm@aegisub.org>
2 //
3 // Permission to use, copy, modify, and distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 
15 /// @file preferences.cpp
16 /// @brief Preferences dialogue
17 /// @ingroup configuration_ui
18 
19 #include "preferences.h"
20 
21 #include "ass_style_storage.h"
22 #include "audio_provider_factory.h"
23 #include "audio_renderer_waveform.h"
24 #include "command/command.h"
25 #include "compat.h"
26 #include "help_button.h"
27 #include "hotkey_data_view_model.h"
28 #include "include/aegisub/audio_player.h"
29 #include "include/aegisub/hotkey.h"
30 #include "include/aegisub/subtitles_provider.h"
31 #include "libresrc/libresrc.h"
32 #include "options.h"
33 #include "preferences_base.h"
34 #include "video_provider_manager.h"
35 
36 #ifdef WITH_PORTAUDIO
37 #include "audio_player_portaudio.h"
38 #endif
39 
40 #ifdef WITH_FFMS2
41 #include <ffms.h>
42 #endif
43 
44 #include <libaegisub/hotkey.h>
45 
46 #include <unordered_set>
47 
48 #include <wx/checkbox.h>
49 #include <wx/combobox.h>
50 #include <wx/dc.h>
51 #include <wx/event.h>
52 #include <wx/listctrl.h>
53 #include <wx/msgdlg.h>
54 #include <wx/srchctrl.h>
55 #include <wx/sizer.h>
56 #include <wx/spinctrl.h>
57 #include <wx/stattext.h>
58 #include <wx/treebook.h>
59 
60 namespace {
61 /// General preferences page
General(wxTreebook * book,Preferences * parent)62 void General(wxTreebook *book, Preferences *parent) {
63 	auto p = new OptionPage(book, parent, _("General"));
64 
65 	auto general = p->PageSizer(_("General"));
66 	p->OptionAdd(general, _("Check for updates on startup"), "App/Auto/Check For Updates");
67 	p->OptionAdd(general, _("Show main toolbar"), "App/Show Toolbar");
68 	p->OptionAdd(general, _("Save UI state in subtitles files"), "App/Save UI State");
69 	p->CellSkip(general);
70 
71 	p->OptionAdd(general, _("Toolbar Icon Size"), "App/Toolbar Icon Size");
72 	wxString autoload_modes[] = { _("Never"), _("Always"), _("Ask") };
73 	wxArrayString autoload_modes_arr(3, autoload_modes);
74 	p->OptionChoice(general, _("Automatically load linked files"), autoload_modes_arr, "App/Auto/Load Linked Files");
75 	p->OptionAdd(general, _("Undo Levels"), "Limits/Undo Levels", 2, 10000);
76 
77 	auto recent = p->PageSizer(_("Recently Used Lists"));
78 	p->OptionAdd(recent, _("Files"), "Limits/MRU", 0, 16);
79 	p->OptionAdd(recent, _("Find/Replace"), "Limits/Find Replace");
80 
81 	p->SetSizerAndFit(p->sizer);
82 }
83 
General_DefaultStyles(wxTreebook * book,Preferences * parent)84 void General_DefaultStyles(wxTreebook *book, Preferences *parent) {
85 	auto p = new OptionPage(book, parent, _("Default styles"), OptionPage::PAGE_SUB);
86 
87 	auto staticbox = new wxStaticBoxSizer(wxVERTICAL, p, _("Default style catalogs"));
88 	p->sizer->Add(staticbox, 0, wxEXPAND, 5);
89 	p->sizer->AddSpacer(8);
90 
91 	auto instructions = new wxStaticText(p, wxID_ANY, _("The chosen style catalogs will be loaded when you start a new file or import files in the various formats.\n\nYou can set up style catalogs in the Style Manager."));
92 	p->sizer->Fit(p);
93 	instructions->Wrap(400);
94 	staticbox->Add(instructions, 0, wxALL, 5);
95 	staticbox->AddSpacer(16);
96 
97 	auto general = new wxFlexGridSizer(2, 5, 5);
98 	general->AddGrowableCol(0, 1);
99 	staticbox->Add(general, 1, wxEXPAND, 5);
100 
101 	// Build a list of available style catalogs, and wished-available ones
102 	auto const& avail_catalogs = AssStyleStorage::GetCatalogs();
103 	std::unordered_set<std::string> catalogs_set(begin(avail_catalogs), end(avail_catalogs));
104 	// Always include one named "Default" even if it doesn't exist (ensure there is at least one on the list)
105 	catalogs_set.insert("Default");
106 	// Include all catalogs named in the existing configuration
107 	static const char *formats[] = { "ASS", "MicroDVD", "SRT", "TTXT", "TXT" };
108 	for (auto formatname : formats)
109 		catalogs_set.insert(OPT_GET("Subtitle Format/" + std::string(formatname) + "/Default Style Catalog")->GetString());
110 	// Sorted version
111 	wxArrayString catalogs;
112 	for (auto const& cn : catalogs_set)
113 		catalogs.Add(to_wx(cn));
114 	catalogs.Sort();
115 
116 	p->OptionChoice(general, _("New files"), catalogs, "Subtitle Format/ASS/Default Style Catalog");
117 	p->OptionChoice(general, _("MicroDVD import"), catalogs, "Subtitle Format/MicroDVD/Default Style Catalog");
118 	p->OptionChoice(general, _("SRT import"), catalogs, "Subtitle Format/SRT/Default Style Catalog");
119 	p->OptionChoice(general, _("TTXT import"), catalogs, "Subtitle Format/TTXT/Default Style Catalog");
120 	p->OptionChoice(general, _("Plain text import"), catalogs, "Subtitle Format/TXT/Default Style Catalog");
121 
122 	p->SetSizerAndFit(p->sizer);
123 }
124 
125 /// Audio preferences page
Audio(wxTreebook * book,Preferences * parent)126 void Audio(wxTreebook *book, Preferences *parent) {
127 	auto p = new OptionPage(book, parent, _("Audio"));
128 
129 	auto general = p->PageSizer(_("Options"));
130 	p->OptionAdd(general, _("Default mouse wheel to zoom"), "Audio/Wheel Default to Zoom");
131 	p->OptionAdd(general, _("Lock scroll on cursor"), "Audio/Lock Scroll on Cursor");
132 	p->OptionAdd(general, _("Snap markers by default"), "Audio/Snap/Enable");
133 	p->OptionAdd(general, _("Auto-focus on mouse over"), "Audio/Auto/Focus");
134 	p->OptionAdd(general, _("Play audio when stepping in video"), "Audio/Plays When Stepping Video");
135 	p->OptionAdd(general, _("Left-click-drag moves end marker"), "Audio/Drag Timing");
136 	p->OptionAdd(general, _("Default timing length (ms)"), "Timing/Default Duration", 0, 36000);
137 	p->OptionAdd(general, _("Default lead-in length (ms)"), "Audio/Lead/IN", 0, 36000);
138 	p->OptionAdd(general, _("Default lead-out length (ms)"), "Audio/Lead/OUT", 0, 36000);
139 
140 	p->OptionAdd(general, _("Marker drag-start sensitivity (px)"), "Audio/Start Drag Sensitivity", 1, 15);
141 	p->OptionAdd(general, _("Line boundary thickness (px)"), "Audio/Line Boundaries Thickness", 1, 5);
142 	p->OptionAdd(general, _("Maximum snap distance (px)"), "Audio/Snap/Distance", 0, 25);
143 
144 	const wxString dtl_arr[] = { _("Don't show"), _("Show previous"), _("Show previous and next"), _("Show all") };
145 	wxArrayString choice_dtl(4, dtl_arr);
146 	p->OptionChoice(general, _("Show inactive lines"), choice_dtl, "Audio/Inactive Lines Display Mode");
147 	p->CellSkip(general);
148 	p->OptionAdd(general, _("Include commented inactive lines"), "Audio/Display/Draw/Inactive Comments");
149 
150 	auto display = p->PageSizer(_("Display Visual Options"));
151 	p->OptionAdd(display, _("Keyframes in dialogue mode"), "Audio/Display/Draw/Keyframes in Dialogue Mode");
152 	p->OptionAdd(display, _("Keyframes in karaoke mode"), "Audio/Display/Draw/Keyframes in Karaoke Mode");
153 	p->OptionAdd(display, _("Cursor time"), "Audio/Display/Draw/Cursor Time");
154 	p->OptionAdd(display, _("Video position"), "Audio/Display/Draw/Video Position");
155 	p->OptionAdd(display, _("Seconds boundaries"), "Audio/Display/Draw/Seconds");
156 	p->CellSkip(display);
157 	p->OptionChoice(display, _("Waveform Style"), AudioWaveformRenderer::GetWaveformStyles(), "Audio/Display/Waveform Style");
158 
159 	auto label = p->PageSizer(_("Audio labels"));
160 	p->OptionFont(label, "Audio/Karaoke/");
161 
162 	p->SetSizerAndFit(p->sizer);
163 }
164 
165 /// Video preferences page
Video(wxTreebook * book,Preferences * parent)166 void Video(wxTreebook *book, Preferences *parent) {
167 	auto p = new OptionPage(book, parent, _("Video"));
168 
169 	auto general = p->PageSizer(_("Options"));
170 	p->OptionAdd(general, _("Show keyframes in slider"), "Video/Slider/Show Keyframes");
171 	p->CellSkip(general);
172 	p->OptionAdd(general, _("Only show visual tools when mouse is over video"), "Tool/Visual/Autohide");
173 	p->CellSkip(general);
174 	p->OptionAdd(general, _("Seek video to line start on selection change"), "Video/Subtitle Sync");
175 	p->CellSkip(general);
176 	p->OptionAdd(general, _("Automatically open audio when opening video"), "Video/Open Audio");
177 	p->CellSkip(general);
178 
179 	const wxString czoom_arr[24] = { "12.5%", "25%", "37.5%", "50%", "62.5%", "75%", "87.5%", "100%", "112.5%", "125%", "137.5%", "150%", "162.5%", "175%", "187.5%", "200%", "212.5%", "225%", "237.5%", "250%", "262.5%", "275%", "287.5%", "300%" };
180 	wxArrayString choice_zoom(24, czoom_arr);
181 	p->OptionChoice(general, _("Default Zoom"), choice_zoom, "Video/Default Zoom");
182 
183 	p->OptionAdd(general, _("Fast jump step in frames"), "Video/Slider/Fast Jump Step");
184 
185 	const wxString cscr_arr[3] = { "?video", "?script", "." };
186 	wxArrayString scr_res(3, cscr_arr);
187 	p->OptionChoice(general, _("Screenshot save path"), scr_res, "Path/Screenshot");
188 
189 	auto resolution = p->PageSizer(_("Script Resolution"));
190 	wxControl *autocb = p->OptionAdd(resolution, _("Use resolution of first video opened"), "Subtitle/Default Resolution/Auto");
191 	p->CellSkip(resolution);
192 	p->DisableIfChecked(autocb,
193 		p->OptionAdd(resolution, _("Default width"), "Subtitle/Default Resolution/Width"));
194 	p->DisableIfChecked(autocb,
195 		p->OptionAdd(resolution, _("Default height"), "Subtitle/Default Resolution/Height"));
196 
197 	const wxString cres_arr[] = {_("Never"), _("Ask"), _("Always set"), _("Always resample")};
198 	wxArrayString choice_res(4, cres_arr);
199 	p->OptionChoice(resolution, _("Match video resolution on open"), choice_res, "Video/Script Resolution Mismatch");
200 
201 	p->SetSizerAndFit(p->sizer);
202 }
203 
204 /// Interface preferences page
Interface(wxTreebook * book,Preferences * parent)205 void Interface(wxTreebook *book, Preferences *parent) {
206 	auto p = new OptionPage(book, parent, _("Interface"));
207 
208 	auto edit_box = p->PageSizer(_("Edit Box"));
209 	p->OptionAdd(edit_box, _("Enable call tips"), "App/Call Tips");
210 	p->OptionAdd(edit_box, _("Overwrite in time boxes"), "Subtitle/Time Edit/Insert Mode");
211 	p->CellSkip(edit_box);
212 	p->OptionAdd(edit_box, _("Enable syntax highlighting"), "Subtitle/Highlight/Syntax");
213 	p->OptionBrowse(edit_box, _("Dictionaries path"), "Path/Dictionary");
214 	p->OptionFont(edit_box, "Subtitle/Edit Box/");
215 
216 	auto character_count = p->PageSizer(_("Character Counter"));
217 	p->OptionAdd(character_count, _("Maximum characters per line"), "Subtitle/Character Limit", 0, 1000);
218 	p->OptionAdd(character_count, _("Characters Per Second Warning Threshold"), "Subtitle/Character Counter/CPS Warning Threshold", 0, 1000);
219 	p->OptionAdd(character_count, _("Characters Per Second Error Threshold"), "Subtitle/Character Counter/CPS Error Threshold", 0, 1000);
220 	p->OptionAdd(character_count, _("Ignore whitespace"), "Subtitle/Character Counter/Ignore Whitespace");
221 	p->OptionAdd(character_count, _("Ignore punctuation"), "Subtitle/Character Counter/Ignore Punctuation");
222 
223 	auto grid = p->PageSizer(_("Grid"));
224 	p->OptionAdd(grid, _("Focus grid on click"), "Subtitle/Grid/Focus Allow");
225 	p->OptionAdd(grid, _("Highlight visible subtitles"), "Subtitle/Grid/Highlight Subtitles in Frame");
226 	p->OptionAdd(grid, _("Hide overrides symbol"), "Subtitle/Grid/Hide Overrides Char");
227 	p->OptionFont(grid, "Subtitle/Grid/");
228 
229 	p->SetSizerAndFit(p->sizer);
230 }
231 
232 /// Interface Colours preferences subpage
Interface_Colours(wxTreebook * book,Preferences * parent)233 void Interface_Colours(wxTreebook *book, Preferences *parent) {
234 	auto p = new OptionPage(book, parent, _("Colors"), OptionPage::PAGE_SCROLL|OptionPage::PAGE_SUB);
235 
236 	delete p->sizer;
237 	wxSizer *main_sizer = new wxBoxSizer(wxHORIZONTAL);
238 
239 	p->sizer = new wxBoxSizer(wxVERTICAL);
240 	main_sizer->Add(p->sizer, wxEXPAND);
241 
242 	auto audio = p->PageSizer(_("Audio Display"));
243 	p->OptionAdd(audio, _("Play cursor"), "Colour/Audio Display/Play Cursor");
244 	p->OptionAdd(audio, _("Line boundary start"), "Colour/Audio Display/Line boundary Start");
245 	p->OptionAdd(audio, _("Line boundary end"), "Colour/Audio Display/Line boundary End");
246 	p->OptionAdd(audio, _("Line boundary inactive line"), "Colour/Audio Display/Line Boundary Inactive Line");
247 	p->OptionAdd(audio, _("Syllable boundaries"), "Colour/Audio Display/Syllable Boundaries");
248 	p->OptionAdd(audio, _("Seconds boundaries"), "Colour/Audio Display/Seconds Line");
249 
250 	auto syntax = p->PageSizer(_("Syntax Highlighting"));
251 	p->OptionAdd(syntax, _("Background"), "Colour/Subtitle/Background");
252 	p->OptionAdd(syntax, _("Normal"), "Colour/Subtitle/Syntax/Normal");
253 	p->OptionAdd(syntax, _("Comments"), "Colour/Subtitle/Syntax/Comment");
254 	p->OptionAdd(syntax, _("Drawings"), "Colour/Subtitle/Syntax/Drawing");
255 	p->OptionAdd(syntax, _("Brackets"), "Colour/Subtitle/Syntax/Brackets");
256 	p->OptionAdd(syntax, _("Slashes and Parentheses"), "Colour/Subtitle/Syntax/Slashes");
257 	p->OptionAdd(syntax, _("Tags"), "Colour/Subtitle/Syntax/Tags");
258 	p->OptionAdd(syntax, _("Parameters"), "Colour/Subtitle/Syntax/Parameters");
259 	p->OptionAdd(syntax, _("Error"), "Colour/Subtitle/Syntax/Error");
260 	p->OptionAdd(syntax, _("Error Background"), "Colour/Subtitle/Syntax/Background/Error");
261 	p->OptionAdd(syntax, _("Line Break"), "Colour/Subtitle/Syntax/Line Break");
262 	p->OptionAdd(syntax, _("Karaoke templates"), "Colour/Subtitle/Syntax/Karaoke Template");
263 	p->OptionAdd(syntax, _("Karaoke variables"), "Colour/Subtitle/Syntax/Karaoke Variable");
264 
265 	p->sizer = new wxBoxSizer(wxVERTICAL);
266 	main_sizer->AddSpacer(5);
267 	main_sizer->Add(p->sizer, wxEXPAND);
268 
269 	auto color_schemes = p->PageSizer(_("Audio Color Schemes"));
270 	wxArrayString schemes = to_wx(OPT_GET("Audio/Colour Schemes")->GetListString());
271 	p->OptionChoice(color_schemes, _("Spectrum"), schemes, "Colour/Audio Display/Spectrum");
272 	p->OptionChoice(color_schemes, _("Waveform"), schemes, "Colour/Audio Display/Waveform");
273 
274 	auto grid = p->PageSizer(_("Subtitle Grid"));
275 	p->OptionAdd(grid, _("Standard foreground"), "Colour/Subtitle Grid/Standard");
276 	p->OptionAdd(grid, _("Standard background"), "Colour/Subtitle Grid/Background/Background");
277 	p->OptionAdd(grid, _("Selection foreground"), "Colour/Subtitle Grid/Selection");
278 	p->OptionAdd(grid, _("Selection background"), "Colour/Subtitle Grid/Background/Selection");
279 	p->OptionAdd(grid, _("Collision foreground"), "Colour/Subtitle Grid/Collision");
280 	p->OptionAdd(grid, _("In frame background"), "Colour/Subtitle Grid/Background/Inframe");
281 	p->OptionAdd(grid, _("Comment background"), "Colour/Subtitle Grid/Background/Comment");
282 	p->OptionAdd(grid, _("Selected comment background"), "Colour/Subtitle Grid/Background/Selected Comment");
283 	p->OptionAdd(grid, _("Header background"), "Colour/Subtitle Grid/Header");
284 	p->OptionAdd(grid, _("Left Column"), "Colour/Subtitle Grid/Left Column");
285 	p->OptionAdd(grid, _("Active Line Border"), "Colour/Subtitle Grid/Active Border");
286 	p->OptionAdd(grid, _("Lines"), "Colour/Subtitle Grid/Lines");
287 	p->OptionAdd(grid, _("CPS Error"), "Colour/Subtitle Grid/CPS Error");
288 
289 	p->sizer = main_sizer;
290 
291 	p->SetSizerAndFit(p->sizer);
292 }
293 
294 /// Backup preferences page
Backup(wxTreebook * book,Preferences * parent)295 void Backup(wxTreebook *book, Preferences *parent) {
296 	auto p = new OptionPage(book, parent, _("Backup"));
297 
298 	auto save = p->PageSizer(_("Automatic Save"));
299 	wxControl *cb = p->OptionAdd(save, _("Enable"), "App/Auto/Save");
300 	p->CellSkip(save);
301 	p->EnableIfChecked(cb,
302 		p->OptionAdd(save, _("Interval in seconds"), "App/Auto/Save Every Seconds", 1));
303 	p->OptionBrowse(save, _("Path"), "Path/Auto/Save", cb, true);
304 	p->OptionAdd(save, _("Autosave after every change"), "App/Auto/Save on Every Change");
305 
306 	auto backup = p->PageSizer(_("Automatic Backup"));
307 	cb = p->OptionAdd(backup, _("Enable"), "App/Auto/Backup");
308 	p->CellSkip(backup);
309 	p->OptionBrowse(backup, _("Path"), "Path/Auto/Backup", cb, true);
310 
311 	p->SetSizerAndFit(p->sizer);
312 }
313 
314 /// Automation preferences page
Automation(wxTreebook * book,Preferences * parent)315 void Automation(wxTreebook *book, Preferences *parent) {
316 	auto p = new OptionPage(book, parent, _("Automation"));
317 
318 	auto general = p->PageSizer(_("General"));
319 
320 	p->OptionAdd(general, _("Base path"), "Path/Automation/Base");
321 	p->OptionAdd(general, _("Include path"), "Path/Automation/Include");
322 	p->OptionAdd(general, _("Auto-load path"), "Path/Automation/Autoload");
323 
324 	const wxString tl_arr[6] = { _("0: Fatal"), _("1: Error"), _("2: Warning"), _("3: Hint"), _("4: Debug"), _("5: Trace") };
325 	wxArrayString tl_choice(6, tl_arr);
326 	p->OptionChoice(general, _("Trace level"), tl_choice, "Automation/Trace Level");
327 
328 	const wxString ar_arr[4] = { _("No scripts"), _("Subtitle-local scripts"), _("Global autoload scripts"), _("All scripts") };
329 	wxArrayString ar_choice(4, ar_arr);
330 	p->OptionChoice(general, _("Autoreload on Export"), ar_choice, "Automation/Autoreload Mode");
331 
332 	p->SetSizerAndFit(p->sizer);
333 }
334 
335 /// Advanced preferences page
Advanced(wxTreebook * book,Preferences * parent)336 void Advanced(wxTreebook *book, Preferences *parent) {
337 	auto p = new OptionPage(book, parent, _("Advanced"));
338 
339 	auto general = p->PageSizer(_("General"));
340 
341 	auto warning = new wxStaticText(p, wxID_ANY ,_("Changing these settings might result in bugs and/or crashes.  Do not touch these unless you know what you're doing."));
342 	warning->SetFont(wxFont(12, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
343 	p->sizer->Fit(p);
344 	warning->Wrap(400);
345 	general->Add(warning, 0, wxALL, 5);
346 
347 	p->SetSizerAndFit(p->sizer);
348 }
349 
350 /// Advanced Audio preferences subpage
Advanced_Audio(wxTreebook * book,Preferences * parent)351 void Advanced_Audio(wxTreebook *book, Preferences *parent) {
352 	auto p = new OptionPage(book, parent, _("Audio"), OptionPage::PAGE_SUB);
353 
354 	auto expert = p->PageSizer(_("Expert"));
355 
356 	wxArrayString ap_choice = to_wx(GetAudioProviderNames());
357 	p->OptionChoice(expert, _("Audio provider"), ap_choice, "Audio/Provider");
358 
359 	wxArrayString apl_choice = to_wx(AudioPlayerFactory::GetClasses());
360 	p->OptionChoice(expert, _("Audio player"), apl_choice, "Audio/Player");
361 
362 	auto cache = p->PageSizer(_("Cache"));
363 	const wxString ct_arr[3] = { _("None (NOT RECOMMENDED)"), _("RAM"), _("Hard Disk") };
364 	wxArrayString ct_choice(3, ct_arr);
365 	p->OptionChoice(cache, _("Cache type"), ct_choice, "Audio/Cache/Type");
366 	p->OptionBrowse(cache, _("Path"), "Audio/Cache/HD/Location");
367 
368 	auto spectrum = p->PageSizer(_("Spectrum"));
369 
370 	const wxString sq_arr[4] = { _("Regular quality"), _("Better quality"), _("High quality"), _("Insane quality") };
371 	wxArrayString sq_choice(4, sq_arr);
372 	p->OptionChoice(spectrum, _("Quality"), sq_choice, "Audio/Renderer/Spectrum/Quality");
373 
374 	p->OptionAdd(spectrum, _("Cache memory max (MB)"), "Audio/Renderer/Spectrum/Memory Max", 2, 1024);
375 
376 #ifdef WITH_AVISYNTH
377 	auto avisynth = p->PageSizer("Avisynth");
378 	const wxString adm_arr[3] = { "ConvertToMono", "GetLeftChannel", "GetRightChannel" };
379 	wxArrayString adm_choice(3, adm_arr);
380 	p->OptionChoice(avisynth, _("Avisynth down-mixer"), adm_choice, "Audio/Downmixer");
381 	p->OptionAdd(avisynth, _("Force sample rate"), "Provider/Audio/AVS/Sample Rate");
382 #endif
383 
384 #ifdef WITH_FFMS2
385 	auto ffms = p->PageSizer("FFmpegSource");
386 
387 	const wxString error_modes[] = { _("Ignore"), _("Clear"), _("Stop"), _("Abort") };
388 	wxArrayString error_modes_choice(4, error_modes);
389 	p->OptionChoice(ffms, _("Audio indexing error handling mode"), error_modes_choice, "Provider/Audio/FFmpegSource/Decode Error Handling");
390 
391 	p->OptionAdd(ffms, _("Always index all audio tracks"), "Provider/FFmpegSource/Index All Tracks");
392 #endif
393 
394 #ifdef WITH_PORTAUDIO
395 	auto portaudio = p->PageSizer("Portaudio");
396 	p->OptionChoice(portaudio, _("Portaudio device"), PortAudioPlayer::GetOutputDevices(), "Player/Audio/PortAudio/Device Name");
397 #endif
398 
399 #ifdef WITH_OSS
400 	auto oss = p->PageSizer("OSS");
401 	p->OptionBrowse(oss, _("OSS Device"), "Player/Audio/OSS/Device");
402 #endif
403 
404 #ifdef WITH_DIRECTSOUND
405 	auto dsound = p->PageSizer("DirectSound");
406 	p->OptionAdd(dsound, _("Buffer latency"), "Player/Audio/DirectSound/Buffer Latency", 1, 1000);
407 	p->OptionAdd(dsound, _("Buffer length"), "Player/Audio/DirectSound/Buffer Length", 1, 100);
408 #endif
409 
410 	p->SetSizerAndFit(p->sizer);
411 }
412 
413 /// Advanced Video preferences subpage
Advanced_Video(wxTreebook * book,Preferences * parent)414 void Advanced_Video(wxTreebook *book, Preferences *parent) {
415 	auto p = new OptionPage(book, parent, _("Video"), OptionPage::PAGE_SUB);
416 
417 	auto expert = p->PageSizer(_("Expert"));
418 
419 	wxArrayString vp_choice = to_wx(VideoProviderFactory::GetClasses());
420 	p->OptionChoice(expert, _("Video provider"), vp_choice, "Video/Provider");
421 
422 	wxArrayString sp_choice = to_wx(SubtitlesProviderFactory::GetClasses());
423 	p->OptionChoice(expert, _("Subtitles provider"), sp_choice, "Subtitle/Provider");
424 
425 	p->CellSkip(expert);
426 	p->OptionAdd(expert, _("Force BT.601"), "Video/Force BT.601");
427 
428 #ifdef WITH_AVISYNTH
429 	auto avisynth = p->PageSizer("Avisynth");
430 	p->OptionAdd(avisynth, _("Allow pre-2.56a Avisynth"), "Provider/Avisynth/Allow Ancient");
431 	p->CellSkip(avisynth);
432 	p->OptionAdd(avisynth, _("Avisynth memory limit"), "Provider/Avisynth/Memory Max");
433 #endif
434 
435 #ifdef WITH_FFMS2
436 	auto ffms = p->PageSizer("FFmpegSource");
437 
438 	const wxString log_levels[] = { "Quiet", "Panic", "Fatal", "Error", "Warning", "Info", "Verbose", "Debug" };
439 	wxArrayString log_levels_choice(8, log_levels);
440 	p->OptionChoice(ffms, _("Debug log verbosity"), log_levels_choice, "Provider/FFmpegSource/Log Level");
441 
442 	p->OptionAdd(ffms, _("Decoding threads"), "Provider/Video/FFmpegSource/Decoding Threads", -1);
443 	p->OptionAdd(ffms, _("Enable unsafe seeking"), "Provider/Video/FFmpegSource/Unsafe Seeking");
444 #endif
445 
446 	p->SetSizerAndFit(p->sizer);
447 }
448 
449 /// wxDataViewIconTextRenderer with command name autocompletion
450 class CommandRenderer final : public wxDataViewCustomRenderer {
451 	wxArrayString autocomplete;
452 	wxDataViewIconText value;
453 	static const int icon_width = 20;
454 
455 public:
CommandRenderer()456 	CommandRenderer()
457 	: wxDataViewCustomRenderer("wxDataViewIconText", wxDATAVIEW_CELL_EDITABLE)
458 	, autocomplete(to_wx(cmd::get_registered_commands()))
459 	{
460 	}
461 
CreateEditorCtrl(wxWindow * parent,wxRect label_rect,wxVariant const & value)462 	wxWindow *CreateEditorCtrl(wxWindow *parent, wxRect label_rect, wxVariant const& value) override {
463 		wxDataViewIconText iconText;
464 		iconText << value;
465 
466 		wxString text = iconText.GetText();
467 
468 		// adjust the label rect to take the width of the icon into account
469 		label_rect.x += icon_width;
470 		label_rect.width -= icon_width;
471 
472 		wxTextCtrl* ctrl = new wxTextCtrl(parent, -1, text, label_rect.GetPosition(), label_rect.GetSize(), wxTE_PROCESS_ENTER);
473 		ctrl->SetInsertionPointEnd();
474 		ctrl->SelectAll();
475 		ctrl->AutoComplete(autocomplete);
476 		return ctrl;
477 	}
478 
SetValue(wxVariant const & var)479 	bool SetValue(wxVariant const& var) override {
480 		value << var;
481 		return true;
482 	}
483 
Render(wxRect rect,wxDC * dc,int state)484 	bool Render(wxRect rect, wxDC *dc, int state) override {
485 		wxIcon const& icon = value.GetIcon();
486 		if (icon.IsOk())
487 			dc->DrawIcon(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2);
488 
489 		RenderText(value.GetText(), icon_width, rect, dc, state);
490 
491 		return true;
492 	}
493 
GetSize() const494 	wxSize GetSize() const override {
495 		if (!value.GetText().empty()) {
496 			wxSize size = GetTextExtent(value.GetText());
497 			size.x += icon_width;
498 			return size;
499 		}
500 		return wxSize(80,20);
501 	}
502 
GetValueFromEditorCtrl(wxWindow * editor,wxVariant & var)503 	bool GetValueFromEditorCtrl(wxWindow* editor, wxVariant &var) override {
504 		wxTextCtrl *text = static_cast<wxTextCtrl*>(editor);
505 		wxDataViewIconText iconText(text->GetValue(), value.GetIcon());
506 		var << iconText;
507 		return true;
508 	}
509 
GetValue(wxVariant &) const510 	bool GetValue(wxVariant &) const override { return false; }
HasEditorCtrl() const511 	bool HasEditorCtrl() const override { return true; }
512 };
513 
514 class HotkeyRenderer final : public wxDataViewCustomRenderer {
515 	wxString value;
516 	wxTextCtrl *ctrl = nullptr;
517 
518 public:
HotkeyRenderer()519 	HotkeyRenderer()
520 	: wxDataViewCustomRenderer("string", wxDATAVIEW_CELL_EDITABLE)
521 	{ }
522 
CreateEditorCtrl(wxWindow * parent,wxRect label_rect,wxVariant const & var)523 	wxWindow *CreateEditorCtrl(wxWindow *parent, wxRect label_rect, wxVariant const& var) override {
524 		ctrl = new wxTextCtrl(parent, -1, var.GetString(), label_rect.GetPosition(), label_rect.GetSize(), wxTE_PROCESS_ENTER);
525 		ctrl->SetInsertionPointEnd();
526 		ctrl->SelectAll();
527 		ctrl->Bind(wxEVT_CHAR_HOOK, &HotkeyRenderer::OnKeyDown, this);
528 		return ctrl;
529 	}
530 
OnKeyDown(wxKeyEvent & evt)531 	void OnKeyDown(wxKeyEvent &evt) {
532 		ctrl->ChangeValue(to_wx(hotkey::keypress_to_str(evt.GetKeyCode(), evt.GetModifiers())));
533 	}
534 
SetValue(wxVariant const & var)535 	bool SetValue(wxVariant const& var) override {
536 		value = var.GetString();
537 		return true;
538 	}
539 
Render(wxRect rect,wxDC * dc,int state)540 	bool Render(wxRect rect, wxDC *dc, int state) override {
541 		RenderText(value, 0, rect, dc, state);
542 		return true;
543 	}
544 
GetValueFromEditorCtrl(wxWindow *,wxVariant & var)545 	bool GetValueFromEditorCtrl(wxWindow*, wxVariant &var) override {
546 		var = ctrl->GetValue();
547 		return true;
548 	}
549 
GetValue(wxVariant &) const550 	bool GetValue(wxVariant &) const override { return false; }
GetSize() const551 	wxSize GetSize() const override { return !value ? wxSize(80, 20) : GetTextExtent(value); }
HasEditorCtrl() const552 	bool HasEditorCtrl() const override { return true; }
553 };
554 
edit_item(wxDataViewCtrl * dvc,wxDataViewItem item)555 static void edit_item(wxDataViewCtrl *dvc, wxDataViewItem item) {
556 	dvc->EditItem(item, dvc->GetColumn(0));
557 }
558 
559 class Interface_Hotkeys final : public OptionPage {
560 	wxDataViewCtrl *dvc;
561 	wxObjectDataPtr<HotkeyDataViewModel> model;
562 	wxSearchCtrl *quick_search;
563 
564 	void OnNewButton(wxCommandEvent&);
565 	void OnUpdateFilter(wxCommandEvent&);
566 public:
567 	Interface_Hotkeys(wxTreebook *book, Preferences *parent);
568 };
569 
570 /// Interface Hotkeys preferences subpage
Interface_Hotkeys(wxTreebook * book,Preferences * parent)571 Interface_Hotkeys::Interface_Hotkeys(wxTreebook *book, Preferences *parent)
572 : OptionPage(book, parent, _("Hotkeys"), OptionPage::PAGE_SUB)
573 , model(new HotkeyDataViewModel(parent))
574 {
575 	quick_search = new wxSearchCtrl(this, -1);
576 	auto new_button = new wxButton(this, -1, _("&New"));
577 	auto edit_button = new wxButton(this, -1, _("&Edit"));
578 	auto delete_button = new wxButton(this, -1, _("&Delete"));
579 
580 	new_button->Bind(wxEVT_BUTTON, &Interface_Hotkeys::OnNewButton, this);
581 	edit_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { edit_item(dvc, dvc->GetSelection()); });
582 	delete_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { model->Delete(dvc->GetSelection()); });
583 
584 	quick_search->Bind(wxEVT_TEXT, &Interface_Hotkeys::OnUpdateFilter, this);
585 	quick_search->Bind(wxEVT_SEARCHCTRL_CANCEL_BTN, [=](wxCommandEvent&) { quick_search->SetValue(""); });
586 
587 	dvc = new wxDataViewCtrl(this, -1);
588 	dvc->AssociateModel(model.get());
589 #ifndef __APPLE__
590 	dvc->AppendColumn(new wxDataViewColumn("Hotkey", new HotkeyRenderer, 0, 125, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE));
591 	dvc->AppendColumn(new wxDataViewColumn("Command", new CommandRenderer, 1, 250, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE));
592 #else
593 	auto col = new wxDataViewColumn("Hotkey", new wxDataViewTextRenderer("string", wxDATAVIEW_CELL_EDITABLE), 0, 150, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE);
594 	col->SetMinWidth(150);
595 	dvc->AppendColumn(col);
596 	dvc->AppendColumn(new wxDataViewColumn("Command", new wxDataViewIconTextRenderer("wxDataViewIconText", wxDATAVIEW_CELL_EDITABLE), 1, 250, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE));
597 #endif
598 	dvc->AppendTextColumn("Description", 2, wxDATAVIEW_CELL_INERT, 300, wxALIGN_LEFT, wxCOL_SORTABLE | wxCOL_RESIZABLE);
599 
600 	wxSizer *buttons = new wxBoxSizer(wxHORIZONTAL);
601 	buttons->Add(quick_search, wxSizerFlags().Border());
602 	buttons->AddStretchSpacer(1);
603 	buttons->Add(new_button, wxSizerFlags().Border().Right());
604 	buttons->Add(edit_button, wxSizerFlags().Border().Right());
605 	buttons->Add(delete_button, wxSizerFlags().Border().Right());
606 
607 	sizer->Add(buttons, wxSizerFlags().Expand());
608 	sizer->Add(dvc, wxSizerFlags(1).Expand().Border(wxLEFT | wxRIGHT));
609 
610 	SetSizerAndFit(sizer);
611 }
612 
OnNewButton(wxCommandEvent &)613 void Interface_Hotkeys::OnNewButton(wxCommandEvent&) {
614 	wxDataViewItem sel = dvc->GetSelection();
615 	dvc->ExpandAncestors(sel);
616 	dvc->Expand(sel);
617 
618 	wxDataViewItem new_item = model->New(sel);
619 	if (new_item.IsOk()) {
620 		dvc->Select(new_item);
621 		dvc->EnsureVisible(new_item);
622 		edit_item(dvc, new_item);
623 	}
624 }
625 
OnUpdateFilter(wxCommandEvent &)626 void Interface_Hotkeys::OnUpdateFilter(wxCommandEvent&) {
627 	model->SetFilter(quick_search->GetValue());
628 
629 	if (!quick_search->GetValue().empty()) {
630 		wxDataViewItemArray contexts;
631 		model->GetChildren(wxDataViewItem(nullptr), contexts);
632 		for (auto const& context : contexts)
633 			dvc->Expand(context);
634 	}
635 }
636 }
637 
SetOption(std::unique_ptr<agi::OptionValue> new_value)638 void Preferences::SetOption(std::unique_ptr<agi::OptionValue> new_value) {
639 	pending_changes[new_value->GetName()] = std::move(new_value);
640 	applyButton->Enable(true);
641 }
642 
AddPendingChange(Thunk const & callback)643 void Preferences::AddPendingChange(Thunk const& callback) {
644 	pending_callbacks.push_back(callback);
645 	applyButton->Enable(true);
646 }
647 
AddChangeableOption(std::string const & name)648 void Preferences::AddChangeableOption(std::string const& name) {
649 	option_names.push_back(name);
650 }
651 
OnOK(wxCommandEvent & event)652 void Preferences::OnOK(wxCommandEvent &event) {
653 	OnApply(event);
654 	EndModal(0);
655 }
656 
OnApply(wxCommandEvent &)657 void Preferences::OnApply(wxCommandEvent &) {
658 	for (auto const& change : pending_changes)
659 		OPT_SET(change.first)->Set(change.second.get());
660 	pending_changes.clear();
661 
662 	for (auto const& thunk : pending_callbacks)
663 		thunk();
664 	pending_callbacks.clear();
665 
666 	applyButton->Enable(false);
667 	config::opt->Flush();
668 }
669 
OnResetDefault(wxCommandEvent &)670 void Preferences::OnResetDefault(wxCommandEvent&) {
671 	if (wxYES != wxMessageBox(_("Are you sure that you want to restore the defaults? All your settings will be overridden."), _("Restore defaults?"), wxYES_NO))
672 		return;
673 
674 	for (auto const& opt_name : option_names) {
675 		agi::OptionValue *opt = OPT_SET(opt_name);
676 		if (!opt->IsDefault())
677 			opt->Reset();
678 	}
679 	config::opt->Flush();
680 
681 	agi::hotkey::Hotkey def_hotkeys("", GET_DEFAULT_CONFIG(default_hotkey));
682 	hotkey::inst->SetHotkeyMap(def_hotkeys.GetHotkeyMap());
683 
684 	// Close and reopen the dialog to update all the controls with the new values
685 	OPT_SET("Tool/Preferences/Page")->SetInt(book->GetSelection());
686 	EndModal(-1);
687 }
688 
Preferences(wxWindow * parent)689 Preferences::Preferences(wxWindow *parent): wxDialog(parent, -1, _("Preferences"), wxDefaultPosition, wxSize(-1, -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
690 	SetIcon(GETICON(options_button_16));
691 
692 	book = new wxTreebook(this, -1, wxDefaultPosition, wxDefaultSize);
693 	General(book, this);
694 	General_DefaultStyles(book, this);
695 	Audio(book, this);
696 	Video(book, this);
697 	Interface(book, this);
698 	Interface_Colours(book, this);
699 	new Interface_Hotkeys(book, this);
700 	Backup(book, this);
701 	Automation(book, this);
702 	Advanced(book, this);
703 	Advanced_Audio(book, this);
704 	Advanced_Video(book, this);
705 
706 	book->Fit();
707 
708 	book->ChangeSelection(OPT_GET("Tool/Preferences/Page")->GetInt());
709 	book->Bind(wxEVT_TREEBOOK_PAGE_CHANGED, [](wxBookCtrlEvent &evt) {
710 		OPT_SET("Tool/Preferences/Page")->SetInt(evt.GetSelection());
711 	});
712 
713 	// Bottom Buttons
714 	auto stdButtonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxAPPLY | wxHELP);
715 	applyButton = stdButtonSizer->GetApplyButton();
716 	wxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
717 	auto defaultButton = new wxButton(this, -1, _("&Restore Defaults"));
718 	buttonSizer->Add(defaultButton, wxSizerFlags(0).Expand());
719 	buttonSizer->AddStretchSpacer(1);
720 	buttonSizer->Add(stdButtonSizer, wxSizerFlags(0).Expand());
721 
722 	// Main Sizer
723 	wxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
724 	mainSizer->Add(book, wxSizerFlags(1).Expand().Border());
725 	mainSizer->Add(buttonSizer, wxSizerFlags(0).Expand().Border(wxALL & ~wxTOP));
726 
727 	SetSizerAndFit(mainSizer);
728 	CenterOnParent();
729 
730 	applyButton->Enable(false);
731 
732 	Bind(wxEVT_BUTTON, &Preferences::OnOK, this, wxID_OK);
733 	Bind(wxEVT_BUTTON, &Preferences::OnApply, this, wxID_APPLY);
734 	Bind(wxEVT_BUTTON, std::bind(&HelpButton::OpenPage, "Options"), wxID_HELP);
735 	defaultButton->Bind(wxEVT_BUTTON, &Preferences::OnResetDefault, this);
736 }
737