Fix too-small minimum key file size and improve error message.
[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) > 8192) {
916                                         error_dialog (
917                                                 this,
918                                                 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
919                                                 );
920                                         return;
921                                 }
922
923                                 _chain->set_key (dcp::file_to_string (p));
924                                 _set (_chain);
925                                 update_private_key ();
926                         } catch (dcp::MiscError& e) {
927                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
928                         }
929                 }
930
931                 d->Destroy ();
932
933                 update_sensitivity ();
934         }
935
936         void export_private_key ()
937         {
938                 optional<string> key = _chain->key ();
939                 if (!key) {
940                         return;
941                 }
942
943                 wxFileDialog* d = new wxFileDialog (
944                         this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
945                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
946                         );
947
948                 if (d->ShowModal () == wxID_OK) {
949                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
950                         if (!f) {
951                                 throw OpenFileError (wx_to_std (d->GetPath ()));
952                         }
953
954                         string const s = _chain->key().get ();
955                         fwrite (s.c_str(), 1, s.length(), f);
956                         fclose (f);
957                 }
958                 d->Destroy ();
959         }
960
961         wxListCtrl* _certificates;
962         wxButton* _add_certificate;
963         wxButton* _export_certificate;
964         wxButton* _remove_certificate;
965         wxButton* _remake_certificates;
966         wxStaticText* _private_key;
967         wxButton* _load_private_key;
968         wxButton* _export_private_key;
969         wxSizer* _sizer;
970         wxBoxSizer* _button_sizer;
971         shared_ptr<dcp::CertificateChain> _chain;
972         boost::function<void (shared_ptr<dcp::CertificateChain>)> _set;
973         boost::function<shared_ptr<const dcp::CertificateChain> (void)> _get;
974 };
975
976 class KeysPage : public StandardPage
977 {
978 public:
979         KeysPage (wxSize panel_size, int border)
980                 : StandardPage (panel_size, border)
981         {}
982
983         wxString GetName () const
984         {
985                 return _("Keys");
986         }
987
988 #ifdef DCPOMATIC_OSX
989         wxBitmap GetLargeIcon () const
990         {
991                 return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
992         }
993 #endif
994
995 private:
996
997         void setup ()
998         {
999                 _signer = new CertificateChainEditor (
1000                         _panel, _("Signing DCPs and KDMs"), _border,
1001                         boost::bind (&Config::set_signer_chain, Config::instance (), _1),
1002                         boost::bind (&Config::signer_chain, Config::instance ())
1003                         );
1004
1005                 _panel->GetSizer()->Add (_signer);
1006
1007                 _decryption = new CertificateChainEditor (
1008                         _panel, _("Decrypting DCPs"), _border,
1009                         boost::bind (&Config::set_decryption_chain, Config::instance (), _1),
1010                         boost::bind (&Config::decryption_chain, Config::instance ())
1011                         );
1012
1013                 _panel->GetSizer()->Add (_decryption);
1014
1015                 _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption certificate..."));
1016                 _decryption->add_button (_export_decryption_certificate);
1017
1018                 _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
1019         }
1020
1021         void export_decryption_certificate ()
1022         {
1023                 wxFileDialog* d = new wxFileDialog (
1024                         _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1025                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1026                         );
1027
1028                 if (d->ShowModal () == wxID_OK) {
1029                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1030                         if (!f) {
1031                                 throw OpenFileError (wx_to_std (d->GetPath ()));
1032                         }
1033
1034                         string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
1035                         fwrite (s.c_str(), 1, s.length(), f);
1036                         fclose (f);
1037                 }
1038                 d->Destroy ();
1039         }
1040
1041         void config_changed ()
1042         {
1043                 _signer->config_changed ();
1044                 _decryption->config_changed ();
1045         }
1046
1047         CertificateChainEditor* _signer;
1048         CertificateChainEditor* _decryption;
1049         wxButton* _export_decryption_certificate;
1050 };
1051
1052 class TMSPage : public StandardPage
1053 {
1054 public:
1055         TMSPage (wxSize panel_size, int border)
1056                 : StandardPage (panel_size, border)
1057         {}
1058
1059         wxString GetName () const
1060         {
1061                 return _("TMS");
1062         }
1063
1064 #ifdef DCPOMATIC_OSX
1065         wxBitmap GetLargeIcon () const
1066         {
1067                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
1068         }
1069 #endif
1070
1071 private:
1072         void setup ()
1073         {
1074                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1075                 table->AddGrowableCol (1, 1);
1076                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1077
1078                 add_label_to_sizer (table, _panel, _("Protocol"), true);
1079                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
1080                 table->Add (_tms_protocol, 1, wxEXPAND);
1081
1082                 add_label_to_sizer (table, _panel, _("IP address"), true);
1083                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
1084                 table->Add (_tms_ip, 1, wxEXPAND);
1085
1086                 add_label_to_sizer (table, _panel, _("Target path"), true);
1087                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
1088                 table->Add (_tms_path, 1, wxEXPAND);
1089
1090                 add_label_to_sizer (table, _panel, _("User name"), true);
1091                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
1092                 table->Add (_tms_user, 1, wxEXPAND);
1093
1094                 add_label_to_sizer (table, _panel, _("Password"), true);
1095                 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
1096                 table->Add (_tms_password, 1, wxEXPAND);
1097
1098                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
1099                 _tms_protocol->Append (_("FTP (for Dolby)"));
1100
1101                 _tms_protocol->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&TMSPage::tms_protocol_changed, this));
1102                 _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
1103                 _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
1104                 _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
1105                 _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
1106         }
1107
1108         void config_changed ()
1109         {
1110                 Config* config = Config::instance ();
1111
1112                 checked_set (_tms_protocol, config->tms_protocol ());
1113                 checked_set (_tms_ip, config->tms_ip ());
1114                 checked_set (_tms_path, config->tms_path ());
1115                 checked_set (_tms_user, config->tms_user ());
1116                 checked_set (_tms_password, config->tms_password ());
1117         }
1118
1119         void tms_protocol_changed ()
1120         {
1121                 Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
1122         }
1123
1124         void tms_ip_changed ()
1125         {
1126                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
1127         }
1128
1129         void tms_path_changed ()
1130         {
1131                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
1132         }
1133
1134         void tms_user_changed ()
1135         {
1136                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
1137         }
1138
1139         void tms_password_changed ()
1140         {
1141                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
1142         }
1143
1144         wxChoice* _tms_protocol;
1145         wxTextCtrl* _tms_ip;
1146         wxTextCtrl* _tms_path;
1147         wxTextCtrl* _tms_user;
1148         wxTextCtrl* _tms_password;
1149 };
1150
1151 static string
1152 column (string s)
1153 {
1154         return s;
1155 }
1156
1157 class KDMEmailPage : public StandardPage
1158 {
1159 public:
1160
1161         KDMEmailPage (wxSize panel_size, int border)
1162 #ifdef DCPOMATIC_OSX
1163                 /* We have to force both width and height of this one */
1164                 : StandardPage (wxSize (480, 128), border)
1165 #else
1166                 : StandardPage (panel_size, border)
1167 #endif
1168         {}
1169
1170         wxString GetName () const
1171         {
1172                 return _("KDM Email");
1173         }
1174
1175 #ifdef DCPOMATIC_OSX
1176         wxBitmap GetLargeIcon () const
1177         {
1178                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
1179         }
1180 #endif
1181
1182 private:
1183         void setup ()
1184         {
1185                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1186                 table->AddGrowableCol (1, 1);
1187                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
1188
1189                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
1190                 {
1191                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1192                         _mail_server = new wxTextCtrl (_panel, wxID_ANY);
1193                         s->Add (_mail_server, 1, wxEXPAND | wxALL);
1194                         add_label_to_sizer (s, _panel, _("port"), false);
1195                         _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
1196                         _mail_port->SetRange (0, 65535);
1197                         s->Add (_mail_port);
1198                         table->Add (s, 1, wxEXPAND | wxALL);
1199                 }
1200
1201                 add_label_to_sizer (table, _panel, _("Mail user name"), true);
1202                 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
1203                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1204
1205                 add_label_to_sizer (table, _panel, _("Mail password"), true);
1206                 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1207                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1208
1209                 add_label_to_sizer (table, _panel, _("Subject"), true);
1210                 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1211                 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1212
1213                 add_label_to_sizer (table, _panel, _("From address"), true);
1214                 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1215                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1216
1217                 vector<string> columns;
1218                 columns.push_back (wx_to_std (_("Address")));
1219                 add_label_to_sizer (table, _panel, _("CC addresses"), true);
1220                 _kdm_cc = new EditableList<string, EmailDialog> (
1221                         _panel,
1222                         columns,
1223                         bind (&Config::kdm_cc, Config::instance()),
1224                         bind (&Config::set_kdm_cc, Config::instance(), _1),
1225                         bind (&string_not_empty, _1),
1226                         bind (&column, _1)
1227                         );
1228                 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1229
1230                 add_label_to_sizer (table, _panel, _("BCC address"), true);
1231                 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1232                 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1233
1234                 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1235                 _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
1236
1237                 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
1238                 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1239
1240                 _kdm_cc->layout ();
1241
1242                 _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_server_changed, this));
1243                 _mail_port->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&KDMEmailPage::mail_port_changed, this));
1244                 _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_user_changed, this));
1245                 _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_password_changed, this));
1246                 _kdm_subject->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1247                 _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1248                 _kdm_bcc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1249                 _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1250                 _reset_kdm_email->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1251         }
1252
1253         void config_changed ()
1254         {
1255                 Config* config = Config::instance ();
1256
1257                 checked_set (_mail_server, config->mail_server ());
1258                 checked_set (_mail_port, config->mail_port ());
1259                 checked_set (_mail_user, config->mail_user ());
1260                 checked_set (_mail_password, config->mail_password ());
1261                 checked_set (_kdm_subject, config->kdm_subject ());
1262                 checked_set (_kdm_from, config->kdm_from ());
1263                 checked_set (_kdm_bcc, config->kdm_bcc ());
1264                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1265         }
1266
1267         void mail_server_changed ()
1268         {
1269                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1270         }
1271
1272         void mail_port_changed ()
1273         {
1274                 Config::instance()->set_mail_port (_mail_port->GetValue ());
1275         }
1276
1277         void mail_user_changed ()
1278         {
1279                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1280         }
1281
1282         void mail_password_changed ()
1283         {
1284                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1285         }
1286
1287         void kdm_subject_changed ()
1288         {
1289                 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1290         }
1291
1292         void kdm_from_changed ()
1293         {
1294                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1295         }
1296
1297         void kdm_bcc_changed ()
1298         {
1299                 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1300         }
1301
1302         void kdm_email_changed ()
1303         {
1304                 if (_kdm_email->GetValue().IsEmpty ()) {
1305                         /* Sometimes we get sent an erroneous notification that the email
1306                            is empty; I don't know why.
1307                         */
1308                         return;
1309                 }
1310                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1311         }
1312
1313         void reset_kdm_email ()
1314         {
1315                 Config::instance()->reset_kdm_email ();
1316                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1317         }
1318
1319         wxTextCtrl* _mail_server;
1320         wxSpinCtrl* _mail_port;
1321         wxTextCtrl* _mail_user;
1322         wxTextCtrl* _mail_password;
1323         wxTextCtrl* _kdm_subject;
1324         wxTextCtrl* _kdm_from;
1325         EditableList<string, EmailDialog>* _kdm_cc;
1326         wxTextCtrl* _kdm_bcc;
1327         wxTextCtrl* _kdm_email;
1328         wxButton* _reset_kdm_email;
1329 };
1330
1331 /** @class AdvancedPage
1332  *  @brief Advanced page of the preferences dialog.
1333  */
1334 class AdvancedPage : public StockPage
1335 {
1336 public:
1337         AdvancedPage (wxSize panel_size, int border)
1338                 : StockPage (Kind_Advanced, panel_size, border)
1339                 , _maximum_j2k_bandwidth (0)
1340                 , _allow_any_dcp_frame_rate (0)
1341                 , _only_servers_encode (0)
1342                 , _log_general (0)
1343                 , _log_warning (0)
1344                 , _log_error (0)
1345                 , _log_timing (0)
1346                 , _log_debug_decode (0)
1347                 , _log_debug_encode (0)
1348                 , _log_debug_email (0)
1349         {}
1350
1351 private:
1352         void setup ()
1353         {
1354                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1355                 table->AddGrowableCol (1, 1);
1356                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1357
1358                 {
1359                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1360                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1361                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1362                         s->Add (_maximum_j2k_bandwidth, 1);
1363                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1364                         table->Add (s, 1);
1365                 }
1366
1367                 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1368                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1369                 table->AddSpacer (0);
1370
1371                 _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
1372                 table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
1373                 table->AddSpacer (0);
1374
1375 #ifdef __WXOSX__
1376                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log:"));
1377                 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL | wxALIGN_RIGHT, 6);
1378 #else
1379                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log"));
1380                 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL, 6);
1381 #endif
1382
1383                 {
1384                         wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1385                         _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
1386                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1387                         _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
1388                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1389                         _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
1390                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1391                         _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
1392                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1393                         _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
1394                         t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
1395                         _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
1396                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1397                         _log_debug_email = new wxCheckBox (_panel, wxID_ANY, _("Debug: email sending"));
1398                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1399                         table->Add (t, 0, wxALL, 6);
1400                 }
1401
1402 #ifdef DCPOMATIC_WINDOWS
1403                 _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
1404                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1405                 table->AddSpacer (0);
1406 #endif
1407
1408                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1409                 _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1410                 _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1411                 _only_servers_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
1412                 _log_general->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1413                 _log_warning->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1414                 _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1415                 _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1416                 _log_debug_decode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1417                 _log_debug_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1418                 _log_debug_email->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1419 #ifdef DCPOMATIC_WINDOWS
1420                 _win32_console->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::win32_console_changed, this));
1421 #endif
1422         }
1423
1424         void config_changed ()
1425         {
1426                 Config* config = Config::instance ();
1427
1428                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1429                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1430                 checked_set (_only_servers_encode, config->only_servers_encode ());
1431                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1432                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1433                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1434                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1435                 checked_set (_log_debug_decode, config->log_types() & LogEntry::TYPE_DEBUG_DECODE);
1436                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1437                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1438 #ifdef DCPOMATIC_WINDOWS
1439                 checked_set (_win32_console, config->win32_console());
1440 #endif
1441         }
1442
1443         void maximum_j2k_bandwidth_changed ()
1444         {
1445                 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1446         }
1447
1448         void allow_any_dcp_frame_rate_changed ()
1449         {
1450                 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1451         }
1452
1453         void only_servers_encode_changed ()
1454         {
1455                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
1456         }
1457
1458         void log_changed ()
1459         {
1460                 int types = 0;
1461                 if (_log_general->GetValue ()) {
1462                         types |= LogEntry::TYPE_GENERAL;
1463                 }
1464                 if (_log_warning->GetValue ()) {
1465                         types |= LogEntry::TYPE_WARNING;
1466                 }
1467                 if (_log_error->GetValue ())  {
1468                         types |= LogEntry::TYPE_ERROR;
1469                 }
1470                 if (_log_timing->GetValue ()) {
1471                         types |= LogEntry::TYPE_TIMING;
1472                 }
1473                 if (_log_debug_decode->GetValue ()) {
1474                         types |= LogEntry::TYPE_DEBUG_DECODE;
1475                 }
1476                 if (_log_debug_encode->GetValue ()) {
1477                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1478                 }
1479                 if (_log_debug_email->GetValue ()) {
1480                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1481                 }
1482                 Config::instance()->set_log_types (types);
1483         }
1484
1485 #ifdef DCPOMATIC_WINDOWS
1486         void win32_console_changed ()
1487         {
1488                 Config::instance()->set_win32_console (_win32_console->GetValue ());
1489         }
1490 #endif
1491
1492         wxSpinCtrl* _maximum_j2k_bandwidth;
1493         wxCheckBox* _allow_any_dcp_frame_rate;
1494         wxCheckBox* _only_servers_encode;
1495         wxCheckBox* _log_general;
1496         wxCheckBox* _log_warning;
1497         wxCheckBox* _log_error;
1498         wxCheckBox* _log_timing;
1499         wxCheckBox* _log_debug_decode;
1500         wxCheckBox* _log_debug_encode;
1501         wxCheckBox* _log_debug_email;
1502 #ifdef DCPOMATIC_WINDOWS
1503         wxCheckBox* _win32_console;
1504 #endif
1505 };
1506
1507 wxPreferencesEditor*
1508 create_config_dialog ()
1509 {
1510         wxPreferencesEditor* e = new wxPreferencesEditor ();
1511
1512 #ifdef DCPOMATIC_OSX
1513         /* Width that we force some of the config panels to be on OSX so that
1514            the containing window doesn't shrink too much when we select those panels.
1515            This is obviously an unpleasant hack.
1516         */
1517         wxSize ps = wxSize (520, -1);
1518         int const border = 16;
1519 #else
1520         wxSize ps = wxSize (-1, -1);
1521         int const border = 8;
1522 #endif
1523
1524         e->AddPage (new GeneralPage (ps, border));
1525         e->AddPage (new DefaultsPage (ps, border));
1526         e->AddPage (new EncodingServersPage (ps, border));
1527         e->AddPage (new KeysPage (ps, border));
1528         e->AddPage (new TMSPage (ps, border));
1529         e->AddPage (new KDMEmailPage (ps, border));
1530         e->AddPage (new AdvancedPage (ps, border));
1531         return e;
1532 }