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