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