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