e83cdac4da73d64452e1d2aebafb7775be6a3bbd
[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_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_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_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                 add_label_to_sizer (table, _panel, _("Default KDM directory"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
317 #ifdef DCPOMATIC_USE_OWN_PICKER
318                 _kdm_directory = new DirPickerCtrl (_panel);
319 #else
320                 _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
321 #endif
322
323                 table->Add (_kdm_directory, 1, wxEXPAND);
324
325                 _still_length->SetRange (1, 3600);
326                 _still_length->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::still_length_changed, this));
327
328                 _directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
329                 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::kdm_directory_changed, this));
330
331                 for (auto i: Ratio::containers()) {
332                         _container->Append (std_to_wx(i->container_nickname()));
333                 }
334
335                 _container->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::container_changed, this));
336
337                 for (auto i: DCPContentType::all()) {
338                         _dcp_content_type->Append (std_to_wx (i->pretty_name ()));
339                 }
340
341                 setup_audio_channels_choice (_dcp_audio_channels, 2);
342
343                 _dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
344                 _dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
345
346                 _j2k_bandwidth->SetRange (50, 250);
347                 _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
348
349                 _audio_delay->SetRange (-1000, 1000);
350                 _audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
351
352                 _standard->Append (_("SMPTE"));
353                 _standard->Append (_("Interop"));
354                 _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
355         }
356
357         void config_changed ()
358         {
359                 auto config = Config::instance ();
360
361                 auto containers = Ratio::containers ();
362                 for (size_t i = 0; i < containers.size(); ++i) {
363                         if (containers[i] == config->default_container()) {
364                                 _container->SetSelection (i);
365                         }
366                 }
367
368                 auto const ct = DCPContentType::all ();
369                 for (size_t i = 0; i < ct.size(); ++i) {
370                         if (ct[i] == config->default_dcp_content_type()) {
371                                 _dcp_content_type->SetSelection (i);
372                         }
373                 }
374
375                 checked_set (_still_length, config->default_still_length ());
376                 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
377                 _kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
378                 checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
379                 _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
380                 checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
381                 checked_set (_audio_delay, config->default_audio_delay ());
382                 checked_set (_standard, config->default_interop() ? 1 : 0);
383         }
384
385         void j2k_bandwidth_changed ()
386         {
387                 Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
388         }
389
390         void audio_delay_changed ()
391         {
392                 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
393         }
394
395         void dcp_audio_channels_changed ()
396         {
397                 int const s = _dcp_audio_channels->GetSelection ();
398                 if (s != wxNOT_FOUND) {
399                         Config::instance()->set_default_dcp_audio_channels (
400                                 locale_convert<int>(string_client_data(_dcp_audio_channels->GetClientObject(s)))
401                                 );
402                 }
403         }
404
405         void directory_changed ()
406         {
407                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
408         }
409
410         void kdm_directory_changed ()
411         {
412                 Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
413         }
414
415         void still_length_changed ()
416         {
417                 Config::instance()->set_default_still_length (_still_length->GetValue ());
418         }
419
420         void container_changed ()
421         {
422                 auto ratio = Ratio::containers ();
423                 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
424         }
425
426         void dcp_content_type_changed ()
427         {
428                 auto ct = DCPContentType::all ();
429                 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
430         }
431
432         void standard_changed ()
433         {
434                 Config::instance()->set_default_interop (_standard->GetSelection() == 1);
435         }
436
437         wxSpinCtrl* _j2k_bandwidth;
438         wxSpinCtrl* _audio_delay;
439         wxSpinCtrl* _still_length;
440 #ifdef DCPOMATIC_USE_OWN_PICKER
441         DirPickerCtrl* _directory;
442         DirPickerCtrl* _kdm_directory;
443 #else
444         wxDirPickerCtrl* _directory;
445         wxDirPickerCtrl* _kdm_directory;
446 #endif
447         wxChoice* _container;
448         wxChoice* _dcp_content_type;
449         wxChoice* _dcp_audio_channels;
450         wxChoice* _standard;
451 };
452
453
454 class EncodingServersPage : public Page
455 {
456 public:
457         EncodingServersPage (wxSize panel_size, int border)
458                 : Page (panel_size, border)
459         {}
460
461         wxString GetName () const
462         {
463                 return _("Servers");
464         }
465
466 #ifdef DCPOMATIC_OSX
467         wxBitmap GetLargeIcon () const
468         {
469                 return wxBitmap(bitmap_path("servers"), wxBITMAP_TYPE_PNG);
470         }
471 #endif
472
473 private:
474         void setup ()
475         {
476                 _use_any_servers = new CheckBox (_panel, _("Search network for servers"));
477                 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
478
479                 vector<EditableListColumn> columns;
480                 columns.push_back (EditableListColumn(_("IP address / host name")));
481                 _servers_list = new EditableList<string, ServerDialog> (
482                         _panel,
483                         columns,
484                         boost::bind (&Config::servers, Config::instance()),
485                         boost::bind (&Config::set_servers, Config::instance(), _1),
486                         boost::bind (&EncodingServersPage::server_column, this, _1)
487                         );
488
489                 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
490
491                 _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind(&EncodingServersPage::use_any_servers_changed, this));
492         }
493
494         void config_changed ()
495         {
496                 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
497                 _servers_list->refresh ();
498         }
499
500         void use_any_servers_changed ()
501         {
502                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
503         }
504
505         string server_column (string s)
506         {
507                 return s;
508         }
509
510         wxCheckBox* _use_any_servers;
511         EditableList<string, ServerDialog>* _servers_list;
512 };
513
514
515 class TMSPage : public Page
516 {
517 public:
518         TMSPage (wxSize panel_size, int border)
519                 : Page (panel_size, border)
520         {}
521
522         wxString GetName () const
523         {
524                 return _("TMS");
525         }
526
527 #ifdef DCPOMATIC_OSX
528         wxBitmap GetLargeIcon () const
529         {
530                 return wxBitmap(bitmap_path("tms"), wxBITMAP_TYPE_PNG);
531         }
532 #endif
533
534 private:
535         void setup ()
536         {
537                 _upload = new CheckBox (_panel, _("Upload DCP to TMS after creation"));
538                 _panel->GetSizer()->Add (_upload, 0, wxALL | wxEXPAND, _border);
539
540                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
541                 table->AddGrowableCol (1, 1);
542                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
543
544                 add_label_to_sizer (table, _panel, _("Protocol"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
545                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
546                 table->Add (_tms_protocol, 1, wxEXPAND);
547
548                 add_label_to_sizer (table, _panel, _("IP address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
549                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
550                 table->Add (_tms_ip, 1, wxEXPAND);
551
552                 add_label_to_sizer (table, _panel, _("Target path"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
553                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
554                 table->Add (_tms_path, 1, wxEXPAND);
555
556                 add_label_to_sizer (table, _panel, _("User name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
557                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
558                 table->Add (_tms_user, 1, wxEXPAND);
559
560                 add_label_to_sizer (table, _panel, _("Password"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
561                 _tms_password = new PasswordEntry (_panel);
562                 table->Add (_tms_password->get_panel(), 1, wxEXPAND);
563
564                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
565                 _tms_protocol->Append (_("FTP (for Dolby)"));
566
567                 _upload->Bind (wxEVT_CHECKBOX, boost::bind(&TMSPage::upload_changed, this));
568                 _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
569                 _tms_ip->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_ip_changed, this));
570                 _tms_path->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_path_changed, this));
571                 _tms_user->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_user_changed, this));
572                 _tms_password->Changed.connect (boost::bind (&TMSPage::tms_password_changed, this));
573         }
574
575         void config_changed ()
576         {
577                 auto config = Config::instance ();
578
579                 checked_set (_upload, config->upload_after_make_dcp());
580                 checked_set (_tms_protocol, static_cast<int>(config->tms_protocol()));
581                 checked_set (_tms_ip, config->tms_ip ());
582                 checked_set (_tms_path, config->tms_path ());
583                 checked_set (_tms_user, config->tms_user ());
584                 checked_set (_tms_password, config->tms_password ());
585         }
586
587         void upload_changed ()
588         {
589                 Config::instance()->set_upload_after_make_dcp (_upload->GetValue());
590         }
591
592         void tms_protocol_changed ()
593         {
594                 Config::instance()->set_tms_protocol(static_cast<FileTransferProtocol>(_tms_protocol->GetSelection()));
595         }
596
597         void tms_ip_changed ()
598         {
599                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
600         }
601
602         void tms_path_changed ()
603         {
604                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
605         }
606
607         void tms_user_changed ()
608         {
609                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
610         }
611
612         void tms_password_changed ()
613         {
614                 Config::instance()->set_tms_password (_tms_password->get());
615         }
616
617         CheckBox* _upload;
618         wxChoice* _tms_protocol;
619         wxTextCtrl* _tms_ip;
620         wxTextCtrl* _tms_path;
621         wxTextCtrl* _tms_user;
622         PasswordEntry* _tms_password;
623 };
624
625
626 class EmailPage : public Page
627 {
628 public:
629         EmailPage (wxSize panel_size, int border)
630                 : Page (panel_size, border)
631         {}
632
633         wxString GetName () const
634         {
635                 return _("Email");
636         }
637
638 #ifdef DCPOMATIC_OSX
639         wxBitmap GetLargeIcon () const
640         {
641                 return wxBitmap(bitmap_path("email"), wxBITMAP_TYPE_PNG);
642         }
643 #endif
644
645 private:
646         void setup ()
647         {
648                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
649                 table->AddGrowableCol (1, 1);
650                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
651
652                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
653                 {
654                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
655                         _server = new wxTextCtrl (_panel, wxID_ANY);
656                         s->Add (_server, 1, wxEXPAND | wxALL);
657                         add_label_to_sizer (s, _panel, _("port"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
658                         _port = new wxSpinCtrl (_panel, wxID_ANY);
659                         _port->SetRange (0, 65535);
660                         s->Add (_port);
661                         add_label_to_sizer (s, _panel, _("protocol"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
662                         _protocol = new wxChoice (_panel, wxID_ANY);
663                         /* Make sure this matches the switches in config_changed and port_changed below */
664                         _protocol->Append (_("Auto"));
665                         _protocol->Append (_("Plain"));
666                         _protocol->Append (_("STARTTLS"));
667                         _protocol->Append (_("SSL"));
668                         s->Add (_protocol);
669                         table->Add (s, 1, wxEXPAND | wxALL);
670                 }
671
672                 add_label_to_sizer (table, _panel, _("User name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
673                 _user = new wxTextCtrl (_panel, wxID_ANY);
674                 table->Add (_user, 1, wxEXPAND | wxALL);
675
676                 add_label_to_sizer (table, _panel, _("Password"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
677                 _password = new PasswordEntry (_panel);
678                 table->Add (_password->get_panel(), 1, wxEXPAND | wxALL);
679
680                 _server->Bind (wxEVT_TEXT, boost::bind(&EmailPage::server_changed, this));
681                 _port->Bind (wxEVT_SPINCTRL, boost::bind(&EmailPage::port_changed, this));
682                 _protocol->Bind (wxEVT_CHOICE, boost::bind(&EmailPage::protocol_changed, this));
683                 _user->Bind (wxEVT_TEXT, boost::bind(&EmailPage::user_changed, this));
684                 _password->Changed.connect (boost::bind(&EmailPage::password_changed, this));
685         }
686
687         void config_changed ()
688         {
689                 auto config = Config::instance ();
690
691                 checked_set (_server, config->mail_server());
692                 checked_set (_port, config->mail_port());
693                 switch (config->mail_protocol()) {
694                 case EmailProtocol::AUTO:
695                         checked_set (_protocol, 0);
696                         break;
697                 case EmailProtocol::PLAIN:
698                         checked_set (_protocol, 1);
699                         break;
700                 case EmailProtocol::STARTTLS:
701                         checked_set (_protocol, 2);
702                         break;
703                 case EmailProtocol::SSL:
704                         checked_set (_protocol, 3);
705                         break;
706                 }
707                 checked_set (_user, config->mail_user());
708                 checked_set (_password, config->mail_password());
709         }
710
711         void server_changed ()
712         {
713                 Config::instance()->set_mail_server(wx_to_std(_server->GetValue()));
714         }
715
716         void port_changed ()
717         {
718                 Config::instance()->set_mail_port(_port->GetValue());
719         }
720
721         void protocol_changed ()
722         {
723                 switch (_protocol->GetSelection()) {
724                 case 0:
725                         Config::instance()->set_mail_protocol(EmailProtocol::AUTO);
726                         break;
727                 case 1:
728                         Config::instance()->set_mail_protocol(EmailProtocol::PLAIN);
729                         break;
730                 case 2:
731                         Config::instance()->set_mail_protocol(EmailProtocol::STARTTLS);
732                         break;
733                 case 3:
734                         Config::instance()->set_mail_protocol(EmailProtocol::SSL);
735                         break;
736                 }
737         }
738
739         void user_changed ()
740         {
741                 Config::instance()->set_mail_user (wx_to_std (_user->GetValue ()));
742         }
743
744         void password_changed ()
745         {
746                 Config::instance()->set_mail_password(_password->get());
747         }
748
749         wxTextCtrl* _server;
750         wxSpinCtrl* _port;
751         wxChoice* _protocol;
752         wxTextCtrl* _user;
753         PasswordEntry* _password;
754 };
755
756
757 class KDMEmailPage : public Page
758 {
759 public:
760
761         KDMEmailPage (wxSize panel_size, int border)
762 #ifdef DCPOMATIC_OSX
763                 /* We have to force both width and height of this one */
764                 : Page (wxSize (panel_size.GetWidth(), 128), border)
765 #else
766                 : Page (panel_size, border)
767 #endif
768         {}
769
770         wxString GetName () const
771         {
772                 return _("KDM Email");
773         }
774
775 #ifdef DCPOMATIC_OSX
776         wxBitmap GetLargeIcon () const
777         {
778                 return wxBitmap(bitmap_path("kdm_email"), wxBITMAP_TYPE_PNG);
779         }
780 #endif
781
782 private:
783         void setup ()
784         {
785                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
786                 table->AddGrowableCol (1, 1);
787                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
788
789                 add_label_to_sizer (table, _panel, _("Subject"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
790                 _subject = new wxTextCtrl (_panel, wxID_ANY);
791                 table->Add (_subject, 1, wxEXPAND | wxALL);
792
793                 add_label_to_sizer (table, _panel, _("From address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
794                 _from = new wxTextCtrl (_panel, wxID_ANY);
795                 table->Add (_from, 1, wxEXPAND | wxALL);
796
797                 vector<EditableListColumn> columns;
798                 columns.push_back (EditableListColumn(_("Address")));
799                 add_label_to_sizer (table, _panel, _("CC addresses"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
800                 _cc = new EditableList<string, EmailDialog> (
801                         _panel,
802                         columns,
803                         bind (&Config::kdm_cc, Config::instance()),
804                         bind (&Config::set_kdm_cc, Config::instance(), _1),
805                         [] (string s, int) {
806                                 return s;
807                         });
808                 table->Add (_cc, 1, wxEXPAND | wxALL);
809
810                 add_label_to_sizer (table, _panel, _("BCC address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
811                 _bcc = new wxTextCtrl (_panel, wxID_ANY);
812                 table->Add (_bcc, 1, wxEXPAND | wxALL);
813
814                 _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
815                 _panel->GetSizer()->Add (_email, 0, wxEXPAND | wxALL, _border);
816
817                 _reset_email = new Button (_panel, _("Reset to default subject and text"));
818                 _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
819
820                 _cc->layout ();
821
822                 _subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
823                 _from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
824                 _bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
825                 _email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
826                 _reset_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_email, this));
827         }
828
829         void config_changed ()
830         {
831                 Config* config = Config::instance ();
832
833                 checked_set (_subject, config->kdm_subject ());
834                 checked_set (_from, config->kdm_from ());
835                 checked_set (_bcc, config->kdm_bcc ());
836                 checked_set (_email, Config::instance()->kdm_email ());
837         }
838
839         void kdm_subject_changed ()
840         {
841                 Config::instance()->set_kdm_subject (wx_to_std (_subject->GetValue ()));
842         }
843
844         void kdm_from_changed ()
845         {
846                 Config::instance()->set_kdm_from (wx_to_std (_from->GetValue ()));
847         }
848
849         void kdm_bcc_changed ()
850         {
851                 Config::instance()->set_kdm_bcc (wx_to_std (_bcc->GetValue ()));
852         }
853
854         void kdm_email_changed ()
855         {
856                 if (_email->GetValue().IsEmpty ()) {
857                         /* Sometimes we get sent an erroneous notification that the email
858                            is empty; I don't know why.
859                         */
860                         return;
861                 }
862                 Config::instance()->set_kdm_email (wx_to_std (_email->GetValue ()));
863         }
864
865         void reset_email ()
866         {
867                 Config::instance()->reset_kdm_email ();
868                 checked_set (_email, Config::instance()->kdm_email ());
869         }
870
871         wxTextCtrl* _subject;
872         wxTextCtrl* _from;
873         EditableList<string, EmailDialog>* _cc;
874         wxTextCtrl* _bcc;
875         wxTextCtrl* _email;
876         wxButton* _reset_email;
877 };
878
879
880 class NotificationsPage : public Page
881 {
882 public:
883         NotificationsPage (wxSize panel_size, int border)
884 #ifdef DCPOMATIC_OSX
885                 /* We have to force both width and height of this one */
886                 : Page (wxSize (panel_size.GetWidth(), 128), border)
887 #else
888                 : Page (panel_size, border)
889 #endif
890         {}
891
892         wxString GetName () const
893         {
894                 return _("Notifications");
895         }
896
897 #ifdef DCPOMATIC_OSX
898         wxBitmap GetLargeIcon () const
899         {
900                 return wxBitmap(bitmap_path("notifications"), wxBITMAP_TYPE_PNG);
901         }
902 #endif
903
904 private:
905         void setup ()
906         {
907                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
908                 table->AddGrowableCol (1, 1);
909                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
910
911                 _enable_message_box = new CheckBox (_panel, _("Message box"));
912                 table->Add (_enable_message_box, 1, wxEXPAND | wxALL);
913                 table->AddSpacer (0);
914
915                 _enable_email = new CheckBox (_panel, _("Email"));
916                 table->Add (_enable_email, 1, wxEXPAND | wxALL);
917                 table->AddSpacer (0);
918
919                 add_label_to_sizer (table, _panel, _("Subject"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
920                 _subject = new wxTextCtrl (_panel, wxID_ANY);
921                 table->Add (_subject, 1, wxEXPAND | wxALL);
922
923                 add_label_to_sizer (table, _panel, _("From address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
924                 _from = new wxTextCtrl (_panel, wxID_ANY);
925                 table->Add (_from, 1, wxEXPAND | wxALL);
926
927                 add_label_to_sizer (table, _panel, _("To address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
928                 _to = new wxTextCtrl (_panel, wxID_ANY);
929                 table->Add (_to, 1, wxEXPAND | wxALL);
930
931                 vector<EditableListColumn> columns;
932                 columns.push_back (EditableListColumn(_("Address")));
933                 add_label_to_sizer (table, _panel, _("CC addresses"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
934                 _cc = new EditableList<string, EmailDialog> (
935                         _panel,
936                         columns,
937                         bind (&Config::notification_cc, Config::instance()),
938                         bind (&Config::set_notification_cc, Config::instance(), _1),
939                         [] (string s, int) {
940                                 return s;
941                         });
942                 table->Add (_cc, 1, wxEXPAND | wxALL);
943
944                 add_label_to_sizer (table, _panel, _("BCC address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
945                 _bcc = new wxTextCtrl (_panel, wxID_ANY);
946                 table->Add (_bcc, 1, wxEXPAND | wxALL);
947
948                 _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
949                 _panel->GetSizer()->Add (_email, 0, wxEXPAND | wxALL, _border);
950
951                 _reset_email = new Button (_panel, _("Reset to default subject and text"));
952                 _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
953
954                 _cc->layout ();
955
956                 _enable_message_box->Bind (wxEVT_CHECKBOX, boost::bind (&NotificationsPage::type_changed, this, _enable_message_box, Config::MESSAGE_BOX));
957                 _enable_email->Bind (wxEVT_CHECKBOX, boost::bind (&NotificationsPage::type_changed, this, _enable_email, Config::EMAIL));
958
959                 _subject->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_subject_changed, this));
960                 _from->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_from_changed, this));
961                 _to->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_to_changed, this));
962                 _bcc->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_bcc_changed, this));
963                 _email->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_email_changed, this));
964                 _reset_email->Bind (wxEVT_BUTTON, boost::bind (&NotificationsPage::reset_email, this));
965
966                 update_sensitivity ();
967         }
968
969         void update_sensitivity ()
970         {
971                 bool const s = _enable_email->GetValue();
972                 _subject->Enable(s);
973                 _from->Enable(s);
974                 _to->Enable(s);
975                 _cc->Enable(s);
976                 _bcc->Enable(s);
977                 _email->Enable(s);
978                 _reset_email->Enable(s);
979         }
980
981         void config_changed ()
982         {
983                 auto config = Config::instance ();
984
985                 checked_set (_enable_message_box, config->notification(Config::MESSAGE_BOX));
986                 checked_set (_enable_email, config->notification(Config::EMAIL));
987                 checked_set (_subject, config->notification_subject ());
988                 checked_set (_from, config->notification_from ());
989                 checked_set (_to, config->notification_to ());
990                 checked_set (_bcc, config->notification_bcc ());
991                 checked_set (_email, Config::instance()->notification_email ());
992
993                 update_sensitivity ();
994         }
995
996         void notification_subject_changed ()
997         {
998                 Config::instance()->set_notification_subject(wx_to_std(_subject->GetValue()));
999         }
1000
1001         void notification_from_changed ()
1002         {
1003                 Config::instance()->set_notification_from(wx_to_std(_from->GetValue()));
1004         }
1005
1006         void notification_to_changed ()
1007         {
1008                 Config::instance()->set_notification_to(wx_to_std(_to->GetValue()));
1009         }
1010
1011         void notification_bcc_changed ()
1012         {
1013                 Config::instance()->set_notification_bcc(wx_to_std(_bcc->GetValue()));
1014         }
1015
1016         void notification_email_changed ()
1017         {
1018                 if (_email->GetValue().IsEmpty()) {
1019                         /* Sometimes we get sent an erroneous notification that the email
1020                            is empty; I don't know why.
1021                         */
1022                         return;
1023                 }
1024                 Config::instance()->set_notification_email(wx_to_std(_email->GetValue()));
1025         }
1026
1027         void reset_email ()
1028         {
1029                 Config::instance()->reset_notification_email();
1030                 checked_set (_email, Config::instance()->notification_email());
1031         }
1032
1033         void type_changed (wxCheckBox* b, Config::Notification n)
1034         {
1035                 Config::instance()->set_notification(n, b->GetValue());
1036                 update_sensitivity ();
1037         }
1038
1039         wxCheckBox* _enable_message_box;
1040         wxCheckBox* _enable_email;
1041
1042         wxTextCtrl* _subject;
1043         wxTextCtrl* _from;
1044         wxTextCtrl* _to;
1045         EditableList<string, EmailDialog>* _cc;
1046         wxTextCtrl* _bcc;
1047         wxTextCtrl* _email;
1048         wxButton* _reset_email;
1049 };
1050
1051
1052 class CoverSheetPage : public Page
1053 {
1054 public:
1055
1056         CoverSheetPage (wxSize panel_size, int border)
1057 #ifdef DCPOMATIC_OSX
1058                 /* We have to force both width and height of this one */
1059                 : Page (wxSize (panel_size.GetWidth(), 128), border)
1060 #else
1061                 : Page (panel_size, border)
1062 #endif
1063         {}
1064
1065         wxString GetName () const
1066         {
1067                 return _("Cover Sheet");
1068         }
1069
1070 #ifdef DCPOMATIC_OSX
1071         wxBitmap GetLargeIcon () const
1072         {
1073                 return wxBitmap(bitmap_path("cover_sheet"), wxBITMAP_TYPE_PNG);
1074         }
1075 #endif
1076
1077 private:
1078         void setup ()
1079         {
1080                 _cover_sheet = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1081                 _panel->GetSizer()->Add (_cover_sheet, 0, wxEXPAND | wxALL, _border);
1082
1083                 _reset_cover_sheet = new Button (_panel, _("Reset to default text"));
1084                 _panel->GetSizer()->Add (_reset_cover_sheet, 0, wxEXPAND | wxALL, _border);
1085
1086                 _cover_sheet->Bind (wxEVT_TEXT, boost::bind (&CoverSheetPage::cover_sheet_changed, this));
1087                 _reset_cover_sheet->Bind (wxEVT_BUTTON, boost::bind (&CoverSheetPage::reset_cover_sheet, this));
1088         }
1089
1090         void config_changed ()
1091         {
1092                 checked_set (_cover_sheet, Config::instance()->cover_sheet());
1093         }
1094
1095         void cover_sheet_changed ()
1096         {
1097                 if (_cover_sheet->GetValue().IsEmpty ()) {
1098                         /* Sometimes we get sent an erroneous notification that the cover sheet
1099                            is empty; I don't know why.
1100                         */
1101                         return;
1102                 }
1103                 Config::instance()->set_cover_sheet(wx_to_std(_cover_sheet->GetValue()));
1104         }
1105
1106         void reset_cover_sheet ()
1107         {
1108                 Config::instance()->reset_cover_sheet();
1109                 checked_set (_cover_sheet, Config::instance()->cover_sheet());
1110         }
1111
1112         wxTextCtrl* _cover_sheet;
1113         wxButton* _reset_cover_sheet;
1114 };
1115
1116
1117 class IdentifiersPage : public Page
1118 {
1119 public:
1120         IdentifiersPage (wxSize panel_size, int border)
1121                 : Page (panel_size, border)
1122         {}
1123
1124         wxString GetName () const
1125         {
1126                 return _("Identifiers");
1127         }
1128
1129 #ifdef DCPOMATIC_OSX
1130         wxBitmap GetLargeIcon () const
1131         {
1132                 return wxBitmap(bitmap_path("identifiers"), wxBITMAP_TYPE_PNG);
1133         }
1134 #endif
1135
1136 private:
1137         void setup ()
1138         {
1139                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1140                 table->AddGrowableCol (1, 1);
1141
1142                 add_label_to_sizer (table, _panel, _("Issuer"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1143                 _issuer = new wxTextCtrl (_panel, wxID_ANY);
1144                 _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."));
1145                 table->Add (_issuer, 1, wxALL | wxEXPAND);
1146
1147                 add_label_to_sizer (table, _panel, _("Creator"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1148                 _creator = new wxTextCtrl (_panel, wxID_ANY);
1149                 _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."));
1150                 table->Add (_creator, 1, wxALL | wxEXPAND);
1151
1152                 add_label_to_sizer (table, _panel, _("Company name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1153                 _company_name = new wxTextCtrl (_panel, wxID_ANY);
1154                 _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."));
1155                 table->Add (_company_name, 1, wxALL | wxEXPAND);
1156
1157                 add_label_to_sizer (table, _panel, _("Product name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1158                 _product_name = new wxTextCtrl (_panel, wxID_ANY);
1159                 _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."));
1160                 table->Add (_product_name, 1, wxALL | wxEXPAND);
1161
1162                 add_label_to_sizer (table, _panel, _("Product version"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1163                 _product_version = new wxTextCtrl (_panel, wxID_ANY);
1164                 _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."));
1165                 table->Add (_product_version, 1, wxALL | wxEXPAND);
1166
1167                 add_label_to_sizer (table, _panel, _("JPEG2000 comment"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1168                 _j2k_comment = new wxTextCtrl (_panel, wxID_ANY);
1169                 _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."));
1170                 table->Add (_j2k_comment, 1, wxALL | wxEXPAND);
1171
1172                 _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
1173
1174                 _issuer->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::issuer_changed, this));
1175                 _creator->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::creator_changed, this));
1176                 _company_name->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::company_name_changed, this));
1177                 _product_name->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::product_name_changed, this));
1178                 _product_version->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::product_version_changed, this));
1179                 _j2k_comment->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::j2k_comment_changed, this));
1180         }
1181
1182         void config_changed ()
1183         {
1184                 auto config = Config::instance ();
1185                 checked_set (_issuer, config->dcp_issuer ());
1186                 checked_set (_creator, config->dcp_creator ());
1187                 checked_set (_company_name, config->dcp_company_name ());
1188                 checked_set (_product_name, config->dcp_product_name ());
1189                 checked_set (_product_version, config->dcp_product_version ());
1190                 checked_set (_j2k_comment, config->dcp_j2k_comment ());
1191         }
1192
1193         void issuer_changed ()
1194         {
1195                 Config::instance()->set_dcp_issuer(wx_to_std(_issuer->GetValue()));
1196         }
1197
1198         void creator_changed ()
1199         {
1200                 Config::instance()->set_dcp_creator(wx_to_std(_creator->GetValue()));
1201         }
1202
1203         void company_name_changed ()
1204         {
1205                 Config::instance()->set_dcp_company_name(wx_to_std(_company_name->GetValue()));
1206         }
1207
1208         void product_name_changed ()
1209         {
1210                 Config::instance()->set_dcp_product_name(wx_to_std(_product_name->GetValue()));
1211         }
1212
1213         void product_version_changed ()
1214         {
1215                 Config::instance()->set_dcp_product_version(wx_to_std(_product_version->GetValue()));
1216         }
1217
1218         void j2k_comment_changed ()
1219         {
1220                 Config::instance()->set_dcp_j2k_comment (wx_to_std(_j2k_comment->GetValue()));
1221         }
1222
1223         wxTextCtrl* _issuer;
1224         wxTextCtrl* _creator;
1225         wxTextCtrl* _company_name;
1226         wxTextCtrl* _product_name;
1227         wxTextCtrl* _product_version;
1228         wxTextCtrl* _j2k_comment;
1229 };
1230
1231
1232 /** @class AdvancedPage
1233  *  @brief Advanced page of the preferences dialog.
1234  */
1235 class AdvancedPage : public Page
1236 {
1237 public:
1238         AdvancedPage (wxSize panel_size, int border)
1239                 : Page (panel_size, border)
1240         {}
1241
1242         wxString GetName () const
1243         {
1244                 return _("Advanced");
1245         }
1246
1247 #ifdef DCPOMATIC_OSX
1248         wxBitmap GetLargeIcon () const
1249         {
1250                 return wxBitmap(bitmap_path("advanced"), wxBITMAP_TYPE_PNG);
1251         }
1252 #endif
1253
1254 private:
1255         void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
1256         {
1257                 int flags = wxALIGN_TOP | wxTOP | wxLEFT;
1258 #ifdef __WXOSX__
1259                 flags |= wxALIGN_RIGHT;
1260                 text += wxT (":");
1261 #endif
1262                 wxStaticText* m = new StaticText (parent, text);
1263                 table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
1264         }
1265
1266         void setup ()
1267         {
1268                 auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1269                 table->AddGrowableCol (1, 1);
1270                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1271
1272                 {
1273                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1274                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1275                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1276                         s->Add (_maximum_j2k_bandwidth, 1);
1277                         add_label_to_sizer (s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
1278                         table->Add (s, 1);
1279                 }
1280
1281                 add_label_to_sizer (table, _panel, _("Video display mode"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1282                 _video_display_mode = new wxChoice (_panel, wxID_ANY);
1283                 table->Add (_video_display_mode);
1284
1285                 auto restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false);
1286                 auto font = restart->GetFont();
1287                 font.SetStyle (wxFONTSTYLE_ITALIC);
1288                 font.SetPointSize (font.GetPointSize() - 1);
1289                 restart->SetFont (font);
1290                 table->AddSpacer (0);
1291
1292                 _allow_any_dcp_frame_rate = new CheckBox (_panel, _("Allow any DCP frame rate"));
1293                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1294                 table->AddSpacer (0);
1295
1296                 _allow_any_container = new CheckBox (_panel, _("Allow full-frame and non-standard container ratios"));
1297                 table->Add (_allow_any_container, 1, wxEXPAND | wxALL);
1298                 table->AddSpacer (0);
1299
1300                 restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to see all ratios)"), false);
1301                 restart->SetFont (font);
1302                 table->AddSpacer (0);
1303
1304                 _show_experimental_audio_processors = new CheckBox (_panel, _("Show experimental audio processors"));
1305                 table->Add (_show_experimental_audio_processors, 1, wxEXPAND | wxALL);
1306                 table->AddSpacer (0);
1307
1308                 _only_servers_encode = new CheckBox (_panel, _("Only servers encode"));
1309                 table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
1310                 table->AddSpacer (0);
1311
1312                 {
1313                         add_label_to_sizer (table, _panel, _("Maximum number of frames to store per thread"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1314                         auto s = new wxBoxSizer (wxHORIZONTAL);
1315                         _frames_in_memory_multiplier = new wxSpinCtrl (_panel);
1316                         s->Add (_frames_in_memory_multiplier, 1);
1317                         table->Add (s, 1);
1318                 }
1319
1320                 {
1321                         add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
1322                         dcp::NameFormat::Map titles;
1323                         titles['t'] = wx_to_std (_("type (cpl/pkl)"));
1324                         dcp::NameFormat::Map examples;
1325                         examples['t'] = "cpl";
1326                         _dcp_metadata_filename_format = new NameFormatEditor (
1327                                 _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
1328                                 );
1329                         table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
1330                 }
1331
1332                 {
1333                         add_top_aligned_label_to_sizer (table, _panel, _("DCP asset filename format"));
1334                         dcp::NameFormat::Map titles;
1335                         titles['t'] = wx_to_std (_("type (j2c/pcm/sub)"));
1336                         titles['r'] = wx_to_std (_("reel number"));
1337                         titles['n'] = wx_to_std (_("number of reels"));
1338                         titles['c'] = wx_to_std (_("content filename"));
1339                         dcp::NameFormat::Map examples;
1340                         examples['t'] = "j2c";
1341                         examples['r'] = "1";
1342                         examples['n'] = "4";
1343                         examples['c'] = "myfile.mp4";
1344                         _dcp_asset_filename_format = new NameFormatEditor (
1345                                 _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
1346                                 );
1347                         table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
1348                 }
1349
1350                 {
1351                         add_top_aligned_label_to_sizer (table, _panel, _("Log"));
1352                         auto t = new wxBoxSizer (wxVERTICAL);
1353                         _log_general = new CheckBox (_panel, _("General"));
1354                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1355                         _log_warning = new CheckBox (_panel, _("Warnings"));
1356                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1357                         _log_error = new CheckBox (_panel, _("Errors"));
1358                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1359                         /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
1360                         _log_timing = new CheckBox (_panel, S_("Config|Timing"));
1361                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1362                         _log_debug_threed = new CheckBox (_panel, _("Debug: 3D"));
1363                         t->Add (_log_debug_threed, 1, wxEXPAND | wxALL);
1364                         _log_debug_encode = new CheckBox (_panel, _("Debug: encode"));
1365                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1366                         _log_debug_email = new CheckBox (_panel, _("Debug: email sending"));
1367                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1368                         _log_debug_video_view = new CheckBox (_panel, _("Debug: video view"));
1369                         t->Add (_log_debug_video_view, 1, wxEXPAND | wxALL);
1370                         _log_debug_player = new CheckBox (_panel, _("Debug: player"));
1371                         t->Add (_log_debug_player, 1, wxEXPAND | wxALL);
1372                         _log_debug_audio_analysis = new CheckBox (_panel, _("Debug: audio analysis"));
1373                         t->Add (_log_debug_audio_analysis, 1, wxEXPAND | wxALL);
1374                         table->Add (t, 0, wxALL, 6);
1375                 }
1376
1377 #ifdef DCPOMATIC_WINDOWS
1378                 _win32_console = new CheckBox (_panel, _("Open console window"));
1379                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1380                 table->AddSpacer (0);
1381 #endif
1382
1383                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1384                 _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1385                 _video_display_mode->Append (_("Simple (safer)"));
1386                 _video_display_mode->Append (_("OpenGL (faster)"));
1387                 _video_display_mode->Bind (wxEVT_CHOICE, boost::bind(&AdvancedPage::video_display_mode_changed, this));
1388                 _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1389                 _allow_any_container->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_container_changed, this));
1390                 _show_experimental_audio_processors->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::show_experimental_audio_processors_changed, this));
1391                 _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
1392                 _frames_in_memory_multiplier->Bind (wxEVT_SPINCTRL, boost::bind(&AdvancedPage::frames_in_memory_multiplier_changed, this));
1393                 _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
1394                 _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
1395                 _log_general->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1396                 _log_warning->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1397                 _log_error->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1398                 _log_timing->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1399                 _log_debug_threed->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1400                 _log_debug_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1401                 _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1402                 _log_debug_video_view->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1403                 _log_debug_player->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1404                 _log_debug_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1405 #ifdef DCPOMATIC_WINDOWS
1406                 _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
1407 #endif
1408         }
1409
1410         void config_changed ()
1411         {
1412                 auto config = Config::instance ();
1413
1414                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1415                 switch (config->video_view_type()) {
1416                 case Config::VIDEO_VIEW_SIMPLE:
1417                         checked_set (_video_display_mode, 0);
1418                         break;
1419                 case Config::VIDEO_VIEW_OPENGL:
1420                         checked_set (_video_display_mode, 1);
1421                         break;
1422                 }
1423                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1424                 checked_set (_allow_any_container, config->allow_any_container ());
1425                 checked_set (_show_experimental_audio_processors, config->show_experimental_audio_processors ());
1426                 checked_set (_only_servers_encode, config->only_servers_encode ());
1427                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1428                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1429                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1430                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1431                 checked_set (_log_debug_threed, config->log_types() & LogEntry::TYPE_DEBUG_THREE_D);
1432                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1433                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1434                 checked_set (_log_debug_video_view, config->log_types() & LogEntry::TYPE_DEBUG_VIDEO_VIEW);
1435                 checked_set (_log_debug_player, config->log_types() & LogEntry::TYPE_DEBUG_PLAYER);
1436                 checked_set (_log_debug_audio_analysis, config->log_types() & LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS);
1437                 checked_set (_frames_in_memory_multiplier, config->frames_in_memory_multiplier());
1438 #ifdef DCPOMATIC_WINDOWS
1439                 checked_set (_win32_console, config->win32_console());
1440 #endif
1441         }
1442
1443         void maximum_j2k_bandwidth_changed ()
1444         {
1445                 Config::instance()->set_maximum_j2k_bandwidth(_maximum_j2k_bandwidth->GetValue() * 1000000);
1446         }
1447
1448         void video_display_mode_changed ()
1449         {
1450                 if (_video_display_mode->GetSelection() == 0) {
1451                         Config::instance()->set_video_view_type(Config::VIDEO_VIEW_SIMPLE);
1452                 } else {
1453                         Config::instance()->set_video_view_type(Config::VIDEO_VIEW_OPENGL);
1454                 }
1455         }
1456
1457         void frames_in_memory_multiplier_changed ()
1458         {
1459                 Config::instance()->set_frames_in_memory_multiplier(_frames_in_memory_multiplier->GetValue());
1460         }
1461
1462         void allow_any_dcp_frame_rate_changed ()
1463         {
1464                 Config::instance()->set_allow_any_dcp_frame_rate(_allow_any_dcp_frame_rate->GetValue());
1465         }
1466
1467         void allow_any_container_changed ()
1468         {
1469                 Config::instance()->set_allow_any_container(_allow_any_container->GetValue());
1470         }
1471
1472         void show_experimental_audio_processors_changed ()
1473         {
1474                 Config::instance()->set_show_experimental_audio_processors(_show_experimental_audio_processors->GetValue());
1475         }
1476
1477         void only_servers_encode_changed ()
1478         {
1479                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue());
1480         }
1481
1482         void dcp_metadata_filename_format_changed ()
1483         {
1484                 Config::instance()->set_dcp_metadata_filename_format(_dcp_metadata_filename_format->get());
1485         }
1486
1487         void dcp_asset_filename_format_changed ()
1488         {
1489                 Config::instance()->set_dcp_asset_filename_format(_dcp_asset_filename_format->get());
1490         }
1491
1492         void log_changed ()
1493         {
1494                 int types = 0;
1495                 if (_log_general->GetValue ()) {
1496                         types |= LogEntry::TYPE_GENERAL;
1497                 }
1498                 if (_log_warning->GetValue ()) {
1499                         types |= LogEntry::TYPE_WARNING;
1500                 }
1501                 if (_log_error->GetValue ())  {
1502                         types |= LogEntry::TYPE_ERROR;
1503                 }
1504                 if (_log_timing->GetValue ()) {
1505                         types |= LogEntry::TYPE_TIMING;
1506                 }
1507                 if (_log_debug_threed->GetValue ()) {
1508                         types |= LogEntry::TYPE_DEBUG_THREE_D;
1509                 }
1510                 if (_log_debug_encode->GetValue ()) {
1511                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1512                 }
1513                 if (_log_debug_email->GetValue ()) {
1514                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1515                 }
1516                 if (_log_debug_video_view->GetValue()) {
1517                         types |= LogEntry::TYPE_DEBUG_VIDEO_VIEW;
1518                 }
1519                 if (_log_debug_player->GetValue()) {
1520                         types |= LogEntry::TYPE_DEBUG_PLAYER;
1521                 }
1522                 if (_log_debug_audio_analysis->GetValue()) {
1523                         types |= LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS;
1524                 }
1525                 Config::instance()->set_log_types (types);
1526         }
1527
1528 #ifdef DCPOMATIC_WINDOWS
1529         void win32_console_changed ()
1530         {
1531                 Config::instance()->set_win32_console(_win32_console->GetValue());
1532         }
1533 #endif
1534
1535         wxSpinCtrl* _maximum_j2k_bandwidth = nullptr;
1536         wxChoice* _video_display_mode = nullptr;
1537         wxSpinCtrl* _frames_in_memory_multiplier = nullptr;
1538         wxCheckBox* _allow_any_dcp_frame_rate = nullptr;
1539         wxCheckBox* _allow_any_container = nullptr;
1540         wxCheckBox* _show_experimental_audio_processors = nullptr;
1541         wxCheckBox* _only_servers_encode = nullptr;
1542         NameFormatEditor* _dcp_metadata_filename_format = nullptr;
1543         NameFormatEditor* _dcp_asset_filename_format = nullptr;
1544         wxCheckBox* _log_general = nullptr;
1545         wxCheckBox* _log_warning = nullptr;
1546         wxCheckBox* _log_error = nullptr;
1547         wxCheckBox* _log_timing = nullptr;
1548         wxCheckBox* _log_debug_threed = nullptr;
1549         wxCheckBox* _log_debug_encode = nullptr;
1550         wxCheckBox* _log_debug_email = nullptr;
1551         wxCheckBox* _log_debug_video_view = nullptr;
1552         wxCheckBox* _log_debug_player = nullptr;
1553         wxCheckBox* _log_debug_audio_analysis = nullptr;
1554 #ifdef DCPOMATIC_WINDOWS
1555         wxCheckBox* _win32_console = nullptr;
1556 #endif
1557 };
1558
1559
1560 wxPreferencesEditor*
1561 create_full_config_dialog ()
1562 {
1563         auto e = new wxPreferencesEditor ();
1564
1565 #ifdef DCPOMATIC_OSX
1566         /* Width that we force some of the config panels to be on OSX so that
1567            the containing window doesn't shrink too much when we select those panels.
1568            This is obviously an unpleasant hack.
1569         */
1570         wxSize ps = wxSize (700, -1);
1571         int const border = 16;
1572 #else
1573         wxSize ps = wxSize (-1, -1);
1574         int const border = 8;
1575 #endif
1576
1577         e->AddPage (new FullGeneralPage    (ps, border));
1578         e->AddPage (new SoundPage          (ps, border));
1579         e->AddPage (new DefaultsPage       (ps, border));
1580         e->AddPage (new EncodingServersPage(ps, border));
1581         e->AddPage (new KeysPage           (ps, border));
1582         e->AddPage (new TMSPage            (ps, border));
1583         e->AddPage (new EmailPage          (ps, border));
1584         e->AddPage (new KDMEmailPage       (ps, border));
1585         e->AddPage (new NotificationsPage  (ps, border));
1586         e->AddPage (new CoverSheetPage     (ps, border));
1587         e->AddPage (new IdentifiersPage    (ps, border));
1588         e->AddPage (new AdvancedPage       (ps, border));
1589         return e;
1590 }