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