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