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