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