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