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