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