Merge master.
[dcpomatic.git] / src / wx / config_dialog.cc
1 /*
2     Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 /** @file src/config_dialog.cc
21  *  @brief A dialogue to edit DCP-o-matic configuration.
22  */
23
24 #include <iostream>
25 #include <boost/lexical_cast.hpp>
26 #include <boost/filesystem.hpp>
27 #include <wx/stdpaths.h>
28 #include <wx/preferences.h>
29 #include <wx/filepicker.h>
30 #include <wx/spinctrl.h>
31 #include <dcp/colour_matrix.h>
32 #include "lib/config.h"
33 #include "lib/ratio.h"
34 #include "lib/scaler.h"
35 #include "lib/filter.h"
36 #include "lib/dcp_content_type.h"
37 #include "lib/colour_conversion.h"
38 #include "config_dialog.h"
39 #include "wx_util.h"
40 #include "editable_list.h"
41 #include "filter_dialog.h"
42 #include "dir_picker_ctrl.h"
43 #include "dci_metadata_dialog.h"
44 #include "preset_colour_conversion_dialog.h"
45 #include "server_dialog.h"
46
47 using std::vector;
48 using std::string;
49 using std::list;
50 using std::cout;
51 using boost::bind;
52 using boost::shared_ptr;
53 using boost::lexical_cast;
54
55 class Page
56 {
57 public:
58         Page (wxSize panel_size, int border)
59                 : _panel_size (panel_size)
60                 , _border (border)
61         {}
62
63 protected:
64         wxSize _panel_size;
65         int _border;
66 };
67
68 class GeneralPage : public wxStockPreferencesPage, public Page
69 {
70 public:
71         GeneralPage (wxSize panel_size, int border)
72                 : wxStockPreferencesPage (Kind_General)
73                 , Page (panel_size, border)
74         {}
75
76         wxWindow* CreateWindow (wxWindow* parent)
77         {
78                 wxPanel* panel = new wxPanel (parent);
79                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
80                 panel->SetSizer (s);
81
82                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
83                 table->AddGrowableCol (1, 1);
84                 s->Add (table, 1, wxALL | wxEXPAND, _border);
85                 
86                 _set_language = new wxCheckBox (panel, wxID_ANY, _("Set language"));
87                 table->Add (_set_language, 1);
88                 _language = new wxChoice (panel, wxID_ANY);
89                 _language->Append (wxT ("English"));
90                 _language->Append (wxT ("Français"));
91                 _language->Append (wxT ("Italiano"));
92                 _language->Append (wxT ("Español"));
93                 _language->Append (wxT ("Svenska"));
94                 _language->Append (wxT ("Deutsch"));
95                 table->Add (_language);
96                 
97                 wxStaticText* restart = add_label_to_sizer (table, panel, _("(restart DCP-o-matic to see language changes)"), false);
98                 wxFont font = restart->GetFont();
99                 font.SetStyle (wxFONTSTYLE_ITALIC);
100                 font.SetPointSize (font.GetPointSize() - 1);
101                 restart->SetFont (font);
102                 table->AddSpacer (0);
103                 
104                 add_label_to_sizer (table, panel, _("Threads to use for encoding on this host"), true);
105                 _num_local_encoding_threads = new wxSpinCtrl (panel);
106                 table->Add (_num_local_encoding_threads, 1);
107                 
108                 add_label_to_sizer (table, panel, _("Outgoing mail server"), true);
109                 _mail_server = new wxTextCtrl (panel, wxID_ANY);
110                 table->Add (_mail_server, 1, wxEXPAND | wxALL);
111                 
112                 add_label_to_sizer (table, panel, _("Mail user name"), true);
113                 _mail_user = new wxTextCtrl (panel, wxID_ANY);
114                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
115                 
116                 add_label_to_sizer (table, panel, _("Mail password"), true);
117                 _mail_password = new wxTextCtrl (panel, wxID_ANY);
118                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
119                 
120                 wxStaticText* plain = add_label_to_sizer (table, panel, _("(password will be stored on disk in plaintext)"), false);
121                 plain->SetFont (font);
122                 table->AddSpacer (0);
123                 
124                 add_label_to_sizer (table, panel, _("From address for KDM emails"), true);
125                 _kdm_from = new wxTextCtrl (panel, wxID_ANY);
126                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
127                 
128                 _check_for_updates = new wxCheckBox (panel, wxID_ANY, _("Check for updates on startup"));
129                 table->Add (_check_for_updates, 1, wxEXPAND | wxALL);
130                 table->AddSpacer (0);
131                 
132                 _check_for_test_updates = new wxCheckBox (panel, wxID_ANY, _("Check for testing updates as well as stable ones"));
133                 table->Add (_check_for_test_updates, 1, wxEXPAND | wxALL);
134                 table->AddSpacer (0);
135                 
136                 Config* config = Config::instance ();
137                 
138                 _set_language->SetValue (config->language ());
139                 
140                 if (config->language().get_value_or ("") == "fr") {
141                         _language->SetSelection (1);
142                 } else if (config->language().get_value_or ("") == "it") {
143                 _language->SetSelection (2);
144                 } else if (config->language().get_value_or ("") == "es") {
145                         _language->SetSelection (3);
146                 } else if (config->language().get_value_or ("") == "sv") {
147                         _language->SetSelection (4);
148                 } else if (config->language().get_value_or ("") == "de") {
149                         _language->SetSelection (5);
150                 } else {
151                         _language->SetSelection (0);
152                 }
153                 
154                 setup_language_sensitivity ();
155                 
156                 _set_language->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::set_language_changed, this));
157                 _language->Bind     (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&GeneralPage::language_changed,     this));
158                 
159                 _num_local_encoding_threads->SetRange (1, 128);
160                 _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
161                 _num_local_encoding_threads->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&GeneralPage::num_local_encoding_threads_changed, this));
162                 
163                 _mail_server->SetValue (std_to_wx (config->mail_server ()));
164                 _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::mail_server_changed, this));
165                 _mail_user->SetValue (std_to_wx (config->mail_user ()));
166                 _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::mail_user_changed, this));
167                 _mail_password->SetValue (std_to_wx (config->mail_password ()));
168                 _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::mail_password_changed, this));
169                 _kdm_from->SetValue (std_to_wx (config->kdm_from ()));
170                 _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::kdm_from_changed, this));
171                 _check_for_updates->SetValue (config->check_for_updates ());
172                 _check_for_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_updates_changed, this));
173                 _check_for_test_updates->SetValue (config->check_for_test_updates ());
174                 _check_for_test_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
175                 
176                 return panel;
177         }
178
179 private:        
180         void setup_language_sensitivity ()
181         {
182                 _language->Enable (_set_language->GetValue ());
183         }
184
185         void set_language_changed ()
186         {
187                 setup_language_sensitivity ();
188                 if (_set_language->GetValue ()) {
189                         language_changed ();
190                 } else {
191                         Config::instance()->unset_language ();
192                 }
193         }
194
195         void language_changed ()
196         {
197                 switch (_language->GetSelection ()) {
198                 case 0:
199                         Config::instance()->set_language ("en");
200                         break;
201                 case 1:
202                         Config::instance()->set_language ("fr");
203                         break;
204                 case 2:
205                         Config::instance()->set_language ("it");
206                         break;
207                 case 3:
208                         Config::instance()->set_language ("es");
209                         break;
210                 case 4:
211                         Config::instance()->set_language ("sv");
212                         break;
213                 case 5:
214                         Config::instance()->set_language ("de");
215                         break;
216                 }
217         }
218         
219         void mail_server_changed ()
220         {
221                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
222         }
223         
224         void mail_user_changed ()
225         {
226                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
227         }
228         
229         void mail_password_changed ()
230         {
231                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
232         }
233         
234         void kdm_from_changed ()
235         {
236                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
237         }
238
239         void check_for_updates_changed ()
240         {
241                 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
242         }
243         
244         void check_for_test_updates_changed ()
245         {
246                 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
247         }
248
249         void num_local_encoding_threads_changed ()
250         {
251                 Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
252         }
253         
254         wxCheckBox* _set_language;
255         wxChoice* _language;
256         wxSpinCtrl* _num_local_encoding_threads;
257         wxTextCtrl* _mail_server;
258         wxTextCtrl* _mail_user;
259         wxTextCtrl* _mail_password;
260         wxTextCtrl* _kdm_from;
261         wxCheckBox* _check_for_updates;
262         wxCheckBox* _check_for_test_updates;
263 };
264
265 class DefaultsPage : public wxPreferencesPage, public Page
266 {
267 public:
268         DefaultsPage (wxSize panel_size, int border)
269                 : Page (panel_size, border)
270         {}
271         
272         wxString GetName () const
273         {
274                 return _("Defaults");
275         }
276
277 #ifdef DCPOMATIC_OSX    
278         wxBitmap GetLargeIcon () const
279         {
280                 return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
281         }
282 #endif  
283
284         wxWindow* CreateWindow (wxWindow* parent)
285         {
286                 wxPanel* panel = new wxPanel (parent);
287                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
288                 panel->SetSizer (s);
289
290                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
291                 table->AddGrowableCol (1, 1);
292                 s->Add (table, 1, wxALL | wxEXPAND, _border);
293                 
294                 {
295                         add_label_to_sizer (table, panel, _("Default duration of still images"), true);
296                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
297                         _still_length = new wxSpinCtrl (panel);
298                         s->Add (_still_length);
299                         add_label_to_sizer (s, panel, _("s"), false);
300                         table->Add (s, 1);
301                 }
302                 
303                 add_label_to_sizer (table, panel, _("Default directory for new films"), true);
304 #ifdef DCPOMATIC_USE_OWN_DIR_PICKER
305                 _directory = new DirPickerCtrl (panel);
306 #else   
307                 _directory = new wxDirPickerCtrl (panel, wxDD_DIR_MUST_EXIST);
308 #endif
309                 table->Add (_directory, 1, wxEXPAND);
310                 
311                 add_label_to_sizer (table, panel, _("Default DCI name details"), true);
312                 _dci_metadata_button = new wxButton (panel, wxID_ANY, _("Edit..."));
313                 table->Add (_dci_metadata_button);
314                 
315                 add_label_to_sizer (table, panel, _("Default container"), true);
316                 _container = new wxChoice (panel, wxID_ANY);
317                 table->Add (_container);
318                 
319                 add_label_to_sizer (table, panel, _("Default content type"), true);
320                 _dcp_content_type = new wxChoice (panel, wxID_ANY);
321                 table->Add (_dcp_content_type);
322                 
323                 {
324                         add_label_to_sizer (table, panel, _("Default JPEG2000 bandwidth"), true);
325                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
326                         _j2k_bandwidth = new wxSpinCtrl (panel);
327                         s->Add (_j2k_bandwidth);
328                         add_label_to_sizer (s, panel, _("Mbit/s"), false);
329                         table->Add (s, 1);
330                 }
331                 
332                 {
333                         add_label_to_sizer (table, panel, _("Default audio delay"), true);
334                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
335                         _audio_delay = new wxSpinCtrl (panel);
336                         s->Add (_audio_delay);
337                         add_label_to_sizer (s, panel, _("ms"), false);
338                         table->Add (s, 1);
339                 }
340
341                 add_label_to_sizer (table, panel, _("Default issuer"), true);
342                 _issuer = new wxTextCtrl (panel, wxID_ANY);
343                 table->Add (_issuer, 1, wxEXPAND);
344
345                 add_label_to_sizer (table, panel, _("Default creator"), true);
346                 _creator = new wxTextCtrl (panel, wxID_ANY);
347                 table->Add (_creator, 1, wxEXPAND);
348                 
349                 Config* config = Config::instance ();
350                 
351                 _still_length->SetRange (1, 3600);
352                 _still_length->SetValue (config->default_still_length ());
353                 _still_length->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::still_length_changed, this));
354                 
355                 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
356                 _directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
357                 
358                 _dci_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_dci_metadata_clicked, this, parent));
359                 
360                 vector<Ratio const *> ratio = Ratio::all ();
361                 int n = 0;
362                 for (vector<Ratio const *>::iterator i = ratio.begin(); i != ratio.end(); ++i) {
363                         _container->Append (std_to_wx ((*i)->nickname ()));
364                         if (*i == config->default_container ()) {
365                                 _container->SetSelection (n);
366                         }
367                         ++n;
368                 }
369                 
370                 _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::container_changed, this));
371                 
372                 vector<DCPContentType const *> const ct = DCPContentType::all ();
373                 n = 0;
374                 for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
375                         _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
376                         if (*i == config->default_dcp_content_type ()) {
377                                 _dcp_content_type->SetSelection (n);
378                         }
379                         ++n;
380                 }
381                 
382                 _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
383                 
384                 _j2k_bandwidth->SetRange (50, 250);
385                 _j2k_bandwidth->SetValue (config->default_j2k_bandwidth() / 1000000);
386                 _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
387                 
388                 _audio_delay->SetRange (-1000, 1000);
389                 _audio_delay->SetValue (config->default_audio_delay ());
390                 _audio_delay->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::audio_delay_changed, this));
391
392                 _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
393                 _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::issuer_changed, this));
394                 _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
395                 _creator->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::creator_changed, this));
396
397                 return panel;
398         }
399
400 private:
401         void j2k_bandwidth_changed ()
402         {
403                 Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
404         }
405         
406         void audio_delay_changed ()
407         {
408                 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
409         }
410
411         void directory_changed ()
412         {
413                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
414         }
415
416         void edit_dci_metadata_clicked (wxWindow* parent)
417         {
418                 DCIMetadataDialog* d = new DCIMetadataDialog (parent, Config::instance()->default_dci_metadata ());
419                 d->ShowModal ();
420                 Config::instance()->set_default_dci_metadata (d->dci_metadata ());
421                 d->Destroy ();
422         }
423
424         void still_length_changed ()
425         {
426                 Config::instance()->set_default_still_length (_still_length->GetValue ());
427         }
428         
429         void container_changed ()
430         {
431                 vector<Ratio const *> ratio = Ratio::all ();
432                 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
433         }
434         
435         void dcp_content_type_changed ()
436         {
437                 vector<DCPContentType const *> ct = DCPContentType::all ();
438                 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
439         }
440
441         void issuer_changed ()
442         {
443                 libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
444                 m.issuer = wx_to_std (_issuer->GetValue ());
445                 Config::instance()->set_dcp_metadata (m);
446         }
447         
448         void creator_changed ()
449         {
450                 libdcp::XMLMetadata m = Config::instance()->dcp_metadata ();
451                 m.creator = wx_to_std (_creator->GetValue ());
452                 Config::instance()->set_dcp_metadata (m);
453         }
454         
455         wxSpinCtrl* _j2k_bandwidth;
456         wxSpinCtrl* _audio_delay;
457         wxButton* _dci_metadata_button;
458         wxSpinCtrl* _still_length;
459 #ifdef DCPOMATIC_USE_OWN_DIR_PICKER
460         DirPickerCtrl* _directory;
461 #else
462         wxDirPickerCtrl* _directory;
463 #endif
464         wxChoice* _container;
465         wxChoice* _dcp_content_type;
466         wxTextCtrl* _issuer;
467         wxTextCtrl* _creator;
468 };
469
470 class EncodingServersPage : public wxPreferencesPage, public Page
471 {
472 public:
473         EncodingServersPage (wxSize panel_size, int border)
474                 : Page (panel_size, border)
475         {}
476         
477         wxString GetName () const
478         {
479                 return _("Servers");
480         }
481
482 #ifdef DCPOMATIC_OSX    
483         wxBitmap GetLargeIcon () const
484         {
485                 return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
486         }
487 #endif  
488
489         wxWindow* CreateWindow (wxWindow* parent)
490         {
491                 wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
492                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
493                 panel->SetSizer (s);
494                 
495                 _use_any_servers = new wxCheckBox (panel, wxID_ANY, _("Use all servers"));
496                 s->Add (_use_any_servers, 0, wxALL, _border);
497                 
498                 vector<string> columns;
499                 columns.push_back (wx_to_std (_("IP address / host name")));
500                 _servers_list = new EditableList<string, ServerDialog> (
501                         panel,
502                         columns,
503                         boost::bind (&Config::servers, Config::instance()),
504                         boost::bind (&Config::set_servers, Config::instance(), _1),
505                         boost::bind (&EncodingServersPage::server_column, this, _1)
506                         );
507                 
508                 s->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
509                 
510                 _use_any_servers->SetValue (Config::instance()->use_any_servers ());
511                 _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
512
513                 return panel;
514         }
515
516 private:        
517
518         void use_any_servers_changed ()
519         {
520                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
521         }
522
523         string server_column (string s)
524         {
525                 return s;
526         }
527
528         wxCheckBox* _use_any_servers;
529         EditableList<string, ServerDialog>* _servers_list;
530 };
531
532 class ColourConversionsPage : public wxPreferencesPage, public Page
533 {
534 public:
535         ColourConversionsPage (wxSize panel_size, int border)
536                 : Page (panel_size, border)
537         {}
538         
539         wxString GetName () const
540         {
541                 return _("Colour Conversions");
542         }
543
544 #ifdef DCPOMATIC_OSX    
545         wxBitmap GetLargeIcon () const
546         {
547                 return wxBitmap ("colour_conversions", wxBITMAP_TYPE_PNG_RESOURCE);
548         }
549 #endif  
550         wxWindow* CreateWindow (wxWindow* parent)
551         {
552                 wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
553                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
554                 panel->SetSizer (s);
555
556                 vector<string> columns;
557                 columns.push_back (wx_to_std (_("Name")));
558                 wxPanel* list = new EditableList<PresetColourConversion, PresetColourConversionDialog> (
559                         panel,
560                         columns,
561                         boost::bind (&Config::colour_conversions, Config::instance()),
562                         boost::bind (&Config::set_colour_conversions, Config::instance(), _1),
563                         boost::bind (&ColourConversionsPage::colour_conversion_column, this, _1),
564                         300
565                         );
566
567                 s->Add (list, 1, wxEXPAND | wxALL, _border);
568                 return panel;
569         }
570
571 private:
572         string colour_conversion_column (PresetColourConversion c)
573         {
574                 return c.name;
575         }
576 };
577
578 class TMSPage : public wxPreferencesPage, public Page
579 {
580 public:
581         TMSPage (wxSize panel_size, int border)
582                 : Page (panel_size, border)
583         {}
584
585         wxString GetName () const
586         {
587                 return _("TMS");
588         }
589
590 #ifdef DCPOMATIC_OSX    
591         wxBitmap GetLargeIcon () const
592         {
593                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
594         }
595 #endif  
596
597         wxWindow* CreateWindow (wxWindow* parent)
598         {
599                 wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
600                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
601                 panel->SetSizer (s);
602
603                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
604                 table->AddGrowableCol (1, 1);
605                 s->Add (table, 1, wxALL | wxEXPAND, _border);
606                 
607                 add_label_to_sizer (table, panel, _("IP address"), true);
608                 _tms_ip = new wxTextCtrl (panel, wxID_ANY);
609                 table->Add (_tms_ip, 1, wxEXPAND);
610                 
611                 add_label_to_sizer (table, panel, _("Target path"), true);
612                 _tms_path = new wxTextCtrl (panel, wxID_ANY);
613                 table->Add (_tms_path, 1, wxEXPAND);
614                 
615                 add_label_to_sizer (table, panel, _("User name"), true);
616                 _tms_user = new wxTextCtrl (panel, wxID_ANY);
617                 table->Add (_tms_user, 1, wxEXPAND);
618                 
619                 add_label_to_sizer (table, panel, _("Password"), true);
620                 _tms_password = new wxTextCtrl (panel, wxID_ANY);
621                 table->Add (_tms_password, 1, wxEXPAND);
622                 
623                 Config* config = Config::instance ();
624                 
625                 _tms_ip->SetValue (std_to_wx (config->tms_ip ()));
626                 _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
627                 _tms_path->SetValue (std_to_wx (config->tms_path ()));
628                 _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
629                 _tms_user->SetValue (std_to_wx (config->tms_user ()));
630                 _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
631                 _tms_password->SetValue (std_to_wx (config->tms_password ()));
632                 _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
633
634                 return panel;
635         }
636
637 private:
638         void tms_ip_changed ()
639         {
640                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
641         }
642         
643         void tms_path_changed ()
644         {
645                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
646         }
647         
648         void tms_user_changed ()
649         {
650                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
651         }
652         
653         void tms_password_changed ()
654         {
655                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
656         }
657
658         wxTextCtrl* _tms_ip;
659         wxTextCtrl* _tms_path;
660         wxTextCtrl* _tms_user;
661         wxTextCtrl* _tms_password;
662 };
663
664 class KDMEmailPage : public wxPreferencesPage, public Page
665 {
666 public:
667
668         KDMEmailPage (wxSize panel_size, int border)
669                 : Page (panel_size, border)
670         {}
671         
672         wxString GetName () const
673         {
674                 return _("KDM Email");
675         }
676
677 #ifdef DCPOMATIC_OSX    
678         wxBitmap GetLargeIcon () const
679         {
680                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
681         }
682 #endif  
683
684         wxWindow* CreateWindow (wxWindow* parent)
685         {
686                 /* We have to force both width and height of this one */
687 #ifdef DCPOMATIC_OSX
688                 wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, wxSize (480, 128));
689 #else           
690                 wxPanel* panel = new wxPanel (parent);
691 #endif          
692                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
693                 panel->SetSizer (s);
694                 
695                 _kdm_email = new wxTextCtrl (panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (480, 128), wxTE_MULTILINE);
696                 s->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
697                 
698                 _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
699                 _kdm_email->SetValue (wx_to_std (Config::instance()->kdm_email ()));
700
701                 return panel;
702         }
703
704 private:        
705         void kdm_email_changed ()
706         {
707                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
708         }
709
710         wxTextCtrl* _kdm_email;
711 };
712
713 wxPreferencesEditor*
714 create_config_dialog ()
715 {
716         wxPreferencesEditor* e = new wxPreferencesEditor ();
717
718 #ifdef DCPOMATIC_OSX
719         /* Width that we force some of the config panels to be on OSX so that
720            the containing window doesn't shrink too much when we select those panels.
721            This is obviously an unpleasant hack.
722         */
723         wxSize ps = wxSize (480, -1);
724         int const border = 16;
725 #else
726         wxSize ps = wxDefaultSize;
727         int const border = 8;
728 #endif
729         
730         e->AddPage (new GeneralPage (ps, border));
731         e->AddPage (new DefaultsPage (ps, border));
732         e->AddPage (new EncodingServersPage (ps, border));
733         e->AddPage (new ColourConversionsPage (ps, border));
734         e->AddPage (new TMSPage (ps, border));
735         e->AddPage (new KDMEmailPage (ps, border));
736         return e;
737 }