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