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