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