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