887ef3de60c35c8a83ce5270024e35981e5fb183
[dcpomatic.git] / src / wx / config_dialog.cc
1 /*
2     Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 /** @file src/config_dialog.cc
21  *  @brief A dialogue to edit DCP-o-matic configuration.
22  */
23
24 #include "config_dialog.h"
25 #include "wx_util.h"
26 #include "editable_list.h"
27 #include "filter_dialog.h"
28 #include "dir_picker_ctrl.h"
29 #include "file_picker_ctrl.h"
30 #include "isdcf_metadata_dialog.h"
31 #include "server_dialog.h"
32 #include "make_chain_dialog.h"
33 #include "email_dialog.h"
34 #include "lib/config.h"
35 #include "lib/ratio.h"
36 #include "lib/filter.h"
37 #include "lib/dcp_content_type.h"
38 #include "lib/log.h"
39 #include "lib/util.h"
40 #include "lib/raw_convert.h"
41 #include "lib/cross.h"
42 #include "lib/exceptions.h"
43 #include <dcp/exceptions.h>
44 #include <dcp/certificate_chain.h>
45 #include <wx/stdpaths.h>
46 #include <wx/preferences.h>
47 #include <wx/spinctrl.h>
48 #include <wx/filepicker.h>
49 #include <boost/filesystem.hpp>
50 #include <boost/foreach.hpp>
51 #include <iostream>
52
53 using std::vector;
54 using std::string;
55 using std::list;
56 using std::cout;
57 using std::pair;
58 using std::make_pair;
59 using std::map;
60 using boost::bind;
61 using boost::shared_ptr;
62 using boost::function;
63 using boost::optional;
64
65 class Page
66 {
67 public:
68         Page (wxSize panel_size, int border)
69                 : _border (border)
70                 , _panel (0)
71                 , _panel_size (panel_size)
72                 , _window_exists (false)
73         {
74                 _config_connection = Config::instance()->Changed.connect (boost::bind (&Page::config_changed_wrapper, this));
75         }
76
77         virtual ~Page () {}
78
79 protected:
80         wxWindow* create_window (wxWindow* parent)
81         {
82                 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
83                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
84                 _panel->SetSizer (s);
85
86                 setup ();
87                 _window_exists = true;
88                 config_changed ();
89
90                 _panel->Bind (wxEVT_DESTROY, boost::bind (&Page::window_destroyed, this));
91
92                 return _panel;
93         }
94
95         int _border;
96         wxPanel* _panel;
97
98 private:
99         virtual void config_changed () = 0;
100         virtual void setup () = 0;
101
102         void config_changed_wrapper ()
103         {
104                 if (_window_exists) {
105                         config_changed ();
106                 }
107         }
108
109         void window_destroyed ()
110         {
111                 _window_exists = false;
112         }
113
114         wxSize _panel_size;
115         boost::signals2::scoped_connection _config_connection;
116         bool _window_exists;
117 };
118
119 class StockPage : public wxStockPreferencesPage, public Page
120 {
121 public:
122         StockPage (Kind kind, wxSize panel_size, int border)
123                 : wxStockPreferencesPage (kind)
124                 , Page (panel_size, border)
125         {}
126
127         wxWindow* CreateWindow (wxWindow* parent)
128         {
129                 return create_window (parent);
130         }
131 };
132
133 class StandardPage : public wxPreferencesPage, public Page
134 {
135 public:
136         StandardPage (wxSize panel_size, int border)
137                 : Page (panel_size, border)
138         {}
139
140         wxWindow* CreateWindow (wxWindow* parent)
141         {
142                 return create_window (parent);
143         }
144 };
145
146 class GeneralPage : public StockPage
147 {
148 public:
149         GeneralPage (wxSize panel_size, int border)
150                 : StockPage (Kind_General, panel_size, border)
151         {}
152
153 private:
154         void setup ()
155         {
156                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
157                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
158
159                 int r = 0;
160                 _set_language = new wxCheckBox (_panel, wxID_ANY, _("Set language"));
161                 table->Add (_set_language, wxGBPosition (r, 0));
162                 _language = new wxChoice (_panel, wxID_ANY);
163                 vector<pair<string, string> > languages;
164                 languages.push_back (make_pair ("Čeština", "cs_CZ"));
165                 languages.push_back (make_pair ("汉语/漢語", "zh_CN"));
166                 languages.push_back (make_pair ("Dansk", "da_DK"));
167                 languages.push_back (make_pair ("Deutsch", "de_DE"));
168                 languages.push_back (make_pair ("English", "en_GB"));
169                 languages.push_back (make_pair ("Español", "es_ES"));
170                 languages.push_back (make_pair ("Français", "fr_FR"));
171                 languages.push_back (make_pair ("Italiano", "it_IT"));
172                 languages.push_back (make_pair ("Nederlands", "nl_NL"));
173                 languages.push_back (make_pair ("Русский", "ru_RU"));
174                 languages.push_back (make_pair ("Polski", "pl_PL"));
175                 languages.push_back (make_pair ("Português europeu", "pt_PT"));
176                 languages.push_back (make_pair ("Português do Brasil", "pt_BR"));
177                 languages.push_back (make_pair ("Svenska", "sv_SE"));
178                 languages.push_back (make_pair ("Slovenský jazyk", "sk_SK"));
179                 languages.push_back (make_pair ("українська мова", "uk_UA"));
180                 checked_set (_language, languages);
181                 table->Add (_language, wxGBPosition (r, 1));
182                 ++r;
183
184                 wxStaticText* restart = add_label_to_sizer (
185                         table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
186                         );
187                 wxFont font = restart->GetFont();
188                 font.SetStyle (wxFONTSTYLE_ITALIC);
189                 font.SetPointSize (font.GetPointSize() - 1);
190                 restart->SetFont (font);
191                 ++r;
192
193                 add_label_to_sizer (table, _panel, _("Threads to use for encoding on this host"), true, wxGBPosition (r, 0));
194                 _num_local_encoding_threads = new wxSpinCtrl (_panel);
195                 table->Add (_num_local_encoding_threads, wxGBPosition (r, 1));
196                 ++r;
197
198                 add_label_to_sizer (table, _panel, _("Cinema and screen database file"), true, wxGBPosition (r, 0));
199                 _cinemas_file = new FilePickerCtrl (_panel, _("Select cinema and screen database file"), "*.xml");
200                 table->Add (_cinemas_file, wxGBPosition (r, 1));
201                 ++r;
202
203 #ifdef DCPOMATIC_HAVE_PATCHED_FFMPEG
204                 _analyse_ebur128 = new wxCheckBox (_panel, wxID_ANY, _("Find integrated loudness, true peak and loudness range when analysing audio"));
205                 table->Add (_analyse_ebur128, wxGBPosition (r, 0), wxGBSpan (1, 2));
206                 ++r;
207 #endif
208
209                 _automatic_audio_analysis = new wxCheckBox (_panel, wxID_ANY, _("Automatically analyse content audio"));
210                 table->Add (_automatic_audio_analysis, wxGBPosition (r, 0), wxGBSpan (1, 2));
211                 ++r;
212
213                 _check_for_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for updates on startup"));
214                 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
215                 ++r;
216
217                 _check_for_test_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for testing updates on startup"));
218                 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
219                 ++r;
220
221                 wxFlexGridSizer* bottom_table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
222                 bottom_table->AddGrowableCol (1, 1);
223
224                 add_label_to_sizer (bottom_table, _panel, _("Issuer"), true);
225                 _issuer = new wxTextCtrl (_panel, wxID_ANY);
226                 bottom_table->Add (_issuer, 1, wxALL | wxEXPAND);
227
228                 add_label_to_sizer (bottom_table, _panel, _("Creator"), true);
229                 _creator = new wxTextCtrl (_panel, wxID_ANY);
230                 bottom_table->Add (_creator, 1, wxALL | wxEXPAND);
231
232                 table->Add (bottom_table, wxGBPosition (r, 0), wxGBSpan (2, 2), wxEXPAND);
233                 ++r;
234
235                 add_label_to_grid_bag_sizer (table, _panel, _("JPEG2000 encoder"), true, wxGBPosition (r, 0));
236                 _encoder = new wxChoice (_panel, wxID_ANY);
237                 BOOST_FOREACH (shared_ptr<JPEG2000Encoder> i, JPEG2000Encoder::all()) {
238                         _encoder->Append (std_to_wx (i->name ()));
239                 }
240                 table->Add (_encoder, wxGBPosition (r, 1));
241                 ++r;
242
243                 _set_language->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::set_language_changed, this));
244                 _language->Bind     (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&GeneralPage::language_changed,     this));
245                 _cinemas_file->Bind (wxEVT_COMMAND_FILEPICKER_CHANGED, boost::bind (&GeneralPage::cinemas_file_changed, this));
246
247                 _num_local_encoding_threads->SetRange (1, 128);
248                 _num_local_encoding_threads->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&GeneralPage::num_local_encoding_threads_changed, this));
249
250 #ifdef DCPOMATIC_HAVE_PATCHED_FFMPEG
251                 _analyse_ebur128->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::analyse_ebur128_changed, this));
252 #endif
253                 _automatic_audio_analysis->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::automatic_audio_analysis_changed, this));
254                 _check_for_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_updates_changed, this));
255                 _check_for_test_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
256
257                 _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::issuer_changed, this));
258                 _creator->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::creator_changed, this));
259         }
260
261         void config_changed ()
262         {
263                 Config* config = Config::instance ();
264
265                 checked_set (_set_language, static_cast<bool>(config->language()));
266
267                 /* Backwards compatibility of config file */
268
269                 map<string, string> compat_map;
270                 compat_map["fr"] = "fr_FR";
271                 compat_map["it"] = "it_IT";
272                 compat_map["es"] = "es_ES";
273                 compat_map["sv"] = "sv_SE";
274                 compat_map["de"] = "de_DE";
275                 compat_map["nl"] = "nl_NL";
276                 compat_map["ru"] = "ru_RU";
277                 compat_map["pl"] = "pl_PL";
278                 compat_map["da"] = "da_DK";
279                 compat_map["pt"] = "pt_PT";
280                 compat_map["sk"] = "sk_SK";
281                 compat_map["cs"] = "cs_CZ";
282                 compat_map["uk"] = "uk_UA";
283
284                 string lang = config->language().get_value_or ("en_GB");
285                 if (compat_map.find (lang) != compat_map.end ()) {
286                         lang = compat_map[lang];
287                 }
288
289                 checked_set (_language, lang);
290
291                 checked_set (_num_local_encoding_threads, config->num_local_encoding_threads ());
292 #ifdef DCPOMATIC_HAVE_PATCHED_FFMPEG
293                 checked_set (_analyse_ebur128, config->analyse_ebur128 ());
294 #endif
295                 checked_set (_automatic_audio_analysis, config->automatic_audio_analysis ());
296                 checked_set (_check_for_updates, config->check_for_updates ());
297                 checked_set (_check_for_test_updates, config->check_for_test_updates ());
298                 checked_set (_issuer, config->dcp_issuer ());
299                 checked_set (_creator, config->dcp_creator ());
300 <<<<<<< 0f3c2864599f9e5a5ec001266b4aefb0205d1e1f
301                 checked_set (_cinemas_file, config->cinemas_file());
302
303                 setup_sensitivity ();
304 =======
305
306                 if (!config->encoder ()) {
307                         checked_set (_encoder, 0);
308                 } else {
309                         vector<shared_ptr<JPEG2000Encoder> > encoders = JPEG2000Encoder::all ();
310                         for (size_t i = 0; i < encoders.size(); ++i) {
311                                 if (encoders[i]->id() == config->encoder().get ()) {
312                                         checked_set (_encoder, i);
313                                 }
314                         }
315                 }
316 >>>>>>> Basic classes for different JPEG2000 encoders; config to choose one.
317         }
318
319         void setup_sensitivity ()
320         {
321                 _language->Enable (_set_language->GetValue ());
322                 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
323         }
324
325         void set_language_changed ()
326         {
327                 setup_sensitivity ();
328                 if (_set_language->GetValue ()) {
329                         language_changed ();
330                 } else {
331                         Config::instance()->unset_language ();
332                 }
333         }
334
335         void language_changed ()
336         {
337                 int const sel = _language->GetSelection ();
338                 if (sel != -1) {
339                         Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
340                 } else {
341                         Config::instance()->unset_language ();
342                 }
343         }
344
345 #ifdef DCPOMATIC_HAVE_PATCHED_FFMPEG
346         void analyse_ebur128_changed ()
347         {
348                 Config::instance()->set_analyse_ebur128 (_analyse_ebur128->GetValue ());
349         }
350 #endif
351
352         void automatic_audio_analysis_changed ()
353         {
354                 Config::instance()->set_automatic_audio_analysis (_automatic_audio_analysis->GetValue ());
355         }
356
357         void check_for_updates_changed ()
358         {
359                 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
360         }
361
362         void check_for_test_updates_changed ()
363         {
364                 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
365         }
366
367         void num_local_encoding_threads_changed ()
368         {
369                 Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
370         }
371
372         void issuer_changed ()
373         {
374                 Config::instance()->set_dcp_issuer (wx_to_std (_issuer->GetValue ()));
375         }
376
377         void creator_changed ()
378         {
379                 Config::instance()->set_dcp_creator (wx_to_std (_creator->GetValue ()));
380         }
381
382         void cinemas_file_changed ()
383         {
384                 Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
385         }
386
387         wxCheckBox* _set_language;
388         wxChoice* _language;
389         wxSpinCtrl* _num_local_encoding_threads;
390         FilePickerCtrl* _cinemas_file;
391 #ifdef DCPOMATIC_HAVE_PATCHED_FFMPEG
392         wxCheckBox* _analyse_ebur128;
393 #endif
394         wxCheckBox* _automatic_audio_analysis;
395         wxCheckBox* _check_for_updates;
396         wxCheckBox* _check_for_test_updates;
397         wxTextCtrl* _issuer;
398         wxTextCtrl* _creator;
399         wxChoice* _encoder;
400 };
401
402 class DefaultsPage : public StandardPage
403 {
404 public:
405         DefaultsPage (wxSize panel_size, int border)
406                 : StandardPage (panel_size, border)
407         {}
408
409         wxString GetName () const
410         {
411                 return _("Defaults");
412         }
413
414 #ifdef DCPOMATIC_OSX
415         wxBitmap GetLargeIcon () const
416         {
417                 return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
418         }
419 #endif
420
421 private:
422         void setup ()
423         {
424                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
425                 table->AddGrowableCol (1, 1);
426                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
427
428                 {
429                         add_label_to_sizer (table, _panel, _("Default duration of still images"), true);
430                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
431                         _still_length = new wxSpinCtrl (_panel);
432                         s->Add (_still_length);
433                         add_label_to_sizer (s, _panel, _("s"), false);
434                         table->Add (s, 1);
435                 }
436
437                 add_label_to_sizer (table, _panel, _("Default directory for new films"), true);
438 #ifdef DCPOMATIC_USE_OWN_PICKER
439                 _directory = new DirPickerCtrl (_panel);
440 #else
441                 _directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
442 #endif
443                 table->Add (_directory, 1, wxEXPAND);
444
445                 add_label_to_sizer (table, _panel, _("Default ISDCF name details"), true);
446                 _isdcf_metadata_button = new wxButton (_panel, wxID_ANY, _("Edit..."));
447                 table->Add (_isdcf_metadata_button);
448
449                 add_label_to_sizer (table, _panel, _("Default container"), true);
450                 _container = new wxChoice (_panel, wxID_ANY);
451                 table->Add (_container);
452
453                 add_label_to_sizer (table, _panel, _("Default content type"), true);
454                 _dcp_content_type = new wxChoice (_panel, wxID_ANY);
455                 table->Add (_dcp_content_type);
456
457                 {
458                         add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true);
459                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
460                         _j2k_bandwidth = new wxSpinCtrl (_panel);
461                         s->Add (_j2k_bandwidth);
462                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
463                         table->Add (s, 1);
464                 }
465
466                 {
467                         add_label_to_sizer (table, _panel, _("Default audio delay"), true);
468                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
469                         _audio_delay = new wxSpinCtrl (_panel);
470                         s->Add (_audio_delay);
471                         add_label_to_sizer (s, _panel, _("ms"), false);
472                         table->Add (s, 1);
473                 }
474
475                 add_label_to_sizer (table, _panel, _("Default standard"), true);
476                 _standard = new wxChoice (_panel, wxID_ANY);
477                 table->Add (_standard);
478
479                 _still_length->SetRange (1, 3600);
480                 _still_length->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::still_length_changed, this));
481
482                 _directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
483
484                 _isdcf_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
485
486                 vector<Ratio const *> ratios = Ratio::all ();
487                 for (size_t i = 0; i < ratios.size(); ++i) {
488                         _container->Append (std_to_wx (ratios[i]->nickname ()));
489                 }
490
491                 _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::container_changed, this));
492
493                 vector<DCPContentType const *> const ct = DCPContentType::all ();
494                 for (size_t i = 0; i < ct.size(); ++i) {
495                         _dcp_content_type->Append (std_to_wx (ct[i]->pretty_name ()));
496                 }
497
498                 _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
499
500                 _j2k_bandwidth->SetRange (50, 250);
501                 _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
502
503                 _audio_delay->SetRange (-1000, 1000);
504                 _audio_delay->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::audio_delay_changed, this));
505
506                 _standard->Append (_("SMPTE"));
507                 _standard->Append (_("Interop"));
508                 _standard->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::standard_changed, this));
509         }
510
511         void config_changed ()
512         {
513                 Config* config = Config::instance ();
514
515                 vector<Ratio const *> ratios = Ratio::all ();
516                 for (size_t i = 0; i < ratios.size(); ++i) {
517                         if (ratios[i] == config->default_container ()) {
518                                 _container->SetSelection (i);
519                         }
520                 }
521
522                 vector<DCPContentType const *> const ct = DCPContentType::all ();
523                 for (size_t i = 0; i < ct.size(); ++i) {
524                         if (ct[i] == config->default_dcp_content_type ()) {
525                                 _dcp_content_type->SetSelection (i);
526                         }
527                 }
528
529                 checked_set (_still_length, config->default_still_length ());
530                 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
531                 checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
532                 _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
533                 checked_set (_audio_delay, config->default_audio_delay ());
534                 checked_set (_standard, config->default_interop() ? 1 : 0);
535         }
536
537         void j2k_bandwidth_changed ()
538         {
539                 Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
540         }
541
542         void audio_delay_changed ()
543         {
544                 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
545         }
546
547         void directory_changed ()
548         {
549                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
550         }
551
552         void edit_isdcf_metadata_clicked ()
553         {
554                 ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata (), false);
555                 d->ShowModal ();
556                 Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
557                 d->Destroy ();
558         }
559
560         void still_length_changed ()
561         {
562                 Config::instance()->set_default_still_length (_still_length->GetValue ());
563         }
564
565         void container_changed ()
566         {
567                 vector<Ratio const *> ratio = Ratio::all ();
568                 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
569         }
570
571         void dcp_content_type_changed ()
572         {
573                 vector<DCPContentType const *> ct = DCPContentType::all ();
574                 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
575         }
576
577         void standard_changed ()
578         {
579                 Config::instance()->set_default_interop (_standard->GetSelection() == 1);
580         }
581
582         wxSpinCtrl* _j2k_bandwidth;
583         wxSpinCtrl* _audio_delay;
584         wxButton* _isdcf_metadata_button;
585         wxSpinCtrl* _still_length;
586 #ifdef DCPOMATIC_USE_OWN_PICKER
587         DirPickerCtrl* _directory;
588 #else
589         wxDirPickerCtrl* _directory;
590 #endif
591         wxChoice* _container;
592         wxChoice* _dcp_content_type;
593         wxChoice* _standard;
594 };
595
596 class EncodingServersPage : public StandardPage
597 {
598 public:
599         EncodingServersPage (wxSize panel_size, int border)
600                 : StandardPage (panel_size, border)
601         {}
602
603         wxString GetName () const
604         {
605                 return _("Servers");
606         }
607
608 #ifdef DCPOMATIC_OSX
609         wxBitmap GetLargeIcon () const
610         {
611                 return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
612         }
613 #endif
614
615 private:
616         void setup ()
617         {
618                 _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Search network for servers"));
619                 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
620
621                 vector<string> columns;
622                 columns.push_back (wx_to_std (_("IP address / host name")));
623                 _servers_list = new EditableList<string, ServerDialog> (
624                         _panel,
625                         columns,
626                         boost::bind (&Config::servers, Config::instance()),
627                         boost::bind (&Config::set_servers, Config::instance(), _1),
628                         boost::bind (&always_valid),
629                         boost::bind (&EncodingServersPage::server_column, this, _1)
630                         );
631
632                 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
633
634                 _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
635         }
636
637         void config_changed ()
638         {
639                 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
640                 _servers_list->refresh ();
641         }
642
643         void use_any_servers_changed ()
644         {
645                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
646         }
647
648         string server_column (string s)
649         {
650                 return s;
651         }
652
653         wxCheckBox* _use_any_servers;
654         EditableList<string, ServerDialog>* _servers_list;
655 };
656
657 class CertificateChainEditor : public wxPanel
658 {
659 public:
660         CertificateChainEditor (
661                 wxWindow* parent,
662                 wxString title,
663                 int border,
664                 function<void (shared_ptr<dcp::CertificateChain>)> set,
665                 function<shared_ptr<const dcp::CertificateChain> (void)> get
666                 )
667                 : wxPanel (parent)
668                 , _set (set)
669                 , _get (get)
670         {
671                 wxFont subheading_font (*wxNORMAL_FONT);
672                 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
673
674                 _sizer = new wxBoxSizer (wxVERTICAL);
675
676                 {
677                         wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
678                         m->SetFont (subheading_font);
679                         _sizer->Add (m, 0, wxALL, border);
680                 }
681
682                 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
683                 _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
684
685                 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
686
687                 {
688                         wxListItem ip;
689                         ip.SetId (0);
690                         ip.SetText (_("Type"));
691                         ip.SetWidth (100);
692                         _certificates->InsertColumn (0, ip);
693                 }
694
695                 {
696                         wxListItem ip;
697                         ip.SetId (1);
698                         ip.SetText (_("Thumbprint"));
699                         ip.SetWidth (340);
700
701                         wxFont font = ip.GetFont ();
702                         font.SetFamily (wxFONTFAMILY_TELETYPE);
703                         ip.SetFont (font);
704
705                         _certificates->InsertColumn (1, ip);
706                 }
707
708                 certificates_sizer->Add (_certificates, 1, wxEXPAND);
709
710                 {
711                         wxSizer* s = new wxBoxSizer (wxVERTICAL);
712                         _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
713                         s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
714                         _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
715                         s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
716                         _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
717                         s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
718                         certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
719                 }
720
721                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
722                 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
723                 int r = 0;
724
725                 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
726                 _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
727                 wxFont font = _private_key->GetFont ();
728                 font.SetFamily (wxFONTFAMILY_TELETYPE);
729                 _private_key->SetFont (font);
730                 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
731                 _load_private_key = new wxButton (this, wxID_ANY, _("Load..."));
732                 table->Add (_load_private_key, wxGBPosition (r, 2));
733                 _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
734                 table->Add (_export_private_key, wxGBPosition (r, 3));
735                 ++r;
736
737                 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
738                 _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates and key..."));
739                 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
740                 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
741                 ++r;
742
743                 _add_certificate->Bind     (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::add_certificate, this));
744                 _remove_certificate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::remove_certificate, this));
745                 _export_certificate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::export_certificate, this));
746                 _certificates->Bind        (wxEVT_COMMAND_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
747                 _certificates->Bind        (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
748                 _remake_certificates->Bind (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::remake_certificates, this));
749                 _load_private_key->Bind    (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::load_private_key, this));
750                 _export_private_key->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::export_private_key, this));
751
752                 SetSizerAndFit (_sizer);
753         }
754
755         void config_changed ()
756         {
757                 _chain.reset (new dcp::CertificateChain (*_get().get ()));
758
759                 update_certificate_list ();
760                 update_private_key ();
761                 update_sensitivity ();
762         }
763
764         void add_button (wxWindow* button)
765         {
766                 _button_sizer->Add (button);
767                 _sizer->Layout ();
768         }
769
770 private:
771         void add_certificate ()
772         {
773                 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
774
775                 if (d->ShowModal() == wxID_OK) {
776                         try {
777                                 dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
778                                 _chain->add (c);
779                                 _set (_chain);
780                                 update_certificate_list ();
781                         } catch (dcp::MiscError& e) {
782                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
783                         }
784                 }
785
786                 d->Destroy ();
787
788                 update_sensitivity ();
789         }
790
791         void remove_certificate ()
792         {
793                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
794                 if (i == -1) {
795                         return;
796                 }
797
798                 _certificates->DeleteItem (i);
799                 _chain->remove (i);
800                 _set (_chain);
801
802                 update_sensitivity ();
803         }
804
805         void export_certificate ()
806         {
807                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
808                 if (i == -1) {
809                         return;
810                 }
811
812                 wxFileDialog* d = new wxFileDialog (
813                         this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
814                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
815                         );
816
817                 dcp::CertificateChain::List all = _chain->root_to_leaf ();
818                 dcp::CertificateChain::List::iterator j = all.begin ();
819                 for (int k = 0; k < i; ++k) {
820                         ++j;
821                 }
822
823                 if (d->ShowModal () == wxID_OK) {
824                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
825                         if (!f) {
826                                 throw OpenFileError (wx_to_std (d->GetPath ()));
827                         }
828
829                         string const s = j->certificate (true);
830                         fwrite (s.c_str(), 1, s.length(), f);
831                         fclose (f);
832                 }
833                 d->Destroy ();
834         }
835
836         void update_certificate_list ()
837         {
838                 _certificates->DeleteAllItems ();
839                 size_t n = 0;
840                 dcp::CertificateChain::List certs = _chain->root_to_leaf ();
841                 BOOST_FOREACH (dcp::Certificate const & i, certs) {
842                         wxListItem item;
843                         item.SetId (n);
844                         _certificates->InsertItem (item);
845                         _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
846
847                         if (n == 0) {
848                                 _certificates->SetItem (n, 0, _("Root"));
849                         } else if (n == (certs.size() - 1)) {
850                                 _certificates->SetItem (n, 0, _("Leaf"));
851                         } else {
852                                 _certificates->SetItem (n, 0, _("Intermediate"));
853                         }
854
855                         ++n;
856                 }
857         }
858
859         void remake_certificates ()
860         {
861                 shared_ptr<const dcp::CertificateChain> chain = _get ();
862
863                 string subject_organization_name;
864                 string subject_organizational_unit_name;
865                 string root_common_name;
866                 string intermediate_common_name;
867                 string leaf_common_name;
868
869                 dcp::CertificateChain::List all = chain->root_to_leaf ();
870
871                 if (all.size() >= 1) {
872                         /* Have a root */
873                         subject_organization_name = chain->root().subject_organization_name ();
874                         subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
875                         root_common_name = chain->root().subject_common_name ();
876                 }
877
878                 if (all.size() >= 2) {
879                         /* Have a leaf */
880                         leaf_common_name = chain->leaf().subject_common_name ();
881                 }
882
883                 if (all.size() >= 3) {
884                         /* Have an intermediate */
885                         dcp::CertificateChain::List::iterator i = all.begin ();
886                         ++i;
887                         intermediate_common_name = i->subject_common_name ();
888                 }
889
890                 MakeChainDialog* d = new MakeChainDialog (
891                         this,
892                         subject_organization_name,
893                         subject_organizational_unit_name,
894                         root_common_name,
895                         intermediate_common_name,
896                         leaf_common_name
897                         );
898
899                 if (d->ShowModal () == wxID_OK) {
900                         _chain.reset (
901                                 new dcp::CertificateChain (
902                                         openssl_path (),
903                                         d->organisation (),
904                                         d->organisational_unit (),
905                                         d->root_common_name (),
906                                         d->intermediate_common_name (),
907                                         d->leaf_common_name ()
908                                         )
909                                 );
910
911                         _set (_chain);
912                         update_certificate_list ();
913                         update_private_key ();
914                 }
915
916                 d->Destroy ();
917         }
918
919         void update_sensitivity ()
920         {
921                 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
922                 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
923         }
924
925         void update_private_key ()
926         {
927                 checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
928                 _sizer->Layout ();
929         }
930
931         void load_private_key ()
932         {
933                 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
934
935                 if (d->ShowModal() == wxID_OK) {
936                         try {
937                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
938                                 if (boost::filesystem::file_size (p) > 1024) {
939                                         error_dialog (this, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ())));
940                                         return;
941                                 }
942
943                                 _chain->set_key (dcp::file_to_string (p));
944                                 _set (_chain);
945                                 update_private_key ();
946                         } catch (dcp::MiscError& e) {
947                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
948                         }
949                 }
950
951                 d->Destroy ();
952
953                 update_sensitivity ();
954         }
955
956         void export_private_key ()
957         {
958                 optional<string> key = _chain->key ();
959                 if (!key) {
960                         return;
961                 }
962
963                 wxFileDialog* d = new wxFileDialog (
964                         this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
965                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
966                         );
967
968                 if (d->ShowModal () == wxID_OK) {
969                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
970                         if (!f) {
971                                 throw OpenFileError (wx_to_std (d->GetPath ()));
972                         }
973
974                         string const s = _chain->key().get ();
975                         fwrite (s.c_str(), 1, s.length(), f);
976                         fclose (f);
977                 }
978                 d->Destroy ();
979         }
980
981         wxListCtrl* _certificates;
982         wxButton* _add_certificate;
983         wxButton* _export_certificate;
984         wxButton* _remove_certificate;
985         wxButton* _remake_certificates;
986         wxStaticText* _private_key;
987         wxButton* _load_private_key;
988         wxButton* _export_private_key;
989         wxSizer* _sizer;
990         wxBoxSizer* _button_sizer;
991         shared_ptr<dcp::CertificateChain> _chain;
992         boost::function<void (shared_ptr<dcp::CertificateChain>)> _set;
993         boost::function<shared_ptr<const dcp::CertificateChain> (void)> _get;
994 };
995
996 class KeysPage : public StandardPage
997 {
998 public:
999         KeysPage (wxSize panel_size, int border)
1000                 : StandardPage (panel_size, border)
1001         {}
1002
1003         wxString GetName () const
1004         {
1005                 return _("Keys");
1006         }
1007
1008 #ifdef DCPOMATIC_OSX
1009         wxBitmap GetLargeIcon () const
1010         {
1011                 return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
1012         }
1013 #endif
1014
1015 private:
1016
1017         void setup ()
1018         {
1019                 _signer = new CertificateChainEditor (
1020                         _panel, _("Signing DCPs and KDMs"), _border,
1021                         boost::bind (&Config::set_signer_chain, Config::instance (), _1),
1022                         boost::bind (&Config::signer_chain, Config::instance ())
1023                         );
1024
1025                 _panel->GetSizer()->Add (_signer);
1026
1027                 _decryption = new CertificateChainEditor (
1028                         _panel, _("Decrypting DCPs"), _border,
1029                         boost::bind (&Config::set_decryption_chain, Config::instance (), _1),
1030                         boost::bind (&Config::decryption_chain, Config::instance ())
1031                         );
1032
1033                 _panel->GetSizer()->Add (_decryption);
1034
1035                 _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption certificate..."));
1036                 _decryption->add_button (_export_decryption_certificate);
1037
1038                 _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
1039         }
1040
1041         void export_decryption_certificate ()
1042         {
1043                 wxFileDialog* d = new wxFileDialog (
1044                         _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1045                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1046                         );
1047
1048                 if (d->ShowModal () == wxID_OK) {
1049                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1050                         if (!f) {
1051                                 throw OpenFileError (wx_to_std (d->GetPath ()));
1052                         }
1053
1054                         string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
1055                         fwrite (s.c_str(), 1, s.length(), f);
1056                         fclose (f);
1057                 }
1058                 d->Destroy ();
1059         }
1060
1061         void config_changed ()
1062         {
1063                 _signer->config_changed ();
1064                 _decryption->config_changed ();
1065         }
1066
1067         CertificateChainEditor* _signer;
1068         CertificateChainEditor* _decryption;
1069         wxButton* _export_decryption_certificate;
1070 };
1071
1072 class TMSPage : public StandardPage
1073 {
1074 public:
1075         TMSPage (wxSize panel_size, int border)
1076                 : StandardPage (panel_size, border)
1077         {}
1078
1079         wxString GetName () const
1080         {
1081                 return _("TMS");
1082         }
1083
1084 #ifdef DCPOMATIC_OSX
1085         wxBitmap GetLargeIcon () const
1086         {
1087                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
1088         }
1089 #endif
1090
1091 private:
1092         void setup ()
1093         {
1094                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1095                 table->AddGrowableCol (1, 1);
1096                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1097
1098                 add_label_to_sizer (table, _panel, _("Protocol"), true);
1099                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
1100                 table->Add (_tms_protocol, 1, wxEXPAND);
1101
1102                 add_label_to_sizer (table, _panel, _("IP address"), true);
1103                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
1104                 table->Add (_tms_ip, 1, wxEXPAND);
1105
1106                 add_label_to_sizer (table, _panel, _("Target path"), true);
1107                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
1108                 table->Add (_tms_path, 1, wxEXPAND);
1109
1110                 add_label_to_sizer (table, _panel, _("User name"), true);
1111                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
1112                 table->Add (_tms_user, 1, wxEXPAND);
1113
1114                 add_label_to_sizer (table, _panel, _("Password"), true);
1115                 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
1116                 table->Add (_tms_password, 1, wxEXPAND);
1117
1118                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
1119                 _tms_protocol->Append (_("FTP (for Dolby)"));
1120
1121                 _tms_protocol->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&TMSPage::tms_protocol_changed, this));
1122                 _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
1123                 _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
1124                 _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
1125                 _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
1126         }
1127
1128         void config_changed ()
1129         {
1130                 Config* config = Config::instance ();
1131
1132                 checked_set (_tms_protocol, config->tms_protocol ());
1133                 checked_set (_tms_ip, config->tms_ip ());
1134                 checked_set (_tms_path, config->tms_path ());
1135                 checked_set (_tms_user, config->tms_user ());
1136                 checked_set (_tms_password, config->tms_password ());
1137         }
1138
1139         void tms_protocol_changed ()
1140         {
1141                 Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
1142         }
1143
1144         void tms_ip_changed ()
1145         {
1146                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
1147         }
1148
1149         void tms_path_changed ()
1150         {
1151                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
1152         }
1153
1154         void tms_user_changed ()
1155         {
1156                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
1157         }
1158
1159         void tms_password_changed ()
1160         {
1161                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
1162         }
1163
1164         wxChoice* _tms_protocol;
1165         wxTextCtrl* _tms_ip;
1166         wxTextCtrl* _tms_path;
1167         wxTextCtrl* _tms_user;
1168         wxTextCtrl* _tms_password;
1169 };
1170
1171 static string
1172 column (string s)
1173 {
1174         return s;
1175 }
1176
1177 class KDMEmailPage : public StandardPage
1178 {
1179 public:
1180
1181         KDMEmailPage (wxSize panel_size, int border)
1182 #ifdef DCPOMATIC_OSX
1183                 /* We have to force both width and height of this one */
1184                 : StandardPage (wxSize (480, 128), border)
1185 #else
1186                 : StandardPage (panel_size, border)
1187 #endif
1188         {}
1189
1190         wxString GetName () const
1191         {
1192                 return _("KDM Email");
1193         }
1194
1195 #ifdef DCPOMATIC_OSX
1196         wxBitmap GetLargeIcon () const
1197         {
1198                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
1199         }
1200 #endif
1201
1202 private:
1203         void setup ()
1204         {
1205                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1206                 table->AddGrowableCol (1, 1);
1207                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
1208
1209                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
1210                 {
1211                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1212                         _mail_server = new wxTextCtrl (_panel, wxID_ANY);
1213                         s->Add (_mail_server, 1, wxEXPAND | wxALL);
1214                         add_label_to_sizer (s, _panel, _("port"), false);
1215                         _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
1216                         _mail_port->SetRange (0, 65535);
1217                         s->Add (_mail_port);
1218                         table->Add (s, 1, wxEXPAND | wxALL);
1219                 }
1220
1221                 add_label_to_sizer (table, _panel, _("Mail user name"), true);
1222                 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
1223                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1224
1225                 add_label_to_sizer (table, _panel, _("Mail password"), true);
1226                 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1227                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1228
1229                 add_label_to_sizer (table, _panel, _("Subject"), true);
1230                 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1231                 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1232
1233                 add_label_to_sizer (table, _panel, _("From address"), true);
1234                 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1235                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1236
1237                 vector<string> columns;
1238                 columns.push_back (wx_to_std (_("Address")));
1239                 add_label_to_sizer (table, _panel, _("CC addresses"), true);
1240                 _kdm_cc = new EditableList<string, EmailDialog> (
1241                         _panel,
1242                         columns,
1243                         bind (&Config::kdm_cc, Config::instance()),
1244                         bind (&Config::set_kdm_cc, Config::instance(), _1),
1245                         bind (&string_not_empty, _1),
1246                         bind (&column, _1)
1247                         );
1248                 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1249
1250                 add_label_to_sizer (table, _panel, _("BCC address"), true);
1251                 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1252                 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1253
1254                 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1255                 _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
1256
1257                 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
1258                 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1259
1260                 _kdm_cc->layout ();
1261
1262                 _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_server_changed, this));
1263                 _mail_port->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&KDMEmailPage::mail_port_changed, this));
1264                 _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_user_changed, this));
1265                 _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_password_changed, this));
1266                 _kdm_subject->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1267                 _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1268                 _kdm_bcc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1269                 _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1270                 _reset_kdm_email->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1271         }
1272
1273         void config_changed ()
1274         {
1275                 Config* config = Config::instance ();
1276
1277                 checked_set (_mail_server, config->mail_server ());
1278                 checked_set (_mail_port, config->mail_port ());
1279                 checked_set (_mail_user, config->mail_user ());
1280                 checked_set (_mail_password, config->mail_password ());
1281                 checked_set (_kdm_subject, config->kdm_subject ());
1282                 checked_set (_kdm_from, config->kdm_from ());
1283                 checked_set (_kdm_bcc, config->kdm_bcc ());
1284                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1285         }
1286
1287         void mail_server_changed ()
1288         {
1289                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1290         }
1291
1292         void mail_port_changed ()
1293         {
1294                 Config::instance()->set_mail_port (_mail_port->GetValue ());
1295         }
1296
1297         void mail_user_changed ()
1298         {
1299                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1300         }
1301
1302         void mail_password_changed ()
1303         {
1304                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1305         }
1306
1307         void kdm_subject_changed ()
1308         {
1309                 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1310         }
1311
1312         void kdm_from_changed ()
1313         {
1314                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1315         }
1316
1317         void kdm_bcc_changed ()
1318         {
1319                 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1320         }
1321
1322         void kdm_email_changed ()
1323         {
1324                 if (_kdm_email->GetValue().IsEmpty ()) {
1325                         /* Sometimes we get sent an erroneous notification that the email
1326                            is empty; I don't know why.
1327                         */
1328                         return;
1329                 }
1330                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1331         }
1332
1333         void reset_kdm_email ()
1334         {
1335                 Config::instance()->reset_kdm_email ();
1336                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1337         }
1338
1339         wxTextCtrl* _mail_server;
1340         wxSpinCtrl* _mail_port;
1341         wxTextCtrl* _mail_user;
1342         wxTextCtrl* _mail_password;
1343         wxTextCtrl* _kdm_subject;
1344         wxTextCtrl* _kdm_from;
1345         EditableList<string, EmailDialog>* _kdm_cc;
1346         wxTextCtrl* _kdm_bcc;
1347         wxTextCtrl* _kdm_email;
1348         wxButton* _reset_kdm_email;
1349 };
1350
1351 /** @class AdvancedPage
1352  *  @brief Advanced page of the preferences dialog.
1353  */
1354 class AdvancedPage : public StockPage
1355 {
1356 public:
1357         AdvancedPage (wxSize panel_size, int border)
1358                 : StockPage (Kind_Advanced, panel_size, border)
1359                 , _maximum_j2k_bandwidth (0)
1360                 , _allow_any_dcp_frame_rate (0)
1361                 , _only_servers_encode (0)
1362                 , _log_general (0)
1363                 , _log_warning (0)
1364                 , _log_error (0)
1365                 , _log_timing (0)
1366                 , _log_debug_decode (0)
1367                 , _log_debug_encode (0)
1368                 , _log_debug_email (0)
1369         {}
1370
1371 private:
1372         void setup ()
1373         {
1374                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1375                 table->AddGrowableCol (1, 1);
1376                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1377
1378                 {
1379                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1380                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1381                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1382                         s->Add (_maximum_j2k_bandwidth, 1);
1383                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1384                         table->Add (s, 1);
1385                 }
1386
1387                 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1388                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1389                 table->AddSpacer (0);
1390
1391                 _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
1392                 table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
1393                 table->AddSpacer (0);
1394
1395 #ifdef __WXOSX__
1396                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log:"));
1397                 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL | wxALIGN_RIGHT, 6);
1398 #else
1399                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log"));
1400                 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL, 6);
1401 #endif
1402
1403                 {
1404                         wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1405                         _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
1406                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1407                         _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
1408                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1409                         _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
1410                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1411                         _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
1412                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1413                         _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
1414                         t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
1415                         _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
1416                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1417                         _log_debug_email = new wxCheckBox (_panel, wxID_ANY, _("Debug: email sending"));
1418                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1419                         table->Add (t, 0, wxALL, 6);
1420                 }
1421
1422 #ifdef DCPOMATIC_WINDOWS
1423                 _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
1424                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1425                 table->AddSpacer (0);
1426 #endif
1427
1428                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1429                 _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1430                 _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1431                 _only_servers_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
1432                 _log_general->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1433                 _log_warning->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1434                 _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1435                 _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1436                 _log_debug_decode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1437                 _log_debug_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1438                 _log_debug_email->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1439 #ifdef DCPOMATIC_WINDOWS
1440                 _win32_console->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::win32_console_changed, this));
1441 #endif
1442         }
1443
1444         void config_changed ()
1445         {
1446                 Config* config = Config::instance ();
1447
1448                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1449                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1450                 checked_set (_only_servers_encode, config->only_servers_encode ());
1451                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1452                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1453                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1454                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1455                 checked_set (_log_debug_decode, config->log_types() & LogEntry::TYPE_DEBUG_DECODE);
1456                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1457                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1458 #ifdef DCPOMATIC_WINDOWS
1459                 checked_set (_win32_console, config->win32_console());
1460 #endif
1461         }
1462
1463         void maximum_j2k_bandwidth_changed ()
1464         {
1465                 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1466         }
1467
1468         void allow_any_dcp_frame_rate_changed ()
1469         {
1470                 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1471         }
1472
1473         void only_servers_encode_changed ()
1474         {
1475                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
1476         }
1477
1478         void log_changed ()
1479         {
1480                 int types = 0;
1481                 if (_log_general->GetValue ()) {
1482                         types |= LogEntry::TYPE_GENERAL;
1483                 }
1484                 if (_log_warning->GetValue ()) {
1485                         types |= LogEntry::TYPE_WARNING;
1486                 }
1487                 if (_log_error->GetValue ())  {
1488                         types |= LogEntry::TYPE_ERROR;
1489                 }
1490                 if (_log_timing->GetValue ()) {
1491                         types |= LogEntry::TYPE_TIMING;
1492                 }
1493                 if (_log_debug_decode->GetValue ()) {
1494                         types |= LogEntry::TYPE_DEBUG_DECODE;
1495                 }
1496                 if (_log_debug_encode->GetValue ()) {
1497                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1498                 }
1499                 if (_log_debug_email->GetValue ()) {
1500                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1501                 }
1502                 Config::instance()->set_log_types (types);
1503         }
1504
1505 #ifdef DCPOMATIC_WINDOWS
1506         void win32_console_changed ()
1507         {
1508                 Config::instance()->set_win32_console (_win32_console->GetValue ());
1509         }
1510 #endif
1511
1512         wxSpinCtrl* _maximum_j2k_bandwidth;
1513         wxCheckBox* _allow_any_dcp_frame_rate;
1514         wxCheckBox* _only_servers_encode;
1515         wxCheckBox* _log_general;
1516         wxCheckBox* _log_warning;
1517         wxCheckBox* _log_error;
1518         wxCheckBox* _log_timing;
1519         wxCheckBox* _log_debug_decode;
1520         wxCheckBox* _log_debug_encode;
1521         wxCheckBox* _log_debug_email;
1522 #ifdef DCPOMATIC_WINDOWS
1523         wxCheckBox* _win32_console;
1524 #endif
1525 };
1526
1527 wxPreferencesEditor*
1528 create_config_dialog ()
1529 {
1530         wxPreferencesEditor* e = new wxPreferencesEditor ();
1531
1532 #ifdef DCPOMATIC_OSX
1533         /* Width that we force some of the config panels to be on OSX so that
1534            the containing window doesn't shrink too much when we select those panels.
1535            This is obviously an unpleasant hack.
1536         */
1537         wxSize ps = wxSize (520, -1);
1538         int const border = 16;
1539 #else
1540         wxSize ps = wxSize (-1, -1);
1541         int const border = 8;
1542 #endif
1543
1544         e->AddPage (new GeneralPage (ps, border));
1545         e->AddPage (new DefaultsPage (ps, border));
1546         e->AddPage (new EncodingServersPage (ps, border));
1547         e->AddPage (new KeysPage (ps, border));
1548         e->AddPage (new TMSPage (ps, border));
1549         e->AddPage (new KDMEmailPage (ps, border));
1550         e->AddPage (new AdvancedPage (ps, border));
1551         return e;
1552 }