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