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