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