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