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