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