Merge branch '2.0' of ssh://git.carlh.net/home/carl/git/dcpomatic2 into 2.0
[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                 MakeSignerChainDialog* d = new MakeSignerChainDialog (_panel);
735                 if (d->ShowModal () == wxID_OK) {
736                         _signer.reset (
737                                 new dcp::Signer (
738                                         openssl_path (),
739                                         d->organisation (),
740                                         d->organisational_unit (),
741                                         d->root_common_name (),
742                                         d->intermediate_common_name (),
743                                         d->leaf_common_name ()
744                                         )
745                                 );
746
747                         Config::instance()->set_signer (_signer);
748                         update_certificate_list ();
749                         update_signer_private_key ();
750                 }
751
752                 d->Destroy ();
753         }
754
755         void update_sensitivity ()
756         {
757                 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
758         }
759
760         void update_signer_private_key ()
761         {
762                 checked_set (_signer_private_key, dcp::private_key_fingerprint (_signer->key ()));
763         }
764
765         void load_signer_private_key ()
766         {
767                 wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
768
769                 if (d->ShowModal() == wxID_OK) {
770                         try {
771                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
772                                 if (boost::filesystem::file_size (p) > 1024) {
773                                         error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ())));
774                                         return;
775                                 }
776
777                                 _signer->set_key (dcp::file_to_string (p));
778                                 Config::instance()->set_signer (_signer);
779                                 update_signer_private_key ();
780                         } catch (dcp::MiscError& e) {
781                                 error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
782                         }
783                 }
784
785                 d->Destroy ();
786
787                 update_sensitivity ();
788
789         }
790
791         void load_decryption_certificate ()
792         {
793                 wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
794
795                 if (d->ShowModal() == wxID_OK) {
796                         try {
797                                 dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
798                                 Config::instance()->set_decryption_certificate (c);
799                                 update_decryption_certificate ();
800                         } catch (dcp::MiscError& e) {
801                                 error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
802                         }
803                 }
804
805                 d->Destroy ();
806         }
807
808         void update_decryption_certificate ()
809         {
810                 checked_set (_decryption_certificate, Config::instance()->decryption_certificate().thumbprint ());
811         }
812
813         void load_decryption_private_key ()
814         {
815                 wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
816
817                 if (d->ShowModal() == wxID_OK) {
818                         try {
819                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
820                                 Config::instance()->set_decryption_private_key (dcp::file_to_string (p));
821                                 update_decryption_private_key ();
822                         } catch (dcp::MiscError& e) {
823                                 error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), e.what ()));
824                         }
825                 }
826
827                 d->Destroy ();
828         }
829
830         void update_decryption_private_key ()
831         {
832                 checked_set (_decryption_private_key, dcp::private_key_fingerprint (Config::instance()->decryption_private_key()));
833         }
834
835         void export_decryption_certificate ()
836         {
837                 wxFileDialog* d = new wxFileDialog (
838                         _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
839                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
840                         );
841
842                 if (d->ShowModal () == wxID_OK) {
843                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
844                         if (!f) {
845                                 throw OpenFileError (wx_to_std (d->GetPath ()));
846                         }
847
848                         string const s = Config::instance()->decryption_certificate().certificate (true);
849                         fwrite (s.c_str(), 1, s.length(), f);
850                         fclose (f);
851                 }
852                 d->Destroy ();
853         }
854
855         wxListCtrl* _certificates;
856         wxButton* _add_certificate;
857         wxButton* _remove_certificate;
858         wxButton* _remake_certificates;
859         wxStaticText* _signer_private_key;
860         wxButton* _load_signer_private_key;
861         wxStaticText* _decryption_certificate;
862         wxButton* _load_decryption_certificate;
863         wxStaticText* _decryption_private_key;
864         wxButton* _load_decryption_private_key;
865         wxButton* _export_decryption_certificate;
866         shared_ptr<dcp::Signer> _signer;
867 };
868
869 class TMSPage : public StandardPage
870 {
871 public:
872         TMSPage (wxSize panel_size, int border)
873                 : StandardPage (panel_size, border)
874         {}
875
876         wxString GetName () const
877         {
878                 return _("TMS");
879         }
880
881 #ifdef DCPOMATIC_OSX
882         wxBitmap GetLargeIcon () const
883         {
884                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
885         }
886 #endif
887
888 private:
889         void setup ()
890         {
891                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
892                 table->AddGrowableCol (1, 1);
893                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
894
895                 add_label_to_sizer (table, _panel, _("IP address"), true);
896                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
897                 table->Add (_tms_ip, 1, wxEXPAND);
898
899                 add_label_to_sizer (table, _panel, _("Target path"), true);
900                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
901                 table->Add (_tms_path, 1, wxEXPAND);
902
903                 add_label_to_sizer (table, _panel, _("User name"), true);
904                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
905                 table->Add (_tms_user, 1, wxEXPAND);
906
907                 add_label_to_sizer (table, _panel, _("Password"), true);
908                 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
909                 table->Add (_tms_password, 1, wxEXPAND);
910
911                 _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
912                 _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
913                 _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
914                 _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
915         }
916
917         void config_changed ()
918         {
919                 Config* config = Config::instance ();
920
921                 checked_set (_tms_ip, config->tms_ip ());
922                 checked_set (_tms_path, config->tms_path ());
923                 checked_set (_tms_user, config->tms_user ());
924                 checked_set (_tms_password, config->tms_password ());
925         }
926
927         void tms_ip_changed ()
928         {
929                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
930         }
931
932         void tms_path_changed ()
933         {
934                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
935         }
936
937         void tms_user_changed ()
938         {
939                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
940         }
941
942         void tms_password_changed ()
943         {
944                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
945         }
946
947         wxTextCtrl* _tms_ip;
948         wxTextCtrl* _tms_path;
949         wxTextCtrl* _tms_user;
950         wxTextCtrl* _tms_password;
951 };
952
953 class KDMEmailPage : public StandardPage
954 {
955 public:
956
957         KDMEmailPage (wxSize panel_size, int border)
958 #ifdef DCPOMATIC_OSX
959                 /* We have to force both width and height of this one */
960                 : StandardPage (wxSize (480, 128), border)
961 #else
962                  : StandardPage (panel_size, border)
963 #endif
964         {}
965
966         wxString GetName () const
967         {
968                 return _("KDM Email");
969         }
970
971 #ifdef DCPOMATIC_OSX
972         wxBitmap GetLargeIcon () const
973         {
974                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
975         }
976 #endif
977
978 private:
979         void setup ()
980         {
981                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
982                 table->AddGrowableCol (1, 1);
983                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
984
985                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
986                 {
987                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
988                         _mail_server = new wxTextCtrl (_panel, wxID_ANY);
989                         s->Add (_mail_server, 1, wxEXPAND | wxALL);
990                         add_label_to_sizer (s, _panel, _("port"), false);
991                         _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
992                         _mail_port->SetRange (0, 65535);
993                         s->Add (_mail_port);
994                         table->Add (s, 1, wxEXPAND | wxALL);
995                 }
996
997                 add_label_to_sizer (table, _panel, _("Mail user name"), true);
998                 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
999                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1000
1001                 add_label_to_sizer (table, _panel, _("Mail password"), true);
1002                 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1003                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1004
1005                 add_label_to_sizer (table, _panel, _("Subject"), true);
1006                 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1007                 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1008
1009                 add_label_to_sizer (table, _panel, _("From address"), true);
1010                 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1011                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1012
1013                 add_label_to_sizer (table, _panel, _("CC address"), true);
1014                 _kdm_cc = new wxTextCtrl (_panel, wxID_ANY);
1015                 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1016
1017                 add_label_to_sizer (table, _panel, _("BCC address"), true);
1018                 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1019                 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1020
1021                 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (480, 128), wxTE_MULTILINE);
1022                 _panel->GetSizer()->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
1023
1024                 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default text"));
1025                 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1026
1027                 _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_server_changed, this));
1028                 _mail_port->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&KDMEmailPage::mail_port_changed, this));
1029                 _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_user_changed, this));
1030                 _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_password_changed, this));
1031                 _kdm_subject->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1032                 _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1033                 _kdm_cc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_cc_changed, this));
1034                 _kdm_bcc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1035                 _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1036                 _reset_kdm_email->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1037         }
1038
1039         void config_changed ()
1040         {
1041                 Config* config = Config::instance ();
1042
1043                 checked_set (_mail_server, config->mail_server ());
1044                 checked_set (_mail_port, config->mail_port ());
1045                 checked_set (_mail_user, config->mail_user ());
1046                 checked_set (_mail_password, config->mail_password ());
1047                 checked_set (_kdm_subject, config->kdm_subject ());
1048                 checked_set (_kdm_from, config->kdm_from ());
1049                 checked_set (_kdm_cc, config->kdm_cc ());
1050                 checked_set (_kdm_bcc, config->kdm_bcc ());
1051                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1052         }
1053
1054         void mail_server_changed ()
1055         {
1056                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1057         }
1058
1059         void mail_port_changed ()
1060         {
1061                 Config::instance()->set_mail_port (_mail_port->GetValue ());
1062         }
1063
1064         void mail_user_changed ()
1065         {
1066                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1067         }
1068
1069         void mail_password_changed ()
1070         {
1071                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1072         }
1073
1074         void kdm_subject_changed ()
1075         {
1076                 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1077         }
1078
1079         void kdm_from_changed ()
1080         {
1081                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1082         }
1083
1084         void kdm_cc_changed ()
1085         {
1086                 Config::instance()->set_kdm_cc (wx_to_std (_kdm_cc->GetValue ()));
1087         }
1088
1089         void kdm_bcc_changed ()
1090         {
1091                 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1092         }
1093
1094         void kdm_email_changed ()
1095         {
1096                 if (_kdm_email->GetValue().IsEmpty ()) {
1097                         /* Sometimes we get sent an erroneous notification that the email
1098                            is empty; I don't know why.
1099                         */
1100                         return;
1101                 }
1102                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1103         }
1104
1105         void reset_kdm_email ()
1106         {
1107                 Config::instance()->reset_kdm_email ();
1108                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1109         }
1110
1111         wxTextCtrl* _mail_server;
1112         wxSpinCtrl* _mail_port;
1113         wxTextCtrl* _mail_user;
1114         wxTextCtrl* _mail_password;
1115         wxTextCtrl* _kdm_subject;
1116         wxTextCtrl* _kdm_from;
1117         wxTextCtrl* _kdm_cc;
1118         wxTextCtrl* _kdm_bcc;
1119         wxTextCtrl* _kdm_email;
1120         wxButton* _reset_kdm_email;
1121 };
1122
1123 /** @class AdvancedPage
1124  *  @brief Advanced page of the preferences dialog.
1125  */
1126 class AdvancedPage : public StockPage
1127 {
1128 public:
1129         AdvancedPage (wxSize panel_size, int border)
1130                 : StockPage (Kind_Advanced, panel_size, border)
1131                 , _maximum_j2k_bandwidth (0)
1132                 , _allow_any_dcp_frame_rate (0)
1133                 , _log_general (0)
1134                 , _log_warning (0)
1135                 , _log_error (0)
1136                 , _log_timing (0)
1137                 , _log_debug_decode (0)
1138                 , _log_debug_encode (0)
1139         {}
1140
1141 private:
1142         void setup ()
1143         {
1144                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1145                 table->AddGrowableCol (1, 1);
1146                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1147
1148                 {
1149                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1150                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1151                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1152                         s->Add (_maximum_j2k_bandwidth, 1);
1153                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1154                         table->Add (s, 1);
1155                 }
1156
1157                 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1158                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1159                 table->AddSpacer (0);
1160
1161 #ifdef __WXOSX__
1162                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log:"));
1163                 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL | wxALIGN_RIGHT, 6);
1164 #else
1165                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log"));
1166                 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL, 6);
1167 #endif
1168
1169                 {
1170                         wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1171                         _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
1172                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1173                         _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
1174                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1175                         _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
1176                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1177                         _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
1178                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1179                         _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
1180                         t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
1181                         _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
1182                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1183                         table->Add (t, 0, wxALL, 6);
1184                 }
1185
1186 #ifdef DCPOMATIC_WINDOWS
1187                 _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
1188                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1189                 table->AddSpacer (0);
1190 #endif
1191
1192                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1193                 _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1194                 _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1195                 _log_general->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1196                 _log_warning->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1197                 _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1198                 _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1199                 _log_debug_decode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1200                 _log_debug_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1201 #ifdef DCPOMATIC_WINDOWS
1202                 _win32_console->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::win32_console_changed, this));
1203 #endif
1204         }
1205
1206         void config_changed ()
1207         {
1208                 Config* config = Config::instance ();
1209
1210                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1211                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1212                 checked_set (_log_general, config->log_types() & Log::TYPE_GENERAL);
1213                 checked_set (_log_warning, config->log_types() & Log::TYPE_WARNING);
1214                 checked_set (_log_error, config->log_types() & Log::TYPE_ERROR);
1215                 checked_set (_log_timing, config->log_types() & Log::TYPE_TIMING);
1216                 checked_set (_log_debug_decode, config->log_types() & Log::TYPE_DEBUG_DECODE);
1217                 checked_set (_log_debug_encode, config->log_types() & Log::TYPE_DEBUG_ENCODE);
1218 #ifdef DCPOMATIC_WINDOWS
1219                 checked_set (_win32_console, config->win32_console());
1220 #endif
1221         }
1222
1223         void maximum_j2k_bandwidth_changed ()
1224         {
1225                 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1226         }
1227
1228         void allow_any_dcp_frame_rate_changed ()
1229         {
1230                 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1231         }
1232
1233         void log_changed ()
1234         {
1235                 int types = 0;
1236                 if (_log_general->GetValue ()) {
1237                         types |= Log::TYPE_GENERAL;
1238                 }
1239                 if (_log_warning->GetValue ()) {
1240                         types |= Log::TYPE_WARNING;
1241                 }
1242                 if (_log_error->GetValue ())  {
1243                         types |= Log::TYPE_ERROR;
1244                 }
1245                 if (_log_timing->GetValue ()) {
1246                         types |= Log::TYPE_TIMING;
1247                 }
1248                 if (_log_debug_decode->GetValue ()) {
1249                         types |= Log::TYPE_DEBUG_DECODE;
1250                 }
1251                 if (_log_debug_encode->GetValue ()) {
1252                         types |= Log::TYPE_DEBUG_ENCODE;
1253                 }
1254                 Config::instance()->set_log_types (types);
1255         }
1256
1257 #ifdef DCPOMATIC_WINDOWS
1258         void win32_console_changed ()
1259         {
1260                 Config::instance()->set_win32_console (_win32_console->GetValue ());
1261         }
1262 #endif
1263
1264         wxSpinCtrl* _maximum_j2k_bandwidth;
1265         wxCheckBox* _allow_any_dcp_frame_rate;
1266         wxCheckBox* _log_general;
1267         wxCheckBox* _log_warning;
1268         wxCheckBox* _log_error;
1269         wxCheckBox* _log_timing;
1270         wxCheckBox* _log_debug_decode;
1271         wxCheckBox* _log_debug_encode;
1272 #ifdef DCPOMATIC_WINDOWS
1273         wxCheckBox* _win32_console;
1274 #endif
1275 };
1276
1277 wxPreferencesEditor*
1278 create_config_dialog ()
1279 {
1280         wxPreferencesEditor* e = new wxPreferencesEditor ();
1281
1282 #ifdef DCPOMATIC_OSX
1283         /* Width that we force some of the config panels to be on OSX so that
1284            the containing window doesn't shrink too much when we select those panels.
1285            This is obviously an unpleasant hack.
1286         */
1287         wxSize ps = wxSize (520, -1);
1288         int const border = 16;
1289 #else
1290         wxSize ps = wxSize (-1, -1);
1291         int const border = 8;
1292 #endif
1293
1294         e->AddPage (new GeneralPage (ps, border));
1295         e->AddPage (new DefaultsPage (ps, border));
1296         e->AddPage (new EncodingServersPage (ps, border));
1297         e->AddPage (new KeysPage (ps, border));
1298         e->AddPage (new TMSPage (ps, border));
1299         e->AddPage (new KDMEmailPage (ps, border));
1300         e->AddPage (new AdvancedPage (ps, border));
1301         return e;
1302 }