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