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