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