Move video into a separate panel.
[dcpomatic.git] / src / wx / film_editor.cc
1 /*
2     Copyright (C) 2012 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/film_editor.cc
21  *  @brief A wx widget to edit a film's metadata, and perform various functions.
22  */
23
24 #include <iostream>
25 #include <iomanip>
26 #include <wx/wx.h>
27 #include <wx/notebook.h>
28 #include <boost/thread.hpp>
29 #include <boost/filesystem.hpp>
30 #include <boost/lexical_cast.hpp>
31 #include "lib/format.h"
32 #include "lib/film.h"
33 #include "lib/transcode_job.h"
34 #include "lib/exceptions.h"
35 #include "lib/ab_transcode_job.h"
36 #include "lib/job_manager.h"
37 #include "lib/filter.h"
38 #include "lib/screen.h"
39 #include "lib/config.h"
40 #include "filter_dialog.h"
41 #include "wx_util.h"
42 #include "film_editor.h"
43 #include "gain_calculator_dialog.h"
44 #include "sound_processor.h"
45 #include "dci_name_dialog.h"
46 #include "scaler.h"
47
48 using std::string;
49 using std::stringstream;
50 using std::pair;
51 using std::fixed;
52 using std::setprecision;
53 using std::list;
54 using std::vector;
55 using boost::shared_ptr;
56
57 /** @param f Film to edit */
58 FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
59         : wxPanel (parent)
60         , _film (f)
61         , _generally_sensitive (true)
62 {
63         wxSizer* s = new wxBoxSizer (wxVERTICAL);
64         SetSizer (s);
65         _notebook = new wxNotebook (this, wxID_ANY);
66         s->Add (_notebook, 1);
67         
68         make_film_panel ();
69         _notebook->AddPage (_film_panel, _("Film"), true);
70         make_video_panel ();
71         _notebook->AddPage (_video_panel, _("Video"), false);
72         make_audio_panel ();
73         _notebook->AddPage (_audio_panel, _("Audio"), false);
74         make_subtitle_panel ();
75         _notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
76
77         set_film (_film);
78         connect_to_widgets ();
79
80         JobManager::instance()->ActiveJobsChanged.connect (
81                 bind (&FilmEditor::active_jobs_changed, this, _1)
82                 );
83         
84         setup_visibility ();
85         setup_formats ();
86 }
87
88 void
89 FilmEditor::make_film_panel ()
90 {
91         _film_panel = new wxPanel (_notebook);
92         _film_sizer = new wxFlexGridSizer (2, 4, 4);
93         _film_panel->SetSizer (_film_sizer);
94
95         add_label_to_sizer (_film_sizer, _film_panel, "Name");
96         _name = new wxTextCtrl (_film_panel, wxID_ANY);
97         _film_sizer->Add (_name, 1, wxEXPAND);
98
99         add_label_to_sizer (_film_sizer, _film_panel, "DCP Name");
100         _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
101         _film_sizer->Add (_dcp_name, 0, wxALIGN_CENTER_VERTICAL | wxSHRINK);
102
103         _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, wxT ("Use DCI name"));
104         _film_sizer->Add (_use_dci_name, 1, wxEXPAND);
105         _edit_dci_button = new wxButton (_film_panel, wxID_ANY, wxT ("Details..."));
106         _film_sizer->Add (_edit_dci_button, 0);
107
108         add_label_to_sizer (_film_sizer, _film_panel, "Content");
109         _content = new wxFilePickerCtrl (_film_panel, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*"));
110         _film_sizer->Add (_content, 1, wxEXPAND);
111
112         add_label_to_sizer (_film_sizer, _film_panel, "Content Type");
113         _dcp_content_type = new wxComboBox (_film_panel, wxID_ANY);
114         _film_sizer->Add (_dcp_content_type);
115
116         video_control (add_label_to_sizer (_film_sizer, _film_panel, "Frames Per Second"));
117         _frames_per_second = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
118         _film_sizer->Add (video_control (_frames_per_second), 1, wxALIGN_CENTER_VERTICAL);
119         
120         video_control (add_label_to_sizer (_film_sizer, _film_panel, "Original Size"));
121         _original_size = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
122         _film_sizer->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL);
123         
124         video_control (add_label_to_sizer (_film_sizer, _film_panel, "Length"));
125         _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
126         _film_sizer->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL);
127
128
129         {
130                 video_control (add_label_to_sizer (_film_sizer, _film_panel, "Trim frames"));
131                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
132                 add_label_to_sizer (s, _film_panel, "Start");
133                 _dcp_trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
134                 s->Add (_dcp_trim_start);
135                 add_label_to_sizer (s, _film_panel, "End");
136                 _dcp_trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
137                 s->Add (_dcp_trim_end);
138
139                 _film_sizer->Add (s);
140         }
141
142         _dcp_ab = new wxCheckBox (_film_panel, wxID_ANY, wxT ("A/B"));
143         video_control (_dcp_ab);
144         _film_sizer->Add (_dcp_ab, 1);
145         _film_sizer->AddSpacer (0);
146
147         /* STILL-only stuff */
148         {
149                 still_control (add_label_to_sizer (_film_sizer, _film_panel, "Duration"));
150                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
151                 _still_duration = new wxSpinCtrl (_film_panel);
152                 still_control (_still_duration);
153                 s->Add (_still_duration, 1, wxEXPAND);
154                 still_control (add_label_to_sizer (s, _film_panel, "s"));
155                 _film_sizer->Add (s);
156         }
157
158         vector<DCPContentType const *> const ct = DCPContentType::all ();
159         for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
160                 _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
161         }
162 }
163
164 void
165 FilmEditor::connect_to_widgets ()
166 {
167         _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this);
168         _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
169         _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
170         _format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
171         _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
172         _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
173         _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
174         _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
175         _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
176         _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
177         _scaler->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
178         _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
179         _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
180         _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
181         _dcp_trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_start_changed), 0, this);
182         _dcp_trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::dcp_trim_end_changed), 0, this);
183         _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
184         _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
185         _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
186         _subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
187         _audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
188         _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
189         _audio_gain_calculate_button->Connect (
190                 wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
191                 );
192         _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
193 }
194
195 void
196 FilmEditor::make_video_panel ()
197 {
198         _video_panel = new wxPanel (_notebook);
199         _video_sizer = new wxFlexGridSizer (2, 4, 4);
200         _video_panel->SetSizer (_video_sizer);
201
202         add_label_to_sizer (_video_sizer, _video_panel, "Format");
203         _format = new wxComboBox (_video_panel, wxID_ANY);
204         _video_sizer->Add (_format);
205
206         {
207                 add_label_to_sizer (_video_sizer, _video_panel, "Crop");
208                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
209
210                 add_label_to_sizer (s, _video_panel, "L");
211                 _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
212                 s->Add (_left_crop, 0);
213                 add_label_to_sizer (s, _video_panel, "R");
214                 _right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
215                 s->Add (_right_crop, 0);
216                 add_label_to_sizer (s, _video_panel, "T");
217                 _top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
218                 s->Add (_top_crop, 0);
219                 add_label_to_sizer (s, _video_panel, "B");
220                 _bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
221                 s->Add (_bottom_crop, 0);
222
223                 _video_sizer->Add (s);
224         }
225
226         /* VIDEO-only stuff */
227         {
228                 video_control (add_label_to_sizer (_video_sizer, _video_panel, "Filters"));
229                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
230                 _filters = new wxStaticText (_video_panel, wxID_ANY, wxT ("None"));
231                 video_control (_filters);
232                 s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
233                 _filters_button = new wxButton (_video_panel, wxID_ANY, wxT ("Edit..."));
234                 video_control (_filters_button);
235                 s->Add (_filters_button, 0);
236                 _video_sizer->Add (s, 1);
237         }
238
239         video_control (add_label_to_sizer (_video_sizer, _video_panel, "Scaler"));
240         _scaler = new wxComboBox (_video_panel, wxID_ANY);
241         _video_sizer->Add (video_control (_scaler), 1);
242
243         vector<Scaler const *> const sc = Scaler::all ();
244         for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
245                 _scaler->Append (std_to_wx ((*i)->name()));
246         }
247
248         _left_crop->SetRange (0, 1024);
249         _top_crop->SetRange (0, 1024);
250         _right_crop->SetRange (0, 1024);
251         _bottom_crop->SetRange (0, 1024);
252         _still_duration->SetRange (0, 60 * 60);
253         _dcp_trim_start->SetRange (0, 100);
254         _dcp_trim_end->SetRange (0, 100);
255 }
256
257 void
258 FilmEditor::make_audio_panel ()
259 {
260         _audio_panel = new wxPanel (_notebook);
261         _audio_sizer = new wxFlexGridSizer (2, 4, 4);
262         _audio_panel->SetSizer (_audio_sizer);
263
264         {
265                 video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Stream"));
266                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
267                 _audio_stream = new wxComboBox (_audio_panel, wxID_ANY);
268                 s->Add (video_control (_audio_stream), 1);
269                 _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
270                 s->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
271                 _audio_sizer->Add (s, 1, wxEXPAND);
272         }
273
274         {
275                 video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Gain"));
276                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
277                 _audio_gain = new wxSpinCtrl (_audio_panel);
278                 s->Add (video_control (_audio_gain), 1);
279                 video_control (add_label_to_sizer (s, _audio_panel, "dB"));
280                 _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
281                 video_control (_audio_gain_calculate_button);
282                 s->Add (_audio_gain_calculate_button, 1, wxEXPAND);
283                 _audio_sizer->Add (s);
284         }
285
286         {
287                 video_control (add_label_to_sizer (_audio_sizer, _audio_panel, "Audio Delay"));
288                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
289                 _audio_delay = new wxSpinCtrl (_audio_panel);
290                 s->Add (video_control (_audio_delay), 1);
291                 video_control (add_label_to_sizer (s, _audio_panel, "ms"));
292                 _audio_sizer->Add (s);
293         }
294
295         _audio_gain->SetRange (-60, 60);
296         _audio_delay->SetRange (-1000, 1000);
297 }
298
299 void
300 FilmEditor::make_subtitle_panel ()
301 {
302         _subtitle_panel = new wxPanel (_notebook);
303         _subtitle_sizer = new wxFlexGridSizer (2, 4, 4);
304         _subtitle_panel->SetSizer (_subtitle_sizer);
305
306         _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, wxT("With Subtitles"));
307         video_control (_with_subtitles);
308         _subtitle_sizer->Add (_with_subtitles, 1);
309         
310         _subtitle_stream = new wxComboBox (_subtitle_panel, wxID_ANY);
311         _subtitle_sizer->Add (_subtitle_stream);
312
313         video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Offset"));
314         _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
315         _subtitle_sizer->Add (video_control (_subtitle_offset), 1);
316
317         {
318                 video_control (add_label_to_sizer (_subtitle_sizer, _subtitle_panel, "Subtitle Scale"));
319                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
320                 _subtitle_scale = new wxSpinCtrl (_subtitle_panel);
321                 s->Add (video_control (_subtitle_scale));
322                 video_control (add_label_to_sizer (s, _subtitle_panel, "%"));
323                 _subtitle_sizer->Add (s);
324         }
325
326         _subtitle_offset->SetRange (-1024, 1024);
327         _subtitle_scale->SetRange (1, 1000);
328 }
329
330 /** Called when the left crop widget has been changed */
331 void
332 FilmEditor::left_crop_changed (wxCommandEvent &)
333 {
334         if (!_film) {
335                 return;
336         }
337
338         _film->set_left_crop (_left_crop->GetValue ());
339 }
340
341 /** Called when the right crop widget has been changed */
342 void
343 FilmEditor::right_crop_changed (wxCommandEvent &)
344 {
345         if (!_film) {
346                 return;
347         }
348
349         _film->set_right_crop (_right_crop->GetValue ());
350 }
351
352 /** Called when the top crop widget has been changed */
353 void
354 FilmEditor::top_crop_changed (wxCommandEvent &)
355 {
356         if (!_film) {
357                 return;
358         }
359
360         _film->set_top_crop (_top_crop->GetValue ());
361 }
362
363 /** Called when the bottom crop value has been changed */
364 void
365 FilmEditor::bottom_crop_changed (wxCommandEvent &)
366 {
367         if (!_film) {
368                 return;
369         }
370
371         _film->set_bottom_crop (_bottom_crop->GetValue ());
372 }
373
374 /** Called when the content filename has been changed */
375 void
376 FilmEditor::content_changed (wxCommandEvent &)
377 {
378         if (!_film) {
379                 return;
380         }
381
382         try {
383                 _film->set_content (wx_to_std (_content->GetPath ()));
384         } catch (std::exception& e) {
385                 _content->SetPath (std_to_wx (_film->directory ()));
386                 error_dialog (this, String::compose ("Could not set content: %1", e.what ()));
387         }
388 }
389
390 /** Called when the DCP A/B switch has been toggled */
391 void
392 FilmEditor::dcp_ab_toggled (wxCommandEvent &)
393 {
394         if (!_film) {
395                 return;
396         }
397         
398         _film->set_dcp_ab (_dcp_ab->GetValue ());
399 }
400
401 /** Called when the name widget has been changed */
402 void
403 FilmEditor::name_changed (wxCommandEvent &)
404 {
405         if (!_film) {
406                 return;
407         }
408
409         _film->set_name (string (_name->GetValue().mb_str()));
410 }
411
412 void
413 FilmEditor::subtitle_offset_changed (wxCommandEvent &)
414 {
415         if (!_film) {
416                 return;
417         }
418
419         _film->set_subtitle_offset (_subtitle_offset->GetValue ());
420 }
421
422 void
423 FilmEditor::subtitle_scale_changed (wxCommandEvent &)
424 {
425         if (!_film) {
426                 return;
427         }
428
429         _film->set_subtitle_scale (_subtitle_scale->GetValue() / 100.0);
430 }
431
432
433 /** Called when the metadata stored in the Film object has changed;
434  *  so that we can update the GUI.
435  *  @param p Property of the Film that has changed.
436  */
437 void
438 FilmEditor::film_changed (Film::Property p)
439 {
440         ensure_ui_thread ();
441         
442         if (!_film) {
443                 return;
444         }
445
446         stringstream s;
447                 
448         switch (p) {
449         case Film::NONE:
450                 break;
451         case Film::CONTENT:
452                 checked_set (_content, _film->content ());
453                 setup_visibility ();
454                 setup_formats ();
455                 setup_subtitle_control_sensitivity ();
456                 setup_streams ();
457                 break;
458         case Film::HAS_SUBTITLES:
459                 setup_subtitle_control_sensitivity ();
460                 setup_streams ();
461                 break;
462         case Film::AUDIO_STREAMS:
463         case Film::SUBTITLE_STREAMS:
464                 setup_streams ();
465                 break;
466         case Film::FORMAT:
467         {
468                 int n = 0;
469                 vector<Format const *>::iterator i = _formats.begin ();
470                 while (i != _formats.end() && *i != _film->format ()) {
471                         ++i;
472                         ++n;
473                 }
474                 checked_set (_format, n);
475                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
476                 break;
477         }
478         case Film::CROP:
479                 checked_set (_left_crop, _film->crop().left);
480                 checked_set (_right_crop, _film->crop().right);
481                 checked_set (_top_crop, _film->crop().top);
482                 checked_set (_bottom_crop, _film->crop().bottom);
483                 break;
484         case Film::FILTERS:
485         {
486                 pair<string, string> p = Filter::ffmpeg_strings (_film->filters ());
487                 if (p.first.empty () && p.second.empty ()) {
488                         _filters->SetLabel (_("None"));
489                 } else {
490                         string const b = p.first + " " + p.second;
491                         _filters->SetLabel (std_to_wx (b));
492                 }
493                 _film_sizer->Layout ();
494                 break;
495         }
496         case Film::NAME:
497                 checked_set (_name, _film->name());
498                 _film->set_dci_date_today ();
499                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
500                 break;
501         case Film::FRAMES_PER_SECOND:
502                 s << fixed << setprecision(2) << _film->frames_per_second();
503                 _frames_per_second->SetLabel (std_to_wx (s.str ()));
504                 break;
505         case Film::AUDIO_SAMPLE_RATE:
506                 setup_audio_details ();
507                 break;
508         case Film::SIZE:
509                 if (_film->size().width == 0 && _film->size().height == 0) {
510                         _original_size->SetLabel (wxT (""));
511                 } else {
512                         s << _film->size().width << " x " << _film->size().height;
513                         _original_size->SetLabel (std_to_wx (s.str ()));
514                 }
515                 break;
516         case Film::LENGTH:
517                 if (_film->frames_per_second() > 0 && _film->length()) {
518                         s << _film->length().get() << " frames; " << seconds_to_hms (_film->length().get() / _film->frames_per_second());
519                 } else if (_film->length()) {
520                         s << _film->length().get() << " frames";
521                 } 
522                 _length->SetLabel (std_to_wx (s.str ()));
523                 if (_film->length()) {
524                         _dcp_trim_start->SetRange (0, _film->length().get());
525                         _dcp_trim_end->SetRange (0, _film->length().get());
526                 }
527                 break;
528         case Film::DCP_CONTENT_TYPE:
529                 checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
530                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
531                 break;
532         case Film::THUMBS:
533                 break;
534         case Film::DCP_AB:
535                 checked_set (_dcp_ab, _film->dcp_ab ());
536                 break;
537         case Film::SCALER:
538                 checked_set (_scaler, Scaler::as_index (_film->scaler ()));
539                 break;
540         case Film::DCP_TRIM_START:
541                 checked_set (_dcp_trim_start, _film->dcp_trim_start());
542                 break;
543         case Film::DCP_TRIM_END:
544                 checked_set (_dcp_trim_end, _film->dcp_trim_end());
545                 break;
546         case Film::AUDIO_GAIN:
547                 checked_set (_audio_gain, _film->audio_gain ());
548                 break;
549         case Film::AUDIO_DELAY:
550                 checked_set (_audio_delay, _film->audio_delay ());
551                 break;
552         case Film::STILL_DURATION:
553                 checked_set (_still_duration, _film->still_duration ());
554                 break;
555         case Film::WITH_SUBTITLES:
556                 checked_set (_with_subtitles, _film->with_subtitles ());
557                 _subtitle_stream->Enable (_film->with_subtitles ());
558                 _subtitle_scale->Enable (_film->with_subtitles ());
559                 _subtitle_offset->Enable (_film->with_subtitles ());
560                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
561                 break;
562         case Film::SUBTITLE_OFFSET:
563                 checked_set (_subtitle_offset, _film->subtitle_offset ());
564                 break;
565         case Film::SUBTITLE_SCALE:
566                 checked_set (_subtitle_scale, _film->subtitle_scale() * 100);
567                 break;
568         case Film::USE_DCI_NAME:
569                 checked_set (_use_dci_name, _film->use_dci_name ());
570                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
571                 break;
572         case Film::DCI_METADATA:
573                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
574                 break;
575         case Film::AUDIO_STREAM:
576                 checked_set (_audio_stream, _film->audio_stream_index ());
577                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
578                 setup_audio_details ();
579                 break;
580         case Film::SUBTITLE_STREAM:
581                 checked_set (_subtitle_stream, _film->subtitle_stream_index ());
582                 break;
583         }
584 }
585
586 /** Called when the format widget has been changed */
587 void
588 FilmEditor::format_changed (wxCommandEvent &)
589 {
590         if (!_film) {
591                 return;
592         }
593
594         int const n = _format->GetSelection ();
595         if (n >= 0) {
596                 assert (n < int (_formats.size()));
597                 _film->set_format (_formats[n]);
598         }
599 }
600
601 /** Called when the DCP content type widget has been changed */
602 void
603 FilmEditor::dcp_content_type_changed (wxCommandEvent &)
604 {
605         if (!_film) {
606                 return;
607         }
608
609         int const n = _dcp_content_type->GetSelection ();
610         if (n >= 0) {
611                 _film->set_dcp_content_type (DCPContentType::from_index (n));
612         }
613 }
614
615 /** Sets the Film that we are editing */
616 void
617 FilmEditor::set_film (shared_ptr<Film> f)
618 {
619         _film = f;
620
621         set_things_sensitive (_film != 0);
622
623         if (_film) {
624                 _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1));
625         }
626
627         if (_film) {
628                 FileChanged (_film->directory ());
629         } else {
630                 FileChanged ("");
631         }
632         
633         film_changed (Film::NAME);
634         film_changed (Film::CONTENT);
635         film_changed (Film::DCP_CONTENT_TYPE);
636         film_changed (Film::FORMAT);
637         film_changed (Film::CROP);
638         film_changed (Film::FILTERS);
639         film_changed (Film::DCP_TRIM_START);
640         film_changed (Film::DCP_TRIM_END);
641         film_changed (Film::DCP_AB);
642         film_changed (Film::SIZE);
643         film_changed (Film::LENGTH);
644         film_changed (Film::FRAMES_PER_SECOND);
645         film_changed (Film::AUDIO_SAMPLE_RATE);
646         film_changed (Film::SCALER);
647         film_changed (Film::AUDIO_GAIN);
648         film_changed (Film::AUDIO_DELAY);
649         film_changed (Film::STILL_DURATION);
650         film_changed (Film::WITH_SUBTITLES);
651         film_changed (Film::HAS_SUBTITLES);
652         film_changed (Film::SUBTITLE_OFFSET);
653         film_changed (Film::SUBTITLE_SCALE);
654         film_changed (Film::USE_DCI_NAME);
655         film_changed (Film::DCI_METADATA);
656 }
657
658 /** Updates the sensitivity of lots of widgets to a given value.
659  *  @param s true to make sensitive, false to make insensitive.
660  */
661 void
662 FilmEditor::set_things_sensitive (bool s)
663 {
664         _generally_sensitive = s;
665         
666         _name->Enable (s);
667         _use_dci_name->Enable (s);
668         _edit_dci_button->Enable (s);
669         _format->Enable (s);
670         _content->Enable (s);
671         _left_crop->Enable (s);
672         _right_crop->Enable (s);
673         _top_crop->Enable (s);
674         _bottom_crop->Enable (s);
675         _filters_button->Enable (s);
676         _scaler->Enable (s);
677         _audio_stream->Enable (s);
678         _dcp_content_type->Enable (s);
679         _dcp_trim_start->Enable (s);
680         _dcp_trim_end->Enable (s);
681         _dcp_ab->Enable (s);
682         _audio_gain->Enable (s);
683         _audio_gain_calculate_button->Enable (s);
684         _audio_delay->Enable (s);
685         _still_duration->Enable (s);
686
687         setup_subtitle_control_sensitivity ();
688 }
689
690 /** Called when the `Edit filters' button has been clicked */
691 void
692 FilmEditor::edit_filters_clicked (wxCommandEvent &)
693 {
694         FilterDialog* d = new FilterDialog (this, _film->filters());
695         d->ActiveChanged.connect (bind (&Film::set_filters, _film, _1));
696         d->ShowModal ();
697         d->Destroy ();
698 }
699
700 /** Called when the scaler widget has been changed */
701 void
702 FilmEditor::scaler_changed (wxCommandEvent &)
703 {
704         if (!_film) {
705                 return;
706         }
707
708         int const n = _scaler->GetSelection ();
709         if (n >= 0) {
710                 _film->set_scaler (Scaler::from_index (n));
711         }
712 }
713
714 void
715 FilmEditor::audio_gain_changed (wxCommandEvent &)
716 {
717         if (!_film) {
718                 return;
719         }
720
721         _film->set_audio_gain (_audio_gain->GetValue ());
722 }
723
724 void
725 FilmEditor::audio_delay_changed (wxCommandEvent &)
726 {
727         if (!_film) {
728                 return;
729         }
730
731         _film->set_audio_delay (_audio_delay->GetValue ());
732 }
733
734 wxControl *
735 FilmEditor::video_control (wxControl* c)
736 {
737         _video_controls.push_back (c);
738         return c;
739 }
740
741 wxControl *
742 FilmEditor::still_control (wxControl* c)
743 {
744         _still_controls.push_back (c);
745         return c;
746 }
747
748 void
749 FilmEditor::setup_visibility ()
750 {
751         ContentType c = VIDEO;
752
753         if (_film) {
754                 c = _film->content_type ();
755         }
756
757         for (list<wxControl*>::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) {
758                 (*i)->Show (c == VIDEO);
759         }
760
761         for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
762                 (*i)->Show (c == STILL);
763         }
764
765         _film_sizer->Layout ();
766 }
767
768 void
769 FilmEditor::still_duration_changed (wxCommandEvent &)
770 {
771         if (!_film) {
772                 return;
773         }
774
775         _film->set_still_duration (_still_duration->GetValue ());
776 }
777
778 void
779 FilmEditor::dcp_trim_start_changed (wxCommandEvent &)
780 {
781         if (!_film) {
782                 return;
783         }
784
785         _film->set_dcp_trim_start (_dcp_trim_start->GetValue ());
786 }
787
788 void
789 FilmEditor::dcp_trim_end_changed (wxCommandEvent &)
790 {
791         if (!_film) {
792                 return;
793         }
794
795         _film->set_dcp_trim_end (_dcp_trim_end->GetValue ());
796 }
797
798 void
799 FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
800 {
801         GainCalculatorDialog* d = new GainCalculatorDialog (this);
802         d->ShowModal ();
803
804         if (d->wanted_fader() == 0 || d->actual_fader() == 0) {
805                 d->Destroy ();
806                 return;
807         }
808         
809         _audio_gain->SetValue (
810                 Config::instance()->sound_processor()->db_for_fader_change (
811                         d->wanted_fader (),
812                         d->actual_fader ()
813                         )
814                 );
815
816         /* This appears to be necessary, as the change is not signalled,
817            I think.
818         */
819         wxCommandEvent dummy;
820         audio_gain_changed (dummy);
821         
822         d->Destroy ();
823 }
824
825 void
826 FilmEditor::setup_formats ()
827 {
828         ContentType c = VIDEO;
829
830         if (_film) {
831                 c = _film->content_type ();
832         }
833         
834         _formats.clear ();
835
836         vector<Format const *> fmt = Format::all ();
837         for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
838                 if (c == VIDEO && dynamic_cast<FixedFormat const *> (*i)) {
839                         _formats.push_back (*i);
840                 } else if (c == STILL && dynamic_cast<VariableFormat const *> (*i)) {
841                         _formats.push_back (*i);
842                 }
843         }
844
845         _format->Clear ();
846         for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
847                 _format->Append (std_to_wx ((*i)->name ()));
848         }
849
850         _film_sizer->Layout ();
851 }
852
853 void
854 FilmEditor::with_subtitles_toggled (wxCommandEvent &)
855 {
856         if (!_film) {
857                 return;
858         }
859
860         _film->set_with_subtitles (_with_subtitles->GetValue ());
861 }
862
863 void
864 FilmEditor::setup_subtitle_control_sensitivity ()
865 {
866         bool h = false;
867         if (_generally_sensitive && _film) {
868                 h = _film->has_subtitles();
869         }
870         
871         _with_subtitles->Enable (h);
872         _subtitle_stream->Enable (h);
873         _subtitle_offset->Enable (h);
874         _subtitle_scale->Enable (h);
875 }
876
877 void
878 FilmEditor::use_dci_name_toggled (wxCommandEvent &)
879 {
880         if (!_film) {
881                 return;
882         }
883
884         _film->set_use_dci_name (_use_dci_name->GetValue ());
885 }
886
887 void
888 FilmEditor::edit_dci_button_clicked (wxCommandEvent &)
889 {
890         if (!_film) {
891                 return;
892         }
893
894         DCINameDialog* d = new DCINameDialog (this, _film);
895         d->ShowModal ();
896         d->Destroy ();
897 }
898
899 void
900 FilmEditor::setup_streams ()
901 {
902         _audio_stream->Clear ();
903         vector<AudioStream> a = _film->audio_streams ();
904         for (vector<AudioStream>::iterator i = a.begin(); i != a.end(); ++i) {
905                 _audio_stream->Append (std_to_wx (i->name()));
906         }
907         _audio_stream->SetSelection (_film->audio_stream_index ());
908
909         _subtitle_stream->Clear ();
910         vector<SubtitleStream> s = _film->subtitle_streams ();
911         for (vector<SubtitleStream>::iterator i = s.begin(); i != s.end(); ++i) {
912                 _subtitle_stream->Append (std_to_wx (i->name()));
913         }
914         _subtitle_stream->SetSelection (_film->subtitle_stream_index ());
915 }
916
917 void
918 FilmEditor::audio_stream_changed (wxCommandEvent &)
919 {
920         if (!_film) {
921                 return;
922         }
923
924         _film->set_audio_stream (_audio_stream->GetSelection ());
925 }
926
927 void
928 FilmEditor::subtitle_stream_changed (wxCommandEvent &)
929 {
930         if (!_film) {
931                 return;
932         }
933
934         _film->set_subtitle_stream (_subtitle_stream->GetSelection ());
935 }
936
937 void
938 FilmEditor::setup_audio_details ()
939 {
940         if (_film->audio_channels() == 0 && _film->audio_sample_rate() == 0) {
941                 _audio->SetLabel (wxT (""));
942         } else {
943                 stringstream s;
944                 s << _film->audio_channels () << " channels, " << _film->audio_sample_rate() << "Hz";
945                 _audio->SetLabel (std_to_wx (s.str ()));
946         }
947 }
948
949 void
950 FilmEditor::active_jobs_changed (bool a)
951 {
952         set_things_sensitive (!a);
953 }