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