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