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