Remove default standard setting (already taken from template).
[dcpomatic.git] / src / wx / full_config_dialog.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 /** @file src/full_config_dialog.cc
23  *  @brief A dialogue to edit all DCP-o-matic configuration.
24  */
25
26
27 #include "check_box.h"
28 #include "config_dialog.h"
29 #include "config_move_dialog.h"
30 #include "dcpomatic_button.h"
31 #include "dcpomatic_choice.h"
32 #include "dir_picker_ctrl.h"
33 #include "editable_list.h"
34 #include "email_dialog.h"
35 #include "file_picker_ctrl.h"
36 #include "filter_dialog.h"
37 #include "full_config_dialog.h"
38 #include "kdm_choice.h"
39 #include "language_tag_widget.h"
40 #include "make_chain_dialog.h"
41 #include "nag_dialog.h"
42 #include "name_format_editor.h"
43 #include "password_entry.h"
44 #include "region_subtag_widget.h"
45 #include "send_test_email_dialog.h"
46 #include "server_dialog.h"
47 #include "static_text.h"
48 #include "wx_util.h"
49 #include "lib/config.h"
50 #include "lib/cross.h"
51 #include "lib/dcp_content_type.h"
52 #include "lib/emailer.h"
53 #include "lib/exceptions.h"
54 #include "lib/filter.h"
55 #include "lib/log.h"
56 #include "lib/ratio.h"
57 #include "lib/util.h"
58 #include <dcp/certificate_chain.h>
59 #include <dcp/exceptions.h>
60 #include <dcp/locale_convert.h>
61 #include <dcp/warnings.h>
62 LIBDCP_DISABLE_WARNINGS
63 #include <wx/filepicker.h>
64 #include <wx/preferences.h>
65 #include <wx/spinctrl.h>
66 #include <wx/stdpaths.h>
67 LIBDCP_ENABLE_WARNINGS
68 #include <RtAudio.h>
69 #include <boost/filesystem.hpp>
70 #include <iostream>
71
72
73 using std::cout;
74 using std::function;
75 using std::list;
76 using std::make_pair;
77 using std::map;
78 using std::pair;
79 using std::shared_ptr;
80 using std::string;
81 using std::vector;
82 using boost::bind;
83 using boost::optional;
84 #if BOOST_VERSION >= 106100
85 using namespace boost::placeholders;
86 #endif
87 using dcp::locale_convert;
88
89
90 class FullGeneralPage : public GeneralPage
91 {
92 public:
93         FullGeneralPage (wxSize panel_size, int border)
94                 : GeneralPage (panel_size, border)
95         {}
96
97 private:
98         void setup () override
99         {
100                 auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
101                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
102
103                 int r = 0;
104                 add_language_controls (table, r);
105
106                 add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic should use"), true, wxGBPosition (r, 0));
107                 _master_encoding_threads = new wxSpinCtrl (_panel);
108                 table->Add (_master_encoding_threads, wxGBPosition (r, 1));
109                 ++r;
110
111                 add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic encode server should use"), true, wxGBPosition (r, 0));
112                 _server_encoding_threads = new wxSpinCtrl (_panel);
113                 table->Add (_server_encoding_threads, wxGBPosition (r, 1));
114                 ++r;
115
116                 add_label_to_sizer (table, _panel, _("Configuration file"), true, wxGBPosition (r, 0));
117                 _config_file = new FilePickerCtrl (_panel, _("Select configuration file"), "*.xml", true, false);
118                 table->Add (_config_file, wxGBPosition (r, 1));
119                 ++r;
120
121                 add_label_to_sizer (table, _panel, _("Cinema and screen database file"), true, wxGBPosition (r, 0));
122                 _cinemas_file = new FilePickerCtrl (_panel, _("Select cinema and screen database file"), "*.xml", true, false);
123                 table->Add (_cinemas_file, wxGBPosition (r, 1));
124                 auto export_cinemas = new Button (_panel, _("Export..."));
125                 table->Add (export_cinemas, wxGBPosition (r, 2));
126                 ++r;
127
128                 add_label_to_sizer(table, _panel, _("Default \"add file\" location"), true, wxGBPosition(r, 0));
129                 _default_add_file_location = new Choice(_panel);
130                 table->Add(_default_add_file_location, wxGBPosition(r, 1));
131                 ++r;
132
133 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
134                 _analyse_ebur128 = new CheckBox (_panel, _("Find integrated loudness, true peak and loudness range when analysing audio"));
135                 table->Add (_analyse_ebur128, wxGBPosition (r, 0), wxGBSpan (1, 2));
136                 ++r;
137 #endif
138
139                 _automatic_audio_analysis = new CheckBox (_panel, _("Automatically analyse content audio"));
140                 table->Add (_automatic_audio_analysis, wxGBPosition (r, 0), wxGBSpan (1, 2));
141                 ++r;
142
143                 add_update_controls (table, r);
144
145                 _default_add_file_location->add(_("Same place as last time"));
146                 _default_add_file_location->add(_("Same place as project"));
147                 _default_add_file_location->bind(&FullGeneralPage::default_add_file_location_changed, this);
148
149                 _config_file->Bind  (wxEVT_FILEPICKER_CHANGED, boost::bind(&FullGeneralPage::config_file_changed,  this));
150                 _cinemas_file->Bind (wxEVT_FILEPICKER_CHANGED, boost::bind(&FullGeneralPage::cinemas_file_changed, this));
151
152                 _master_encoding_threads->SetRange (1, 128);
153                 _master_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&FullGeneralPage::master_encoding_threads_changed, this));
154                 _server_encoding_threads->SetRange (1, 128);
155                 _server_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&FullGeneralPage::server_encoding_threads_changed, this));
156                 export_cinemas->Bind (wxEVT_BUTTON, boost::bind (&FullGeneralPage::export_cinemas_file, this));
157
158 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
159                 _analyse_ebur128->bind(&FullGeneralPage::analyse_ebur128_changed, this);
160 #endif
161                 _automatic_audio_analysis->bind(&FullGeneralPage::automatic_audio_analysis_changed, this);
162         }
163
164         void config_changed () override
165         {
166                 auto config = Config::instance ();
167
168                 checked_set (_master_encoding_threads, config->master_encoding_threads ());
169                 checked_set (_server_encoding_threads, config->server_encoding_threads ());
170 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
171                 checked_set (_analyse_ebur128, config->analyse_ebur128 ());
172 #endif
173                 checked_set (_automatic_audio_analysis, config->automatic_audio_analysis ());
174                 checked_set (_config_file, config->config_read_file());
175                 checked_set (_cinemas_file, config->cinemas_file());
176                 checked_set(_default_add_file_location, config->default_add_file_location() == Config::DefaultAddFileLocation::SAME_AS_LAST_TIME ? 0 : 1);
177
178                 GeneralPage::config_changed ();
179         }
180
181         void export_cinemas_file ()
182         {
183                 wxFileDialog dialog(
184                         _panel, _("Select Cinemas File"), wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml"),
185                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
186                 );
187
188                 if (dialog.ShowModal() == wxID_OK) {
189                         boost::filesystem::copy_file(Config::instance()->cinemas_file(), wx_to_std(dialog.GetPath()), boost::filesystem::copy_option::overwrite_if_exists);
190                 }
191         }
192
193 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
194         void analyse_ebur128_changed ()
195         {
196                 Config::instance()->set_analyse_ebur128 (_analyse_ebur128->GetValue());
197         }
198 #endif
199
200         void automatic_audio_analysis_changed ()
201         {
202                 Config::instance()->set_automatic_audio_analysis (_automatic_audio_analysis->GetValue());
203         }
204
205         void master_encoding_threads_changed ()
206         {
207                 Config::instance()->set_master_encoding_threads (_master_encoding_threads->GetValue());
208         }
209
210         void server_encoding_threads_changed ()
211         {
212                 Config::instance()->set_server_encoding_threads (_server_encoding_threads->GetValue());
213         }
214
215         void config_file_changed ()
216         {
217                 auto config = Config::instance();
218                 boost::filesystem::path new_file = wx_to_std(_config_file->GetPath());
219                 if (new_file == config->config_read_file()) {
220                         return;
221                 }
222                 bool copy_and_link = true;
223                 if (boost::filesystem::exists(new_file)) {
224                         ConfigMoveDialog dialog(_panel, new_file);
225                         if (dialog.ShowModal() == wxID_OK) {
226                                 copy_and_link = false;
227                         }
228                 }
229
230                 if (copy_and_link) {
231                         config->write ();
232                         if (new_file != config->config_read_file()) {
233                                 config->copy_and_link (new_file);
234                         }
235                 } else {
236                         config->link (new_file);
237                 }
238         }
239
240         void cinemas_file_changed ()
241         {
242                 Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
243         }
244
245         void default_add_file_location_changed()
246         {
247                 Config::instance()->set_default_add_file_location(
248                         _default_add_file_location->get().get_value_or(0) == 0 ? Config::DefaultAddFileLocation::SAME_AS_LAST_TIME : Config::DefaultAddFileLocation::SAME_AS_PROJECT
249                         );
250         }
251
252         Choice* _default_add_file_location;
253         wxSpinCtrl* _master_encoding_threads;
254         wxSpinCtrl* _server_encoding_threads;
255         FilePickerCtrl* _config_file;
256         FilePickerCtrl* _cinemas_file;
257 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
258         CheckBox* _analyse_ebur128;
259 #endif
260         CheckBox* _automatic_audio_analysis;
261 };
262
263
264 class DefaultsPage : public Page
265 {
266 public:
267         DefaultsPage (wxSize panel_size, int border)
268                 : Page (panel_size, border)
269         {}
270
271         wxString GetName () const override
272         {
273                 return _("Defaults");
274         }
275
276 #ifdef DCPOMATIC_OSX
277         wxBitmap GetLargeIcon () const override
278         {
279                 return wxBitmap(icon_path("defaults"), wxBITMAP_TYPE_PNG);
280         }
281 #endif
282
283 private:
284         void setup () override
285         {
286                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
287                 table->AddGrowableCol (1, 1);
288                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
289
290                 {
291                         add_label_to_sizer (table, _panel, _("Default duration of still images"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
292                         auto s = new wxBoxSizer (wxHORIZONTAL);
293                         _still_length = new wxSpinCtrl (_panel);
294                         s->Add (_still_length);
295                         add_label_to_sizer (s, _panel, _("s"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
296                         table->Add (s, 1);
297                 }
298
299                 add_label_to_sizer (table, _panel, _("Default directory for new films"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
300 #ifdef DCPOMATIC_USE_OWN_PICKER
301                 _directory = new DirPickerCtrl (_panel);
302 #else
303                 _directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
304 #endif
305                 table->Add (_directory, 1, wxEXPAND);
306
307                 {
308                         add_label_to_sizer (table, _panel, _("Default audio delay"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
309                         auto s = new wxBoxSizer (wxHORIZONTAL);
310                         _audio_delay = new wxSpinCtrl (_panel);
311                         s->Add (_audio_delay);
312                         add_label_to_sizer (s, _panel, _("ms"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
313                         table->Add (s, 1);
314                 }
315
316                 _enable_audio_language = new CheckBox(_panel, _("Default audio language"));
317                 table->Add(_enable_audio_language, 1, wxEXPAND | wxALIGN_CENTRE_VERTICAL);
318                 _audio_language = new LanguageTagWidget(_panel, _("Default audio language to use for new DCPs"), Config::instance()->default_audio_language(), wxString("cmnr-Hant-"));
319                 table->Add(_audio_language->sizer());
320
321                 _enable_territory = new CheckBox(_panel, _("Default territory"));
322                 table->Add(_enable_territory, 1, wxEXPAND | wxALIGN_CENTRE_VERTICAL);
323                 _territory = new RegionSubtagWidget(_panel, _("Default territory to use for new DCPs"), Config::instance()->default_territory(), wxString("cmnr-Hant-"));
324                 table->Add(_territory->sizer());
325
326                 table->Add (_enable_metadata["facility"] = new CheckBox (_panel, _("Default facility")), 0, wxALIGN_CENTRE_VERTICAL);
327                 table->Add (_metadata["facility"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
328
329                 table->Add (_enable_metadata["studio"] = new CheckBox (_panel, _("Default studio")), 0, wxALIGN_CENTRE_VERTICAL);
330                 table->Add (_metadata["studio"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
331
332                 table->Add (_enable_metadata["chain"] = new CheckBox (_panel, _("Default chain")), 0, wxALIGN_CENTRE_VERTICAL);
333                 table->Add (_metadata["chain"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
334
335                 table->Add (_enable_metadata["distributor"] = new CheckBox (_panel, _("Default distributor")), 0, wxALIGN_CENTRE_VERTICAL);
336                 table->Add (_metadata["distributor"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
337
338                 add_label_to_sizer (table, _panel, _("Default KDM directory"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
339 #ifdef DCPOMATIC_USE_OWN_PICKER
340                 _kdm_directory = new DirPickerCtrl (_panel);
341 #else
342                 _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
343 #endif
344                 table->Add (_kdm_directory, 1, wxEXPAND);
345
346                 add_label_to_sizer (table, _panel, _("Default KDM type"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
347                 _kdm_type = new KDMChoice (_panel);
348                 table->Add (_kdm_type, 1, wxEXPAND);
349
350                 add_label_to_sizer (table, _panel, _("Default KDM duration"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
351                 _kdm_duration = new wxSpinCtrl (_panel);
352                 _kdm_duration_unit = new wxChoice (_panel, wxID_ANY);
353                 auto kdm_duration_sizer = new wxBoxSizer (wxHORIZONTAL);
354                 kdm_duration_sizer->Add (_kdm_duration, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
355                 kdm_duration_sizer->Add (_kdm_duration_unit, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
356                 table->Add (kdm_duration_sizer, 1, wxEXPAND);
357
358                 table->Add (_use_isdcf_name_by_default = new CheckBox(_panel, _("Use ISDCF name by default")), 0, wxALIGN_CENTRE_VERTICAL);
359
360                 _still_length->SetRange (1, 3600);
361                 _still_length->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::still_length_changed, this));
362
363                 _directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
364                 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::kdm_directory_changed, this));
365                 _kdm_type->Bind (wxEVT_CHOICE, boost::bind(&DefaultsPage::kdm_type_changed, this));
366                 _kdm_duration_unit->Append (_("days"));
367                 _kdm_duration_unit->Append (_("weeks"));
368                 _kdm_duration_unit->Append (_("months"));
369                 _kdm_duration_unit->Append (_("years"));
370
371                 _kdm_duration->Bind (wxEVT_SPINCTRL, boost::bind(&DefaultsPage::kdm_duration_changed, this));
372                 _kdm_duration_unit->Bind (wxEVT_CHOICE, boost::bind(&DefaultsPage::kdm_duration_changed, this));
373
374                 _use_isdcf_name_by_default->bind(&DefaultsPage::use_isdcf_name_by_default_changed, this);
375
376                 _audio_delay->SetRange (-1000, 1000);
377                 _audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
378
379                 for (auto const& i: _enable_metadata) {
380                         i.second->bind(&DefaultsPage::metadata_changed, this);
381                 }
382
383                 for (auto const& i: _metadata) {
384                         i.second->Bind (wxEVT_TEXT, boost::bind(&DefaultsPage::metadata_changed, this));
385                 }
386
387                 _enable_audio_language->bind(&DefaultsPage::enable_audio_language_toggled, this);
388                 _audio_language->Changed.connect(boost::bind(&DefaultsPage::audio_language_changed, this));
389
390                 _enable_territory->bind(&DefaultsPage::enable_territory_toggled, this);
391                 _territory->Changed.connect(boost::bind(&DefaultsPage::territory_changed, this));
392         }
393
394         void config_changed () override
395         {
396                 auto config = Config::instance ();
397
398                 checked_set (_still_length, config->default_still_length ());
399                 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
400                 _kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
401                 _kdm_type->set (config->default_kdm_type());
402                 checked_set (_use_isdcf_name_by_default, config->use_isdcf_name_by_default());
403                 checked_set (_audio_delay, config->default_audio_delay ());
404                 auto dal = config->default_audio_language();
405                 checked_set(_enable_audio_language, static_cast<bool>(dal));
406                 checked_set(_audio_language, dal ? dal : boost::none);
407                 auto dt = config->default_territory();
408                 checked_set(_enable_territory, static_cast<bool>(dt));
409                 checked_set(_territory, dt ? dt : boost::none);
410
411                 auto metadata = config->default_metadata();
412
413                 for (auto const& i: metadata) {
414                         _enable_metadata[i.first]->SetValue(true);
415                         checked_set (_metadata[i.first], i.second);
416                 }
417
418                 for (auto const& i: _enable_metadata) {
419                         if (metadata.find(i.first) == metadata.end()) {
420                                 checked_set (i.second, false);
421                         }
422                 }
423
424                 for (auto const& i: _metadata) {
425                         if (metadata.find(i.first) == metadata.end()) {
426                                 checked_set (i.second, wxT(""));
427                         }
428                 }
429
430                 checked_set (_kdm_duration, config->default_kdm_duration().duration);
431                 switch (config->default_kdm_duration().unit) {
432                         case RoughDuration::Unit::DAYS:
433                                 _kdm_duration->SetRange(1, 365);
434                                 checked_set (_kdm_duration_unit, 0);
435                                 break;
436                         case RoughDuration::Unit::WEEKS:
437                                 _kdm_duration->SetRange(1, 52);
438                                 checked_set (_kdm_duration_unit, 1);
439                                 break;
440                         case RoughDuration::Unit::MONTHS:
441                                 _kdm_duration->SetRange(1, 12);
442                                 checked_set (_kdm_duration_unit, 2);
443                                 break;
444                         case RoughDuration::Unit::YEARS:
445                                 _kdm_duration->SetRange(1, 40);
446                                 checked_set (_kdm_duration_unit, 3);
447                                 break;
448                 }
449
450                 setup_sensitivity ();
451         }
452
453         void kdm_duration_changed ()
454         {
455                 auto config = Config::instance();
456                 auto duration = _kdm_duration->GetValue();
457                 RoughDuration::Unit unit = RoughDuration::Unit::DAYS;
458                 switch (_kdm_duration_unit->GetSelection()) {
459                 case 0:
460                         unit = RoughDuration::Unit::DAYS;
461                         break;
462                 case 1:
463                         unit = RoughDuration::Unit::WEEKS;
464                         break;
465                 case 2:
466                         unit = RoughDuration::Unit::MONTHS;
467                         break;
468                 case 3:
469                         unit = RoughDuration::Unit::YEARS;
470                         break;
471                 }
472                 config->set_default_kdm_duration (RoughDuration(duration, unit));
473         }
474
475         void audio_delay_changed ()
476         {
477                 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
478         }
479
480         void directory_changed ()
481         {
482                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
483         }
484
485         void kdm_directory_changed ()
486         {
487                 Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
488         }
489
490         void kdm_type_changed ()
491         {
492                 Config::instance()->set_default_kdm_type(_kdm_type->get());
493         }
494
495         void use_isdcf_name_by_default_changed ()
496         {
497                 Config::instance()->set_use_isdcf_name_by_default (_use_isdcf_name_by_default->GetValue());
498         }
499
500         void still_length_changed ()
501         {
502                 Config::instance()->set_default_still_length (_still_length->GetValue ());
503         }
504
505         void metadata_changed ()
506         {
507                 map<string, string> metadata;
508                 for (auto const& i: _enable_metadata) {
509                         if (i.second->GetValue()) {
510                                 metadata[i.first] = wx_to_std(_metadata[i.first]->GetValue());
511                         }
512                 }
513                 Config::instance()->set_default_metadata (metadata);
514                 setup_sensitivity ();
515         }
516
517         void enable_audio_language_toggled()
518         {
519                 setup_sensitivity();
520                 audio_language_changed();
521         }
522
523         void audio_language_changed()
524         {
525                 if (_enable_audio_language->get()) {
526                         Config::instance()->set_default_audio_language(_audio_language->get().get_value_or(dcp::LanguageTag("en-US")));
527                 } else {
528                         Config::instance()->unset_default_audio_language();
529                 }
530         }
531
532         void enable_territory_toggled()
533         {
534                 setup_sensitivity();
535                 territory_changed();
536         }
537
538         void territory_changed()
539         {
540                 if (_enable_territory->get()) {
541                         Config::instance()->set_default_territory(_territory->get().get_value_or(dcp::LanguageTag::RegionSubtag("US")));
542                 } else {
543                         Config::instance()->unset_default_territory();
544                 }
545         }
546
547         void setup_sensitivity ()
548         {
549                 _audio_language->enable(_enable_audio_language->get());
550                 _territory->enable(_enable_territory->get());
551                 for (auto const& i: _enable_metadata) {
552                         _metadata[i.first]->Enable(i.second->GetValue());
553                 }
554         }
555
556         wxSpinCtrl* _audio_delay;
557         wxSpinCtrl* _still_length;
558 #ifdef DCPOMATIC_USE_OWN_PICKER
559         DirPickerCtrl* _directory;
560         DirPickerCtrl* _kdm_directory;
561 #else
562         wxDirPickerCtrl* _directory;
563         wxDirPickerCtrl* _kdm_directory;
564 #endif
565         KDMChoice* _kdm_type;
566         wxSpinCtrl* _kdm_duration;
567         wxChoice* _kdm_duration_unit;
568         CheckBox* _use_isdcf_name_by_default;
569         CheckBox* _enable_audio_language;
570         LanguageTagWidget* _audio_language;
571         CheckBox* _enable_territory;
572         RegionSubtagWidget* _territory;
573         map<string, CheckBox*> _enable_metadata;
574         map<string, wxTextCtrl*> _metadata;
575 };
576
577
578 class EncodingServersPage : public Page
579 {
580 public:
581         EncodingServersPage (wxSize panel_size, int border)
582                 : Page (panel_size, border)
583         {}
584
585         wxString GetName () const override
586         {
587                 return _("Servers");
588         }
589
590 #ifdef DCPOMATIC_OSX
591         wxBitmap GetLargeIcon () const override
592         {
593                 return wxBitmap(icon_path("servers"), wxBITMAP_TYPE_PNG);
594         }
595 #endif
596
597 private:
598         void setup () override
599         {
600                 _use_any_servers = new CheckBox (_panel, _("Search network for servers"));
601                 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
602
603                 vector<EditableListColumn> columns;
604                 columns.push_back (EditableListColumn(_("IP address / host name")));
605                 _servers_list = new EditableList<string, ServerDialog> (
606                         _panel,
607                         columns,
608                         boost::bind (&Config::servers, Config::instance()),
609                         boost::bind (&Config::set_servers, Config::instance(), _1),
610                         boost::bind (&EncodingServersPage::server_column, this, _1),
611                         EditableListTitle::INVISIBLE,
612                         EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
613                         );
614
615                 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
616
617                 _use_any_servers->bind(&EncodingServersPage::use_any_servers_changed, this);
618         }
619
620         void config_changed () override
621         {
622                 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
623                 _servers_list->refresh ();
624         }
625
626         void use_any_servers_changed ()
627         {
628                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
629         }
630
631         string server_column (string s)
632         {
633                 return s;
634         }
635
636         CheckBox* _use_any_servers;
637         EditableList<string, ServerDialog>* _servers_list;
638 };
639
640
641 class TMSPage : public Page
642 {
643 public:
644         TMSPage (wxSize panel_size, int border)
645                 : Page (panel_size, border)
646         {}
647
648         wxString GetName () const override
649         {
650                 return _("TMS");
651         }
652
653 #ifdef DCPOMATIC_OSX
654         wxBitmap GetLargeIcon () const override
655         {
656                 return wxBitmap(icon_path("tms"), wxBITMAP_TYPE_PNG);
657         }
658 #endif
659
660 private:
661         void setup () override
662         {
663                 _upload = new CheckBox (_panel, _("Upload DCP to TMS after creation"));
664                 _panel->GetSizer()->Add (_upload, 0, wxALL | wxEXPAND, _border);
665
666                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
667                 table->AddGrowableCol (1, 1);
668                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
669
670                 add_label_to_sizer (table, _panel, _("Protocol"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
671                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
672                 table->Add (_tms_protocol, 1, wxEXPAND);
673
674                 _tms_passive = new CheckBox(_panel, _("Passive mode"));
675                 table->Add(_tms_passive, 1, wxEXPAND);
676                 table->AddSpacer(0);
677
678                 add_label_to_sizer (table, _panel, _("IP address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
679                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
680                 table->Add (_tms_ip, 1, wxEXPAND);
681
682                 add_label_to_sizer (table, _panel, _("Target path"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
683                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
684                 table->Add (_tms_path, 1, wxEXPAND);
685
686                 add_label_to_sizer (table, _panel, _("User name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
687                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
688                 table->Add (_tms_user, 1, wxEXPAND);
689
690                 add_label_to_sizer (table, _panel, _("Password"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
691                 _tms_password = new PasswordEntry (_panel);
692                 table->Add (_tms_password->get_panel(), 1, wxEXPAND);
693
694                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
695                 _tms_protocol->Append (_("FTP (for Dolby)"));
696
697                 _upload->bind(&TMSPage::upload_changed, this);
698                 _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
699                 _tms_passive->bind(&TMSPage::tms_passive_changed, this);
700
701                 _tms_ip->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_ip_changed, this));
702                 _tms_path->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_path_changed, this));
703                 _tms_user->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_user_changed, this));
704                 _tms_password->Changed.connect (boost::bind (&TMSPage::tms_password_changed, this));
705         }
706
707         void config_changed () override
708         {
709                 auto config = Config::instance ();
710
711                 checked_set (_upload, config->upload_after_make_dcp());
712                 checked_set (_tms_protocol, static_cast<int>(config->tms_protocol()));
713                 checked_set(_tms_passive, config->tms_protocol() == FileTransferProtocol::FTP && config->tms_passive());
714                 checked_set (_tms_ip, config->tms_ip ());
715                 checked_set (_tms_path, config->tms_path ());
716                 checked_set (_tms_user, config->tms_user ());
717                 checked_set (_tms_password, config->tms_password ());
718
719                 _tms_passive->Enable(config->tms_protocol() == FileTransferProtocol::FTP);
720         }
721
722         void upload_changed ()
723         {
724                 Config::instance()->set_upload_after_make_dcp (_upload->GetValue());
725         }
726
727         void tms_protocol_changed ()
728         {
729                 Config::instance()->set_tms_protocol(static_cast<FileTransferProtocol>(_tms_protocol->GetSelection()));
730         }
731
732         void tms_passive_changed()
733         {
734                 Config::instance()->set_tms_passive(_tms_passive->get());
735         }
736
737         void tms_ip_changed ()
738         {
739                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
740         }
741
742         void tms_path_changed ()
743         {
744                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
745         }
746
747         void tms_user_changed ()
748         {
749                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
750         }
751
752         void tms_password_changed ()
753         {
754                 Config::instance()->set_tms_password (_tms_password->get());
755         }
756
757         CheckBox* _upload;
758         CheckBox* _tms_passive;
759         wxChoice* _tms_protocol;
760         wxTextCtrl* _tms_ip;
761         wxTextCtrl* _tms_path;
762         wxTextCtrl* _tms_user;
763         PasswordEntry* _tms_password;
764 };
765
766
767 class EmailPage : public Page
768 {
769 public:
770         EmailPage (wxSize panel_size, int border)
771                 : Page (panel_size, border)
772         {}
773
774         wxString GetName () const override
775         {
776                 return _("Email");
777         }
778
779 #ifdef DCPOMATIC_OSX
780         wxBitmap GetLargeIcon () const override
781         {
782                 return wxBitmap(icon_path("email"), wxBITMAP_TYPE_PNG);
783         }
784 #endif
785
786 private:
787         void setup () override
788         {
789                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
790                 table->AddGrowableCol (1, 1);
791                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
792
793                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
794                 {
795                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
796                         _server = new wxTextCtrl (_panel, wxID_ANY);
797                         s->Add (_server, 1, wxEXPAND | wxALL);
798                         add_label_to_sizer (s, _panel, _("port"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
799                         _port = new wxSpinCtrl (_panel, wxID_ANY);
800                         _port->SetRange (0, 65535);
801                         s->Add (_port);
802                         add_label_to_sizer (s, _panel, _("protocol"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
803                         _protocol = new wxChoice (_panel, wxID_ANY);
804                         /* Make sure this matches the switches in config_changed and port_changed below */
805                         _protocol->Append (_("Auto"));
806                         _protocol->Append (_("Plain"));
807                         _protocol->Append (_("STARTTLS"));
808                         _protocol->Append (_("SSL"));
809                         s->Add (_protocol, 1, wxALIGN_CENTER_VERTICAL);
810                         table->Add (s, 1, wxEXPAND | wxALL);
811                 }
812
813                 add_label_to_sizer (table, _panel, _("User name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
814                 _user = new wxTextCtrl (_panel, wxID_ANY);
815                 table->Add (_user, 1, wxEXPAND | wxALL);
816
817                 add_label_to_sizer (table, _panel, _("Password"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
818                 _password = new PasswordEntry (_panel);
819                 table->Add (_password->get_panel(), 1, wxEXPAND | wxALL);
820
821                 table->AddSpacer (0);
822                 _send_test_email = new Button (_panel, _("Send test email..."));
823                 table->Add (_send_test_email);
824
825                 _server->Bind (wxEVT_TEXT, boost::bind(&EmailPage::server_changed, this));
826                 _port->Bind (wxEVT_SPINCTRL, boost::bind(&EmailPage::port_changed, this));
827                 _protocol->Bind (wxEVT_CHOICE, boost::bind(&EmailPage::protocol_changed, this));
828                 _user->Bind (wxEVT_TEXT, boost::bind(&EmailPage::user_changed, this));
829                 _password->Changed.connect (boost::bind(&EmailPage::password_changed, this));
830                 _send_test_email->Bind (wxEVT_BUTTON, boost::bind(&EmailPage::send_test_email_clicked, this));
831         }
832
833         void config_changed () override
834         {
835                 auto config = Config::instance ();
836
837                 checked_set (_server, config->mail_server());
838                 checked_set (_port, config->mail_port());
839                 switch (config->mail_protocol()) {
840                 case EmailProtocol::AUTO:
841                         checked_set (_protocol, 0);
842                         break;
843                 case EmailProtocol::PLAIN:
844                         checked_set (_protocol, 1);
845                         break;
846                 case EmailProtocol::STARTTLS:
847                         checked_set (_protocol, 2);
848                         break;
849                 case EmailProtocol::SSL:
850                         checked_set (_protocol, 3);
851                         break;
852                 }
853                 checked_set (_user, config->mail_user());
854                 checked_set (_password, config->mail_password());
855         }
856
857         void server_changed ()
858         {
859                 Config::instance()->set_mail_server(wx_to_std(_server->GetValue()));
860         }
861
862         void port_changed ()
863         {
864                 Config::instance()->set_mail_port(_port->GetValue());
865         }
866
867         void protocol_changed ()
868         {
869                 switch (_protocol->GetSelection()) {
870                 case 0:
871                         Config::instance()->set_mail_protocol(EmailProtocol::AUTO);
872                         break;
873                 case 1:
874                         Config::instance()->set_mail_protocol(EmailProtocol::PLAIN);
875                         break;
876                 case 2:
877                         Config::instance()->set_mail_protocol(EmailProtocol::STARTTLS);
878                         break;
879                 case 3:
880                         Config::instance()->set_mail_protocol(EmailProtocol::SSL);
881                         break;
882                 }
883         }
884
885         void user_changed ()
886         {
887                 Config::instance()->set_mail_user (wx_to_std (_user->GetValue ()));
888         }
889
890         void password_changed ()
891         {
892                 Config::instance()->set_mail_password(_password->get());
893         }
894
895         void send_test_email_clicked ()
896         {
897                 SendTestEmailDialog dialog(_panel);
898                 if (dialog.ShowModal() != wxID_OK) {
899                         return;
900                 }
901
902                 Emailer emailer(
903                         wx_to_std(dialog.from()),
904                         { wx_to_std(dialog.to()) },
905                         wx_to_std(_("DCP-o-matic test email")),
906                         wx_to_std(_("This is a test email from DCP-o-matic."))
907                         );
908                 auto config = Config::instance();
909                 try {
910                         emailer.send(config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
911                 } catch (NetworkError& e) {
912                         error_dialog(_panel, std_to_wx(e.summary()), std_to_wx(e.detail().get_value_or("")));
913                         return;
914                 } catch (std::exception& e) {
915                         error_dialog(_panel, _("Test email sending failed."), std_to_wx(e.what()));
916                         return;
917                 } catch (...) {
918                         error_dialog(_panel, _("Test email sending failed."));
919                         return;
920                 }
921                 message_dialog(_panel, _("Test email sent."));
922         }
923
924         wxTextCtrl* _server;
925         wxSpinCtrl* _port;
926         wxChoice* _protocol;
927         wxTextCtrl* _user;
928         PasswordEntry* _password;
929         Button* _send_test_email;
930 };
931
932
933 class KDMEmailPage : public Page
934 {
935 public:
936
937         KDMEmailPage (wxSize panel_size, int border)
938 #ifdef DCPOMATIC_OSX
939                 /* We have to force both width and height of this one */
940                 : Page (wxSize (panel_size.GetWidth(), 128), border)
941 #else
942                 : Page (panel_size, border)
943 #endif
944         {}
945
946         wxString GetName () const override
947         {
948                 return _("KDM Email");
949         }
950
951 #ifdef DCPOMATIC_OSX
952         wxBitmap GetLargeIcon () const override
953         {
954                 return wxBitmap(icon_path("kdm_email"), wxBITMAP_TYPE_PNG);
955         }
956 #endif
957
958 private:
959         void setup () override
960         {
961                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
962                 table->AddGrowableCol (1, 1);
963                 _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
964
965                 add_label_to_sizer (table, _panel, _("Subject"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
966                 _subject = new wxTextCtrl (_panel, wxID_ANY);
967                 table->Add (_subject, 1, wxEXPAND | wxALL);
968
969                 add_label_to_sizer (table, _panel, _("From address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
970                 _from = new wxTextCtrl (_panel, wxID_ANY);
971                 table->Add (_from, 1, wxEXPAND | wxALL);
972
973                 vector<EditableListColumn> columns;
974                 columns.push_back (EditableListColumn(_("Address")));
975                 add_label_to_sizer (table, _panel, _("CC addresses"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
976                 _cc = new EditableList<string, EmailDialog> (
977                         _panel,
978                         columns,
979                         bind (&Config::kdm_cc, Config::instance()),
980                         bind (&Config::set_kdm_cc, Config::instance(), _1),
981                         [] (string s, int) {
982                                 return s;
983                         },
984                         EditableListTitle::VISIBLE,
985                         EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
986                         );
987                 table->Add (_cc, 1, wxEXPAND | wxALL);
988
989                 add_label_to_sizer (table, _panel, _("BCC address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
990                 _bcc = new wxTextCtrl (_panel, wxID_ANY);
991                 table->Add (_bcc, 1, wxEXPAND | wxALL);
992
993                 _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
994                 _panel->GetSizer()->Add (_email, 0, wxEXPAND | wxALL, _border);
995
996                 _reset_email = new Button (_panel, _("Reset to default subject and text"));
997                 _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
998
999                 _cc->layout ();
1000
1001                 _subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1002                 _from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1003                 _bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1004                 _email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1005                 _reset_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_email, this));
1006         }
1007
1008         void config_changed () override
1009         {
1010                 auto config = Config::instance ();
1011
1012                 checked_set (_subject, config->kdm_subject ());
1013                 checked_set (_from, config->kdm_from ());
1014                 checked_set (_bcc, config->kdm_bcc ());
1015                 checked_set (_email, Config::instance()->kdm_email ());
1016         }
1017
1018         void kdm_subject_changed ()
1019         {
1020                 Config::instance()->set_kdm_subject (wx_to_std (_subject->GetValue ()));
1021         }
1022
1023         void kdm_from_changed ()
1024         {
1025                 Config::instance()->set_kdm_from (wx_to_std (_from->GetValue ()));
1026         }
1027
1028         void kdm_bcc_changed ()
1029         {
1030                 Config::instance()->set_kdm_bcc (wx_to_std (_bcc->GetValue ()));
1031         }
1032
1033         void kdm_email_changed ()
1034         {
1035                 if (_email->GetValue().IsEmpty ()) {
1036                         /* Sometimes we get sent an erroneous notification that the email
1037                            is empty; I don't know why.
1038                         */
1039                         return;
1040                 }
1041                 Config::instance()->set_kdm_email (wx_to_std (_email->GetValue ()));
1042         }
1043
1044         void reset_email ()
1045         {
1046                 Config::instance()->reset_kdm_email ();
1047                 checked_set (_email, Config::instance()->kdm_email ());
1048         }
1049
1050         wxTextCtrl* _subject;
1051         wxTextCtrl* _from;
1052         EditableList<string, EmailDialog>* _cc;
1053         wxTextCtrl* _bcc;
1054         wxTextCtrl* _email;
1055         wxButton* _reset_email;
1056 };
1057
1058
1059 class NotificationsPage : public Page
1060 {
1061 public:
1062         NotificationsPage (wxSize panel_size, int border)
1063 #ifdef DCPOMATIC_OSX
1064                 /* We have to force both width and height of this one */
1065                 : Page (wxSize (panel_size.GetWidth(), 128), border)
1066 #else
1067                 : Page (panel_size, border)
1068 #endif
1069         {}
1070
1071         wxString GetName () const override
1072         {
1073                 return _("Notifications");
1074         }
1075
1076 #ifdef DCPOMATIC_OSX
1077         wxBitmap GetLargeIcon () const override
1078         {
1079                 return wxBitmap(icon_path("notifications"), wxBITMAP_TYPE_PNG);
1080         }
1081 #endif
1082
1083 private:
1084         void setup () override
1085         {
1086                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1087                 table->AddGrowableCol (1, 1);
1088                 _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
1089
1090                 _enable_message_box = new CheckBox (_panel, _("Message box"));
1091                 table->Add (_enable_message_box, 1, wxEXPAND | wxALL);
1092                 table->AddSpacer (0);
1093
1094                 _enable_email = new CheckBox (_panel, _("Email"));
1095                 table->Add (_enable_email, 1, wxEXPAND | wxALL);
1096                 table->AddSpacer (0);
1097
1098                 add_label_to_sizer (table, _panel, _("Subject"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1099                 _subject = new wxTextCtrl (_panel, wxID_ANY);
1100                 table->Add (_subject, 1, wxEXPAND | wxALL);
1101
1102                 add_label_to_sizer (table, _panel, _("From address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1103                 _from = new wxTextCtrl (_panel, wxID_ANY);
1104                 table->Add (_from, 1, wxEXPAND | wxALL);
1105
1106                 add_label_to_sizer (table, _panel, _("To address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1107                 _to = new wxTextCtrl (_panel, wxID_ANY);
1108                 table->Add (_to, 1, wxEXPAND | wxALL);
1109
1110                 vector<EditableListColumn> columns;
1111                 columns.push_back (EditableListColumn(_("Address")));
1112                 add_label_to_sizer (table, _panel, _("CC addresses"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1113                 _cc = new EditableList<string, EmailDialog> (
1114                         _panel,
1115                         columns,
1116                         bind (&Config::notification_cc, Config::instance()),
1117                         bind (&Config::set_notification_cc, Config::instance(), _1),
1118                         [] (string s, int) {
1119                                 return s;
1120                         },
1121                         EditableListTitle::VISIBLE,
1122                         EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
1123                         );
1124                 table->Add (_cc, 1, wxEXPAND | wxALL);
1125
1126                 add_label_to_sizer (table, _panel, _("BCC address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1127                 _bcc = new wxTextCtrl (_panel, wxID_ANY);
1128                 table->Add (_bcc, 1, wxEXPAND | wxALL);
1129
1130                 _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1131                 _panel->GetSizer()->Add (_email, 0, wxEXPAND | wxALL, _border);
1132
1133                 _reset_email = new Button (_panel, _("Reset to default subject and text"));
1134                 _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
1135
1136                 _cc->layout ();
1137
1138                 _enable_message_box->bind(&NotificationsPage::type_changed, this, _enable_message_box, Config::MESSAGE_BOX);
1139                 _enable_email->bind(&NotificationsPage::type_changed, this, _enable_email, Config::EMAIL);
1140
1141                 _subject->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_subject_changed, this));
1142                 _from->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_from_changed, this));
1143                 _to->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_to_changed, this));
1144                 _bcc->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_bcc_changed, this));
1145                 _email->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_email_changed, this));
1146                 _reset_email->Bind (wxEVT_BUTTON, boost::bind (&NotificationsPage::reset_email, this));
1147
1148                 setup_sensitivity ();
1149         }
1150
1151         void setup_sensitivity ()
1152         {
1153                 bool const s = _enable_email->GetValue();
1154                 _subject->Enable(s);
1155                 _from->Enable(s);
1156                 _to->Enable(s);
1157                 _cc->Enable(s);
1158                 _bcc->Enable(s);
1159                 _email->Enable(s);
1160                 _reset_email->Enable(s);
1161         }
1162
1163         void config_changed () override
1164         {
1165                 auto config = Config::instance ();
1166
1167                 checked_set (_enable_message_box, config->notification(Config::MESSAGE_BOX));
1168                 checked_set (_enable_email, config->notification(Config::EMAIL));
1169                 checked_set (_subject, config->notification_subject ());
1170                 checked_set (_from, config->notification_from ());
1171                 checked_set (_to, config->notification_to ());
1172                 checked_set (_bcc, config->notification_bcc ());
1173                 checked_set (_email, Config::instance()->notification_email ());
1174
1175                 setup_sensitivity ();
1176         }
1177
1178         void notification_subject_changed ()
1179         {
1180                 Config::instance()->set_notification_subject(wx_to_std(_subject->GetValue()));
1181         }
1182
1183         void notification_from_changed ()
1184         {
1185                 Config::instance()->set_notification_from(wx_to_std(_from->GetValue()));
1186         }
1187
1188         void notification_to_changed ()
1189         {
1190                 Config::instance()->set_notification_to(wx_to_std(_to->GetValue()));
1191         }
1192
1193         void notification_bcc_changed ()
1194         {
1195                 Config::instance()->set_notification_bcc(wx_to_std(_bcc->GetValue()));
1196         }
1197
1198         void notification_email_changed ()
1199         {
1200                 if (_email->GetValue().IsEmpty()) {
1201                         /* Sometimes we get sent an erroneous notification that the email
1202                            is empty; I don't know why.
1203                         */
1204                         return;
1205                 }
1206                 Config::instance()->set_notification_email(wx_to_std(_email->GetValue()));
1207         }
1208
1209         void reset_email ()
1210         {
1211                 Config::instance()->reset_notification_email();
1212                 checked_set (_email, Config::instance()->notification_email());
1213         }
1214
1215         void type_changed (CheckBox* b, Config::Notification n)
1216         {
1217                 Config::instance()->set_notification(n, b->GetValue());
1218                 setup_sensitivity ();
1219         }
1220
1221         CheckBox* _enable_message_box;
1222         CheckBox* _enable_email;
1223
1224         wxTextCtrl* _subject;
1225         wxTextCtrl* _from;
1226         wxTextCtrl* _to;
1227         EditableList<string, EmailDialog>* _cc;
1228         wxTextCtrl* _bcc;
1229         wxTextCtrl* _email;
1230         wxButton* _reset_email;
1231 };
1232
1233
1234 class CoverSheetPage : public Page
1235 {
1236 public:
1237
1238         CoverSheetPage (wxSize panel_size, int border)
1239 #ifdef DCPOMATIC_OSX
1240                 /* We have to force both width and height of this one */
1241                 : Page (wxSize (panel_size.GetWidth(), 128), border)
1242 #else
1243                 : Page (panel_size, border)
1244 #endif
1245         {}
1246
1247         wxString GetName () const override
1248         {
1249                 return _("Cover Sheet");
1250         }
1251
1252 #ifdef DCPOMATIC_OSX
1253         wxBitmap GetLargeIcon () const override
1254         {
1255                 return wxBitmap(icon_path("cover_sheet"), wxBITMAP_TYPE_PNG);
1256         }
1257 #endif
1258
1259 private:
1260         void setup () override
1261         {
1262                 _cover_sheet = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1263                 _panel->GetSizer()->Add (_cover_sheet, 0, wxEXPAND | wxALL, _border);
1264
1265                 _reset_cover_sheet = new Button (_panel, _("Reset to default text"));
1266                 _panel->GetSizer()->Add (_reset_cover_sheet, 0, wxEXPAND | wxALL, _border);
1267
1268                 _cover_sheet->Bind (wxEVT_TEXT, boost::bind (&CoverSheetPage::cover_sheet_changed, this));
1269                 _reset_cover_sheet->Bind (wxEVT_BUTTON, boost::bind (&CoverSheetPage::reset_cover_sheet, this));
1270         }
1271
1272         void config_changed () override
1273         {
1274                 checked_set (_cover_sheet, Config::instance()->cover_sheet());
1275         }
1276
1277         void cover_sheet_changed ()
1278         {
1279                 if (_cover_sheet->GetValue().IsEmpty ()) {
1280                         /* Sometimes we get sent an erroneous notification that the cover sheet
1281                            is empty; I don't know why.
1282                         */
1283                         return;
1284                 }
1285                 Config::instance()->set_cover_sheet(wx_to_std(_cover_sheet->GetValue()));
1286         }
1287
1288         void reset_cover_sheet ()
1289         {
1290                 Config::instance()->reset_cover_sheet();
1291                 checked_set (_cover_sheet, Config::instance()->cover_sheet());
1292         }
1293
1294         wxTextCtrl* _cover_sheet;
1295         wxButton* _reset_cover_sheet;
1296 };
1297
1298
1299 class IdentifiersPage : public Page
1300 {
1301 public:
1302         IdentifiersPage (wxSize panel_size, int border)
1303                 : Page (panel_size, border)
1304         {}
1305
1306         wxString GetName () const override
1307         {
1308                 return _("Identifiers");
1309         }
1310
1311 #ifdef DCPOMATIC_OSX
1312         wxBitmap GetLargeIcon () const override
1313         {
1314                 return wxBitmap(icon_path("identifiers"), wxBITMAP_TYPE_PNG);
1315         }
1316 #endif
1317
1318 private:
1319         void setup () override
1320         {
1321                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1322                 table->AddGrowableCol (1, 1);
1323
1324                 add_label_to_sizer (table, _panel, _("Issuer"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1325                 _issuer = new wxTextCtrl (_panel, wxID_ANY);
1326                 _issuer->SetToolTip (_("This will be written to the DCP's XML files as the <Issuer>.  If it is blank, a default value mentioning DCP-o-matic will be used."));
1327                 table->Add (_issuer, 1, wxALL | wxEXPAND);
1328
1329                 add_label_to_sizer (table, _panel, _("Creator"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1330                 _creator = new wxTextCtrl (_panel, wxID_ANY);
1331                 _creator->SetToolTip (_("This will be written to the DCP's XML files as the <Creator>.  If it is blank, a default value mentioning DCP-o-matic will be used."));
1332                 table->Add (_creator, 1, wxALL | wxEXPAND);
1333
1334                 add_label_to_sizer (table, _panel, _("Company name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1335                 _company_name = new wxTextCtrl (_panel, wxID_ANY);
1336                 _company_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'company name'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
1337                 table->Add (_company_name, 1, wxALL | wxEXPAND);
1338
1339                 add_label_to_sizer (table, _panel, _("Product name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1340                 _product_name = new wxTextCtrl (_panel, wxID_ANY);
1341                 _product_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'product name'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
1342                 table->Add (_product_name, 1, wxALL | wxEXPAND);
1343
1344                 add_label_to_sizer (table, _panel, _("Product version"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1345                 _product_version = new wxTextCtrl (_panel, wxID_ANY);
1346                 _product_version->SetToolTip (_("This will be written to the DCP's MXF files as the 'product version'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
1347                 table->Add (_product_version, 1, wxALL | wxEXPAND);
1348
1349                 add_label_to_sizer (table, _panel, _("JPEG2000 comment"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1350                 _j2k_comment = new wxTextCtrl (_panel, wxID_ANY);
1351                 _j2k_comment->SetToolTip (_("This will be written to the DCP's JPEG2000 data as a comment.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
1352                 table->Add (_j2k_comment, 1, wxALL | wxEXPAND);
1353
1354                 _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
1355
1356                 _issuer->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::issuer_changed, this));
1357                 _creator->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::creator_changed, this));
1358                 _company_name->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::company_name_changed, this));
1359                 _product_name->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::product_name_changed, this));
1360                 _product_version->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::product_version_changed, this));
1361                 _j2k_comment->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::j2k_comment_changed, this));
1362         }
1363
1364         void config_changed () override
1365         {
1366                 auto config = Config::instance ();
1367                 checked_set (_issuer, config->dcp_issuer ());
1368                 checked_set (_creator, config->dcp_creator ());
1369                 checked_set (_company_name, config->dcp_company_name ());
1370                 checked_set (_product_name, config->dcp_product_name ());
1371                 checked_set (_product_version, config->dcp_product_version ());
1372                 checked_set (_j2k_comment, config->dcp_j2k_comment ());
1373         }
1374
1375         void issuer_changed ()
1376         {
1377                 Config::instance()->set_dcp_issuer(wx_to_std(_issuer->GetValue()));
1378         }
1379
1380         void creator_changed ()
1381         {
1382                 Config::instance()->set_dcp_creator(wx_to_std(_creator->GetValue()));
1383         }
1384
1385         void company_name_changed ()
1386         {
1387                 Config::instance()->set_dcp_company_name(wx_to_std(_company_name->GetValue()));
1388         }
1389
1390         void product_name_changed ()
1391         {
1392                 Config::instance()->set_dcp_product_name(wx_to_std(_product_name->GetValue()));
1393         }
1394
1395         void product_version_changed ()
1396         {
1397                 Config::instance()->set_dcp_product_version(wx_to_std(_product_version->GetValue()));
1398         }
1399
1400         void j2k_comment_changed ()
1401         {
1402                 Config::instance()->set_dcp_j2k_comment (wx_to_std(_j2k_comment->GetValue()));
1403         }
1404
1405         wxTextCtrl* _issuer;
1406         wxTextCtrl* _creator;
1407         wxTextCtrl* _company_name;
1408         wxTextCtrl* _product_name;
1409         wxTextCtrl* _product_version;
1410         wxTextCtrl* _j2k_comment;
1411 };
1412
1413
1414 class NonStandardPage : public Page
1415 {
1416 public:
1417         NonStandardPage(wxSize panel_size, int border)
1418                 : Page(panel_size, border)
1419         {}
1420
1421         wxString GetName() const override
1422         {
1423                 return _("Non-standard");
1424         }
1425
1426 #ifdef DCPOMATIC_OSX
1427         wxBitmap GetLargeIcon() const override
1428         {
1429                 return wxBitmap(icon_path("non_standard"), wxBITMAP_TYPE_PNG);
1430         }
1431 #endif
1432
1433 private:
1434         void setup() override
1435         {
1436                 auto table = new wxFlexGridSizer(2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1437                 table->AddGrowableCol(1, 1);
1438                 _panel->GetSizer()->Add(table, 1, wxALL | wxEXPAND, _border);
1439
1440                 {
1441                         add_label_to_sizer(table, _panel, _("Maximum JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1442                         auto s = new wxBoxSizer(wxHORIZONTAL);
1443                         _maximum_j2k_bandwidth = new wxSpinCtrl(_panel);
1444                         s->Add(_maximum_j2k_bandwidth, 1);
1445                         add_label_to_sizer(s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
1446                         table->Add(s, 1);
1447                 }
1448
1449                 auto checkbox = [this, table](wxString name, CheckBox*& variable) {
1450                         variable = new CheckBox(_panel, name);
1451                         table->Add(variable, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
1452                         table->AddSpacer(0);
1453                 };
1454
1455                 checkbox(_("Allow any DCP frame rate"),  _allow_any_dcp_frame_rate);
1456
1457                 _allow_any_container = new CheckBox(_panel, _("Allow full-frame and non-standard container ratios"));
1458                 table->Add(_allow_any_container, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
1459                 auto restart = new StaticText(_panel, _("(restart DCP-o-matic to see all ratios)"));
1460                 auto font = restart->GetFont();
1461                 font.SetStyle(wxFONTSTYLE_ITALIC);
1462                 font.SetPointSize(font.GetPointSize() - 1);
1463                 restart->SetFont(font);
1464                 table->Add(restart, 1, wxALIGN_CENTRE_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
1465                 restart->SetFont(font);
1466
1467                 checkbox(_("Allow creation of DCPs with 96kHz audio"), _allow_96khz_audio);
1468                 checkbox(_("Allow mapping to all audio channels"), _use_all_audio_channels);
1469                 checkbox(_("Allow use of SMPTE Bv2.0"), _allow_smpte_bv20);
1470
1471                 {
1472                         add_label_to_sizer(table, _panel, _("ISDCF name part length"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1473                         auto s = new wxBoxSizer(wxHORIZONTAL);
1474                         _isdcf_name_part_length = new wxSpinCtrl(_panel);
1475                         s->Add(_isdcf_name_part_length, 1);
1476                         table->Add(s, 1);
1477                 }
1478
1479                 _maximum_j2k_bandwidth->SetRange(1, 1000);
1480                 _maximum_j2k_bandwidth->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_j2k_bandwidth_changed, this));
1481                 _allow_any_dcp_frame_rate->bind(&NonStandardPage::allow_any_dcp_frame_rate_changed, this);
1482                 _allow_any_container->bind(&NonStandardPage::allow_any_container_changed, this);
1483                 _allow_96khz_audio->bind(&NonStandardPage::allow_96khz_audio_changed, this);
1484                 _use_all_audio_channels->bind(&NonStandardPage::use_all_channels_changed, this);
1485                 _allow_smpte_bv20->bind(&NonStandardPage::allow_smpte_bv20_changed, this);
1486                 _isdcf_name_part_length->SetRange(14, 256);
1487                 _isdcf_name_part_length->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::isdcf_name_part_length_changed, this));
1488         }
1489
1490         void config_changed() override
1491         {
1492                 auto config = Config::instance();
1493
1494                 checked_set(_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1495                 checked_set(_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate());
1496                 checked_set(_allow_any_container, config->allow_any_container());
1497                 checked_set(_allow_96khz_audio, config->allow_96khz_audio());
1498                 checked_set(_use_all_audio_channels, config->use_all_audio_channels());
1499                 checked_set(_allow_smpte_bv20, config->allow_smpte_bv20());
1500                 checked_set(_isdcf_name_part_length, config->isdcf_name_part_length());
1501         }
1502
1503         void maximum_j2k_bandwidth_changed()
1504         {
1505                 Config::instance()->set_maximum_j2k_bandwidth(_maximum_j2k_bandwidth->GetValue() * 1000000);
1506         }
1507
1508         void allow_any_dcp_frame_rate_changed()
1509         {
1510                 Config::instance()->set_allow_any_dcp_frame_rate(_allow_any_dcp_frame_rate->GetValue());
1511         }
1512
1513         void allow_any_container_changed()
1514         {
1515                 Config::instance()->set_allow_any_container(_allow_any_container->GetValue());
1516         }
1517
1518         void allow_96khz_audio_changed()
1519         {
1520                 Config::instance()->set_allow_96hhz_audio(_allow_96khz_audio->GetValue());
1521         }
1522
1523         void use_all_channels_changed()
1524         {
1525                 Config::instance()->set_use_all_audio_channels(_use_all_audio_channels->GetValue());
1526         }
1527
1528         void allow_smpte_bv20_changed()
1529         {
1530                 Config::instance()->set_allow_smpte_bv20(_allow_smpte_bv20->GetValue());
1531         }
1532
1533         void isdcf_name_part_length_changed()
1534         {
1535                 Config::instance()->set_isdcf_name_part_length(_isdcf_name_part_length->GetValue());
1536         }
1537
1538         wxSpinCtrl* _maximum_j2k_bandwidth = nullptr;
1539         CheckBox* _allow_any_dcp_frame_rate = nullptr;
1540         CheckBox* _allow_any_container = nullptr;
1541         CheckBox* _allow_96khz_audio = nullptr;
1542         CheckBox* _use_all_audio_channels = nullptr;
1543         CheckBox* _allow_smpte_bv20 = nullptr;
1544         wxSpinCtrl* _isdcf_name_part_length = nullptr;
1545 };
1546
1547
1548
1549 /** @class AdvancedPage
1550  *  @brief Advanced page of the preferences dialog.
1551  */
1552 class AdvancedPage : public Page
1553 {
1554 public:
1555         AdvancedPage (wxSize panel_size, int border)
1556                 : Page (panel_size, border)
1557         {}
1558
1559         wxString GetName () const override
1560         {
1561                 return _("Advanced");
1562         }
1563
1564 #ifdef DCPOMATIC_OSX
1565         wxBitmap GetLargeIcon () const override
1566         {
1567                 return wxBitmap(icon_path("advanced"), wxBITMAP_TYPE_PNG);
1568         }
1569 #endif
1570
1571 private:
1572         void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
1573         {
1574                 int flags = wxALIGN_TOP | wxTOP | wxLEFT;
1575 #ifdef __WXOSX__
1576                 flags |= wxALIGN_RIGHT;
1577                 text += wxT (":");
1578 #endif
1579                 wxStaticText* m = new StaticText (parent, text);
1580                 table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
1581         }
1582
1583         void setup () override
1584         {
1585                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1586                 table->AddGrowableCol (1, 1);
1587                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1588
1589                 add_label_to_sizer (table, _panel, _("Video display mode"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1590                 _video_display_mode = new wxChoice (_panel, wxID_ANY);
1591                 table->Add (_video_display_mode);
1592
1593                 auto restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false);
1594                 auto font = restart->GetFont();
1595                 font.SetStyle (wxFONTSTYLE_ITALIC);
1596                 font.SetPointSize (font.GetPointSize() - 1);
1597                 restart->SetFont (font);
1598                 table->AddSpacer (0);
1599
1600                 _show_experimental_audio_processors = new CheckBox (_panel, _("Show experimental audio processors"));
1601                 table->Add (_show_experimental_audio_processors, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
1602                 table->AddSpacer (0);
1603
1604                 _only_servers_encode = new CheckBox (_panel, _("Only servers encode"));
1605                 table->Add (_only_servers_encode, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
1606                 table->AddSpacer (0);
1607
1608                 {
1609                         add_label_to_sizer (table, _panel, _("Maximum number of frames to store per thread"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1610                         auto s = new wxBoxSizer (wxHORIZONTAL);
1611                         _frames_in_memory_multiplier = new wxSpinCtrl (_panel);
1612                         s->Add (_frames_in_memory_multiplier, 1);
1613                         table->Add (s, 1);
1614                 }
1615
1616                 {
1617                         auto format = create_label (_panel, _("DCP metadata filename format"), true);
1618 #ifdef DCPOMATIC_OSX
1619                         auto align = new wxBoxSizer (wxHORIZONTAL);
1620                         align->Add (format, 0, wxTOP, 2);
1621                         table->Add (align, 0, wxALIGN_RIGHT | wxRIGHT, DCPOMATIC_SIZER_GAP - 2);
1622 #else
1623                         table->Add (format, 0, wxTOP | wxLEFT | wxRIGHT | wxALIGN_TOP, DCPOMATIC_SIZER_GAP);
1624 #endif
1625                         dcp::NameFormat::Map titles;
1626                         titles['t'] = wx_to_std (_("type (cpl/pkl)"));
1627                         dcp::NameFormat::Map examples;
1628                         examples['t'] = "cpl";
1629                         _dcp_metadata_filename_format = new NameFormatEditor (
1630                                 _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
1631                                 );
1632                         table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
1633                 }
1634
1635                 {
1636                         auto format = create_label (_panel, _("DCP asset filename format"), true);
1637 #ifdef DCPOMATIC_OSX
1638                         auto align = new wxBoxSizer (wxHORIZONTAL);
1639                         align->Add (format, 0, wxTOP, 2);
1640                         table->Add (align, 0, wxALIGN_RIGHT | wxRIGHT, DCPOMATIC_SIZER_GAP - 2);
1641 #else
1642                         table->Add (format, 0, wxTOP | wxLEFT | wxRIGHT | wxALIGN_TOP, DCPOMATIC_SIZER_GAP);
1643 #endif
1644                         dcp::NameFormat::Map titles;
1645                         titles['t'] = wx_to_std (_("type (j2c/pcm/sub)"));
1646                         titles['r'] = wx_to_std (_("reel number"));
1647                         titles['n'] = wx_to_std (_("number of reels"));
1648                         titles['c'] = wx_to_std (_("content filename"));
1649                         dcp::NameFormat::Map examples;
1650                         examples['t'] = "j2c";
1651                         examples['r'] = "1";
1652                         examples['n'] = "4";
1653                         examples['c'] = "myfile.mp4";
1654                         _dcp_asset_filename_format = new NameFormatEditor (
1655                                 _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
1656                                 );
1657                         table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
1658                 }
1659
1660                 {
1661                         add_top_aligned_label_to_sizer (table, _panel, _("Log"));
1662                         auto t = new wxFlexGridSizer (2);
1663                         _log_general = new CheckBox (_panel, _("General"));
1664                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1665                         _log_warning = new CheckBox (_panel, _("Warnings"));
1666                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1667                         _log_error = new CheckBox (_panel, _("Errors"));
1668                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1669                         /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
1670                         _log_timing = new CheckBox (_panel, S_("Config|Timing"));
1671                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1672                         _log_debug_threed = new CheckBox (_panel, _("Debug: 3D"));
1673                         t->Add (_log_debug_threed, 1, wxEXPAND | wxALL);
1674                         _log_debug_encode = new CheckBox (_panel, _("Debug: encode"));
1675                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1676                         _log_debug_email = new CheckBox (_panel, _("Debug: email sending"));
1677                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1678                         _log_debug_video_view = new CheckBox (_panel, _("Debug: video view"));
1679                         t->Add (_log_debug_video_view, 1, wxEXPAND | wxALL);
1680                         _log_debug_player = new CheckBox (_panel, _("Debug: player"));
1681                         t->Add (_log_debug_player, 1, wxEXPAND | wxALL);
1682                         _log_debug_audio_analysis = new CheckBox (_panel, _("Debug: audio analysis"));
1683                         t->Add (_log_debug_audio_analysis, 1, wxEXPAND | wxALL);
1684                         table->Add (t, 0, wxALL, 6);
1685                 }
1686
1687 #ifdef DCPOMATIC_WINDOWS
1688                 _win32_console = new CheckBox (_panel, _("Open console window"));
1689                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1690                 table->AddSpacer (0);
1691 #endif
1692
1693                 _video_display_mode->Append (_("Simple (safer)"));
1694 #if wxCHECK_VERSION(3, 1, 0)
1695                 _video_display_mode->Append (_("OpenGL (faster)"));
1696 #endif
1697                 _video_display_mode->Bind (wxEVT_CHOICE, boost::bind(&AdvancedPage::video_display_mode_changed, this));
1698                 _show_experimental_audio_processors->bind(&AdvancedPage::show_experimental_audio_processors_changed, this);
1699                 _only_servers_encode->bind(&AdvancedPage::only_servers_encode_changed, this);
1700                 _frames_in_memory_multiplier->Bind (wxEVT_SPINCTRL, boost::bind(&AdvancedPage::frames_in_memory_multiplier_changed, this));
1701                 _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
1702                 _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
1703                 _log_general->bind(&AdvancedPage::log_changed, this);
1704                 _log_warning->bind(&AdvancedPage::log_changed, this);
1705                 _log_error->bind(&AdvancedPage::log_changed, this);
1706                 _log_timing->bind(&AdvancedPage::log_changed, this);
1707                 _log_debug_threed->bind(&AdvancedPage::log_changed, this);
1708                 _log_debug_encode->bind(&AdvancedPage::log_changed, this);
1709                 _log_debug_email->bind(&AdvancedPage::log_changed, this);
1710                 _log_debug_video_view->bind(&AdvancedPage::log_changed, this);
1711                 _log_debug_player->bind(&AdvancedPage::log_changed, this);
1712                 _log_debug_audio_analysis->bind(&AdvancedPage::log_changed, this);
1713 #ifdef DCPOMATIC_WINDOWS
1714                 _win32_console->bind(&AdvancedPage::win32_console_changed, this);
1715 #endif
1716         }
1717
1718         void config_changed () override
1719         {
1720                 auto config = Config::instance ();
1721
1722                 switch (config->video_view_type()) {
1723                 case Config::VIDEO_VIEW_SIMPLE:
1724                         checked_set (_video_display_mode, 0);
1725                         break;
1726                 case Config::VIDEO_VIEW_OPENGL:
1727                         checked_set (_video_display_mode, 1);
1728                         break;
1729                 }
1730                 checked_set (_show_experimental_audio_processors, config->show_experimental_audio_processors ());
1731                 checked_set (_only_servers_encode, config->only_servers_encode ());
1732                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1733                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1734                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1735                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1736                 checked_set (_log_debug_threed, config->log_types() & LogEntry::TYPE_DEBUG_THREE_D);
1737                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1738                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1739                 checked_set (_log_debug_video_view, config->log_types() & LogEntry::TYPE_DEBUG_VIDEO_VIEW);
1740                 checked_set (_log_debug_player, config->log_types() & LogEntry::TYPE_DEBUG_PLAYER);
1741                 checked_set (_log_debug_audio_analysis, config->log_types() & LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS);
1742                 checked_set (_frames_in_memory_multiplier, config->frames_in_memory_multiplier());
1743 #ifdef DCPOMATIC_WINDOWS
1744                 checked_set (_win32_console, config->win32_console());
1745 #endif
1746         }
1747
1748         void video_display_mode_changed ()
1749         {
1750                 if (_video_display_mode->GetSelection() == 0) {
1751                         Config::instance()->set_video_view_type(Config::VIDEO_VIEW_SIMPLE);
1752                 } else {
1753                         Config::instance()->set_video_view_type(Config::VIDEO_VIEW_OPENGL);
1754                 }
1755         }
1756
1757         void frames_in_memory_multiplier_changed ()
1758         {
1759                 Config::instance()->set_frames_in_memory_multiplier(_frames_in_memory_multiplier->GetValue());
1760         }
1761
1762         void show_experimental_audio_processors_changed ()
1763         {
1764                 Config::instance()->set_show_experimental_audio_processors(_show_experimental_audio_processors->GetValue());
1765         }
1766
1767         void only_servers_encode_changed ()
1768         {
1769                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue());
1770         }
1771
1772         void dcp_metadata_filename_format_changed ()
1773         {
1774                 Config::instance()->set_dcp_metadata_filename_format(_dcp_metadata_filename_format->get());
1775         }
1776
1777         void dcp_asset_filename_format_changed ()
1778         {
1779                 Config::instance()->set_dcp_asset_filename_format(_dcp_asset_filename_format->get());
1780         }
1781
1782         void log_changed ()
1783         {
1784                 int types = 0;
1785                 if (_log_general->GetValue ()) {
1786                         types |= LogEntry::TYPE_GENERAL;
1787                 }
1788                 if (_log_warning->GetValue ()) {
1789                         types |= LogEntry::TYPE_WARNING;
1790                 }
1791                 if (_log_error->GetValue ())  {
1792                         types |= LogEntry::TYPE_ERROR;
1793                 }
1794                 if (_log_timing->GetValue ()) {
1795                         types |= LogEntry::TYPE_TIMING;
1796                 }
1797                 if (_log_debug_threed->GetValue ()) {
1798                         types |= LogEntry::TYPE_DEBUG_THREE_D;
1799                 }
1800                 if (_log_debug_encode->GetValue ()) {
1801                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1802                 }
1803                 if (_log_debug_email->GetValue ()) {
1804                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1805                 }
1806                 if (_log_debug_video_view->GetValue()) {
1807                         types |= LogEntry::TYPE_DEBUG_VIDEO_VIEW;
1808                 }
1809                 if (_log_debug_player->GetValue()) {
1810                         types |= LogEntry::TYPE_DEBUG_PLAYER;
1811                 }
1812                 if (_log_debug_audio_analysis->GetValue()) {
1813                         types |= LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS;
1814                 }
1815                 Config::instance()->set_log_types (types);
1816         }
1817
1818 #ifdef DCPOMATIC_WINDOWS
1819         void win32_console_changed ()
1820         {
1821                 Config::instance()->set_win32_console(_win32_console->GetValue());
1822         }
1823 #endif
1824
1825         wxChoice* _video_display_mode = nullptr;
1826         wxSpinCtrl* _frames_in_memory_multiplier = nullptr;
1827         CheckBox* _show_experimental_audio_processors = nullptr;
1828         CheckBox* _only_servers_encode = nullptr;
1829         NameFormatEditor* _dcp_metadata_filename_format = nullptr;
1830         NameFormatEditor* _dcp_asset_filename_format = nullptr;
1831         CheckBox* _log_general = nullptr;
1832         CheckBox* _log_warning = nullptr;
1833         CheckBox* _log_error = nullptr;
1834         CheckBox* _log_timing = nullptr;
1835         CheckBox* _log_debug_threed = nullptr;
1836         CheckBox* _log_debug_encode = nullptr;
1837         CheckBox* _log_debug_email = nullptr;
1838         CheckBox* _log_debug_video_view = nullptr;
1839         CheckBox* _log_debug_player = nullptr;
1840         CheckBox* _log_debug_audio_analysis = nullptr;
1841 #ifdef DCPOMATIC_WINDOWS
1842         CheckBox* _win32_console = nullptr;
1843 #endif
1844 };
1845
1846
1847 wxPreferencesEditor*
1848 create_full_config_dialog ()
1849 {
1850         auto e = new wxPreferencesEditor ();
1851
1852 #ifdef DCPOMATIC_OSX
1853         /* Width that we force some of the config panels to be on OSX so that
1854            the containing window doesn't shrink too much when we select those panels.
1855            This is obviously an unpleasant hack.
1856         */
1857         wxSize ps = wxSize (750, -1);
1858         int const border = 16;
1859 #else
1860         wxSize ps = wxSize (-1, -1);
1861         int const border = 8;
1862 #endif
1863
1864         e->AddPage (new FullGeneralPage    (ps, border));
1865         e->AddPage (new SoundPage          (ps, border));
1866         e->AddPage (new DefaultsPage       (ps, border));
1867         e->AddPage (new EncodingServersPage(ps, border));
1868         e->AddPage (new KeysPage           (ps, border));
1869         e->AddPage (new TMSPage            (ps, border));
1870         e->AddPage (new EmailPage          (ps, border));
1871         e->AddPage (new KDMEmailPage       (ps, border));
1872         e->AddPage (new NotificationsPage  (ps, border));
1873         e->AddPage (new CoverSheetPage     (ps, border));
1874         e->AddPage (new IdentifiersPage    (ps, border));
1875         e->AddPage (new NonStandardPage    (ps, border));
1876         e->AddPage (new AdvancedPage       (ps, border));
1877         return e;
1878 }