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