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