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