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