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