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