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