Tweaks.
[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/thumbs_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 "dcp_range_dialog.h"
44
45 using namespace std;
46 using namespace boost;
47
48 /** @param f Film to edit */
49 FilmEditor::FilmEditor (Film* f, wxWindow* parent)
50         : wxPanel (parent)
51         , _ignore_changes (false)
52         , _film (f)
53 {
54         wxSizer* sizer = new wxFlexGridSizer (2, 6, 6);
55         this->SetSizer (sizer);
56
57         add_label_to_sizer (sizer, this, "Name");
58         _name = new wxTextCtrl (this, wxID_ANY);
59         sizer->Add (_name, 1, wxEXPAND);
60
61         add_label_to_sizer (sizer, this, "Content");
62         _content = new wxFilePickerCtrl (this, wxID_ANY, wxT (""), wxT ("Select Content File"), wxT("*.*"));
63         sizer->Add (_content, 1, wxEXPAND);
64
65         add_label_to_sizer (sizer, this, "Content Type");
66         _dcp_content_type = new wxComboBox (this, wxID_ANY);
67         sizer->Add (_dcp_content_type);
68
69         add_label_to_sizer (sizer, this, "Frames Per Second");
70         _frames_per_second = new wxTextCtrl (this, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator (wxFILTER_NUMERIC));
71         sizer->Add (video_control (_frames_per_second));
72
73         add_label_to_sizer (sizer, this, "Format");
74         _format = new wxComboBox (this, wxID_ANY);
75         sizer->Add (_format);
76
77         {
78                 add_label_to_sizer (sizer, this, "Crop");
79                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
80                 wxPanel* p = new wxPanel (this);
81                 p->SetSizer (s);
82
83                 add_label_to_sizer (s, p, "L");
84                 _left_crop = new wxSpinCtrl (p, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
85                 s->Add (_left_crop, 0);
86                 add_label_to_sizer (s, p, "R");
87                 _right_crop = new wxSpinCtrl (p, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
88                 s->Add (_right_crop, 0);
89                 add_label_to_sizer (s, p, "T");
90                 _top_crop = new wxSpinCtrl (p, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
91                 s->Add (_top_crop, 0);
92                 add_label_to_sizer (s, p, "B");
93                 _bottom_crop = new wxSpinCtrl (p, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
94                 s->Add (_bottom_crop, 0);
95
96                 sizer->Add (p);
97         }
98
99         /* VIDEO-only stuff */
100         {
101                 video_control (add_label_to_sizer (sizer, this, "Filters"));
102                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
103                 wxPanel* p = new wxPanel (this);
104                 p->SetSizer (s);
105                 _filters = new wxStaticText (p, wxID_ANY, wxT (""));
106                 s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
107                 _filters_button = new wxButton (p, wxID_ANY, wxT ("Edit..."));
108                 s->Add (_filters_button, 0);
109                 sizer->Add (p, 1);
110         }
111
112         video_control (add_label_to_sizer (sizer, this, "Scaler"));
113         _scaler = new wxComboBox (this, wxID_ANY);
114         sizer->Add (video_control (_scaler), 1);
115
116         {
117                 video_control (add_label_to_sizer (sizer, this, "Audio Gain"));
118                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
119                 wxPanel* p = new wxPanel (this);
120                 p->SetSizer (s);
121                 _audio_gain = new wxSpinCtrl (p);
122                 s->Add (video_control (_audio_gain), 1);
123                 video_control (add_label_to_sizer (s, p, "dB"));
124                 sizer->Add (p);
125         }
126
127         {
128                 video_control (add_label_to_sizer (sizer, this, "Audio Delay"));
129                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
130                 wxPanel* p = new wxPanel (this);
131                 p->SetSizer (s);
132                 _audio_delay = new wxSpinCtrl (p);
133                 s->Add (video_control (_audio_delay), 1);
134                 video_control (add_label_to_sizer (s, p, "ms"));
135                 sizer->Add (p);
136         }
137
138         video_control (add_label_to_sizer (sizer, this, "Original Size"));
139         _original_size = new wxStaticText (this, wxID_ANY, wxT (""));
140         sizer->Add (video_control (_original_size), 1, wxALIGN_CENTER_VERTICAL);
141         
142         video_control (add_label_to_sizer (sizer, this, "Length"));
143         _length = new wxStaticText (this, wxID_ANY, wxT (""));
144         sizer->Add (video_control (_length), 1, wxALIGN_CENTER_VERTICAL);
145
146         video_control (add_label_to_sizer (sizer, this, "Audio"));
147         _audio = new wxStaticText (this, wxID_ANY, wxT (""));
148         sizer->Add (video_control (_audio), 1, wxALIGN_CENTER_VERTICAL);
149
150         {
151                 video_control (add_label_to_sizer (sizer, this, "Range"));
152                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
153                 wxPanel* p = new wxPanel (this);
154                 p->SetSizer (s);
155                 _dcp_range = new wxStaticText (p, wxID_ANY, wxT (""));
156                 s->Add (video_control (_dcp_range), 1, wxALIGN_CENTER_VERTICAL);
157                 _change_dcp_range_button = new wxButton (p, wxID_ANY, wxT ("Edit..."));
158                 s->Add (video_control (_change_dcp_range_button));
159                 sizer->Add (p);
160         }
161
162         _dcp_ab = new wxCheckBox (this, wxID_ANY, wxT ("A/B"));
163         sizer->Add (_dcp_ab, 1);
164         sizer->AddSpacer (0);
165
166         /* STILL-only stuff */
167         still_control (add_label_to_sizer (sizer, this, "Duration"));
168         _still_duration = new wxSpinCtrl (this);
169         sizer->Add (still_control (_still_duration));
170         still_control (add_label_to_sizer (sizer, this, "s"));
171
172         /* Set up our editing widgets */
173         
174         _left_crop->SetRange (0, 1024);
175         _top_crop->SetRange (0, 1024);
176         _right_crop->SetRange (0, 1024);
177         _bottom_crop->SetRange (0, 1024);
178         _audio_gain->SetRange (-60, 60);
179         _audio_delay->SetRange (-1000, 1000);
180         _still_duration->SetRange (0, 60 * 60);
181
182         vector<Format const *> fmt = Format::all ();
183         for (vector<Format const *>::iterator i = fmt.begin(); i != fmt.end(); ++i) {
184                 _format->Append (std_to_wx ((*i)->name ()));
185         }
186
187         vector<DCPContentType const *> const ct = DCPContentType::all ();
188         for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
189                 _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
190         }
191
192         vector<Scaler const *> const sc = Scaler::all ();
193         for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
194                 _scaler->Append (std_to_wx ((*i)->name()));
195         }
196
197         /* And set their values from the Film */
198         set_film (f);
199         
200         /* Now connect to them, since initial values are safely set */
201         _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this);
202         _frames_per_second->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::frames_per_second_changed), 0, this);
203         _format->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
204         _content->Connect (wxID_ANY, wxEVT_COMMAND_FILEPICKER_CHANGED, wxCommandEventHandler (FilmEditor::content_changed), 0, this);
205         _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
206         _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
207         _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
208         _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
209         _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
210         _scaler->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
211         _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
212         _dcp_ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::dcp_ab_toggled), 0, this);
213         _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
214         _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
215         _still_duration->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::still_duration_changed), 0, this);
216         _change_dcp_range_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::change_dcp_range_clicked), 0, this);
217
218         setup_visibility ();
219 }
220
221 /** Called when the left crop widget has been changed */
222 void
223 FilmEditor::left_crop_changed (wxCommandEvent &)
224 {
225         if (!_film) {
226                 return;
227         }
228
229         _ignore_changes = true;
230         _film->set_left_crop (_left_crop->GetValue ());
231         _ignore_changes = false;
232 }
233
234 /** Called when the right crop widget has been changed */
235 void
236 FilmEditor::right_crop_changed (wxCommandEvent &)
237 {
238         if (!_film) {
239                 return;
240         }
241
242         _ignore_changes = true;
243         _film->set_right_crop (_right_crop->GetValue ());
244         _ignore_changes = false;
245 }
246
247 /** Called when the top crop widget has been changed */
248 void
249 FilmEditor::top_crop_changed (wxCommandEvent &)
250 {
251         if (!_film) {
252                 return;
253         }
254
255         _ignore_changes = true;
256         _film->set_top_crop (_top_crop->GetValue ());
257         _ignore_changes = false;
258 }
259
260 /** Called when the bottom crop value has been changed */
261 void
262 FilmEditor::bottom_crop_changed (wxCommandEvent &)
263 {
264         if (!_film) {
265                 return;
266         }
267
268         _ignore_changes = true;
269         _film->set_bottom_crop (_bottom_crop->GetValue ());
270         _ignore_changes = false;
271 }
272
273 /** Called when the content filename has been changed */
274 void
275 FilmEditor::content_changed (wxCommandEvent &)
276 {
277         if (!_film) {
278                 return;
279         }
280
281         _ignore_changes = true;
282         
283         try {
284                 _film->set_content (wx_to_std (_content->GetPath ()));
285         } catch (std::exception& e) {
286                 _content->SetPath (std_to_wx (_film->directory ()));
287                 stringstream m;
288                 m << "Could not set content: " << e.what() << ".";
289                 wxMessageDialog* d = new wxMessageDialog (this, std_to_wx (m.str ()), wxT ("DVD-o-matic"), wxOK);
290                 d->ShowModal ();
291                 d->Destroy ();
292         }
293
294         _ignore_changes = false;
295 }
296
297 /** Called when the DCP A/B switch has been toggled */
298 void
299 FilmEditor::dcp_ab_toggled (wxCommandEvent &)
300 {
301         if (!_film) {
302                 return;
303         }
304         
305         _ignore_changes = true;
306         _film->set_dcp_ab (_dcp_ab->GetValue ());
307         _ignore_changes = false;
308 }
309
310 /** Called when the name widget has been changed */
311 void
312 FilmEditor::name_changed (wxCommandEvent &)
313 {
314         if (!_film) {
315                 return;
316         }
317
318         _ignore_changes = true;
319         _film->set_name (string (_name->GetValue().mb_str()));
320         _ignore_changes = false;
321 }
322
323 /** Called when the metadata stored in the Film object has changed;
324  *  so that we can update the GUI.
325  *  @param p Property of the Film that has changed.
326  */
327 void
328 FilmEditor::film_changed (Film::Property p)
329 {
330         if (!_film || _ignore_changes) {
331                 return;
332         }
333
334         stringstream s;
335                 
336         switch (p) {
337         case Film::CONTENT:
338                 _content->SetPath (std_to_wx (_film->content ()));
339                 setup_visibility ();
340                 break;
341         case Film::FORMAT:
342                 _format->SetSelection (Format::as_index (_film->format ()));
343                 break;
344         case Film::LEFT_CROP:
345                 _left_crop->SetValue (_film->left_crop ());
346                 break;
347         case Film::RIGHT_CROP:
348                 _right_crop->SetValue (_film->right_crop ());
349                 break;
350         case Film::TOP_CROP:
351                 _top_crop->SetValue (_film->top_crop ());
352                 break;
353         case Film::BOTTOM_CROP:
354                 _bottom_crop->SetValue (_film->bottom_crop ());
355                 break;
356         case Film::FILTERS:
357         {
358                 pair<string, string> p = Filter::ffmpeg_strings (_film->filters ());
359                 string const b = p.first + " " + p.second;
360                 _filters->SetLabel (std_to_wx (b));
361                 break;
362         }
363         case Film::NAME:
364                 _name->ChangeValue (std_to_wx (_film->name ()));
365                 break;
366         case Film::FRAMES_PER_SECOND:
367         {
368                 stringstream s;
369                 s << fixed << setprecision(2) << _film->frames_per_second();
370                 _frames_per_second->ChangeValue (std_to_wx (s.str ()));
371                 break;
372         }
373         case Film::AUDIO_CHANNELS:
374         case Film::AUDIO_SAMPLE_RATE:
375                 if (_film->audio_channels() == 0 && _film->audio_sample_rate() == 0) {
376                         _audio->SetLabel (wxT (""));
377                 } else {
378                         s << _film->audio_channels () << " channels, " << _film->audio_sample_rate() << "Hz";
379                         _audio->SetLabel (std_to_wx (s.str ()));
380                 }
381                 break;
382         case Film::SIZE:
383                 if (_film->size().width == 0 && _film->size().height == 0) {
384                         _original_size->SetLabel (wxT (""));
385                 } else {
386                         s << _film->size().width << " x " << _film->size().height;
387                         _original_size->SetLabel (std_to_wx (s.str ()));
388                 }
389                 break;
390         case Film::LENGTH:
391                 if (_film->frames_per_second() > 0 && _film->length() > 0) {
392                         s << _film->length() << " frames; " << seconds_to_hms (_film->length() / _film->frames_per_second());
393                 } else if (_film->length() > 0) {
394                         s << _film->length() << " frames";
395                 } 
396                 _length->SetLabel (std_to_wx (s.str ()));
397                 break;
398         case Film::DCP_CONTENT_TYPE:
399                 _dcp_content_type->SetSelection (DCPContentType::as_index (_film->dcp_content_type ()));
400                 break;
401         case Film::THUMBS:
402                 break;
403         case Film::DCP_FRAMES:
404                 if (_film->dcp_frames() == 0) {
405                         _dcp_range->SetLabel (wxT ("Whole film"));
406                 } else {
407                         stringstream s;
408                         s << "First " << _film->dcp_frames() << " frames";
409                         _dcp_range->SetLabel (std_to_wx (s.str ()));
410                 }
411                 break;
412         case Film::DCP_TRIM_ACTION:
413                 break;
414         case Film::DCP_AB:
415                 _dcp_ab->SetValue (_film->dcp_ab ());
416                 break;
417         case Film::SCALER:
418                 _scaler->SetSelection (Scaler::as_index (_film->scaler ()));
419                 break;
420         case Film::AUDIO_GAIN:
421                 _audio_gain->SetValue (_film->audio_gain ());
422                 break;
423         case Film::AUDIO_DELAY:
424                 _audio_delay->SetValue (_film->audio_delay ());
425                 break;
426         case Film::STILL_DURATION:
427                 _still_duration->SetValue (_film->still_duration ());
428                 break;
429         }
430 }
431
432 /** Called when the format widget has been changed */
433 void
434 FilmEditor::format_changed (wxCommandEvent &)
435 {
436         if (!_film) {
437                 return;
438         }
439
440         _ignore_changes = true;
441         int const n = _format->GetSelection ();
442         if (n >= 0) {
443                 _film->set_format (Format::from_index (n));
444         }
445         _ignore_changes = false;
446 }
447
448 /** Called when the DCP content type widget has been changed */
449 void
450 FilmEditor::dcp_content_type_changed (wxCommandEvent &)
451 {
452         if (!_film) {
453                 return;
454         }
455
456         _ignore_changes = true;
457         int const n = _dcp_content_type->GetSelection ();
458         if (n >= 0) {
459                 _film->set_dcp_content_type (DCPContentType::from_index (n));
460         }
461         _ignore_changes = false;
462 }
463
464 /** Sets the Film that we are editing */
465 void
466 FilmEditor::set_film (Film* f)
467 {
468         _film = f;
469
470         set_things_sensitive (_film != 0);
471
472         if (_film) {
473                 _film->Changed.connect (sigc::mem_fun (*this, &FilmEditor::film_changed));
474         }
475
476         if (_film) {
477                 FileChanged (_film->directory ());
478         } else {
479                 FileChanged ("");
480         }
481         
482         film_changed (Film::NAME);
483         film_changed (Film::CONTENT);
484         film_changed (Film::DCP_CONTENT_TYPE);
485         film_changed (Film::FORMAT);
486         film_changed (Film::LEFT_CROP);
487         film_changed (Film::RIGHT_CROP);
488         film_changed (Film::TOP_CROP);
489         film_changed (Film::BOTTOM_CROP);
490         film_changed (Film::FILTERS);
491         film_changed (Film::DCP_FRAMES);
492         film_changed (Film::DCP_TRIM_ACTION);
493         film_changed (Film::DCP_AB);
494         film_changed (Film::SIZE);
495         film_changed (Film::LENGTH);
496         film_changed (Film::FRAMES_PER_SECOND);
497         film_changed (Film::AUDIO_CHANNELS);
498         film_changed (Film::AUDIO_SAMPLE_RATE);
499         film_changed (Film::SCALER);
500         film_changed (Film::AUDIO_GAIN);
501         film_changed (Film::AUDIO_DELAY);
502         film_changed (Film::STILL_DURATION);
503 }
504
505 /** Updates the sensitivity of lots of widgets to a given value.
506  *  @param s true to make sensitive, false to make insensitive.
507  */
508 void
509 FilmEditor::set_things_sensitive (bool s)
510 {
511         _name->Enable (s);
512         _frames_per_second->Enable (s);
513         _format->Enable (s);
514         _content->Enable (s);
515         _left_crop->Enable (s);
516         _right_crop->Enable (s);
517         _top_crop->Enable (s);
518         _bottom_crop->Enable (s);
519         _filters_button->Enable (s);
520         _scaler->Enable (s);
521         _dcp_content_type->Enable (s);
522         _dcp_range->Enable (s);
523         _change_dcp_range_button->Enable (s);
524         _dcp_ab->Enable (s);
525         _audio_gain->Enable (s);
526         _audio_delay->Enable (s);
527         _still_duration->Enable (s);
528 }
529
530 /** Called when the `Edit filters' button has been clicked */
531 void
532 FilmEditor::edit_filters_clicked (wxCommandEvent &)
533 {
534 //      FilterDialog d (_film->filters ());
535 //      d.ActiveChanged.connect (sigc::mem_fun (*_film, &Film::set_filters));
536 //      d.run ();
537 }
538
539 /** Called when the scaler widget has been changed */
540 void
541 FilmEditor::scaler_changed (wxCommandEvent &)
542 {
543         if (!_film) {
544                 return;
545         }
546
547         _ignore_changes = true;
548         int const n = _scaler->GetSelection ();
549         if (n >= 0) {
550                 _film->set_scaler (Scaler::from_index (n));
551         }
552         _ignore_changes = false;
553 }
554
555 /** Called when the frames per second widget has been changed */
556 void
557 FilmEditor::frames_per_second_changed (wxCommandEvent &)
558 {
559         if (!_film) {
560                 return;
561         }
562
563         _ignore_changes = true;
564         _film->set_frames_per_second (boost::lexical_cast<float> (wx_to_std (_frames_per_second->GetValue ())));
565         _ignore_changes = false;
566 }
567
568 void
569 FilmEditor::audio_gain_changed (wxCommandEvent &)
570 {
571         if (!_film) {
572                 return;
573         }
574
575         _ignore_changes = true;
576         _film->set_audio_gain (_audio_gain->GetValue ());
577         _ignore_changes = false;
578 }
579
580 void
581 FilmEditor::audio_delay_changed (wxCommandEvent &)
582 {
583         if (!_film) {
584                 return;
585         }
586
587         _ignore_changes = true;
588         _film->set_audio_delay (_audio_delay->GetValue ());
589         _ignore_changes = false;
590 }
591
592 wxControl *
593 FilmEditor::video_control (wxControl* c)
594 {
595         _video_controls.push_back (c);
596         return c;
597 }
598
599 wxControl *
600 FilmEditor::still_control (wxControl* c)
601 {
602         _still_controls.push_back (c);
603         return c;
604 }
605
606 void
607 FilmEditor::setup_visibility ()
608 {
609         ContentType c = VIDEO;
610
611         if (_film) {
612                 c = _film->content_type ();
613         }
614
615         for (list<wxControl*>::iterator i = _video_controls.begin(); i != _video_controls.end(); ++i) {
616                 (*i)->Show (c == VIDEO);
617         }
618
619         for (list<wxControl*>::iterator i = _still_controls.begin(); i != _still_controls.end(); ++i) {
620                 (*i)->Show (c == STILL);
621         }
622 }
623
624 void
625 FilmEditor::still_duration_changed (wxCommandEvent &)
626 {
627         if (!_film) {
628                 return;
629         }
630
631         _ignore_changes = true;
632         _film->set_still_duration (_still_duration->GetValue ());
633         _ignore_changes = false;
634 }
635
636 void
637 FilmEditor::change_dcp_range_clicked (wxCommandEvent &)
638 {
639 //XXX   DCPRangeDialog d (_film);
640 //XXX   d.Changed.connect (sigc::mem_fun (*this, &FilmEditor::dcp_range_changed));
641 //XXX   d.run ();
642 }
643
644 void
645 FilmEditor::dcp_range_changed (int frames, TrimAction action)
646 {
647         _ignore_changes = true;
648         _film->set_dcp_frames (frames);
649         _film->set_dcp_trim_action (action);
650         _ignore_changes = false;
651 }