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