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