Change bitmap_path to take a full name with extension.
[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.png"), 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.png"), 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.png"), 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.png"), 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.png"), 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.png"), 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.png"), 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.png"), 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.png"), 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                 _use_all_audio_channels = new CheckBox(_panel, _("Allow mapping to all audio channels"));
1499                 table->Add(_use_all_audio_channels, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
1500                 table->AddSpacer(0);
1501
1502                 _show_experimental_audio_processors = new CheckBox (_panel, _("Show experimental audio processors"));
1503                 table->Add (_show_experimental_audio_processors, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
1504                 table->AddSpacer (0);
1505
1506                 _only_servers_encode = new CheckBox (_panel, _("Only servers encode"));
1507                 table->Add (_only_servers_encode, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
1508                 table->AddSpacer (0);
1509
1510                 {
1511                         add_label_to_sizer (table, _panel, _("Maximum number of frames to store per thread"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1512                         auto s = new wxBoxSizer (wxHORIZONTAL);
1513                         _frames_in_memory_multiplier = new wxSpinCtrl (_panel);
1514                         s->Add (_frames_in_memory_multiplier, 1);
1515                         table->Add (s, 1);
1516                 }
1517
1518                 {
1519                         auto format = create_label (_panel, _("DCP metadata filename format"), true);
1520 #ifdef DCPOMATIC_OSX
1521                         auto align = new wxBoxSizer (wxHORIZONTAL);
1522                         align->Add (format, 0, wxTOP, 2);
1523                         table->Add (align, 0, wxALIGN_RIGHT | wxRIGHT, DCPOMATIC_SIZER_GAP - 2);
1524 #else
1525                         table->Add (format, 0, wxTOP | wxLEFT | wxRIGHT | wxALIGN_TOP, DCPOMATIC_SIZER_GAP);
1526 #endif
1527                         dcp::NameFormat::Map titles;
1528                         titles['t'] = wx_to_std (_("type (cpl/pkl)"));
1529                         dcp::NameFormat::Map examples;
1530                         examples['t'] = "cpl";
1531                         _dcp_metadata_filename_format = new NameFormatEditor (
1532                                 _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
1533                                 );
1534                         table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
1535                 }
1536
1537                 {
1538                         auto format = create_label (_panel, _("DCP asset filename format"), true);
1539 #ifdef DCPOMATIC_OSX
1540                         auto align = new wxBoxSizer (wxHORIZONTAL);
1541                         align->Add (format, 0, wxTOP, 2);
1542                         table->Add (align, 0, wxALIGN_RIGHT | wxRIGHT, DCPOMATIC_SIZER_GAP - 2);
1543 #else
1544                         table->Add (format, 0, wxTOP | wxLEFT | wxRIGHT | wxALIGN_TOP, DCPOMATIC_SIZER_GAP);
1545 #endif
1546                         dcp::NameFormat::Map titles;
1547                         titles['t'] = wx_to_std (_("type (j2c/pcm/sub)"));
1548                         titles['r'] = wx_to_std (_("reel number"));
1549                         titles['n'] = wx_to_std (_("number of reels"));
1550                         titles['c'] = wx_to_std (_("content filename"));
1551                         dcp::NameFormat::Map examples;
1552                         examples['t'] = "j2c";
1553                         examples['r'] = "1";
1554                         examples['n'] = "4";
1555                         examples['c'] = "myfile.mp4";
1556                         _dcp_asset_filename_format = new NameFormatEditor (
1557                                 _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
1558                                 );
1559                         table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
1560                 }
1561
1562                 {
1563                         add_top_aligned_label_to_sizer (table, _panel, _("Log"));
1564                         auto t = new wxFlexGridSizer (2);
1565                         _log_general = new CheckBox (_panel, _("General"));
1566                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1567                         _log_warning = new CheckBox (_panel, _("Warnings"));
1568                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1569                         _log_error = new CheckBox (_panel, _("Errors"));
1570                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1571                         /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
1572                         _log_timing = new CheckBox (_panel, S_("Config|Timing"));
1573                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1574                         _log_debug_threed = new CheckBox (_panel, _("Debug: 3D"));
1575                         t->Add (_log_debug_threed, 1, wxEXPAND | wxALL);
1576                         _log_debug_encode = new CheckBox (_panel, _("Debug: encode"));
1577                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1578                         _log_debug_email = new CheckBox (_panel, _("Debug: email sending"));
1579                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1580                         _log_debug_video_view = new CheckBox (_panel, _("Debug: video view"));
1581                         t->Add (_log_debug_video_view, 1, wxEXPAND | wxALL);
1582                         _log_debug_player = new CheckBox (_panel, _("Debug: player"));
1583                         t->Add (_log_debug_player, 1, wxEXPAND | wxALL);
1584                         _log_debug_audio_analysis = new CheckBox (_panel, _("Debug: audio analysis"));
1585                         t->Add (_log_debug_audio_analysis, 1, wxEXPAND | wxALL);
1586                         table->Add (t, 0, wxALL, 6);
1587                 }
1588
1589 #ifdef DCPOMATIC_WINDOWS
1590                 _win32_console = new CheckBox (_panel, _("Open console window"));
1591                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1592                 table->AddSpacer (0);
1593 #endif
1594
1595                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1596                 _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1597                 _video_display_mode->Append (_("Simple (safer)"));
1598 #if wxCHECK_VERSION(3, 1, 0)
1599                 _video_display_mode->Append (_("OpenGL (faster)"));
1600 #endif
1601                 _video_display_mode->Bind (wxEVT_CHOICE, boost::bind(&AdvancedPage::video_display_mode_changed, this));
1602                 _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1603                 _allow_any_container->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_container_changed, this));
1604                 _allow_96khz_audio->Bind (wxEVT_CHECKBOX, boost::bind(&AdvancedPage::allow_96khz_audio_changed, this));
1605                 _use_all_audio_channels->Bind(wxEVT_CHECKBOX, boost::bind(&AdvancedPage::use_all_channels_changed, this));
1606                 _show_experimental_audio_processors->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::show_experimental_audio_processors_changed, this));
1607                 _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
1608                 _frames_in_memory_multiplier->Bind (wxEVT_SPINCTRL, boost::bind(&AdvancedPage::frames_in_memory_multiplier_changed, this));
1609                 _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
1610                 _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
1611                 _log_general->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1612                 _log_warning->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1613                 _log_error->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1614                 _log_timing->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1615                 _log_debug_threed->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1616                 _log_debug_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1617                 _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1618                 _log_debug_video_view->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1619                 _log_debug_player->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1620                 _log_debug_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1621 #ifdef DCPOMATIC_WINDOWS
1622                 _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
1623 #endif
1624         }
1625
1626         void config_changed () override
1627         {
1628                 auto config = Config::instance ();
1629
1630                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1631                 switch (config->video_view_type()) {
1632                 case Config::VIDEO_VIEW_SIMPLE:
1633                         checked_set (_video_display_mode, 0);
1634                         break;
1635                 case Config::VIDEO_VIEW_OPENGL:
1636                         checked_set (_video_display_mode, 1);
1637                         break;
1638                 }
1639                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1640                 checked_set (_allow_any_container, config->allow_any_container ());
1641                 checked_set (_allow_96khz_audio, config->allow_96khz_audio());
1642                 checked_set (_use_all_audio_channels, config->use_all_audio_channels());
1643                 checked_set (_show_experimental_audio_processors, config->show_experimental_audio_processors ());
1644                 checked_set (_only_servers_encode, config->only_servers_encode ());
1645                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1646                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1647                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1648                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1649                 checked_set (_log_debug_threed, config->log_types() & LogEntry::TYPE_DEBUG_THREE_D);
1650                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1651                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1652                 checked_set (_log_debug_video_view, config->log_types() & LogEntry::TYPE_DEBUG_VIDEO_VIEW);
1653                 checked_set (_log_debug_player, config->log_types() & LogEntry::TYPE_DEBUG_PLAYER);
1654                 checked_set (_log_debug_audio_analysis, config->log_types() & LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS);
1655                 checked_set (_frames_in_memory_multiplier, config->frames_in_memory_multiplier());
1656 #ifdef DCPOMATIC_WINDOWS
1657                 checked_set (_win32_console, config->win32_console());
1658 #endif
1659         }
1660
1661         void maximum_j2k_bandwidth_changed ()
1662         {
1663                 Config::instance()->set_maximum_j2k_bandwidth(_maximum_j2k_bandwidth->GetValue() * 1000000);
1664         }
1665
1666         void video_display_mode_changed ()
1667         {
1668                 if (_video_display_mode->GetSelection() == 0) {
1669                         Config::instance()->set_video_view_type(Config::VIDEO_VIEW_SIMPLE);
1670                 } else {
1671                         Config::instance()->set_video_view_type(Config::VIDEO_VIEW_OPENGL);
1672                 }
1673         }
1674
1675         void frames_in_memory_multiplier_changed ()
1676         {
1677                 Config::instance()->set_frames_in_memory_multiplier(_frames_in_memory_multiplier->GetValue());
1678         }
1679
1680         void allow_any_dcp_frame_rate_changed ()
1681         {
1682                 Config::instance()->set_allow_any_dcp_frame_rate(_allow_any_dcp_frame_rate->GetValue());
1683         }
1684
1685         void allow_any_container_changed ()
1686         {
1687                 Config::instance()->set_allow_any_container(_allow_any_container->GetValue());
1688         }
1689
1690         void allow_96khz_audio_changed ()
1691         {
1692                 Config::instance()->set_allow_96hhz_audio(_allow_96khz_audio->GetValue());
1693         }
1694
1695         void use_all_channels_changed ()
1696         {
1697                 Config::instance()->set_use_all_audio_channels(_use_all_audio_channels->GetValue());
1698         }
1699
1700         void show_experimental_audio_processors_changed ()
1701         {
1702                 Config::instance()->set_show_experimental_audio_processors(_show_experimental_audio_processors->GetValue());
1703         }
1704
1705         void only_servers_encode_changed ()
1706         {
1707                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue());
1708         }
1709
1710         void dcp_metadata_filename_format_changed ()
1711         {
1712                 Config::instance()->set_dcp_metadata_filename_format(_dcp_metadata_filename_format->get());
1713         }
1714
1715         void dcp_asset_filename_format_changed ()
1716         {
1717                 Config::instance()->set_dcp_asset_filename_format(_dcp_asset_filename_format->get());
1718         }
1719
1720         void log_changed ()
1721         {
1722                 int types = 0;
1723                 if (_log_general->GetValue ()) {
1724                         types |= LogEntry::TYPE_GENERAL;
1725                 }
1726                 if (_log_warning->GetValue ()) {
1727                         types |= LogEntry::TYPE_WARNING;
1728                 }
1729                 if (_log_error->GetValue ())  {
1730                         types |= LogEntry::TYPE_ERROR;
1731                 }
1732                 if (_log_timing->GetValue ()) {
1733                         types |= LogEntry::TYPE_TIMING;
1734                 }
1735                 if (_log_debug_threed->GetValue ()) {
1736                         types |= LogEntry::TYPE_DEBUG_THREE_D;
1737                 }
1738                 if (_log_debug_encode->GetValue ()) {
1739                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1740                 }
1741                 if (_log_debug_email->GetValue ()) {
1742                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1743                 }
1744                 if (_log_debug_video_view->GetValue()) {
1745                         types |= LogEntry::TYPE_DEBUG_VIDEO_VIEW;
1746                 }
1747                 if (_log_debug_player->GetValue()) {
1748                         types |= LogEntry::TYPE_DEBUG_PLAYER;
1749                 }
1750                 if (_log_debug_audio_analysis->GetValue()) {
1751                         types |= LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS;
1752                 }
1753                 Config::instance()->set_log_types (types);
1754         }
1755
1756 #ifdef DCPOMATIC_WINDOWS
1757         void win32_console_changed ()
1758         {
1759                 Config::instance()->set_win32_console(_win32_console->GetValue());
1760         }
1761 #endif
1762
1763         wxSpinCtrl* _maximum_j2k_bandwidth = nullptr;
1764         wxChoice* _video_display_mode = nullptr;
1765         wxSpinCtrl* _frames_in_memory_multiplier = nullptr;
1766         wxCheckBox* _allow_any_dcp_frame_rate = nullptr;
1767         wxCheckBox* _allow_any_container = nullptr;
1768         wxCheckBox* _allow_96khz_audio = nullptr;
1769         wxCheckBox* _use_all_audio_channels = nullptr;
1770         wxCheckBox* _show_experimental_audio_processors = nullptr;
1771         wxCheckBox* _only_servers_encode = nullptr;
1772         NameFormatEditor* _dcp_metadata_filename_format = nullptr;
1773         NameFormatEditor* _dcp_asset_filename_format = nullptr;
1774         wxCheckBox* _log_general = nullptr;
1775         wxCheckBox* _log_warning = nullptr;
1776         wxCheckBox* _log_error = nullptr;
1777         wxCheckBox* _log_timing = nullptr;
1778         wxCheckBox* _log_debug_threed = nullptr;
1779         wxCheckBox* _log_debug_encode = nullptr;
1780         wxCheckBox* _log_debug_email = nullptr;
1781         wxCheckBox* _log_debug_video_view = nullptr;
1782         wxCheckBox* _log_debug_player = nullptr;
1783         wxCheckBox* _log_debug_audio_analysis = nullptr;
1784 #ifdef DCPOMATIC_WINDOWS
1785         wxCheckBox* _win32_console = nullptr;
1786 #endif
1787 };
1788
1789
1790 wxPreferencesEditor*
1791 create_full_config_dialog ()
1792 {
1793         auto e = new wxPreferencesEditor ();
1794
1795 #ifdef DCPOMATIC_OSX
1796         /* Width that we force some of the config panels to be on OSX so that
1797            the containing window doesn't shrink too much when we select those panels.
1798            This is obviously an unpleasant hack.
1799         */
1800         wxSize ps = wxSize (750, -1);
1801         int const border = 16;
1802 #else
1803         wxSize ps = wxSize (-1, -1);
1804         int const border = 8;
1805 #endif
1806
1807         e->AddPage (new FullGeneralPage    (ps, border));
1808         e->AddPage (new SoundPage          (ps, border));
1809         e->AddPage (new DefaultsPage       (ps, border));
1810         e->AddPage (new EncodingServersPage(ps, border));
1811         e->AddPage (new KeysPage           (ps, border));
1812         e->AddPage (new TMSPage            (ps, border));
1813         e->AddPage (new EmailPage          (ps, border));
1814         e->AddPage (new KDMEmailPage       (ps, border));
1815         e->AddPage (new NotificationsPage  (ps, border));
1816         e->AddPage (new CoverSheetPage     (ps, border));
1817         e->AddPage (new IdentifiersPage    (ps, border));
1818         e->AddPage (new AdvancedPage       (ps, border));
1819         return e;
1820 }