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