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