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