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