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