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