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