Lots of #include <iostream>s for Arch.
[dcpomatic.git] / src / wx / kdm_dialog.cc
1 /*
2     Copyright (C) 2012-2015 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 #include "kdm_dialog.h"
21 #include "cinema_dialog.h"
22 #include "screen_dialog.h"
23 #include "wx_util.h"
24 #include "lib/cinema.h"
25 #include "lib/config.h"
26 #include "lib/film.h"
27 #include "lib/screen.h"
28 #include <libcxml/cxml.h>
29 #ifdef DCPOMATIC_USE_OWN_DIR_PICKER
30 #include "dir_picker_ctrl.h"
31 #else
32 #include <wx/filepicker.h>
33 #endif
34 #include <wx/treectrl.h>
35 #include <wx/datectrl.h>
36 #include <wx/timectrl.h>
37 #include <wx/stdpaths.h>
38 #include <wx/listctrl.h>
39 #include <iostream>
40
41 using std::string;
42 using std::map;
43 using std::list;
44 using std::pair;
45 using std::cout;
46 using std::vector;
47 using std::make_pair;
48 using boost::shared_ptr;
49
50 KDMDialog::KDMDialog (wxWindow* parent, boost::shared_ptr<const Film> film)
51         : wxDialog (parent, wxID_ANY, _("Make KDMs"))
52 {
53         /* Main sizer */
54         wxBoxSizer* vertical = new wxBoxSizer (wxVERTICAL);
55
56         /* Font for sub-headings */
57         wxFont subheading_font (*wxNORMAL_FONT);
58         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
59
60
61         /* Sub-heading: Screens */
62         wxStaticText* h = new wxStaticText (this, wxID_ANY, _("Screens"));
63         h->SetFont (subheading_font);
64         vertical->Add (h, 0, wxALIGN_CENTER_VERTICAL);
65
66         wxBoxSizer* targets = new wxBoxSizer (wxHORIZONTAL);
67         _targets = new wxTreeCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_HIDE_ROOT | wxTR_MULTIPLE | wxTR_HAS_BUTTONS | wxTR_LINES_AT_ROOT);
68         targets->Add (_targets, 1, wxEXPAND | wxTOP | wxRIGHT, DCPOMATIC_SIZER_GAP);
69
70         _root = _targets->AddRoot ("Foo");
71
72         list<shared_ptr<Cinema> > c = Config::instance()->cinemas ();
73         for (list<shared_ptr<Cinema> >::iterator i = c.begin(); i != c.end(); ++i) {
74                 add_cinema (*i);
75         }
76
77         _targets->ExpandAll ();
78
79         wxBoxSizer* target_buttons = new wxBoxSizer (wxVERTICAL);
80
81         _add_cinema = new wxButton (this, wxID_ANY, _("Add Cinema..."));
82         target_buttons->Add (_add_cinema, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
83         _edit_cinema = new wxButton (this, wxID_ANY, _("Edit Cinema..."));
84         target_buttons->Add (_edit_cinema, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
85         _remove_cinema = new wxButton (this, wxID_ANY, _("Remove Cinema"));
86         target_buttons->Add (_remove_cinema, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
87
88         _add_screen = new wxButton (this, wxID_ANY, _("Add Screen..."));
89         target_buttons->Add (_add_screen, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
90         _edit_screen = new wxButton (this, wxID_ANY, _("Edit Screen..."));
91         target_buttons->Add (_edit_screen, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
92         _remove_screen = new wxButton (this, wxID_ANY, _("Remove Screen"));
93         target_buttons->Add (_remove_screen, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
94
95         targets->Add (target_buttons, 0, 0);
96
97         vertical->Add (targets, 1, wxEXPAND);
98
99
100         /* Sub-heading: Timing */
101         h = new wxStaticText (this, wxID_ANY, S_("KDM|Timing"));
102         h->SetFont (subheading_font);
103         vertical->Add (h, 0, wxALIGN_CENTER_VERTICAL | wxTOP, DCPOMATIC_SIZER_Y_GAP * 2);
104
105         wxFlexGridSizer* table = new wxFlexGridSizer (3, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
106         add_label_to_sizer (table, this, _("From"), true);
107         wxDateTime from;
108         from.SetToCurrent ();
109         _from_date = new wxDatePickerCtrl (this, wxID_ANY, from);
110         table->Add (_from_date, 1, wxEXPAND);
111         _from_time = new wxTimePickerCtrl (this, wxID_ANY, from);
112         table->Add (_from_time, 1, wxEXPAND);
113
114         add_label_to_sizer (table, this, _("Until"), true);
115         wxDateTime to = from;
116         /* 1 week from now */
117         to.Add (wxDateSpan (0, 0, 1, 0));
118         _until_date = new wxDatePickerCtrl (this, wxID_ANY, to);
119         table->Add (_until_date, 1, wxEXPAND);
120         _until_time = new wxTimePickerCtrl (this, wxID_ANY, to);
121         table->Add (_until_time, 1, wxEXPAND);
122
123         vertical->Add (table, 0, wxEXPAND | wxTOP, DCPOMATIC_SIZER_GAP);
124
125
126         /* Sub-heading: CPL */
127         h = new wxStaticText (this, wxID_ANY, _("CPL"));
128         h->SetFont (subheading_font);
129         vertical->Add (h, 0, wxALIGN_CENTER_VERTICAL | wxTOP, DCPOMATIC_SIZER_Y_GAP * 2);
130
131         /* CPL choice */
132         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
133         add_label_to_sizer (s, this, _("CPL"), true);
134         _cpl = new wxChoice (this, wxID_ANY);
135         s->Add (_cpl, 1, wxEXPAND);
136         _cpl_browse = new wxButton (this, wxID_ANY, _("Browse..."));
137         s->Add (_cpl_browse, 0);
138         vertical->Add (s, 0, wxEXPAND | wxTOP, DCPOMATIC_SIZER_GAP + 2);
139
140         /* CPL details */
141         table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
142         add_label_to_sizer (table, this, _("DCP directory"), true);
143         _dcp_directory = new wxStaticText (this, wxID_ANY, "");
144         table->Add (_dcp_directory);
145         add_label_to_sizer (table, this, _("CPL ID"), true);
146         _cpl_id = new wxStaticText (this, wxID_ANY, "");
147         table->Add (_cpl_id);
148         add_label_to_sizer (table, this, _("CPL annotation text"), true);
149         _cpl_annotation_text = new wxStaticText (this, wxID_ANY, "");
150         table->Add (_cpl_annotation_text);
151         vertical->Add (table, 0, wxEXPAND | wxTOP, DCPOMATIC_SIZER_GAP + 2);
152
153         _cpls = film->cpls ();
154         update_cpl_choice ();
155
156
157         /* Sub-heading: Output */
158         h = new wxStaticText (this, wxID_ANY, _("Output"));
159         h->SetFont (subheading_font);
160         vertical->Add (h, 0, wxALIGN_CENTER_VERTICAL | wxTOP, DCPOMATIC_SIZER_Y_GAP * 2);
161
162         table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, 0);
163
164         add_label_to_sizer (table, this, _("KDM type"), true);
165         _type = new wxChoice (this, wxID_ANY);
166         _type->Append ("Modified Transitional 1", ((void *) dcp::MODIFIED_TRANSITIONAL_1));
167         if (!film->interop ()) {
168                 _type->Append ("DCI Any", ((void *) dcp::DCI_ANY));
169                 _type->Append ("DCI Specific", ((void *) dcp::DCI_SPECIFIC));
170         }
171         table->Add (_type, 1, wxEXPAND);
172         _type->SetSelection (0);
173
174         _write_to = new wxRadioButton (this, wxID_ANY, _("Write to"));
175         table->Add (_write_to, 1, wxEXPAND);
176
177 #ifdef DCPOMATIC_USE_OWN_DIR_PICKER
178         _folder = new DirPickerCtrl (this);
179 #else
180         _folder = new wxDirPickerCtrl (this, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
181 #endif
182
183         _folder->SetPath (wxStandardPaths::Get().GetDocumentsDir());
184
185         table->Add (_folder, 1, wxEXPAND);
186
187         _email = new wxRadioButton (this, wxID_ANY, _("Send by email"));
188         table->Add (_email, 1, wxEXPAND);
189         table->AddSpacer (0);
190
191         vertical->Add (table, 0, wxEXPAND | wxTOP, DCPOMATIC_SIZER_GAP);
192
193         /* Make an overall sizer to get a nice border, and put some buttons in */
194
195         wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
196         overall_sizer->Add (vertical, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, DCPOMATIC_DIALOG_BORDER);
197
198         wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
199         if (buttons) {
200                 overall_sizer->Add (buttons, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_Y_GAP);
201         }
202
203         _write_to->SetValue (true);
204
205         /* Bind */
206
207         _targets->Bind       (wxEVT_COMMAND_TREE_SEL_CHANGED, boost::bind (&KDMDialog::setup_sensitivity, this));
208
209         _add_cinema->Bind    (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMDialog::add_cinema_clicked, this));
210         _edit_cinema->Bind   (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMDialog::edit_cinema_clicked, this));
211         _remove_cinema->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMDialog::remove_cinema_clicked, this));
212
213         _add_screen->Bind    (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMDialog::add_screen_clicked, this));
214         _edit_screen->Bind   (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMDialog::edit_screen_clicked, this));
215         _remove_screen->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMDialog::remove_screen_clicked, this));
216
217         _cpl->Bind           (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&KDMDialog::update_cpl_summary, this));
218         _cpl_browse->Bind    (wxEVT_COMMAND_BUTTON_CLICKED,  boost::bind (&KDMDialog::cpl_browse_clicked, this));
219
220         _write_to->Bind      (wxEVT_COMMAND_RADIOBUTTON_SELECTED, boost::bind (&KDMDialog::setup_sensitivity, this));
221         _email->Bind         (wxEVT_COMMAND_RADIOBUTTON_SELECTED, boost::bind (&KDMDialog::setup_sensitivity, this));
222
223         setup_sensitivity ();
224
225         SetSizer (overall_sizer);
226         overall_sizer->Layout ();
227         overall_sizer->SetSizeHints (this);
228 }
229
230 list<pair<wxTreeItemId, shared_ptr<Cinema> > >
231 KDMDialog::selected_cinemas () const
232 {
233         wxArrayTreeItemIds s;
234         _targets->GetSelections (s);
235
236         list<pair<wxTreeItemId, shared_ptr<Cinema> > > c;
237         for (size_t i = 0; i < s.GetCount(); ++i) {
238                 map<wxTreeItemId, shared_ptr<Cinema> >::const_iterator j = _cinemas.find (s[i]);
239                 if (j != _cinemas.end ()) {
240                         c.push_back (make_pair (j->first, j->second));
241                 }
242         }
243
244         return c;
245 }
246
247 list<pair<wxTreeItemId, shared_ptr<Screen> > >
248 KDMDialog::selected_screens () const
249 {
250         wxArrayTreeItemIds s;
251         _targets->GetSelections (s);
252
253         list<pair<wxTreeItemId, shared_ptr<Screen> > > c;
254         for (size_t i = 0; i < s.GetCount(); ++i) {
255                 map<wxTreeItemId, shared_ptr<Screen> >::const_iterator j = _screens.find (s[i]);
256                 if (j != _screens.end ()) {
257                         c.push_back (make_pair (j->first, j->second));
258                 }
259         }
260
261         return c;
262 }
263
264 void
265 KDMDialog::setup_sensitivity ()
266 {
267         bool const sc = selected_cinemas().size() == 1;
268         bool const ss = selected_screens().size() == 1;
269         bool const sd = _cpl->GetSelection() != -1;
270
271         _edit_cinema->Enable (sc);
272         _remove_cinema->Enable (sc);
273
274         _add_screen->Enable (sc);
275         _edit_screen->Enable (ss);
276         _remove_screen->Enable (ss);
277
278         wxButton* ok = dynamic_cast<wxButton *> (FindWindowById (wxID_OK, this));
279         if (ok) {
280                 ok->Enable ((selected_cinemas().size() > 0 || selected_screens().size() > 0) && sd);
281         }
282
283         _folder->Enable (_write_to->GetValue ());
284 }
285
286 void
287 KDMDialog::add_cinema (shared_ptr<Cinema> c)
288 {
289         _cinemas[_targets->AppendItem (_root, std_to_wx (c->name))] = c;
290
291         list<shared_ptr<Screen> > sc = c->screens ();
292         for (list<shared_ptr<Screen> >::iterator i = sc.begin(); i != sc.end(); ++i) {
293                 add_screen (c, *i);
294         }
295 }
296
297 void
298 KDMDialog::add_screen (shared_ptr<Cinema> c, shared_ptr<Screen> s)
299 {
300         map<wxTreeItemId, shared_ptr<Cinema> >::const_iterator i = _cinemas.begin();
301         while (i != _cinemas.end() && i->second != c) {
302                 ++i;
303         }
304
305         if (i == _cinemas.end()) {
306                 return;
307         }
308
309         _screens[_targets->AppendItem (i->first, std_to_wx (s->name))] = s;
310         _targets->Expand (i->first);
311 }
312
313 void
314 KDMDialog::add_cinema_clicked ()
315 {
316         CinemaDialog* d = new CinemaDialog (this, "Add Cinema");
317         if (d->ShowModal () == wxID_OK) {
318                 shared_ptr<Cinema> c (new Cinema (d->name(), d->email()));
319                 Config::instance()->add_cinema (c);
320                 add_cinema (c);
321         }
322
323         d->Destroy ();
324 }
325
326 void
327 KDMDialog::edit_cinema_clicked ()
328 {
329         if (selected_cinemas().size() != 1) {
330                 return;
331         }
332
333         pair<wxTreeItemId, shared_ptr<Cinema> > c = selected_cinemas().front();
334
335         CinemaDialog* d = new CinemaDialog (this, "Edit cinema", c.second->name, c.second->email);
336         if (d->ShowModal () == wxID_OK) {
337                 c.second->name = d->name ();
338                 c.second->email = d->email ();
339                 _targets->SetItemText (c.first, std_to_wx (d->name()));
340                 Config::instance()->changed ();
341         }
342
343         d->Destroy ();
344 }
345
346 void
347 KDMDialog::remove_cinema_clicked ()
348 {
349         if (selected_cinemas().size() != 1) {
350                 return;
351         }
352
353         pair<wxTreeItemId, shared_ptr<Cinema> > c = selected_cinemas().front();
354
355         Config::instance()->remove_cinema (c.second);
356         _targets->Delete (c.first);
357 }
358
359 void
360 KDMDialog::add_screen_clicked ()
361 {
362         if (selected_cinemas().size() != 1) {
363                 return;
364         }
365
366         shared_ptr<Cinema> c = selected_cinemas().front().second;
367
368         ScreenDialog* d = new ScreenDialog (this, "Add Screen");
369         if (d->ShowModal () != wxID_OK) {
370                 return;
371         }
372
373         shared_ptr<Screen> s (new Screen (d->name(), d->certificate()));
374         c->add_screen (s);
375         add_screen (c, s);
376
377         Config::instance()->changed ();
378
379         d->Destroy ();
380 }
381
382 void
383 KDMDialog::edit_screen_clicked ()
384 {
385         if (selected_screens().size() != 1) {
386                 return;
387         }
388
389         pair<wxTreeItemId, shared_ptr<Screen> > s = selected_screens().front();
390
391         ScreenDialog* d = new ScreenDialog (this, "Edit screen", s.second->name, s.second->certificate);
392         if (d->ShowModal () == wxID_OK) {
393                 s.second->name = d->name ();
394                 s.second->certificate = d->certificate ();
395                 _targets->SetItemText (s.first, std_to_wx (d->name()));
396                 Config::instance()->changed ();
397         }
398
399         d->Destroy ();
400 }
401
402 void
403 KDMDialog::remove_screen_clicked ()
404 {
405         if (selected_screens().size() != 1) {
406                 return;
407         }
408
409         pair<wxTreeItemId, shared_ptr<Screen> > s = selected_screens().front();
410
411         map<wxTreeItemId, shared_ptr<Cinema> >::iterator i = _cinemas.begin ();
412         while (i != _cinemas.end ()) {
413                 list<shared_ptr<Screen> > sc = i->second->screens ();
414                 if (find (sc.begin(), sc.end(), s.second) != sc.end ()) {
415                         break;
416                 }
417         }
418
419         if (i == _cinemas.end()) {
420                 return;
421         }
422
423         i->second->remove_screen (s.second);
424         _targets->Delete (s.first);
425
426         Config::instance()->changed ();
427 }
428
429 list<shared_ptr<Screen> >
430 KDMDialog::screens () const
431 {
432         list<shared_ptr<Screen> > s;
433
434         list<pair<wxTreeItemId, shared_ptr<Cinema> > > cinemas = selected_cinemas ();
435         for (list<pair<wxTreeItemId, shared_ptr<Cinema> > >::iterator i = cinemas.begin(); i != cinemas.end(); ++i) {
436                 list<shared_ptr<Screen> > sc = i->second->screens ();
437                 for (list<shared_ptr<Screen> >::const_iterator j = sc.begin(); j != sc.end(); ++j) {
438                         s.push_back (*j);
439                 }
440         }
441
442         list<pair<wxTreeItemId, shared_ptr<Screen> > > screens = selected_screens ();
443         for (list<pair<wxTreeItemId, shared_ptr<Screen> > >::iterator i = screens.begin(); i != screens.end(); ++i) {
444                 s.push_back (i->second);
445         }
446
447         s.sort ();
448         s.unique ();
449
450         return s;
451 }
452
453 boost::posix_time::ptime
454 KDMDialog::from () const
455 {
456         return posix_time (_from_date, _from_time);
457 }
458
459 boost::posix_time::ptime
460 KDMDialog::posix_time (wxDatePickerCtrl* date_picker, wxTimePickerCtrl* time_picker)
461 {
462         wxDateTime const date = date_picker->GetValue ();
463         wxDateTime const time = time_picker->GetValue ();
464         return boost::posix_time::ptime (
465                 boost::gregorian::date (date.GetYear(), date.GetMonth() + 1, date.GetDay()),
466                 boost::posix_time::time_duration (time.GetHour(), time.GetMinute(), time.GetSecond())
467                 );
468 }
469
470 boost::posix_time::ptime
471 KDMDialog::until () const
472 {
473         return posix_time (_until_date, _until_time);
474 }
475
476 boost::filesystem::path
477 KDMDialog::cpl () const
478 {
479         int const item = _cpl->GetSelection ();
480         DCPOMATIC_ASSERT (item >= 0);
481         return _cpls[item].cpl_file;
482 }
483
484 boost::filesystem::path
485 KDMDialog::directory () const
486 {
487         return wx_to_std (_folder->GetPath ());
488 }
489
490 bool
491 KDMDialog::write_to () const
492 {
493         return _write_to->GetValue ();
494 }
495
496 dcp::Formulation
497 KDMDialog::formulation () const
498 {
499         return (dcp::Formulation) reinterpret_cast<intptr_t> (_type->GetClientData (_type->GetSelection()));
500 }
501
502 void
503 KDMDialog::update_cpl_choice ()
504 {
505         _cpl->Clear ();
506
507         for (vector<CPLSummary>::const_iterator i = _cpls.begin(); i != _cpls.end(); ++i) {
508                 _cpl->Append (std_to_wx (i->cpl_id));
509
510                 if (_cpls.size() > 0) {
511                         _cpl->SetSelection (0);
512                 }
513         }
514
515         update_cpl_summary ();
516 }
517
518 void
519 KDMDialog::update_cpl_summary ()
520 {
521         int const n = _cpl->GetSelection();
522         if (n == wxNOT_FOUND) {
523                 return;
524         }
525
526         _dcp_directory->SetLabel (std_to_wx (_cpls[n].dcp_directory));
527         _cpl_id->SetLabel (std_to_wx (_cpls[n].cpl_id));
528         _cpl_annotation_text->SetLabel (std_to_wx (_cpls[n].cpl_annotation_text));
529 }
530
531 void
532 KDMDialog::cpl_browse_clicked ()
533 {
534         wxFileDialog* d = new wxFileDialog (this, _("Select CPL XML file"), wxEmptyString, wxEmptyString, "*.xml");
535         if (d->ShowModal() == wxID_CANCEL) {
536                 d->Destroy ();
537                 return;
538         }
539
540         boost::filesystem::path cpl_file (wx_to_std (d->GetPath ()));
541         boost::filesystem::path dcp_dir = cpl_file.parent_path ();
542
543         d->Destroy ();
544
545         /* XXX: hack alert */
546         cxml::Document cpl_document ("CompositionPlaylist");
547         cpl_document.read_file (cpl_file);
548
549         try {
550                 _cpls.push_back (
551                         CPLSummary (
552                                 dcp_dir.filename().string(),
553                                 cpl_document.string_child("Id").substr (9),
554                                 cpl_document.string_child ("ContentTitleText"),
555                                 cpl_file
556                                 )
557                         );
558         } catch (cxml::Error) {
559                 error_dialog (this, _("This is not a valid CPL file"));
560                 return;
561         }
562
563         update_cpl_choice ();
564         _cpl->SetSelection (_cpls.size() - 1);
565         update_cpl_summary ();
566 }