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