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