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