efbf212d037bbbc6dd08a445319d1f66a2c97d0e
[dcpomatic.git] / src / wx / film_editor.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 /** @file src/film_editor.cc
21  *  @brief A wx widget to edit a film's metadata, and perform various functions.
22  */
23
24 #include <iostream>
25 #include <iomanip>
26 #include <wx/wx.h>
27 #include <wx/notebook.h>
28 #include <wx/listctrl.h>
29 #include <boost/thread.hpp>
30 #include <boost/filesystem.hpp>
31 #include <boost/lexical_cast.hpp>
32 #include "lib/format.h"
33 #include "lib/film.h"
34 #include "lib/transcode_job.h"
35 #include "lib/exceptions.h"
36 #include "lib/ab_transcode_job.h"
37 #include "lib/job_manager.h"
38 #include "lib/filter.h"
39 #include "lib/config.h"
40 #include "lib/ffmpeg_decoder.h"
41 #include "lib/imagemagick_content.h"
42 #include "lib/sndfile_content.h"
43 #include "lib/dcp_content_type.h"
44 #include "filter_dialog.h"
45 #include "wx_util.h"
46 #include "film_editor.h"
47 #include "gain_calculator_dialog.h"
48 #include "sound_processor.h"
49 #include "dci_metadata_dialog.h"
50 #include "scaler.h"
51 #include "audio_dialog.h"
52
53 using std::string;
54 using std::cout;
55 using std::stringstream;
56 using std::pair;
57 using std::fixed;
58 using std::setprecision;
59 using std::list;
60 using std::vector;
61 using boost::shared_ptr;
62 using boost::dynamic_pointer_cast;
63 using boost::lexical_cast;
64
65 /** @param f Film to edit */
66 FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
67         : wxPanel (parent)
68         , _film (f)
69         , _generally_sensitive (true)
70         , _audio_dialog (0)
71 {
72         wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
73         _notebook = new wxNotebook (this, wxID_ANY);
74         s->Add (_notebook, 1);
75
76         make_film_panel ();
77         _notebook->AddPage (_film_panel, _("Film"), true);
78         make_content_panel ();
79         _notebook->AddPage (_content_panel, _("Content"), false);
80         make_video_panel ();
81         _notebook->AddPage (_video_panel, _("Video"), false);
82         make_audio_panel ();
83         _notebook->AddPage (_audio_panel, _("Audio"), false);
84         make_subtitle_panel ();
85         _notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
86
87         set_film (_film);
88         connect_to_widgets ();
89
90         JobManager::instance()->ActiveJobsChanged.connect (
91                 bind (&FilmEditor::active_jobs_changed, this, _1)
92                 );
93         
94         setup_formats ();
95
96         SetSizerAndFit (s);
97 }
98
99 void
100 FilmEditor::make_film_panel ()
101 {
102         _film_panel = new wxPanel (_notebook);
103         _film_sizer = new wxBoxSizer (wxVERTICAL);
104         _film_panel->SetSizer (_film_sizer);
105
106         wxGridBagSizer* grid = new wxGridBagSizer (4, 4);
107         _film_sizer->Add (grid, 0, wxALL, 8);
108
109         int r = 0;
110         
111         add_label_to_grid_bag_sizer (grid, _film_panel, _("Name"), wxGBPosition (r, 0));
112         _name = new wxTextCtrl (_film_panel, wxID_ANY);
113         grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
114         ++r;
115         
116         add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Name"), wxGBPosition (r, 0));
117         _dcp_name = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
118         grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
119         ++r;
120
121         _use_dci_name = new wxCheckBox (_film_panel, wxID_ANY, _("Use DCI name"));
122         grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
123         _edit_dci_button = new wxButton (_film_panel, wxID_ANY, _("Details..."));
124         grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan);
125         ++r;
126
127         _trust_content_headers = new wxCheckBox (_film_panel, wxID_ANY, _("Trust content's header"));
128         grid->Add (_trust_content_headers, wxGBPosition (r, 0), wxGBSpan(1, 2));
129         ++r;
130
131         add_label_to_grid_bag_sizer (grid, _film_panel, _("Content Type"), wxGBPosition (r, 0));
132         _dcp_content_type = new wxChoice (_film_panel, wxID_ANY);
133         grid->Add (_dcp_content_type, wxGBPosition (r, 1));
134         ++r;
135
136         {
137                 add_label_to_grid_bag_sizer (grid, _film_panel, _("DCP Frame Rate"), wxGBPosition (r, 0));
138                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
139                 _dcp_frame_rate = new wxChoice (_film_panel, wxID_ANY);
140                 s->Add (_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL);
141                 _best_dcp_frame_rate = new wxButton (_film_panel, wxID_ANY, _("Use best"));
142                 s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 6);
143                 grid->Add (s, wxGBPosition (r, 1));
144         }
145         ++r;
146
147         _frame_rate_description = new wxStaticText (_film_panel, wxID_ANY, wxT (" \n "), wxDefaultPosition, wxDefaultSize);
148         grid->Add (_frame_rate_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
149         wxFont font = _frame_rate_description->GetFont();
150         font.SetStyle(wxFONTSTYLE_ITALIC);
151         font.SetPointSize(font.GetPointSize() - 1);
152         _frame_rate_description->SetFont(font);
153         ++r;
154         
155         add_label_to_grid_bag_sizer (grid, _film_panel, _("Length"), wxGBPosition (r, 0));
156         _length = new wxStaticText (_film_panel, wxID_ANY, wxT (""));
157         grid->Add (_length, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
158         ++r;
159
160
161         {
162                 add_label_to_grid_bag_sizer (grid, _film_panel, _("Trim frames"), wxGBPosition (r, 0));
163                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
164                 add_label_to_sizer (s, _film_panel, _("Start"));
165                 _trim_start = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
166                 s->Add (_trim_start);
167                 add_label_to_sizer (s, _film_panel, _("End"));
168                 _trim_end = new wxSpinCtrl (_film_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
169                 s->Add (_trim_end);
170
171                 grid->Add (s, wxGBPosition (r, 1));
172         }
173         ++r;
174
175         _ab = new wxCheckBox (_film_panel, wxID_ANY, _("A/B"));
176         grid->Add (_ab, wxGBPosition (r, 0));
177         ++r;
178
179         vector<DCPContentType const *> const ct = DCPContentType::all ();
180         for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
181                 _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
182         }
183
184         list<int> const dfr = Config::instance()->allowed_dcp_frame_rates ();
185         for (list<int>::const_iterator i = dfr.begin(); i != dfr.end(); ++i) {
186                 _dcp_frame_rate->Append (std_to_wx (boost::lexical_cast<string> (*i)));
187         }
188 }
189
190 void
191 FilmEditor::connect_to_widgets ()
192 {
193         _name->Connect (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler (FilmEditor::name_changed), 0, this);
194         _use_dci_name->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
195         _edit_dci_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
196         _format->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::format_changed), 0, this);
197         _trust_content_headers->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::trust_content_headers_changed), 0, this);
198         _content->Connect (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler (FilmEditor::content_item_selected), 0, this);
199         _content_add->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this);
200         _content_remove->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this);
201         _content_earlier->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_earlier_clicked), 0, this);
202         _content_later->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::content_later_clicked), 0, this);
203         _left_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
204         _right_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::right_crop_changed), 0, this);
205         _top_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::top_crop_changed), 0, this);
206         _bottom_crop->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::bottom_crop_changed), 0, this);
207         _filters_button->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::edit_filters_clicked), 0, this);
208         _scaler->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::scaler_changed), 0, this);
209         _dcp_content_type->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_content_type_changed), 0, this);
210         _dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::dcp_frame_rate_changed), 0, this);
211         _best_dcp_frame_rate->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::best_dcp_frame_rate_clicked), 0, this);
212         _ab->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::ab_toggled), 0, this);
213         _trim_start->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_start_changed), 0, this);
214         _trim_end->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::trim_end_changed), 0, this);
215         _with_subtitles->Connect (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
216         _subtitle_offset->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
217         _subtitle_scale->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
218         _colour_lut->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
219         _j2k_bandwidth->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
220         _ffmpeg_subtitle_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ffmpeg_subtitle_stream_changed), 0, this);
221         _ffmpeg_audio_stream->Connect (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler (FilmEditor::ffmpeg_audio_stream_changed), 0, this);
222         _audio_gain->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
223         _audio_gain_calculate_button->Connect (
224                 wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
225                 );
226         _show_audio->Connect (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
227         _audio_delay->Connect (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED, wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
228 }
229
230 void
231 FilmEditor::make_video_panel ()
232 {
233         _video_panel = new wxPanel (_notebook);
234         _video_sizer = new wxBoxSizer (wxVERTICAL);
235         _video_panel->SetSizer (_video_sizer);
236         
237         wxGridBagSizer* grid = new wxGridBagSizer (4, 4);
238         _video_sizer->Add (grid, 0, wxALL, 8);
239
240         int r = 0;
241         add_label_to_grid_bag_sizer (grid, _video_panel, _("Format"), wxGBPosition (r, 0));
242         _format = new wxChoice (_video_panel, wxID_ANY);
243         grid->Add (_format, wxGBPosition (r, 1));
244         ++r;
245
246         _format_description = new wxStaticText (_video_panel, wxID_ANY, wxT (""), wxDefaultPosition, wxDefaultSize);
247         grid->Add (_format_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
248         wxFont font = _format_description->GetFont();
249         font.SetStyle(wxFONTSTYLE_ITALIC);
250         font.SetPointSize(font.GetPointSize() - 1);
251         _format_description->SetFont(font);
252         ++r;
253
254         add_label_to_grid_bag_sizer (grid, _video_panel, _("Left crop"), wxGBPosition (r, 0));
255         _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
256         grid->Add (_left_crop, wxGBPosition (r, 1));
257         ++r;
258
259         add_label_to_grid_bag_sizer (grid, _video_panel, _("Right crop"), wxGBPosition (r, 0));
260         _right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
261         grid->Add (_right_crop, wxGBPosition (r, 1));
262         ++r;
263         
264         add_label_to_grid_bag_sizer (grid, _video_panel, _("Top crop"), wxGBPosition (r, 0));
265         _top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
266         grid->Add (_top_crop, wxGBPosition (r, 1));
267         ++r;
268         
269         add_label_to_grid_bag_sizer (grid, _video_panel, _("Bottom crop"), wxGBPosition (r, 0));
270         _bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
271         grid->Add (_bottom_crop, wxGBPosition (r, 1));
272         ++r;
273
274         /* VIDEO-only stuff */
275         {
276                 add_label_to_grid_bag_sizer (grid, _video_panel, _("Filters"), wxGBPosition (r, 0));
277                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
278                 _filters = new wxStaticText (_video_panel, wxID_ANY, _("None"));
279                 s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
280                 _filters_button = new wxButton (_video_panel, wxID_ANY, _("Edit..."));
281                 s->Add (_filters_button, 0);
282                 grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
283         }
284         ++r;
285
286         add_label_to_grid_bag_sizer (grid, _video_panel, _("Scaler"), wxGBPosition (r, 0));
287         _scaler = new wxChoice (_video_panel, wxID_ANY);
288         grid->Add (_scaler, wxGBPosition (r, 1));
289         ++r;
290
291         vector<Scaler const *> const sc = Scaler::all ();
292         for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
293                 _scaler->Append (std_to_wx ((*i)->name()));
294         }
295
296         add_label_to_grid_bag_sizer (grid, _video_panel, _("Colour look-up table"), wxGBPosition (r, 0));
297         _colour_lut = new wxChoice (_video_panel, wxID_ANY);
298         for (int i = 0; i < 2; ++i) {
299                 _colour_lut->Append (std_to_wx (colour_lut_index_to_name (i)));
300         }
301         _colour_lut->SetSelection (0);
302         grid->Add (_colour_lut, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
303         ++r;
304
305         {
306                 add_label_to_grid_bag_sizer (grid, _video_panel, _("JPEG2000 bandwidth"), wxGBPosition (r, 0));
307                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
308                 _j2k_bandwidth = new wxSpinCtrl (_video_panel, wxID_ANY);
309                 s->Add (_j2k_bandwidth, 1);
310                 add_label_to_sizer (s, _video_panel, _("MBps"));
311                 grid->Add (s, wxGBPosition (r, 1));
312         }
313         ++r;
314
315         _left_crop->SetRange (0, 1024);
316         _top_crop->SetRange (0, 1024);
317         _right_crop->SetRange (0, 1024);
318         _bottom_crop->SetRange (0, 1024);
319         _trim_start->SetRange (0, 100);
320         _trim_end->SetRange (0, 100);
321         _j2k_bandwidth->SetRange (50, 250);
322 }
323
324 void
325 FilmEditor::make_content_panel ()
326 {
327         _content_panel = new wxPanel (_notebook);
328         _content_sizer = new wxBoxSizer (wxVERTICAL);
329         _content_panel->SetSizer (_content_sizer);
330         
331         {
332                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
333                 
334                 _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL);
335                 s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6);
336
337                 _content->InsertColumn (0, wxT(""));
338                 _content->SetColumnWidth (0, 512);
339
340                 wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
341                 _content_add = new wxButton (_content_panel, wxID_ANY, _("Add..."));
342                 b->Add (_content_add);
343                 _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
344                 b->Add (_content_remove);
345                 _content_earlier = new wxButton (_content_panel, wxID_ANY, _("Earlier"));
346                 b->Add (_content_earlier);
347                 _content_later = new wxButton (_content_panel, wxID_ANY, _("Later"));
348                 b->Add (_content_later);
349
350                 s->Add (b, 0, wxALL, 4);
351
352                 _content_sizer->Add (s, 1, wxEXPAND | wxALL, 6);
353         }
354
355         _content_information = new wxTextCtrl (_content_panel, wxID_ANY, wxT ("\n\n\n\n"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxTE_MULTILINE);
356         _content_sizer->Add (_content_information, 1, wxEXPAND | wxALL, 6);
357 }
358
359 void
360 FilmEditor::make_audio_panel ()
361 {
362         _audio_panel = new wxPanel (_notebook);
363         _audio_sizer = new wxBoxSizer (wxVERTICAL);
364         _audio_panel->SetSizer (_audio_sizer);
365         
366         wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
367         _audio_sizer->Add (grid, 0, wxALL, 8);
368
369         _show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio..."));
370         grid->Add (_show_audio, 1);
371         grid->AddSpacer (0);
372
373         {
374                 add_label_to_sizer (grid, _audio_panel, _("Audio Gain"));
375                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
376                 _audio_gain = new wxSpinCtrl (_audio_panel);
377                 s->Add (_audio_gain, 1);
378                 add_label_to_sizer (s, _audio_panel, _("dB"));
379                 _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
380                 s->Add (_audio_gain_calculate_button, 1, wxEXPAND);
381                 grid->Add (s);
382         }
383
384         {
385                 add_label_to_sizer (grid, _audio_panel, _("Audio Delay"));
386                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
387                 _audio_delay = new wxSpinCtrl (_audio_panel);
388                 s->Add (_audio_delay, 1);
389                 /// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
390                 add_label_to_sizer (s, _audio_panel, _("ms"));
391                 grid->Add (s);
392         }
393
394         {
395                 add_label_to_sizer (grid, _audio_panel, _("Audio Stream"));
396                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
397                 _ffmpeg_audio_stream = new wxChoice (_audio_panel, wxID_ANY);
398                 s->Add (_ffmpeg_audio_stream, 1);
399                 _audio = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
400                 s->Add (_audio, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
401                 grid->Add (s, 1, wxEXPAND);
402         }
403
404         _audio_gain->SetRange (-60, 60);
405         _audio_delay->SetRange (-1000, 1000);
406 }
407
408 void
409 FilmEditor::make_subtitle_panel ()
410 {
411         _subtitle_panel = new wxPanel (_notebook);
412         _subtitle_sizer = new wxBoxSizer (wxVERTICAL);
413         _subtitle_panel->SetSizer (_subtitle_sizer);
414         wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
415         _subtitle_sizer->Add (grid, 0, wxALL, 8);
416
417         _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles"));
418         grid->Add (_with_subtitles, 1);
419         
420         _ffmpeg_subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
421         grid->Add (_ffmpeg_subtitle_stream);
422
423         add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"));
424         _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
425         grid->Add (_subtitle_offset, 1);
426
427         {
428                 add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"));
429                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
430                 _subtitle_scale = new wxSpinCtrl (_subtitle_panel);
431                 s->Add (_subtitle_scale);
432                 add_label_to_sizer (s, _subtitle_panel, _("%"));
433                 grid->Add (s);
434         }
435
436         _subtitle_offset->SetRange (-1024, 1024);
437         _subtitle_scale->SetRange (1, 1000);
438 }
439
440 /** Called when the left crop widget has been changed */
441 void
442 FilmEditor::left_crop_changed (wxCommandEvent &)
443 {
444         if (!_film) {
445                 return;
446         }
447
448         _film->set_left_crop (_left_crop->GetValue ());
449 }
450
451 /** Called when the right crop widget has been changed */
452 void
453 FilmEditor::right_crop_changed (wxCommandEvent &)
454 {
455         if (!_film) {
456                 return;
457         }
458
459         _film->set_right_crop (_right_crop->GetValue ());
460 }
461
462 /** Called when the top crop widget has been changed */
463 void
464 FilmEditor::top_crop_changed (wxCommandEvent &)
465 {
466         if (!_film) {
467                 return;
468         }
469
470         _film->set_top_crop (_top_crop->GetValue ());
471 }
472
473 /** Called when the bottom crop value has been changed */
474 void
475 FilmEditor::bottom_crop_changed (wxCommandEvent &)
476 {
477         if (!_film) {
478                 return;
479         }
480
481         _film->set_bottom_crop (_bottom_crop->GetValue ());
482 }
483
484 void
485 FilmEditor::trust_content_headers_changed (wxCommandEvent &)
486 {
487         if (!_film) {
488                 return;
489         }
490
491         _film->set_trust_content_headers (_trust_content_headers->GetValue ());
492 }
493
494 /** Called when the DCP A/B switch has been toggled */
495 void
496 FilmEditor::ab_toggled (wxCommandEvent &)
497 {
498         if (!_film) {
499                 return;
500         }
501         
502         _film->set_ab (_ab->GetValue ());
503 }
504
505 /** Called when the name widget has been changed */
506 void
507 FilmEditor::name_changed (wxCommandEvent &)
508 {
509         if (!_film) {
510                 return;
511         }
512
513         _film->set_name (string (_name->GetValue().mb_str()));
514 }
515
516 void
517 FilmEditor::subtitle_offset_changed (wxCommandEvent &)
518 {
519         if (!_film) {
520                 return;
521         }
522
523         _film->set_subtitle_offset (_subtitle_offset->GetValue ());
524 }
525
526 void
527 FilmEditor::subtitle_scale_changed (wxCommandEvent &)
528 {
529         if (!_film) {
530                 return;
531         }
532
533         _film->set_subtitle_scale (_subtitle_scale->GetValue() / 100.0);
534 }
535
536 void
537 FilmEditor::colour_lut_changed (wxCommandEvent &)
538 {
539         if (!_film) {
540                 return;
541         }
542         
543         _film->set_colour_lut (_colour_lut->GetSelection ());
544 }
545
546 void
547 FilmEditor::j2k_bandwidth_changed (wxCommandEvent &)
548 {
549         if (!_film) {
550                 return;
551         }
552         
553         _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1e6);
554 }
555
556 void
557 FilmEditor::dcp_frame_rate_changed (wxCommandEvent &)
558 {
559         if (!_film) {
560                 return;
561         }
562
563         _film->set_dcp_frame_rate (
564                 boost::lexical_cast<int> (
565                         wx_to_std (_dcp_frame_rate->GetString (_dcp_frame_rate->GetSelection ()))
566                         )
567                 );
568 }
569
570
571 /** Called when the metadata stored in the Film object has changed;
572  *  so that we can update the GUI.
573  *  @param p Property of the Film that has changed.
574  */
575 void
576 FilmEditor::film_changed (Film::Property p)
577 {
578         ensure_ui_thread ();
579         
580         if (!_film) {
581                 return;
582         }
583
584         stringstream s;
585                 
586         switch (p) {
587         case Film::NONE:
588                 break;
589         case Film::CONTENT:
590                 setup_content ();
591                 setup_formats ();
592                 setup_format ();
593                 setup_subtitle_control_sensitivity ();
594                 setup_streams ();
595                 setup_show_audio_sensitivity ();
596                 setup_length ();
597                 break;
598         case Film::TRUST_CONTENT_HEADERS:
599                 checked_set (_trust_content_headers, _film->trust_content_headers ());
600                 break;
601         case Film::FORMAT:
602                 setup_format ();
603                 break;
604         case Film::CROP:
605                 checked_set (_left_crop, _film->crop().left);
606                 checked_set (_right_crop, _film->crop().right);
607                 checked_set (_top_crop, _film->crop().top);
608                 checked_set (_bottom_crop, _film->crop().bottom);
609                 break;
610         case Film::FILTERS:
611         {
612                 pair<string, string> p = Filter::ffmpeg_strings (_film->filters ());
613                 if (p.first.empty () && p.second.empty ()) {
614                         _filters->SetLabel (_("None"));
615                 } else {
616                         string const b = p.first + " " + p.second;
617                         _filters->SetLabel (std_to_wx (b));
618                 }
619                 _film_sizer->Layout ();
620                 break;
621         }
622         case Film::NAME:
623                 checked_set (_name, _film->name());
624                 setup_dcp_name ();
625                 break;
626         case Film::DCP_CONTENT_TYPE:
627                 checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
628                 setup_dcp_name ();
629                 break;
630         case Film::AB:
631                 checked_set (_ab, _film->ab ());
632                 break;
633         case Film::SCALER:
634                 checked_set (_scaler, Scaler::as_index (_film->scaler ()));
635                 break;
636         case Film::TRIM_START:
637                 checked_set (_trim_start, _film->trim_start());
638                 break;
639         case Film::TRIM_END:
640                 checked_set (_trim_end, _film->trim_end());
641                 break;
642         case Film::AUDIO_GAIN:
643                 checked_set (_audio_gain, _film->audio_gain ());
644                 break;
645         case Film::AUDIO_DELAY:
646                 checked_set (_audio_delay, _film->audio_delay ());
647                 break;
648         case Film::WITH_SUBTITLES:
649                 checked_set (_with_subtitles, _film->with_subtitles ());
650                 setup_subtitle_control_sensitivity ();
651                 setup_dcp_name ();
652                 break;
653         case Film::SUBTITLE_OFFSET:
654                 checked_set (_subtitle_offset, _film->subtitle_offset ());
655                 break;
656         case Film::SUBTITLE_SCALE:
657                 checked_set (_subtitle_scale, _film->subtitle_scale() * 100);
658                 break;
659         case Film::COLOUR_LUT:
660                 checked_set (_colour_lut, _film->colour_lut ());
661                 break;
662         case Film::J2K_BANDWIDTH:
663                 checked_set (_j2k_bandwidth, double (_film->j2k_bandwidth()) / 1e6);
664                 break;
665         case Film::USE_DCI_NAME:
666                 checked_set (_use_dci_name, _film->use_dci_name ());
667                 setup_dcp_name ();
668                 break;
669         case Film::DCI_METADATA:
670                 setup_dcp_name ();
671                 break;
672         case Film::DCP_FRAME_RATE:
673                 for (unsigned int i = 0; i < _dcp_frame_rate->GetCount(); ++i) {
674                         if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_frame_rate())) {
675                                 if (_dcp_frame_rate->GetSelection() != int(i)) {
676                                         _dcp_frame_rate->SetSelection (i);
677                                         break;
678                                 }
679                         }
680                 }
681
682                 if (_film->video_frame_rate()) {
683                         _frame_rate_description->SetLabel (std_to_wx (FrameRateConversion (_film->video_frame_rate(), _film->dcp_frame_rate()).description));
684                         _best_dcp_frame_rate->Enable (best_dcp_frame_rate (_film->video_frame_rate ()) != _film->dcp_frame_rate ());
685                 } else {
686                         _frame_rate_description->SetLabel (wxT (""));
687                         _best_dcp_frame_rate->Disable ();
688                 }
689         }
690 }
691
692 void
693 FilmEditor::film_content_changed (int p)
694 {
695         if (!_film) {
696                 /* We call this method ourselves (as well as using it as a signal handler)
697                    so _film can be 0.
698                 */
699                 return;
700         }
701                 
702         if (p == FFmpegContentProperty::SUBTITLE_STREAMS) {
703                 setup_subtitle_control_sensitivity ();
704                 setup_streams ();
705         } else if (p == FFmpegContentProperty::AUDIO_STREAMS) {
706                 setup_streams ();
707                 setup_show_audio_sensitivity ();
708         } else if (p == VideoContentProperty::VIDEO_LENGTH) {
709                 setup_length ();
710                 setup_content_information ();
711         } else if (p == FFmpegContentProperty::AUDIO_STREAM) {
712                 if (_film->ffmpeg_audio_stream()) {
713                         checked_set (_ffmpeg_audio_stream, boost::lexical_cast<string> (_film->ffmpeg_audio_stream()->id));
714                 }
715                 setup_dcp_name ();
716                 setup_audio_details ();
717                 setup_show_audio_sensitivity ();
718         } else if (p == FFmpegContentProperty::SUBTITLE_STREAM) {
719                 if (_film->ffmpeg_subtitle_stream()) {
720                         checked_set (_ffmpeg_subtitle_stream, boost::lexical_cast<string> (_film->ffmpeg_subtitle_stream()->id));
721                 }
722         }
723 }
724
725 void
726 FilmEditor::setup_format ()
727 {
728         int n = 0;
729         vector<Format const *>::iterator i = _formats.begin ();
730         while (i != _formats.end() && *i != _film->format ()) {
731                 ++i;
732                 ++n;
733         }
734         if (i == _formats.end()) {
735                 checked_set (_format, -1);
736         } else {
737                 checked_set (_format, n);
738         }
739         setup_dcp_name ();
740         
741         if (_film->format ()) {
742                 _format_description->SetLabel (std_to_wx (_film->format()->description ()));
743         } else {
744                 _format_description->SetLabel (wxT (""));
745         }
746 }       
747
748 void
749 FilmEditor::setup_length ()
750 {
751         stringstream s;
752         if (_film->video_frame_rate() > 0 && _film->video_length()) {
753                 s << _film->video_length() << " "
754                   << wx_to_std (_("frames")) << "; " << seconds_to_hms (_film->video_length() / _film->video_frame_rate());
755         } else if (_film->video_length()) {
756                 s << _film->video_length() << " "
757                   << wx_to_std (_("frames"));
758         } 
759         _length->SetLabel (std_to_wx (s.str ()));
760         if (_film->video_length()) {
761                 _trim_start->SetRange (0, _film->video_length());
762                 _trim_end->SetRange (0, _film->video_length());
763         }
764 }       
765
766
767 /** Called when the format widget has been changed */
768 void
769 FilmEditor::format_changed (wxCommandEvent &)
770 {
771         if (!_film) {
772                 return;
773         }
774
775         int const n = _format->GetSelection ();
776         if (n >= 0) {
777                 assert (n < int (_formats.size()));
778                 _film->set_format (_formats[n]);
779         }
780 }
781
782 /** Called when the DCP content type widget has been changed */
783 void
784 FilmEditor::dcp_content_type_changed (wxCommandEvent &)
785 {
786         if (!_film) {
787                 return;
788         }
789
790         int const n = _dcp_content_type->GetSelection ();
791         if (n != wxNOT_FOUND) {
792                 _film->set_dcp_content_type (DCPContentType::from_index (n));
793         }
794 }
795
796 /** Sets the Film that we are editing */
797 void
798 FilmEditor::set_film (shared_ptr<Film> f)
799 {
800         _film = f;
801
802         set_things_sensitive (_film != 0);
803
804         if (_film) {
805                 _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1));
806                 _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _1));
807         }
808
809         if (_film) {
810                 FileChanged (_film->directory ());
811         } else {
812                 FileChanged ("");
813         }
814
815         if (_audio_dialog) {
816                 _audio_dialog->set_film (_film);
817         }
818         
819         film_changed (Film::NAME);
820         film_changed (Film::USE_DCI_NAME);
821         film_changed (Film::CONTENT);
822         film_changed (Film::TRUST_CONTENT_HEADERS);
823         film_changed (Film::DCP_CONTENT_TYPE);
824         film_changed (Film::FORMAT);
825         film_changed (Film::CROP);
826         film_changed (Film::FILTERS);
827         film_changed (Film::SCALER);
828         film_changed (Film::TRIM_START);
829         film_changed (Film::TRIM_END);
830         film_changed (Film::AB);
831         film_changed (Film::AUDIO_GAIN);
832         film_changed (Film::AUDIO_DELAY);
833         film_changed (Film::WITH_SUBTITLES);
834         film_changed (Film::SUBTITLE_OFFSET);
835         film_changed (Film::SUBTITLE_SCALE);
836         film_changed (Film::COLOUR_LUT);
837         film_changed (Film::J2K_BANDWIDTH);
838         film_changed (Film::DCI_METADATA);
839         film_changed (Film::DCP_FRAME_RATE);
840
841         film_content_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
842         film_content_changed (FFmpegContentProperty::SUBTITLE_STREAM);
843         film_content_changed (FFmpegContentProperty::AUDIO_STREAMS);
844         film_content_changed (FFmpegContentProperty::AUDIO_STREAM);
845 }
846
847 /** Updates the sensitivity of lots of widgets to a given value.
848  *  @param s true to make sensitive, false to make insensitive.
849  */
850 void
851 FilmEditor::set_things_sensitive (bool s)
852 {
853         _generally_sensitive = s;
854         
855         _name->Enable (s);
856         _use_dci_name->Enable (s);
857         _edit_dci_button->Enable (s);
858         _format->Enable (s);
859         _content->Enable (s);
860         _trust_content_headers->Enable (s);
861         _content->Enable (s);
862         _left_crop->Enable (s);
863         _right_crop->Enable (s);
864         _top_crop->Enable (s);
865         _bottom_crop->Enable (s);
866         _filters_button->Enable (s);
867         _scaler->Enable (s);
868         _ffmpeg_audio_stream->Enable (s);
869         _dcp_content_type->Enable (s);
870         _dcp_frame_rate->Enable (s);
871         _trim_start->Enable (s);
872         _trim_end->Enable (s);
873         _ab->Enable (s);
874         _colour_lut->Enable (s);
875         _j2k_bandwidth->Enable (s);
876         _audio_gain->Enable (s);
877         _audio_gain_calculate_button->Enable (s);
878         _show_audio->Enable (s);
879         _audio_delay->Enable (s);
880
881         setup_subtitle_control_sensitivity ();
882         setup_show_audio_sensitivity ();
883         setup_content_button_sensitivity ();
884 }
885
886 /** Called when the `Edit filters' button has been clicked */
887 void
888 FilmEditor::edit_filters_clicked (wxCommandEvent &)
889 {
890         FilterDialog* d = new FilterDialog (this, _film->filters());
891         d->ActiveChanged.connect (bind (&Film::set_filters, _film, _1));
892         d->ShowModal ();
893         d->Destroy ();
894 }
895
896 /** Called when the scaler widget has been changed */
897 void
898 FilmEditor::scaler_changed (wxCommandEvent &)
899 {
900         if (!_film) {
901                 return;
902         }
903
904         int const n = _scaler->GetSelection ();
905         if (n >= 0) {
906                 _film->set_scaler (Scaler::from_index (n));
907         }
908 }
909
910 void
911 FilmEditor::audio_gain_changed (wxCommandEvent &)
912 {
913         if (!_film) {
914                 return;
915         }
916
917         _film->set_audio_gain (_audio_gain->GetValue ());
918 }
919
920 void
921 FilmEditor::audio_delay_changed (wxCommandEvent &)
922 {
923         if (!_film) {
924                 return;
925         }
926
927         _film->set_audio_delay (_audio_delay->GetValue ());
928 }
929
930 void
931 FilmEditor::trim_start_changed (wxCommandEvent &)
932 {
933         if (!_film) {
934                 return;
935         }
936
937         _film->set_trim_start (_trim_start->GetValue ());
938 }
939
940 void
941 FilmEditor::trim_end_changed (wxCommandEvent &)
942 {
943         if (!_film) {
944                 return;
945         }
946
947         _film->set_trim_end (_trim_end->GetValue ());
948 }
949
950 void
951 FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
952 {
953         GainCalculatorDialog* d = new GainCalculatorDialog (this);
954         d->ShowModal ();
955
956         if (d->wanted_fader() == 0 || d->actual_fader() == 0) {
957                 d->Destroy ();
958                 return;
959         }
960         
961         _audio_gain->SetValue (
962                 Config::instance()->sound_processor()->db_for_fader_change (
963                         d->wanted_fader (),
964                         d->actual_fader ()
965                         )
966                 );
967
968         /* This appears to be necessary, as the change is not signalled,
969            I think.
970         */
971         wxCommandEvent dummy;
972         audio_gain_changed (dummy);
973         
974         d->Destroy ();
975 }
976
977 void
978 FilmEditor::setup_formats ()
979 {
980         _formats = Format::all ();
981
982         _format->Clear ();
983         for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
984                 _format->Append (std_to_wx ((*i)->name ()));
985         }
986
987         _film_sizer->Layout ();
988 }
989
990 void
991 FilmEditor::with_subtitles_toggled (wxCommandEvent &)
992 {
993         if (!_film) {
994                 return;
995         }
996
997         _film->set_with_subtitles (_with_subtitles->GetValue ());
998 }
999
1000 void
1001 FilmEditor::setup_subtitle_control_sensitivity ()
1002 {
1003         bool h = false;
1004         if (_generally_sensitive && _film) {
1005                 h = !_film->ffmpeg_subtitle_streams().empty();
1006         }
1007         
1008         _with_subtitles->Enable (h);
1009
1010         bool j = false;
1011         if (_film) {
1012                 j = _film->with_subtitles ();
1013         }
1014         
1015         _ffmpeg_subtitle_stream->Enable (j);
1016         _subtitle_offset->Enable (j);
1017         _subtitle_scale->Enable (j);
1018 }
1019
1020 void
1021 FilmEditor::use_dci_name_toggled (wxCommandEvent &)
1022 {
1023         if (!_film) {
1024                 return;
1025         }
1026
1027         _film->set_use_dci_name (_use_dci_name->GetValue ());
1028 }
1029
1030 void
1031 FilmEditor::edit_dci_button_clicked (wxCommandEvent &)
1032 {
1033         if (!_film) {
1034                 return;
1035         }
1036
1037         DCIMetadataDialog* d = new DCIMetadataDialog (this, _film->dci_metadata ());
1038         d->ShowModal ();
1039         _film->set_dci_metadata (d->dci_metadata ());
1040         d->Destroy ();
1041 }
1042
1043 void
1044 FilmEditor::setup_streams ()
1045 {
1046         if (!_film) {
1047                 return;
1048         }
1049         
1050         _ffmpeg_audio_stream->Clear ();
1051         vector<FFmpegAudioStream> a = _film->ffmpeg_audio_streams ();
1052         for (vector<FFmpegAudioStream>::iterator i = a.begin(); i != a.end(); ++i) {
1053                 _ffmpeg_audio_stream->Append (std_to_wx (i->name), new wxStringClientData (std_to_wx (boost::lexical_cast<string> (i->id))));
1054         }
1055         
1056         if (_film->ffmpeg_audio_stream()) {
1057                 checked_set (_ffmpeg_audio_stream, boost::lexical_cast<string> (_film->ffmpeg_audio_stream()->id));
1058         }
1059
1060         _ffmpeg_subtitle_stream->Clear ();
1061         vector<FFmpegSubtitleStream> s = _film->ffmpeg_subtitle_streams ();
1062         for (vector<FFmpegSubtitleStream>::iterator i = s.begin(); i != s.end(); ++i) {
1063                 _ffmpeg_subtitle_stream->Append (std_to_wx (i->name), new wxStringClientData (std_to_wx (boost::lexical_cast<string> (i->id))));
1064         }
1065         
1066         if (_film->ffmpeg_subtitle_stream()) {
1067                 checked_set (_ffmpeg_subtitle_stream, boost::lexical_cast<string> (_film->ffmpeg_subtitle_stream()->id));
1068         } else {
1069                 _ffmpeg_subtitle_stream->SetSelection (wxNOT_FOUND);
1070         }
1071 }
1072
1073 void
1074 FilmEditor::ffmpeg_audio_stream_changed (wxCommandEvent &)
1075 {
1076         if (!_film) {
1077                 return;
1078         }
1079
1080         vector<FFmpegAudioStream> a = _film->ffmpeg_audio_streams ();
1081         vector<FFmpegAudioStream>::iterator i = a.begin ();
1082         string const s = string_client_data (_ffmpeg_audio_stream->GetClientObject (_ffmpeg_audio_stream->GetSelection ()));
1083         while (i != a.end() && lexical_cast<string> (i->id) != s) {
1084                 ++i;
1085         }
1086
1087         if (i != a.end ()) {
1088                 _film->set_ffmpeg_audio_stream (*i);
1089         }
1090 }
1091
1092 void
1093 FilmEditor::ffmpeg_subtitle_stream_changed (wxCommandEvent &)
1094 {
1095         if (!_film) {
1096                 return;
1097         }
1098
1099         vector<FFmpegSubtitleStream> a = _film->ffmpeg_subtitle_streams ();
1100         vector<FFmpegSubtitleStream>::iterator i = a.begin ();
1101         string const s = string_client_data (_ffmpeg_subtitle_stream->GetClientObject (_ffmpeg_subtitle_stream->GetSelection ()));
1102         while (i != a.end() && lexical_cast<string> (i->id) != s) {
1103                 ++i;
1104         }
1105
1106         if (i != a.end ()) {
1107                 _film->set_ffmpeg_subtitle_stream (*i);
1108         }
1109 }
1110
1111 void
1112 FilmEditor::setup_audio_details ()
1113 {
1114         if (!_film->ffmpeg_audio_stream()) {
1115                 _audio->SetLabel (wxT (""));
1116         } else {
1117                 stringstream s;
1118                 if (_film->audio_channels() == 1) {
1119                         s << wx_to_std (_("1 channel"));
1120                 } else {
1121                         s << _film->audio_channels() << " " << wx_to_std (_("channels"));
1122                 }
1123                 s << ", " << _film->audio_frame_rate() << wx_to_std (_("Hz"));
1124                 _audio->SetLabel (std_to_wx (s.str ()));
1125         }
1126 }
1127
1128 void
1129 FilmEditor::active_jobs_changed (bool a)
1130 {
1131         set_things_sensitive (!a);
1132 }
1133
1134 void
1135 FilmEditor::setup_dcp_name ()
1136 {
1137         string s = _film->dcp_name (true);
1138         if (s.length() > 28) {
1139                 _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
1140                 _dcp_name->SetToolTip (std_to_wx (s));
1141         } else {
1142                 _dcp_name->SetLabel (std_to_wx (s));
1143         }
1144 }
1145
1146 void
1147 FilmEditor::show_audio_clicked (wxCommandEvent &)
1148 {
1149         if (_audio_dialog) {
1150                 _audio_dialog->Destroy ();
1151                 _audio_dialog = 0;
1152         }
1153         
1154         _audio_dialog = new AudioDialog (this);
1155         _audio_dialog->Show ();
1156         _audio_dialog->set_film (_film);
1157 }
1158
1159 void
1160 FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
1161 {
1162         if (!_film) {
1163                 return;
1164         }
1165         
1166         _film->set_dcp_frame_rate (best_dcp_frame_rate (_film->video_frame_rate ()));
1167 }
1168
1169 void
1170 FilmEditor::setup_show_audio_sensitivity ()
1171 {
1172         _show_audio->Enable (_film && _film->has_audio ());
1173 }
1174
1175 void
1176 FilmEditor::setup_content ()
1177 {
1178         string selected_summary;
1179         int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1180         if (s != -1) {
1181                 selected_summary = wx_to_std (_content->GetItemText (s));
1182         }
1183         
1184         _content->DeleteAllItems ();
1185
1186         ContentList content = _film->content ();
1187         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
1188                 int const t = _content->GetItemCount ();
1189                 _content->InsertItem (t, std_to_wx ((*i)->summary ()));
1190                 if ((*i)->summary() == selected_summary) {
1191                         _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1192                 }
1193         }
1194
1195         if (selected_summary.empty () && !content.empty ()) {
1196                 /* Select the first item of content if non was selected before */
1197                 _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1198         }
1199 }
1200
1201 void
1202 FilmEditor::content_add_clicked (wxCommandEvent &)
1203 {
1204         wxFileDialog* d = new wxFileDialog (this);
1205         int const r = d->ShowModal ();
1206         d->Destroy ();
1207
1208         if (r != wxID_OK) {
1209                 return;
1210         }
1211
1212         boost::filesystem::path p (wx_to_std (d->GetPath()));
1213
1214         if (ImageMagickContent::valid_file (p)) {
1215                 _film->add_content (shared_ptr<ImageMagickContent> (new ImageMagickContent (p)));
1216         } else if (SndfileContent::valid_file (p)) {
1217                 _film->add_content (shared_ptr<SndfileContent> (new SndfileContent (p)));
1218         } else {
1219                 _film->add_content (shared_ptr<FFmpegContent> (new FFmpegContent (p)));
1220         }
1221         
1222 }
1223
1224 void
1225 FilmEditor::content_remove_clicked (wxCommandEvent &)
1226 {
1227         int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1228         if (s == -1) {
1229                 return;
1230         }
1231
1232         ContentList c = _film->content ();
1233         assert (s >= 0 && size_t (s) < c.size ());
1234         _film->remove_content (c[s]);
1235 }
1236
1237 void
1238 FilmEditor::content_earlier_clicked (wxCommandEvent &)
1239 {
1240         int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1241         if (s == -1) {
1242                 return;
1243         }
1244
1245         ContentList c = _film->content ();
1246         assert (s >= 0 && size_t (s) < c.size ());
1247         _film->move_content_earlier (c[s]);
1248 }
1249
1250 void
1251 FilmEditor::content_later_clicked (wxCommandEvent &)
1252 {
1253         int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1254         if (s == -1) {
1255                 return;
1256         }
1257
1258         ContentList c = _film->content ();
1259         assert (s >= 0 && size_t (s) < c.size ());
1260         _film->move_content_later (c[s]);
1261 }
1262
1263 void
1264 FilmEditor::content_item_selected (wxListEvent &)
1265 {
1266         setup_content_button_sensitivity ();
1267         setup_content_information ();
1268 }
1269
1270 void
1271 FilmEditor::setup_content_information ()
1272 {
1273         int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1274         if (s == -1) {
1275                 _content_information->SetValue (wxT (""));
1276                 return;
1277         }
1278
1279         ContentList c = _film->content ();
1280         assert (s >= 0 && size_t (s) < c.size ());
1281         _content_information->SetValue (std_to_wx (c[s]->information ()));
1282 }
1283
1284 void
1285 FilmEditor::setup_content_button_sensitivity ()
1286 {
1287         _content_add->Enable (_generally_sensitive);
1288
1289         bool const have_selection = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1;
1290         _content_remove->Enable (have_selection && _generally_sensitive);
1291         _content_earlier->Enable (have_selection && _generally_sensitive);
1292         _content_later->Enable (have_selection && _generally_sensitive);
1293 }