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