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