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