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