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