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