2 Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
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.
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.
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.
20 /** @file src/config_dialog.cc
21 * @brief A dialogue to edit DCP-o-matic configuration.
24 #include "config_dialog.h"
26 #include "editable_list.h"
27 #include "filter_dialog.h"
28 #include "dir_picker_ctrl.h"
29 #include "isdcf_metadata_dialog.h"
30 #include "server_dialog.h"
31 #include "make_signer_chain_dialog.h"
32 #include "lib/config.h"
33 #include "lib/ratio.h"
34 #include "lib/filter.h"
35 #include "lib/dcp_content_type.h"
38 #include "lib/raw_convert.h"
39 #include "lib/cross.h"
40 #include "lib/exceptions.h"
41 #include <dcp/exceptions.h>
42 #include <dcp/certificate_chain.h>
43 #include <wx/stdpaths.h>
44 #include <wx/preferences.h>
45 #include <wx/filepicker.h>
46 #include <wx/spinctrl.h>
47 #include <boost/lexical_cast.hpp>
48 #include <boost/filesystem.hpp>
56 using boost::shared_ptr;
57 using boost::lexical_cast;
62 Page (wxSize panel_size, int border)
65 , _panel_size (panel_size)
66 , _window_exists (false)
68 _config_connection = Config::instance()->Changed.connect (boost::bind (&Page::config_changed_wrapper, this));
74 wxWindow* create_window (wxWindow* parent)
76 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
77 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
81 _window_exists = true;
84 _panel->Bind (wxEVT_DESTROY, boost::bind (&Page::window_destroyed, this));
93 virtual void config_changed () = 0;
94 virtual void setup () = 0;
96 void config_changed_wrapper ()
103 void window_destroyed ()
105 _window_exists = false;
109 boost::signals2::scoped_connection _config_connection;
113 class StockPage : public wxStockPreferencesPage, public Page
116 StockPage (Kind kind, wxSize panel_size, int border)
117 : wxStockPreferencesPage (kind)
118 , Page (panel_size, border)
121 wxWindow* CreateWindow (wxWindow* parent)
123 return create_window (parent);
127 class StandardPage : public wxPreferencesPage, public Page
130 StandardPage (wxSize panel_size, int border)
131 : Page (panel_size, border)
134 wxWindow* CreateWindow (wxWindow* parent)
136 return create_window (parent);
140 class GeneralPage : public StockPage
143 GeneralPage (wxSize panel_size, int border)
144 : StockPage (Kind_General, panel_size, border)
150 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
151 table->AddGrowableCol (1, 1);
152 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
154 _set_language = new wxCheckBox (_panel, wxID_ANY, _("Set language"));
155 table->Add (_set_language, 1);
156 _language = new wxChoice (_panel, wxID_ANY);
157 _language->Append (wxT ("Deutsch"));
158 _language->Append (wxT ("English"));
159 _language->Append (wxT ("Español"));
160 _language->Append (wxT ("Français"));
161 _language->Append (wxT ("Italiano"));
162 _language->Append (wxT ("Nederlands"));
163 _language->Append (wxT ("Svenska"));
164 _language->Append (wxT ("Русский"));
165 _language->Append (wxT ("Polski"));
166 _language->Append (wxT ("Danske"));
167 table->Add (_language);
169 wxStaticText* restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to see language changes)"), false);
170 wxFont font = restart->GetFont();
171 font.SetStyle (wxFONTSTYLE_ITALIC);
172 font.SetPointSize (font.GetPointSize() - 1);
173 restart->SetFont (font);
174 table->AddSpacer (0);
176 add_label_to_sizer (table, _panel, _("Threads to use for encoding on this host"), true);
177 _num_local_encoding_threads = new wxSpinCtrl (_panel);
178 table->Add (_num_local_encoding_threads, 1);
180 _check_for_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for updates on startup"));
181 table->Add (_check_for_updates, 1, wxEXPAND | wxALL);
182 table->AddSpacer (0);
184 _check_for_test_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for testing updates as well as stable ones"));
185 table->Add (_check_for_test_updates, 1, wxEXPAND | wxALL);
186 table->AddSpacer (0);
188 _set_language->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::set_language_changed, this));
189 _language->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&GeneralPage::language_changed, this));
191 _num_local_encoding_threads->SetRange (1, 128);
192 _num_local_encoding_threads->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&GeneralPage::num_local_encoding_threads_changed, this));
194 _check_for_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_updates_changed, this));
195 _check_for_test_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
198 void config_changed ()
200 Config* config = Config::instance ();
202 checked_set (_set_language, config->language ());
204 if (config->language().get_value_or ("") == "fr") {
205 checked_set (_language, 3);
206 } else if (config->language().get_value_or ("") == "it") {
207 checked_set (_language, 4);
208 } else if (config->language().get_value_or ("") == "es") {
209 checked_set (_language, 2);
210 } else if (config->language().get_value_or ("") == "sv") {
211 checked_set (_language, 6);
212 } else if (config->language().get_value_or ("") == "de") {
213 checked_set (_language, 0);
214 } else if (config->language().get_value_or ("") == "nl") {
215 checked_set (_language, 5);
216 } else if (config->language().get_value_or ("") == "ru") {
217 checked_set (_language, 7);
218 } else if (config->language().get_value_or ("") == "pl") {
219 checked_set (_language, 8);
220 } else if (config->language().get_value_or ("") == "da") {
221 checked_set (_language, 9);
223 _language->SetSelection (1);
226 setup_language_sensitivity ();
228 checked_set (_num_local_encoding_threads, config->num_local_encoding_threads ());
229 checked_set (_check_for_updates, config->check_for_updates ());
230 checked_set (_check_for_test_updates, config->check_for_test_updates ());
233 void setup_language_sensitivity ()
235 _language->Enable (_set_language->GetValue ());
238 void set_language_changed ()
240 setup_language_sensitivity ();
241 if (_set_language->GetValue ()) {
244 Config::instance()->unset_language ();
248 void language_changed ()
250 switch (_language->GetSelection ()) {
252 Config::instance()->set_language ("de");
255 Config::instance()->set_language ("en");
258 Config::instance()->set_language ("es");
261 Config::instance()->set_language ("fr");
264 Config::instance()->set_language ("it");
267 Config::instance()->set_language ("nl");
270 Config::instance()->set_language ("sv");
273 Config::instance()->set_language ("ru");
276 Config::instance()->set_language ("pl");
279 Config::instance()->set_language ("da");
284 void check_for_updates_changed ()
286 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
289 void check_for_test_updates_changed ()
291 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
294 void num_local_encoding_threads_changed ()
296 Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
299 wxCheckBox* _set_language;
301 wxSpinCtrl* _num_local_encoding_threads;
302 wxCheckBox* _check_for_updates;
303 wxCheckBox* _check_for_test_updates;
306 class DefaultsPage : public StandardPage
309 DefaultsPage (wxSize panel_size, int border)
310 : StandardPage (panel_size, border)
313 wxString GetName () const
315 return _("Defaults");
319 wxBitmap GetLargeIcon () const
321 return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
328 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
329 table->AddGrowableCol (1, 1);
330 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
333 add_label_to_sizer (table, _panel, _("Default duration of still images"), true);
334 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
335 _still_length = new wxSpinCtrl (_panel);
336 s->Add (_still_length);
337 add_label_to_sizer (s, _panel, _("s"), false);
341 add_label_to_sizer (table, _panel, _("Default directory for new films"), true);
342 #ifdef DCPOMATIC_USE_OWN_DIR_PICKER
343 _directory = new DirPickerCtrl (_panel);
345 _directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
347 table->Add (_directory, 1, wxEXPAND);
349 add_label_to_sizer (table, _panel, _("Default ISDCF name details"), true);
350 _isdcf_metadata_button = new wxButton (_panel, wxID_ANY, _("Edit..."));
351 table->Add (_isdcf_metadata_button);
353 add_label_to_sizer (table, _panel, _("Default container"), true);
354 _container = new wxChoice (_panel, wxID_ANY);
355 table->Add (_container);
357 add_label_to_sizer (table, _panel, _("Default content type"), true);
358 _dcp_content_type = new wxChoice (_panel, wxID_ANY);
359 table->Add (_dcp_content_type);
362 add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true);
363 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
364 _j2k_bandwidth = new wxSpinCtrl (_panel);
365 s->Add (_j2k_bandwidth);
366 add_label_to_sizer (s, _panel, _("Mbit/s"), false);
371 add_label_to_sizer (table, _panel, _("Default audio delay"), true);
372 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
373 _audio_delay = new wxSpinCtrl (_panel);
374 s->Add (_audio_delay);
375 add_label_to_sizer (s, _panel, _("ms"), false);
379 add_label_to_sizer (table, _panel, _("Default issuer"), true);
380 _issuer = new wxTextCtrl (_panel, wxID_ANY);
381 table->Add (_issuer, 1, wxEXPAND);
383 _still_length->SetRange (1, 3600);
384 _still_length->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::still_length_changed, this));
386 _directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
388 _isdcf_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
390 vector<Ratio const *> ratios = Ratio::all ();
391 for (size_t i = 0; i < ratios.size(); ++i) {
392 _container->Append (std_to_wx (ratios[i]->nickname ()));
395 _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::container_changed, this));
397 vector<DCPContentType const *> const ct = DCPContentType::all ();
398 for (size_t i = 0; i < ct.size(); ++i) {
399 _dcp_content_type->Append (std_to_wx (ct[i]->pretty_name ()));
402 _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
404 _j2k_bandwidth->SetRange (50, 250);
405 _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
407 _audio_delay->SetRange (-1000, 1000);
408 _audio_delay->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::audio_delay_changed, this));
410 _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::issuer_changed, this));
413 void config_changed ()
415 Config* config = Config::instance ();
417 vector<Ratio const *> ratios = Ratio::all ();
418 for (size_t i = 0; i < ratios.size(); ++i) {
419 if (ratios[i] == config->default_container ()) {
420 _container->SetSelection (i);
424 vector<DCPContentType const *> const ct = DCPContentType::all ();
425 for (size_t i = 0; i < ct.size(); ++i) {
426 if (ct[i] == config->default_dcp_content_type ()) {
427 _dcp_content_type->SetSelection (i);
431 checked_set (_still_length, config->default_still_length ());
432 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
433 checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
434 _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
435 checked_set (_audio_delay, config->default_audio_delay ());
436 checked_set (_issuer, config->dcp_issuer ());
439 void j2k_bandwidth_changed ()
441 Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
444 void audio_delay_changed ()
446 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
449 void directory_changed ()
451 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
454 void edit_isdcf_metadata_clicked ()
456 ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata ());
458 Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
462 void still_length_changed ()
464 Config::instance()->set_default_still_length (_still_length->GetValue ());
467 void container_changed ()
469 vector<Ratio const *> ratio = Ratio::all ();
470 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
473 void dcp_content_type_changed ()
475 vector<DCPContentType const *> ct = DCPContentType::all ();
476 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
479 void issuer_changed ()
481 Config::instance()->set_dcp_issuer (wx_to_std (_issuer->GetValue ()));
484 wxSpinCtrl* _j2k_bandwidth;
485 wxSpinCtrl* _audio_delay;
486 wxButton* _isdcf_metadata_button;
487 wxSpinCtrl* _still_length;
488 #ifdef DCPOMATIC_USE_OWN_DIR_PICKER
489 DirPickerCtrl* _directory;
491 wxDirPickerCtrl* _directory;
493 wxChoice* _container;
494 wxChoice* _dcp_content_type;
498 class EncodingServersPage : public StandardPage
501 EncodingServersPage (wxSize panel_size, int border)
502 : StandardPage (panel_size, border)
505 wxString GetName () const
511 wxBitmap GetLargeIcon () const
513 return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
520 _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Use all servers"));
521 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
523 vector<string> columns;
524 columns.push_back (wx_to_std (_("IP address / host name")));
525 _servers_list = new EditableList<string, ServerDialog> (
528 boost::bind (&Config::servers, Config::instance()),
529 boost::bind (&Config::set_servers, Config::instance(), _1),
530 boost::bind (&EncodingServersPage::server_column, this, _1)
533 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
535 _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
538 void config_changed ()
540 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
541 _servers_list->refresh ();
544 void use_any_servers_changed ()
546 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
549 string server_column (string s)
554 wxCheckBox* _use_any_servers;
555 EditableList<string, ServerDialog>* _servers_list;
558 class KeysPage : public StandardPage
561 KeysPage (wxSize panel_size, int border)
562 : StandardPage (panel_size, border)
565 wxString GetName () const
571 wxBitmap GetLargeIcon () const
573 return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
580 wxFont subheading_font (*wxNORMAL_FONT);
581 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
584 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Signing DCPs and KDMs"));
585 m->SetFont (subheading_font);
586 _panel->GetSizer()->Add (m, 0, wxALL, _border);
589 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
590 _panel->GetSizer()->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, _border);
592 _certificates = new wxListCtrl (_panel, wxID_ANY, wxDefaultPosition, wxSize (400, 200), wxLC_REPORT | wxLC_SINGLE_SEL);
597 ip.SetText (_("Type"));
599 _certificates->InsertColumn (0, ip);
605 ip.SetText (_("Thumbprint"));
608 wxFont font = ip.GetFont ();
609 font.SetFamily (wxFONTFAMILY_TELETYPE);
612 _certificates->InsertColumn (1, ip);
615 certificates_sizer->Add (_certificates, 1, wxEXPAND);
618 wxSizer* s = new wxBoxSizer (wxVERTICAL);
619 _add_certificate = new wxButton (_panel, wxID_ANY, _("Add..."));
620 s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
621 _remove_certificate = new wxButton (_panel, wxID_ANY, _("Remove"));
622 s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
623 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
626 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
627 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
631 add_label_to_grid_bag_sizer (table, _panel, _("Leaf private key"), true, wxGBPosition (r, 0));
632 _signer_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
633 wxFont font = _signer_private_key->GetFont ();
634 font.SetFamily (wxFONTFAMILY_TELETYPE);
635 _signer_private_key->SetFont (font);
636 table->Add (_signer_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
637 _load_signer_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
638 table->Add (_load_signer_private_key, wxGBPosition (r, 2));
641 _remake_certificates = new wxButton (_panel, wxID_ANY, _("Re-make certificates and key..."));
642 table->Add (_remake_certificates, wxGBPosition (r, 0), wxGBSpan (1, 3));
646 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Decrypting DCPs"));
647 m->SetFont (subheading_font);
648 table->Add (m, wxGBPosition (r, 0), wxGBSpan (1, 3), wxTOP, _border * 1.5);
652 add_label_to_grid_bag_sizer (table, _panel, _("Certificate"), true, wxGBPosition (r, 0));
653 _decryption_certificate = new wxStaticText (_panel, wxID_ANY, wxT (""));
654 _decryption_certificate->SetFont (font);
655 table->Add (_decryption_certificate, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
656 _load_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Load..."));
657 table->Add (_load_decryption_certificate, wxGBPosition (r, 2));
660 add_label_to_grid_bag_sizer (table, _panel, _("Private key"), true, wxGBPosition (r, 0));
661 _decryption_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
662 _decryption_private_key->SetFont (font);
663 table->Add (_decryption_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
664 _load_decryption_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
665 table->Add (_load_decryption_private_key, wxGBPosition (r, 2));
668 _export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export DCP decryption certificate..."));
669 table->Add (_export_decryption_certificate, wxGBPosition (r, 0), wxGBSpan (1, 3));
672 _add_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::add_certificate, this));
673 _remove_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remove_certificate, this));
674 _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&KeysPage::update_sensitivity, this));
675 _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&KeysPage::update_sensitivity, this));
676 _remake_certificates->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remake_certificates, this));
677 _load_signer_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_signer_private_key, this));
678 _load_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_certificate, this));
679 _load_decryption_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_private_key, this));
680 _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
683 void config_changed ()
685 _signer.reset (new dcp::CertificateChain (*Config::instance()->signer().get ()));
687 update_certificate_list ();
688 update_signer_private_key ();
689 update_decryption_certificate ();
690 update_decryption_private_key ();
691 update_sensitivity ();
694 void add_certificate ()
696 wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
698 if (d->ShowModal() == wxID_OK) {
700 dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
702 Config::instance()->set_signer (_signer);
703 update_certificate_list ();
704 } catch (dcp::MiscError& e) {
705 error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
711 update_sensitivity ();
714 void remove_certificate ()
716 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
721 _certificates->DeleteItem (i);
723 Config::instance()->set_signer (_signer);
725 update_sensitivity ();
728 void update_certificate_list ()
730 _certificates->DeleteAllItems ();
731 dcp::CertificateChain::List certs = _signer->root_to_leaf ();
733 for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) {
736 _certificates->InsertItem (item);
737 _certificates->SetItem (n, 1, std_to_wx (i->thumbprint ()));
740 _certificates->SetItem (n, 0, _("Root"));
741 } else if (n == (certs.size() - 1)) {
742 _certificates->SetItem (n, 0, _("Leaf"));
744 _certificates->SetItem (n, 0, _("Intermediate"));
751 void remake_certificates ()
753 shared_ptr<const dcp::CertificateChain> chain = Config::instance()->signer();
755 string intermediate_common_name;
756 if (chain->root_to_leaf().size() >= 3) {
757 dcp::CertificateChain::List all = chain->root_to_leaf ();
758 dcp::CertificateChain::List::iterator i = all.begin ();
760 intermediate_common_name = i->subject_common_name ();
763 MakeSignerChainDialog* d = new MakeSignerChainDialog (
765 chain->root().subject_organization_name (),
766 chain->root().subject_organizational_unit_name (),
767 chain->root().subject_common_name (),
768 intermediate_common_name,
769 chain->leaf().subject_common_name ()
772 if (d->ShowModal () == wxID_OK) {
774 new dcp::CertificateChain (
777 d->organisational_unit (),
778 d->root_common_name (),
779 d->intermediate_common_name (),
780 d->leaf_common_name ()
784 Config::instance()->set_signer (_signer);
785 update_certificate_list ();
786 update_signer_private_key ();
792 void update_sensitivity ()
794 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
797 void update_signer_private_key ()
799 checked_set (_signer_private_key, dcp::private_key_fingerprint (_signer->key().get ()));
802 void load_signer_private_key ()
804 wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
806 if (d->ShowModal() == wxID_OK) {
808 boost::filesystem::path p (wx_to_std (d->GetPath ()));
809 if (boost::filesystem::file_size (p) > 1024) {
810 error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ())));
814 _signer->set_key (dcp::file_to_string (p));
815 Config::instance()->set_signer (_signer);
816 update_signer_private_key ();
817 } catch (dcp::MiscError& e) {
818 error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
824 update_sensitivity ();
828 void load_decryption_certificate ()
830 wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
832 if (d->ShowModal() == wxID_OK) {
834 dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
835 Config::instance()->set_decryption_certificate (c);
836 update_decryption_certificate ();
837 } catch (dcp::MiscError& e) {
838 error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
845 void update_decryption_certificate ()
847 checked_set (_decryption_certificate, Config::instance()->decryption_certificate().thumbprint ());
850 void load_decryption_private_key ()
852 wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
854 if (d->ShowModal() == wxID_OK) {
856 boost::filesystem::path p (wx_to_std (d->GetPath ()));
857 Config::instance()->set_decryption_private_key (dcp::file_to_string (p));
858 update_decryption_private_key ();
859 } catch (dcp::MiscError& e) {
860 error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), e.what ()));
867 void update_decryption_private_key ()
869 checked_set (_decryption_private_key, dcp::private_key_fingerprint (Config::instance()->decryption_private_key()));
872 void export_decryption_certificate ()
874 wxFileDialog* d = new wxFileDialog (
875 _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
876 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
879 if (d->ShowModal () == wxID_OK) {
880 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
882 throw OpenFileError (wx_to_std (d->GetPath ()));
885 string const s = Config::instance()->decryption_certificate().certificate (true);
886 fwrite (s.c_str(), 1, s.length(), f);
892 wxListCtrl* _certificates;
893 wxButton* _add_certificate;
894 wxButton* _remove_certificate;
895 wxButton* _remake_certificates;
896 wxStaticText* _signer_private_key;
897 wxButton* _load_signer_private_key;
898 wxStaticText* _decryption_certificate;
899 wxButton* _load_decryption_certificate;
900 wxStaticText* _decryption_private_key;
901 wxButton* _load_decryption_private_key;
902 wxButton* _export_decryption_certificate;
903 shared_ptr<dcp::CertificateChain> _signer;
906 class TMSPage : public StandardPage
909 TMSPage (wxSize panel_size, int border)
910 : StandardPage (panel_size, border)
913 wxString GetName () const
919 wxBitmap GetLargeIcon () const
921 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
928 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
929 table->AddGrowableCol (1, 1);
930 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
932 add_label_to_sizer (table, _panel, _("Protocol"), true);
933 _tms_protocol = new wxChoice (_panel, wxID_ANY);
934 table->Add (_tms_protocol, 1, wxEXPAND);
936 add_label_to_sizer (table, _panel, _("IP address"), true);
937 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
938 table->Add (_tms_ip, 1, wxEXPAND);
940 add_label_to_sizer (table, _panel, _("Target path"), true);
941 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
942 table->Add (_tms_path, 1, wxEXPAND);
944 add_label_to_sizer (table, _panel, _("User name"), true);
945 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
946 table->Add (_tms_user, 1, wxEXPAND);
948 add_label_to_sizer (table, _panel, _("Password"), true);
949 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
950 table->Add (_tms_password, 1, wxEXPAND);
952 _tms_protocol->Append (_("SCP (for AAM)"));
953 _tms_protocol->Append (_("FTP (for Dolby)"));
955 _tms_protocol->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&TMSPage::tms_protocol_changed, this));
956 _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
957 _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
958 _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
959 _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
962 void config_changed ()
964 Config* config = Config::instance ();
966 checked_set (_tms_protocol, config->tms_protocol ());
967 checked_set (_tms_ip, config->tms_ip ());
968 checked_set (_tms_path, config->tms_path ());
969 checked_set (_tms_user, config->tms_user ());
970 checked_set (_tms_password, config->tms_password ());
973 void tms_protocol_changed ()
975 Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
978 void tms_ip_changed ()
980 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
983 void tms_path_changed ()
985 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
988 void tms_user_changed ()
990 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
993 void tms_password_changed ()
995 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
998 wxChoice* _tms_protocol;
1000 wxTextCtrl* _tms_path;
1001 wxTextCtrl* _tms_user;
1002 wxTextCtrl* _tms_password;
1005 class KDMEmailPage : public StandardPage
1009 KDMEmailPage (wxSize panel_size, int border)
1010 #ifdef DCPOMATIC_OSX
1011 /* We have to force both width and height of this one */
1012 : StandardPage (wxSize (480, 128), border)
1014 : StandardPage (panel_size, border)
1018 wxString GetName () const
1020 return _("KDM Email");
1023 #ifdef DCPOMATIC_OSX
1024 wxBitmap GetLargeIcon () const
1026 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
1033 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1034 table->AddGrowableCol (1, 1);
1035 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
1037 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
1039 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1040 _mail_server = new wxTextCtrl (_panel, wxID_ANY);
1041 s->Add (_mail_server, 1, wxEXPAND | wxALL);
1042 add_label_to_sizer (s, _panel, _("port"), false);
1043 _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
1044 _mail_port->SetRange (0, 65535);
1045 s->Add (_mail_port);
1046 table->Add (s, 1, wxEXPAND | wxALL);
1049 add_label_to_sizer (table, _panel, _("Mail user name"), true);
1050 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
1051 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1053 add_label_to_sizer (table, _panel, _("Mail password"), true);
1054 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1055 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1057 add_label_to_sizer (table, _panel, _("Subject"), true);
1058 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1059 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1061 add_label_to_sizer (table, _panel, _("From address"), true);
1062 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1063 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1065 add_label_to_sizer (table, _panel, _("CC address"), true);
1066 _kdm_cc = new wxTextCtrl (_panel, wxID_ANY);
1067 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1069 add_label_to_sizer (table, _panel, _("BCC address"), true);
1070 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1071 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1073 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (480, 128), wxTE_MULTILINE);
1074 _panel->GetSizer()->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
1076 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default text"));
1077 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1079 _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_server_changed, this));
1080 _mail_port->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&KDMEmailPage::mail_port_changed, this));
1081 _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_user_changed, this));
1082 _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_password_changed, this));
1083 _kdm_subject->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1084 _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1085 _kdm_cc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_cc_changed, this));
1086 _kdm_bcc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1087 _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1088 _reset_kdm_email->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1091 void config_changed ()
1093 Config* config = Config::instance ();
1095 checked_set (_mail_server, config->mail_server ());
1096 checked_set (_mail_port, config->mail_port ());
1097 checked_set (_mail_user, config->mail_user ());
1098 checked_set (_mail_password, config->mail_password ());
1099 checked_set (_kdm_subject, config->kdm_subject ());
1100 checked_set (_kdm_from, config->kdm_from ());
1101 checked_set (_kdm_cc, config->kdm_cc ());
1102 checked_set (_kdm_bcc, config->kdm_bcc ());
1103 checked_set (_kdm_email, Config::instance()->kdm_email ());
1106 void mail_server_changed ()
1108 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1111 void mail_port_changed ()
1113 Config::instance()->set_mail_port (_mail_port->GetValue ());
1116 void mail_user_changed ()
1118 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1121 void mail_password_changed ()
1123 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1126 void kdm_subject_changed ()
1128 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1131 void kdm_from_changed ()
1133 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1136 void kdm_cc_changed ()
1138 Config::instance()->set_kdm_cc (wx_to_std (_kdm_cc->GetValue ()));
1141 void kdm_bcc_changed ()
1143 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1146 void kdm_email_changed ()
1148 if (_kdm_email->GetValue().IsEmpty ()) {
1149 /* Sometimes we get sent an erroneous notification that the email
1150 is empty; I don't know why.
1154 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1157 void reset_kdm_email ()
1159 Config::instance()->reset_kdm_email ();
1160 checked_set (_kdm_email, Config::instance()->kdm_email ());
1163 wxTextCtrl* _mail_server;
1164 wxSpinCtrl* _mail_port;
1165 wxTextCtrl* _mail_user;
1166 wxTextCtrl* _mail_password;
1167 wxTextCtrl* _kdm_subject;
1168 wxTextCtrl* _kdm_from;
1169 wxTextCtrl* _kdm_cc;
1170 wxTextCtrl* _kdm_bcc;
1171 wxTextCtrl* _kdm_email;
1172 wxButton* _reset_kdm_email;
1175 /** @class AdvancedPage
1176 * @brief Advanced page of the preferences dialog.
1178 class AdvancedPage : public StockPage
1181 AdvancedPage (wxSize panel_size, int border)
1182 : StockPage (Kind_Advanced, panel_size, border)
1183 , _maximum_j2k_bandwidth (0)
1184 , _allow_any_dcp_frame_rate (0)
1189 , _log_debug_decode (0)
1190 , _log_debug_encode (0)
1196 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1197 table->AddGrowableCol (1, 1);
1198 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1201 add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1202 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1203 _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1204 s->Add (_maximum_j2k_bandwidth, 1);
1205 add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1209 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1210 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1211 table->AddSpacer (0);
1214 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log:"));
1215 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL | wxALIGN_RIGHT, 6);
1217 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log"));
1218 table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL, 6);
1222 wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1223 _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
1224 t->Add (_log_general, 1, wxEXPAND | wxALL);
1225 _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
1226 t->Add (_log_warning, 1, wxEXPAND | wxALL);
1227 _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
1228 t->Add (_log_error, 1, wxEXPAND | wxALL);
1229 _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
1230 t->Add (_log_timing, 1, wxEXPAND | wxALL);
1231 _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
1232 t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
1233 _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
1234 t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1235 table->Add (t, 0, wxALL, 6);
1238 #ifdef DCPOMATIC_WINDOWS
1239 _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
1240 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1241 table->AddSpacer (0);
1244 _maximum_j2k_bandwidth->SetRange (1, 1000);
1245 _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1246 _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1247 _log_general->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1248 _log_warning->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1249 _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1250 _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1251 _log_debug_decode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1252 _log_debug_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
1253 #ifdef DCPOMATIC_WINDOWS
1254 _win32_console->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::win32_console_changed, this));
1258 void config_changed ()
1260 Config* config = Config::instance ();
1262 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1263 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1264 checked_set (_log_general, config->log_types() & Log::TYPE_GENERAL);
1265 checked_set (_log_warning, config->log_types() & Log::TYPE_WARNING);
1266 checked_set (_log_error, config->log_types() & Log::TYPE_ERROR);
1267 checked_set (_log_timing, config->log_types() & Log::TYPE_TIMING);
1268 checked_set (_log_debug_decode, config->log_types() & Log::TYPE_DEBUG_DECODE);
1269 checked_set (_log_debug_encode, config->log_types() & Log::TYPE_DEBUG_ENCODE);
1270 #ifdef DCPOMATIC_WINDOWS
1271 checked_set (_win32_console, config->win32_console());
1275 void maximum_j2k_bandwidth_changed ()
1277 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1280 void allow_any_dcp_frame_rate_changed ()
1282 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1288 if (_log_general->GetValue ()) {
1289 types |= Log::TYPE_GENERAL;
1291 if (_log_warning->GetValue ()) {
1292 types |= Log::TYPE_WARNING;
1294 if (_log_error->GetValue ()) {
1295 types |= Log::TYPE_ERROR;
1297 if (_log_timing->GetValue ()) {
1298 types |= Log::TYPE_TIMING;
1300 if (_log_debug_decode->GetValue ()) {
1301 types |= Log::TYPE_DEBUG_DECODE;
1303 if (_log_debug_encode->GetValue ()) {
1304 types |= Log::TYPE_DEBUG_ENCODE;
1306 Config::instance()->set_log_types (types);
1309 #ifdef DCPOMATIC_WINDOWS
1310 void win32_console_changed ()
1312 Config::instance()->set_win32_console (_win32_console->GetValue ());
1316 wxSpinCtrl* _maximum_j2k_bandwidth;
1317 wxCheckBox* _allow_any_dcp_frame_rate;
1318 wxCheckBox* _log_general;
1319 wxCheckBox* _log_warning;
1320 wxCheckBox* _log_error;
1321 wxCheckBox* _log_timing;
1322 wxCheckBox* _log_debug_decode;
1323 wxCheckBox* _log_debug_encode;
1324 #ifdef DCPOMATIC_WINDOWS
1325 wxCheckBox* _win32_console;
1329 wxPreferencesEditor*
1330 create_config_dialog ()
1332 wxPreferencesEditor* e = new wxPreferencesEditor ();
1334 #ifdef DCPOMATIC_OSX
1335 /* Width that we force some of the config panels to be on OSX so that
1336 the containing window doesn't shrink too much when we select those panels.
1337 This is obviously an unpleasant hack.
1339 wxSize ps = wxSize (520, -1);
1340 int const border = 16;
1342 wxSize ps = wxSize (-1, -1);
1343 int const border = 8;
1346 e->AddPage (new GeneralPage (ps, border));
1347 e->AddPage (new DefaultsPage (ps, border));
1348 e->AddPage (new EncodingServersPage (ps, border));
1349 e->AddPage (new KeysPage (ps, border));
1350 e->AddPage (new TMSPage (ps, border));
1351 e->AddPage (new KDMEmailPage (ps, border));
1352 e->AddPage (new AdvancedPage (ps, border));