Fix crash on using delay; fix x-thread GUI access caused by FilmState default copy...
[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         ensure_ui_thread ();
405         
406         if (!_film || _ignore_changes == p) {
407                 return;
408         }
409
410         stringstream s;
411                 
412         switch (p) {
413         case FilmState::NONE:
414                 break;
415         case FilmState::CONTENT:
416                 _content->SetPath (std_to_wx (_film->content ()));
417                 setup_visibility ();
418                 setup_formats ();
419                 setup_subtitle_button ();
420                 setup_streams ();
421                 break;
422         case FilmState::HAS_SUBTITLES:
423                 setup_subtitle_button ();
424                 setup_streams ();
425                 break;
426         case FilmState::AUDIO_STREAMS:
427         case FilmState::SUBTITLE_STREAMS:
428                 setup_streams ();
429                 break;
430         case FilmState::FORMAT:
431         {
432                 int n = 0;
433                 vector<Format const *>::iterator i = _formats.begin ();
434                 while (i != _formats.end() && *i != _film->format ()) {
435                         ++i;
436                         ++n;
437                 }
438                 _format->SetSelection (n);
439                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
440                 break;
441         }
442         case FilmState::CROP:
443                 _left_crop->SetValue (_film->crop().left);
444                 _right_crop->SetValue (_film->crop().right);
445                 _top_crop->SetValue (_film->crop().top);
446                 _bottom_crop->SetValue (_film->crop().bottom);
447                 break;
448         case FilmState::FILTERS:
449         {
450                 pair<string, string> p = Filter::ffmpeg_strings (_film->filters ());
451                 if (p.first.empty () && p.second.empty ()) {
452                         _filters->SetLabel (_("None"));
453                 } else {
454                         string const b = p.first + " " + p.second;
455                         _filters->SetLabel (std_to_wx (b));
456                 }
457                 _sizer->Layout ();
458                 break;
459         }
460         case FilmState::NAME:
461                 _name->ChangeValue (std_to_wx (_film->name ()));
462                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
463                 break;
464         case FilmState::FRAMES_PER_SECOND:
465                 s << fixed << setprecision(2) << _film->frames_per_second();
466                 _frames_per_second->SetLabel (std_to_wx (s.str ()));
467                 break;
468         case FilmState::AUDIO_SAMPLE_RATE:
469                 setup_audio_details ();
470                 break;
471         case FilmState::SIZE:
472                 if (_film->size().width == 0 && _film->size().height == 0) {
473                         _original_size->SetLabel (wxT (""));
474                 } else {
475                         s << _film->size().width << " x " << _film->size().height;
476                         _original_size->SetLabel (std_to_wx (s.str ()));
477                 }
478                 break;
479         case FilmState::LENGTH:
480                 if (_film->frames_per_second() > 0 && _film->length() > 0) {
481                         s << _film->length() << " frames; " << seconds_to_hms (_film->length() / _film->frames_per_second());
482                 } else if (_film->length() > 0) {
483                         s << _film->length() << " frames";
484                 } 
485                 _length->SetLabel (std_to_wx (s.str ()));
486                 break;
487         case FilmState::DCP_CONTENT_TYPE:
488                 _dcp_content_type->SetSelection (DCPContentType::as_index (_film->dcp_content_type ()));
489                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
490                 break;
491         case FilmState::THUMBS:
492                 break;
493         case FilmState::DCP_FRAMES:
494                 if (_film->dcp_frames() == 0) {
495                         _dcp_range->SetLabel (wxT ("Whole film"));
496                 } else {
497                         stringstream s;
498                         s << "First " << _film->dcp_frames() << " frames";
499                         _dcp_range->SetLabel (std_to_wx (s.str ()));
500                 }
501                 _sizer->Layout ();
502                 break;
503         case FilmState::DCP_TRIM_ACTION:
504                 break;
505         case FilmState::DCP_AB:
506                 _dcp_ab->SetValue (_film->dcp_ab ());
507                 break;
508         case FilmState::SCALER:
509                 _scaler->SetSelection (Scaler::as_index (_film->scaler ()));
510                 break;
511         case FilmState::AUDIO_GAIN:
512                 _audio_gain->SetValue (_film->audio_gain ());
513                 break;
514         case FilmState::AUDIO_DELAY:
515                 _audio_delay->SetValue (_film->audio_delay ());
516                 break;
517         case FilmState::STILL_DURATION:
518                 _still_duration->SetValue (_film->still_duration ());
519                 break;
520         case FilmState::WITH_SUBTITLES:
521                 _with_subtitles->SetValue (_film->with_subtitles ());
522                 _subtitle_scale->Enable (_film->with_subtitles ());
523                 _subtitle_offset->Enable (_film->with_subtitles ());
524                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
525                 break;
526         case FilmState::SUBTITLE_OFFSET:
527                 _subtitle_offset->SetValue (_film->subtitle_offset ());
528                 break;
529         case FilmState::SUBTITLE_SCALE:
530                 _subtitle_scale->SetValue (_film->subtitle_scale() * 100);
531                 break;
532         case FilmState::USE_DCI_NAME:
533                 _use_dci_name->SetValue (_film->use_dci_name ());
534                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
535                 break;
536         case FilmState::DCI_METADATA:
537                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
538                 break;
539         case FilmState::AUDIO_STREAM:
540                 _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
541                 _audio_stream->SetSelection (_film->audio_stream_index ());
542                 setup_audio_details ();
543                 break;
544         case FilmState::SUBTITLE_STREAM:
545                 _subtitle_stream->SetSelection (_film->subtitle_stream_index ());
546                 break;
547         }
548 }
549
550 /** Called when the format widget has been changed */
551 void
552 FilmEditor::format_changed (wxCommandEvent &)
553 {
554         if (!_film) {
555                 return;
556         }
557
558         _ignore_changes = FilmState::FORMAT;
559         int const n = _format->GetSelection ();
560         if (n >= 0) {
561                 assert (n < int (_formats.size()));
562                 _film->set_format (_formats[n]);
563         }
564         _ignore_changes = FilmState::NONE;
565
566         _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
567 }
568
569 /** Called when the DCP content type widget has been changed */
570 void
571 FilmEditor::dcp_content_type_changed (wxCommandEvent &)
572 {
573         if (!_film) {
574                 return;
575         }
576
577         _ignore_changes = FilmState::DCP_CONTENT_TYPE;
578         int const n = _dcp_content_type->GetSelection ();
579         if (n >= 0) {
580                 _film->set_dcp_content_type (DCPContentType::from_index (n));
581         }
582         _ignore_changes = FilmState::NONE;
583
584         _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
585 }
586
587 /** Sets the Film that we are editing */
588 void
589 FilmEditor::set_film (Film* f)
590 {
591         _film = f;
592
593         set_things_sensitive (_film != 0);
594
595         if (_film) {
596                 _film->Changed.connect (sigc::mem_fun (*this, &FilmEditor::film_changed));
597         }
598
599         if (_film) {
600                 FileChanged (_film->directory ());
601         } else {
602                 FileChanged ("");
603         }
604         
605         film_changed (FilmState::NAME);
606         film_changed (FilmState::CONTENT);
607         film_changed (FilmState::DCP_CONTENT_TYPE);
608         film_changed (FilmState::FORMAT);
609         film_changed (FilmState::CROP);
610         film_changed (FilmState::FILTERS);
611         film_changed (FilmState::DCP_FRAMES);
612         film_changed (FilmState::DCP_TRIM_ACTION);
613         film_changed (FilmState::DCP_AB);
614         film_changed (FilmState::SIZE);
615         film_changed (FilmState::LENGTH);
616         film_changed (FilmState::FRAMES_PER_SECOND);
617         film_changed (FilmState::AUDIO_SAMPLE_RATE);
618         film_changed (FilmState::SCALER);
619         film_changed (FilmState::AUDIO_GAIN);
620         film_changed (FilmState::AUDIO_DELAY);
621         film_changed (FilmState::STILL_DURATION);
622         film_changed (FilmState::WITH_SUBTITLES);
623         film_changed (FilmState::SUBTITLE_OFFSET);
624         film_changed (FilmState::SUBTITLE_SCALE);
625         film_changed (FilmState::USE_DCI_NAME);
626         film_changed (FilmState::DCI_METADATA);
627 }
628
629 /** Updates the sensitivity of lots of widgets to a given value.
630  *  @param s true to make sensitive, false to make insensitive.
631  */
632 void
633 FilmEditor::set_things_sensitive (bool s)
634 {
635         _name->Enable (s);
636         _use_dci_name->Enable (s);
637         _edit_dci_button->Enable (s);
638         _frames_per_second->Enable (s);
639         _format->Enable (s);
640         _content->Enable (s);
641         _left_crop->Enable (s);
642         _right_crop->Enable (s);
643         _top_crop->Enable (s);
644         _bottom_crop->Enable (s);
645         _filters_button->Enable (s);
646         _scaler->Enable (s);
647         _dcp_content_type->Enable (s);
648         _dcp_range->Enable (s);
649         _change_dcp_range_button->Enable (s);
650         _dcp_ab->Enable (s);
651         _audio_gain->Enable (s);
652         _audio_gain_calculate_button->Enable (s);
653         _audio_delay->Enable (s);
654         _still_duration->Enable (s);
655         _with_subtitles->Enable (s);
656         _subtitle_offset->Enable (s);
657         _subtitle_scale->Enable (s);
658 }
659
660 /** Called when the `Edit filters' button has been clicked */
661 void
662 FilmEditor::edit_filters_clicked (wxCommandEvent &)
663 {
664         FilterDialog* d = new FilterDialog (this, _film->filters());
665         d->ActiveChanged.connect (sigc::mem_fun (*_film, &Film::set_filters));
666         d->ShowModal ();
667         d->Destroy ();
668 }
669
670 /** Called when the scaler widget has been changed */
671 void
672 FilmEditor::scaler_changed (wxCommandEvent &)
673 {
674         if (!_film) {
675                 return;
676         }
677
678         _ignore_changes = Film::SCALER;
679         int const n = _scaler->GetSelection ();
680         if (n >= 0) {
681                 _film->set_scaler (Scaler::from_index (n));
682         }
683         _ignore_changes = Film::NONE;
684 }
685
686 void
687 FilmEditor::audio_gain_changed (wxCommandEvent &)
688 {
689         if (!_film) {
690                 return;
691         }
692
693         _ignore_changes = Film::AUDIO_GAIN;
694         _film->set_audio_gain (_audio_gain->GetValue ());
695         _ignore_changes = Film::NONE;
696 }
697
698 void
699 FilmEditor::audio_delay_changed (wxCommandEvent &)
700 {
701         if (!_film) {
702                 return;
703         }
704
705         _ignore_changes = Film::AUDIO_DELAY;
706         _film->set_audio_delay (_audio_delay->GetValue ());
707         _ignore_changes = Film::NONE;
708 }
709
710 wxControl *
711 FilmEditor::video_control (wxControl* c)
712 {
713         _video_controls.push_back (c);
714         return c;
715 }
716
717 wxControl *
718 FilmEditor::still_control (wxControl* c)
719 {
720         _still_controls.push_back (c);
721         return c;
722 }
723
724 void
725 FilmEditor::setup_visibility ()
726 {
727         ContentType c = VIDEO;
728
729         if (_film) {
730                 c = _film->content_type ();
731         }
732
733         for (list<wxControl*>::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) {
734                 (*i)->Show (c == VIDEO);
735         }
736
737         for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
738                 (*i)->Show (c == STILL);
739         }
740
741         _sizer->Layout ();
742 }
743
744 void
745 FilmEditor::still_duration_changed (wxCommandEvent &)
746 {
747         if (!_film) {
748                 return;
749         }
750
751         _ignore_changes = Film::STILL_DURATION;
752         _film->set_still_duration (_still_duration->GetValue ());
753         _ignore_changes = Film::NONE;
754 }
755
756 void
757 FilmEditor::change_dcp_range_clicked (wxCommandEvent &)
758 {
759         DCPRangeDialog* d = new DCPRangeDialog (this, _film);
760         d->Changed.connect (sigc::mem_fun (*this, &FilmEditor::dcp_range_changed));
761         d->ShowModal ();
762 }
763
764 void
765 FilmEditor::dcp_range_changed (int frames, TrimAction action)
766 {
767         _film->set_dcp_frames (frames);
768         _film->set_dcp_trim_action (action);
769 }
770
771 void
772 FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
773 {
774         GainCalculatorDialog* d = new GainCalculatorDialog (this);
775         d->ShowModal ();
776
777         if (d->wanted_fader() == 0 || d->actual_fader() == 0) {
778                 d->Destroy ();
779                 return;
780         }
781         
782         _audio_gain->SetValue (
783                 Config::instance()->sound_processor()->db_for_fader_change (
784                         d->wanted_fader (),
785                         d->actual_fader ()
786                         )
787                 );
788
789         /* This appears to be necessary, as the change is not signalled,
790            I think.
791         */
792         wxCommandEvent dummy;
793         audio_gain_changed (dummy);
794         
795         d->Destroy ();
796 }
797
798 void
799 FilmEditor::setup_formats ()
800 {
801         ContentType c = VIDEO;
802
803         if (_film) {
804                 c = _film->content_type ();
805         }
806         
807         _formats.clear ();
808
809         vector<Format const *> fmt = Format::all ();
810         for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
811                 if (c == VIDEO && dynamic_cast<FixedFormat const *> (*i)) {
812                         _formats.push_back (*i);
813                 } else if (c == STILL && dynamic_cast<VariableFormat const *> (*i)) {
814                         _formats.push_back (*i);
815                 }
816         }
817
818         _format->Clear ();
819         for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
820                 _format->Append (std_to_wx ((*i)->name ()));
821         }
822
823         _sizer->Layout ();
824 }
825
826 void
827 FilmEditor::with_subtitles_toggled (wxCommandEvent &)
828 {
829         if (!_film) {
830                 return;
831         }
832
833         _ignore_changes = Film::WITH_SUBTITLES;
834         _film->set_with_subtitles (_with_subtitles->GetValue ());
835         _ignore_changes = Film::NONE;
836
837         _subtitle_scale->Enable (_film->with_subtitles ());
838         _subtitle_offset->Enable (_film->with_subtitles ());
839 }
840
841 void
842 FilmEditor::setup_subtitle_button ()
843 {
844         _with_subtitles->Enable (_film->has_subtitles ());
845         if (!_film->has_subtitles ()) {
846                 _with_subtitles->SetValue (false);
847         }
848 }
849
850 void
851 FilmEditor::use_dci_name_toggled (wxCommandEvent &)
852 {
853         if (!_film) {
854                 return;
855         }
856
857         _ignore_changes = Film::USE_DCI_NAME;
858         _film->set_use_dci_name (_use_dci_name->GetValue ());
859         _ignore_changes = Film::NONE;
860
861         _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
862 }
863
864 void
865 FilmEditor::edit_dci_button_clicked (wxCommandEvent &)
866 {
867         if (!_film) {
868                 return;
869         }
870
871         DCINameDialog* d = new DCINameDialog (this, _film);
872         d->ShowModal ();
873         d->Destroy ();
874 }
875
876 void
877 FilmEditor::setup_streams ()
878 {
879         _audio_stream->Clear ();
880         vector<AudioStream> a = _film->audio_streams ();
881         for (vector<AudioStream>::iterator i = a.begin(); i != a.end(); ++i) {
882                 _audio_stream->Append (std_to_wx (i->name()));
883         }
884         _audio_stream->SetSelection (_film->audio_stream_index ());
885
886         _subtitle_stream->Clear ();
887         vector<SubtitleStream> s = _film->subtitle_streams ();
888         for (vector<SubtitleStream>::iterator i = s.begin(); i != s.end(); ++i) {
889                 _subtitle_stream->Append (std_to_wx (i->name()));
890         }
891         _subtitle_stream->SetSelection (_film->subtitle_stream_index ());
892 }
893
894 void
895 FilmEditor::audio_stream_changed (wxCommandEvent &)
896 {
897         if (!_film) {
898                 return;
899         }
900
901         _ignore_changes = Film::AUDIO_STREAM;
902         _film->set_audio_stream (_audio_stream->GetSelection ());
903         _ignore_changes = Film::NONE;
904
905         _dcp_name->SetLabel (std_to_wx (_film->dcp_name ()));
906         setup_audio_details ();
907 }
908
909 void
910 FilmEditor::subtitle_stream_changed (wxCommandEvent &)
911 {
912         if (!_film) {
913                 return;
914         }
915
916         _ignore_changes = Film::SUBTITLE_STREAM;
917         _film->set_subtitle_stream (_subtitle_stream->GetSelection ());
918         _ignore_changes = Film::NONE;
919 }
920
921 void
922 FilmEditor::setup_audio_details ()
923 {
924         if (_film->audio_channels() == 0 && _film->audio_sample_rate() == 0) {
925                 _audio->SetLabel (wxT (""));
926         } else {
927                 stringstream s;
928                 s << _film->audio_channels () << " channels, " << _film->audio_sample_rate() << "Hz";
929                 _audio->SetLabel (std_to_wx (s.str ()));
930         }
931 }