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