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