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