Slightly improve keys config dialog layout.
[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                 wxFont subheading_font (*wxNORMAL_FONT);
575                 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
576
577                 {
578                         wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Signing DCPs and KDMs"));
579                         m->SetFont (subheading_font);
580                         _panel->GetSizer()->Add (m, 0, wxALL, _border);
581                 }
582
583                 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
584                 _panel->GetSizer()->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, _border);
585
586                 _certificates = new wxListCtrl (_panel, wxID_ANY, wxDefaultPosition, wxSize (400, 200), wxLC_REPORT | wxLC_SINGLE_SEL);
587
588                 {
589                         wxListItem ip;
590                         ip.SetId (0);
591                         ip.SetText (_("Type"));
592                         ip.SetWidth (100);
593                         _certificates->InsertColumn (0, ip);
594                 }
595
596                 {
597                         wxListItem ip;
598                         ip.SetId (1);
599                         ip.SetText (_("Thumbprint"));
600                         ip.SetWidth (300);
601
602                         wxFont font = ip.GetFont ();
603                         font.SetFamily (wxFONTFAMILY_TELETYPE);
604                         ip.SetFont (font);
605
606                         _certificates->InsertColumn (1, ip);
607                 }
608
609                 certificates_sizer->Add (_certificates, 1, wxEXPAND);
610
611                 {
612                         wxSizer* s = new wxBoxSizer (wxVERTICAL);
613                         _add_certificate = new wxButton (_panel, wxID_ANY, _("Add..."));
614                         s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
615                         _remove_certificate = new wxButton (_panel, wxID_ANY, _("Remove"));
616                         s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
617                         certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
618                 }
619
620                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
621                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
622
623                 int r = 0;
624
625                 add_label_to_grid_bag_sizer (table, _panel, _("Leaf private key"), true, wxGBPosition (r, 0));
626                 _signer_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
627                 wxFont font = _signer_private_key->GetFont ();
628                 font.SetFamily (wxFONTFAMILY_TELETYPE);
629                 _signer_private_key->SetFont (font);
630                 table->Add (_signer_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
631                 _load_signer_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
632                 table->Add (_load_signer_private_key, wxGBPosition (r, 2));
633                 ++r;
634
635                 _remake_certificates = new wxButton (_panel, wxID_ANY, _("Re-make certificates and key..."));
636                 table->Add (_remake_certificates, wxGBPosition (r, 0), wxGBSpan (1, 3));
637                 ++r;
638
639                 {
640                         wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Decrypting DCPs"));
641                         m->SetFont (subheading_font);
642                         table->Add (m, wxGBPosition (r, 0), wxGBSpan (1, 3), wxTOP, _border * 1.5);
643                         ++r;
644                 }
645
646                 add_label_to_grid_bag_sizer (table, _panel, _("Certificate"), true, wxGBPosition (r, 0));
647                 _decryption_certificate = new wxStaticText (_panel, wxID_ANY, wxT (""));
648                 _decryption_certificate->SetFont (font);
649                 table->Add (_decryption_certificate, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
650                 _load_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Load..."));
651                 table->Add (_load_decryption_certificate, wxGBPosition (r, 2));
652                 ++r;
653
654                 add_label_to_grid_bag_sizer (table, _panel, _("Private key"), true, wxGBPosition (r, 0));
655                 _decryption_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
656                 _decryption_private_key->SetFont (font);
657                 table->Add (_decryption_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
658                 _load_decryption_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
659                 table->Add (_load_decryption_private_key, wxGBPosition (r, 2));
660                 ++r;
661
662                 _export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export DCP decryption certificate..."));
663                 table->Add (_export_decryption_certificate, wxGBPosition (r, 0), wxGBSpan (1, 3));
664                 ++r;
665
666                 _add_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::add_certificate, this));
667                 _remove_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remove_certificate, this));
668                 _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&KeysPage::update_sensitivity, this));
669                 _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&KeysPage::update_sensitivity, this));
670                 _remake_certificates->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remake_certificates, this));
671                 _load_signer_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_signer_private_key, this));
672                 _load_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_certificate, this));
673                 _load_decryption_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_private_key, this));
674                 _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
675         }
676
677         void config_changed ()
678         {
679                 _signer.reset (new dcp::Signer (*Config::instance()->signer().get ()));
680
681                 update_certificate_list ();
682                 update_signer_private_key ();
683                 update_decryption_certificate ();
684                 update_decryption_private_key ();
685                 update_sensitivity ();
686         }
687
688         void add_certificate ()
689         {
690                 wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
691
692                 if (d->ShowModal() == wxID_OK) {
693                         try {
694                                 dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
695                                 _signer->certificates().add (c);
696                                 Config::instance()->set_signer (_signer);
697                                 update_certificate_list ();
698                         } catch (dcp::MiscError& e) {
699                                 error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
700                         }
701                 }
702
703                 d->Destroy ();
704
705                 update_sensitivity ();
706         }
707
708         void remove_certificate ()
709         {
710                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
711                 if (i == -1) {
712                         return;
713                 }
714
715                 _certificates->DeleteItem (i);
716                 _signer->certificates().remove (i);
717                 Config::instance()->set_signer (_signer);
718
719                 update_sensitivity ();
720         }
721
722         void update_certificate_list ()
723         {
724                 _certificates->DeleteAllItems ();
725                 dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf ();
726                 size_t n = 0;
727                 for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) {
728                         wxListItem item;
729                         item.SetId (n);
730                         _certificates->InsertItem (item);
731                         _certificates->SetItem (n, 1, std_to_wx (i->thumbprint ()));
732
733                         if (n == 0) {
734                                 _certificates->SetItem (n, 0, _("Root"));
735                         } else if (n == (certs.size() - 1)) {
736                                 _certificates->SetItem (n, 0, _("Leaf"));
737                         } else {
738                                 _certificates->SetItem (n, 0, _("Intermediate"));
739                         }
740
741                         ++n;
742                 }
743         }
744
745         void remake_certificates ()
746         {
747                 dcp::CertificateChain chain = Config::instance()->signer()->certificates ();
748
749                 string intermediate_common_name;
750                 if (chain.root_to_leaf().size() >= 3) {
751                         dcp::CertificateChain::List all = chain.root_to_leaf ();
752                         dcp::CertificateChain::List::iterator i = all.begin ();
753                         ++i;
754                         intermediate_common_name = i->subject_common_name ();
755                 }
756
757                 MakeSignerChainDialog* d = new MakeSignerChainDialog (
758                         _panel,
759                         chain.root().subject_organization_name (),
760                         chain.root().subject_organizational_unit_name (),
761                         chain.root().subject_common_name (),
762                         intermediate_common_name,
763                         chain.leaf().subject_common_name ()
764                         );
765
766                 if (d->ShowModal () == wxID_OK) {
767                         _signer.reset (
768                                 new dcp::Signer (
769                                         openssl_path (),
770                                         d->organisation (),
771                                         d->organisational_unit (),
772                                         d->root_common_name (),
773                                         d->intermediate_common_name (),
774                                         d->leaf_common_name ()
775                                         )
776                                 );
777
778                         Config::instance()->set_signer (_signer);
779                         update_certificate_list ();
780                         update_signer_private_key ();
781                 }
782
783                 d->Destroy ();
784         }
785
786         void update_sensitivity ()
787         {
788                 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
789         }
790
791         void update_signer_private_key ()
792         {
793                 checked_set (_signer_private_key, dcp::private_key_fingerprint (_signer->key ()));
794         }
795
796         void load_signer_private_key ()
797         {
798                 wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
799
800                 if (d->ShowModal() == wxID_OK) {
801                         try {
802                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
803                                 if (boost::filesystem::file_size (p) > 1024) {
804                                         error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ())));
805                                         return;
806                                 }
807
808                                 _signer->set_key (dcp::file_to_string (p));
809                                 Config::instance()->set_signer (_signer);
810                                 update_signer_private_key ();
811                         } catch (dcp::MiscError& e) {
812                                 error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
813                         }
814                 }
815
816                 d->Destroy ();
817
818                 update_sensitivity ();
819
820         }
821
822         void load_decryption_certificate ()
823         {
824                 wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
825
826                 if (d->ShowModal() == wxID_OK) {
827                         try {
828                                 dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
829                                 Config::instance()->set_decryption_certificate (c);
830                                 update_decryption_certificate ();
831                         } catch (dcp::MiscError& e) {
832                                 error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
833                         }
834                 }
835
836                 d->Destroy ();
837         }
838
839         void update_decryption_certificate ()
840         {
841                 checked_set (_decryption_certificate, Config::instance()->decryption_certificate().thumbprint ());
842         }
843
844         void load_decryption_private_key ()
845         {
846                 wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
847
848                 if (d->ShowModal() == wxID_OK) {
849                         try {
850                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
851                                 Config::instance()->set_decryption_private_key (dcp::file_to_string (p));
852                                 update_decryption_private_key ();
853                         } catch (dcp::MiscError& e) {
854                                 error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), e.what ()));
855                         }
856                 }
857
858                 d->Destroy ();
859         }
860
861         void update_decryption_private_key ()
862         {
863                 checked_set (_decryption_private_key, dcp::private_key_fingerprint (Config::instance()->decryption_private_key()));
864         }
865
866         void export_decryption_certificate ()
867         {
868                 wxFileDialog* d = new wxFileDialog (
869                         _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
870                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
871                         );
872
873                 if (d->ShowModal () == wxID_OK) {
874                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
875                         if (!f) {
876                                 throw OpenFileError (wx_to_std (d->GetPath ()));
877                         }
878
879                         string const s = Config::instance()->decryption_certificate().certificate (true);
880                         fwrite (s.c_str(), 1, s.length(), f);
881                         fclose (f);
882                 }
883                 d->Destroy ();
884         }
885
886         wxListCtrl* _certificates;
887         wxButton* _add_certificate;
888         wxButton* _remove_certificate;
889         wxButton* _remake_certificates;
890         wxStaticText* _signer_private_key;
891         wxButton* _load_signer_private_key;
892         wxStaticText* _decryption_certificate;
893         wxButton* _load_decryption_certificate;
894         wxStaticText* _decryption_private_key;
895         wxButton* _load_decryption_private_key;
896         wxButton* _export_decryption_certificate;
897         shared_ptr<dcp::Signer> _signer;
898 };
899
900 class TMSPage : public StandardPage
901 {
902 public:
903         TMSPage (wxSize panel_size, int border)
904                 : StandardPage (panel_size, border)
905         {}
906
907         wxString GetName () const
908         {
909                 return _("TMS");
910         }
911
912 #ifdef DCPOMATIC_OSX
913         wxBitmap GetLargeIcon () const
914         {
915                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
916         }
917 #endif
918
919 private:
920         void setup ()
921         {
922                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
923                 table->AddGrowableCol (1, 1);
924                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
925
926                 add_label_to_sizer (table, _panel, _("Protocol"), true);
927                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
928                 table->Add (_tms_protocol, 1, wxEXPAND);
929
930                 add_label_to_sizer (table, _panel, _("IP address"), true);
931                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
932                 table->Add (_tms_ip, 1, wxEXPAND);
933
934                 add_label_to_sizer (table, _panel, _("Target path"), true);
935                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
936                 table->Add (_tms_path, 1, wxEXPAND);
937
938                 add_label_to_sizer (table, _panel, _("User name"), true);
939                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
940                 table->Add (_tms_user, 1, wxEXPAND);
941
942                 add_label_to_sizer (table, _panel, _("Password"), true);
943                 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
944                 table->Add (_tms_password, 1, wxEXPAND);
945
946                 _tms_protocol->Append (_("SCP (for AAM)"));
947                 _tms_protocol->Append (_("FTP (for Dolby)"));
948
949                 _tms_protocol->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&TMSPage::tms_protocol_changed, this));
950                 _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
951                 _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
952                 _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
953                 _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
954         }
955
956         void config_changed ()
957         {
958                 Config* config = Config::instance ();
959
960                 checked_set (_tms_protocol, config->tms_protocol ());
961                 checked_set (_tms_ip, config->tms_ip ());
962                 checked_set (_tms_path, config->tms_path ());
963                 checked_set (_tms_user, config->tms_user ());
964                 checked_set (_tms_password, config->tms_password ());
965         }
966
967         void tms_protocol_changed ()
968         {
969                 Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
970         }
971
972         void tms_ip_changed ()
973         {
974                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
975         }
976
977         void tms_path_changed ()
978         {
979                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
980         }
981
982         void tms_user_changed ()
983         {
984                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
985         }
986
987         void tms_password_changed ()
988         {
989                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
990         }
991
992         wxChoice* _tms_protocol;
993         wxTextCtrl* _tms_ip;
994         wxTextCtrl* _tms_path;
995         wxTextCtrl* _tms_user;
996         wxTextCtrl* _tms_password;
997 };
998
999 class KDMEmailPage : public StandardPage
1000 {
1001 public:
1002
1003         KDMEmailPage (wxSize panel_size, int border)
1004 #ifdef DCPOMATIC_OSX
1005                 /* We have to force both width and height of this one */
1006                 : StandardPage (wxSize (480, 128), border)
1007 #else
1008                  : StandardPage (panel_size, border)
1009 #endif
1010         {}
1011
1012         wxString GetName () const
1013         {
1014                 return _("KDM Email");
1015         }
1016
1017 #ifdef DCPOMATIC_OSX
1018         wxBitmap GetLargeIcon () const
1019         {
1020                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
1021         }
1022 #endif
1023
1024 private:
1025         void setup ()
1026         {
1027                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1028                 table->AddGrowableCol (1, 1);
1029                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
1030
1031                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
1032                 {
1033                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1034                         _mail_server = new wxTextCtrl (_panel, wxID_ANY);
1035                         s->Add (_mail_server, 1, wxEXPAND | wxALL);
1036                         add_label_to_sizer (s, _panel, _("port"), false);
1037                         _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
1038                         _mail_port->SetRange (0, 65535);
1039                         s->Add (_mail_port);
1040                         table->Add (s, 1, wxEXPAND | wxALL);
1041                 }
1042
1043                 add_label_to_sizer (table, _panel, _("Mail user name"), true);
1044                 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
1045                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1046
1047                 add_label_to_sizer (table, _panel, _("Mail password"), true);
1048                 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1049                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1050
1051                 add_label_to_sizer (table, _panel, _("Subject"), true);
1052                 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1053                 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1054
1055                 add_label_to_sizer (table, _panel, _("From address"), true);
1056                 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1057                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1058
1059                 add_label_to_sizer (table, _panel, _("CC address"), true);
1060                 _kdm_cc = new wxTextCtrl (_panel, wxID_ANY);
1061                 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1062
1063                 add_label_to_sizer (table, _panel, _("BCC address"), true);
1064                 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1065                 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1066
1067                 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (480, 128), wxTE_MULTILINE);
1068                 _panel->GetSizer()->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
1069
1070                 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default text"));
1071                 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1072
1073                 _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_server_changed, this));
1074                 _mail_port->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&KDMEmailPage::mail_port_changed, this));
1075                 _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_user_changed, this));
1076                 _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_password_changed, this));
1077                 _kdm_subject->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1078                 _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1079                 _kdm_cc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_cc_changed, this));
1080                 _kdm_bcc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1081                 _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1082                 _reset_kdm_email->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1083         }
1084
1085         void config_changed ()
1086         {
1087                 Config* config = Config::instance ();
1088
1089                 checked_set (_mail_server, config->mail_server ());
1090                 checked_set (_mail_port, config->mail_port ());
1091                 checked_set (_mail_user, config->mail_user ());
1092                 checked_set (_mail_password, config->mail_password ());
1093                 checked_set (_kdm_subject, config->kdm_subject ());
1094                 checked_set (_kdm_from, config->kdm_from ());
1095                 checked_set (_kdm_cc, config->kdm_cc ());
1096                 checked_set (_kdm_bcc, config->kdm_bcc ());
1097                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1098         }
1099
1100         void mail_server_changed ()
1101         {
1102                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1103         }
1104
1105         void mail_port_changed ()
1106         {
1107                 Config::instance()->set_mail_port (_mail_port->GetValue ());
1108         }
1109
1110         void mail_user_changed ()
1111         {
1112                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1113         }
1114
1115         void mail_password_changed ()
1116         {
1117                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1118         }
1119
1120         void kdm_subject_changed ()
1121         {
1122                 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1123         }
1124
1125         void kdm_from_changed ()
1126         {
1127                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1128         }
1129
1130         void kdm_cc_changed ()
1131         {
1132                 Config::instance()->set_kdm_cc (wx_to_std (_kdm_cc->GetValue ()));
1133         }
1134
1135         void kdm_bcc_changed ()
1136         {
1137                 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1138         }
1139
1140         void kdm_email_changed ()
1141         {
1142                 if (_kdm_email->GetValue().IsEmpty ()) {
1143                         /* Sometimes we get sent an erroneous notification that the email
1144                            is empty; I don't know why.
1145                         */
1146                         return;
1147                 }
1148                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1149         }
1150
1151         void reset_kdm_email ()
1152         {
1153                 Config::instance()->reset_kdm_email ();
1154                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1155         }
1156
1157         wxTextCtrl* _mail_server;
1158         wxSpinCtrl* _mail_port;
1159         wxTextCtrl* _mail_user;
1160         wxTextCtrl* _mail_password;
1161         wxTextCtrl* _kdm_subject;
1162         wxTextCtrl* _kdm_from;
1163         wxTextCtrl* _kdm_cc;
1164         wxTextCtrl* _kdm_bcc;
1165         wxTextCtrl* _kdm_email;
1166         wxButton* _reset_kdm_email;
1167 };
1168
1169 /** @class AdvancedPage
1170  *  @brief Advanced page of the preferences dialog.
1171  */
1172 class AdvancedPage : public StockPage
1173 {
1174 public:
1175         AdvancedPage (wxSize panel_size, int border)
1176                 : StockPage (Kind_Advanced, panel_size, border)
1177                 , _maximum_j2k_bandwidth (0)
1178                 , _allow_any_dcp_frame_rate (0)
1179                 , _log_general (0)
1180                 , _log_warning (0)
1181                 , _log_error (0)
1182                 , _log_timing (0)
1183                 , _log_debug_decode (0)
1184                 , _log_debug_encode (0)
1185         {}
1186
1187 private:
1188         void setup ()
1189         {
1190                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1191                 table->AddGrowableCol (1, 1);
1192                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1193
1194                 {
1195                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1196                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1197                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1198                         s->Add (_maximum_j2k_bandwidth, 1);
1199                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1200                         table->Add (s, 1);
1201                 }
1202
1203                 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1204                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1205                 table->AddSpacer (0);
1206
1207 #ifdef __WXOSX__
1208                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log:"));
1209                 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL | wxALIGN_RIGHT, 6);
1210 #else
1211                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log"));
1212                 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL, 6);
1213 #endif
1214
1215                 {
1216                         wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1217                         _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
1218                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1219                         _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
1220                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1221                         _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
1222                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1223                         _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
1224                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1225                         _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
1226                         t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
1227                         _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
1228                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1229                         table->Add (t, 0, wxALL, 6);
1230                 }
1231
1232 #ifdef DCPOMATIC_WINDOWS
1233                 _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
1234                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1235                 table->AddSpacer (0);
1236 #endif
1237
1238                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1239                 _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1240                 _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1241                 _log_general->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1242                 _log_warning->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1243                 _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1244                 _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1245                 _log_debug_decode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1246                 _log_debug_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1247 #ifdef DCPOMATIC_WINDOWS
1248                 _win32_console->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::win32_console_changed, this));
1249 #endif
1250         }
1251
1252         void config_changed ()
1253         {
1254                 Config* config = Config::instance ();
1255
1256                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1257                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1258                 checked_set (_log_general, config->log_types() & Log::TYPE_GENERAL);
1259                 checked_set (_log_warning, config->log_types() & Log::TYPE_WARNING);
1260                 checked_set (_log_error, config->log_types() & Log::TYPE_ERROR);
1261                 checked_set (_log_timing, config->log_types() & Log::TYPE_TIMING);
1262                 checked_set (_log_debug_decode, config->log_types() & Log::TYPE_DEBUG_DECODE);
1263                 checked_set (_log_debug_encode, config->log_types() & Log::TYPE_DEBUG_ENCODE);
1264 #ifdef DCPOMATIC_WINDOWS
1265                 checked_set (_win32_console, config->win32_console());
1266 #endif
1267         }
1268
1269         void maximum_j2k_bandwidth_changed ()
1270         {
1271                 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1272         }
1273
1274         void allow_any_dcp_frame_rate_changed ()
1275         {
1276                 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1277         }
1278
1279         void log_changed ()
1280         {
1281                 int types = 0;
1282                 if (_log_general->GetValue ()) {
1283                         types |= Log::TYPE_GENERAL;
1284                 }
1285                 if (_log_warning->GetValue ()) {
1286                         types |= Log::TYPE_WARNING;
1287                 }
1288                 if (_log_error->GetValue ())  {
1289                         types |= Log::TYPE_ERROR;
1290                 }
1291                 if (_log_timing->GetValue ()) {
1292                         types |= Log::TYPE_TIMING;
1293                 }
1294                 if (_log_debug_decode->GetValue ()) {
1295                         types |= Log::TYPE_DEBUG_DECODE;
1296                 }
1297                 if (_log_debug_encode->GetValue ()) {
1298                         types |= Log::TYPE_DEBUG_ENCODE;
1299                 }
1300                 Config::instance()->set_log_types (types);
1301         }
1302
1303 #ifdef DCPOMATIC_WINDOWS
1304         void win32_console_changed ()
1305         {
1306                 Config::instance()->set_win32_console (_win32_console->GetValue ());
1307         }
1308 #endif
1309
1310         wxSpinCtrl* _maximum_j2k_bandwidth;
1311         wxCheckBox* _allow_any_dcp_frame_rate;
1312         wxCheckBox* _log_general;
1313         wxCheckBox* _log_warning;
1314         wxCheckBox* _log_error;
1315         wxCheckBox* _log_timing;
1316         wxCheckBox* _log_debug_decode;
1317         wxCheckBox* _log_debug_encode;
1318 #ifdef DCPOMATIC_WINDOWS
1319         wxCheckBox* _win32_console;
1320 #endif
1321 };
1322
1323 wxPreferencesEditor*
1324 create_config_dialog ()
1325 {
1326         wxPreferencesEditor* e = new wxPreferencesEditor ();
1327
1328 #ifdef DCPOMATIC_OSX
1329         /* Width that we force some of the config panels to be on OSX so that
1330            the containing window doesn't shrink too much when we select those panels.
1331            This is obviously an unpleasant hack.
1332         */
1333         wxSize ps = wxSize (520, -1);
1334         int const border = 16;
1335 #else
1336         wxSize ps = wxSize (-1, -1);
1337         int const border = 8;
1338 #endif
1339
1340         e->AddPage (new GeneralPage (ps, border));
1341         e->AddPage (new DefaultsPage (ps, border));
1342         e->AddPage (new EncodingServersPage (ps, border));
1343         e->AddPage (new KeysPage (ps, border));
1344         e->AddPage (new TMSPage (ps, border));
1345         e->AddPage (new KDMEmailPage (ps, border));
1346         e->AddPage (new AdvancedPage (ps, border));
1347         return e;
1348 }