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