Support for keeping video in sequence when changing lengths; tie selection in timelin...
[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 #include "imagemagick_content_dialog.h"
53 #include "timeline_dialog.h"
54 #include "audio_mapping_view.h"
55 #include "container.h"
56 #include "timecode.h"
57
58 using std::string;
59 using std::cout;
60 using std::stringstream;
61 using std::pair;
62 using std::fixed;
63 using std::setprecision;
64 using std::list;
65 using std::vector;
66 using std::max;
67 using boost::shared_ptr;
68 using boost::weak_ptr;
69 using boost::dynamic_pointer_cast;
70 using boost::lexical_cast;
71
72 /** @param f Film to edit */
73 FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
74         : wxPanel (parent)
75         , _generally_sensitive (true)
76         , _audio_dialog (0)
77         , _timeline_dialog (0)
78 {
79         wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
80
81         _main_notebook = new wxNotebook (this, wxID_ANY);
82         s->Add (_main_notebook, 1);
83
84         make_content_panel ();
85         _main_notebook->AddPage (_content_panel, _("Content"), true);
86         make_dcp_panel ();
87         _main_notebook->AddPage (_dcp_panel, _("DCP"), false);
88         
89         setup_formats ();
90
91         set_film (f);
92         connect_to_widgets ();
93
94         JobManager::instance()->ActiveJobsChanged.connect (
95                 bind (&FilmEditor::active_jobs_changed, this, _1)
96                 );
97         
98         SetSizerAndFit (s);
99 }
100
101 void
102 FilmEditor::make_dcp_panel ()
103 {
104         _dcp_panel = new wxPanel (_main_notebook);
105         _dcp_sizer = new wxBoxSizer (wxVERTICAL);
106         _dcp_panel->SetSizer (_dcp_sizer);
107
108         wxGridBagSizer* grid = new wxGridBagSizer (4, 4);
109         _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8);
110
111         int r = 0;
112         
113         add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), wxGBPosition (r, 0));
114         _name = new wxTextCtrl (_dcp_panel, wxID_ANY);
115         grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
116         ++r;
117         
118         add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), wxGBPosition (r, 0));
119         _dcp_name = new wxStaticText (_dcp_panel, wxID_ANY, wxT (""));
120         grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
121         ++r;
122
123         _use_dci_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use DCI name"));
124         grid->Add (_use_dci_name, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
125         _edit_dci_button = new wxButton (_dcp_panel, wxID_ANY, _("Details..."));
126         grid->Add (_edit_dci_button, wxGBPosition (r, 1), wxDefaultSpan);
127         ++r;
128
129         add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), wxGBPosition (r, 0));
130         _container = new wxChoice (_dcp_panel, wxID_ANY);
131         grid->Add (_container, wxGBPosition (r, 1));
132         ++r;
133
134         add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), wxGBPosition (r, 0));
135         _dcp_content_type = new wxChoice (_dcp_panel, wxID_ANY);
136         grid->Add (_dcp_content_type, wxGBPosition (r, 1));
137         ++r;
138
139         {
140                 add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Frame Rate"), wxGBPosition (r, 0));
141                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
142                 _dcp_frame_rate = new wxChoice (_dcp_panel, wxID_ANY);
143                 s->Add (_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL);
144                 _best_dcp_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best"));
145                 s->Add (_best_dcp_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
146                 grid->Add (s, wxGBPosition (r, 1));
147         }
148         ++r;
149
150         {
151                 add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), wxGBPosition (r, 0));
152                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
153                 _j2k_bandwidth = new wxSpinCtrl (_dcp_panel, wxID_ANY);
154                 s->Add (_j2k_bandwidth, 1);
155                 add_label_to_sizer (s, _dcp_panel, _("MBps"));
156                 grid->Add (s, wxGBPosition (r, 1));
157         }
158         ++r;
159
160         add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), wxGBPosition (r, 0));
161         _scaler = new wxChoice (_dcp_panel, wxID_ANY);
162         grid->Add (_scaler, wxGBPosition (r, 1));
163         ++r;
164
165         vector<Scaler const *> const sc = Scaler::all ();
166         for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
167                 _scaler->Append (std_to_wx ((*i)->name()));
168         }
169
170         vector<Container const *> const co = Container::all ();
171         for (vector<Container const *>::const_iterator i = co.begin(); i != co.end(); ++i) {
172                 _container->Append (std_to_wx ((*i)->name ()));
173         }
174
175         vector<DCPContentType const *> const ct = DCPContentType::all ();
176         for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
177                 _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
178         }
179
180         list<int> const dfr = Config::instance()->allowed_dcp_frame_rates ();
181         for (list<int>::const_iterator i = dfr.begin(); i != dfr.end(); ++i) {
182                 _dcp_frame_rate->Append (std_to_wx (boost::lexical_cast<string> (*i)));
183         }
184
185         _j2k_bandwidth->SetRange (50, 250);
186 }
187
188 void
189 FilmEditor::connect_to_widgets ()
190 {
191         _name->Connect                   (wxID_ANY, wxEVT_COMMAND_TEXT_UPDATED,         wxCommandEventHandler (FilmEditor::name_changed), 0, this);
192         _use_dci_name->Connect           (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::use_dci_name_toggled), 0, this);
193         _edit_dci_button->Connect        (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::edit_dci_button_clicked), 0, this);
194         _container->Connect              (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::container_changed), 0, this);
195 //      _format->Connect                 (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::format_changed), 0, this);
196         _content->Connect                (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_SELECTED,   wxListEventHandler    (FilmEditor::content_selection_changed), 0, this);
197         _content->Connect                (wxID_ANY, wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler    (FilmEditor::content_selection_changed), 0, this);
198         _content_add->Connect            (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_add_clicked), 0, this);
199         _content_remove->Connect         (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_remove_clicked), 0, this);
200         _content_timeline->Connect       (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_timeline_clicked), 0, this);
201         _loop_content->Connect           (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::loop_content_toggled), 0, this);
202         _loop_count->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::loop_count_changed), 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         _with_subtitles->Connect         (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::with_subtitles_toggled), 0, this);
213         _subtitle_offset->Connect        (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::subtitle_offset_changed), 0, this);
214         _subtitle_scale->Connect         (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::subtitle_scale_changed), 0, this);
215         _colour_lut->Connect             (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::colour_lut_changed), 0, this);
216         _j2k_bandwidth->Connect          (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::j2k_bandwidth_changed), 0, this);
217         _audio_gain->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::audio_gain_changed), 0, this);
218         _audio_gain_calculate_button->Connect (
219                 wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler (FilmEditor::audio_gain_calculate_button_clicked), 0, this
220                 );
221         _show_audio->Connect             (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::show_audio_clicked), 0, this);
222         _audio_delay->Connect            (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::audio_delay_changed), 0, this);
223         _audio_stream->Connect           (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::audio_stream_changed), 0, this);
224         _subtitle_stream->Connect        (wxID_ANY, wxEVT_COMMAND_CHOICE_SELECTED,      wxCommandEventHandler (FilmEditor::subtitle_stream_changed), 0, this);
225         _audio_mapping->Changed.connect  (boost::bind (&FilmEditor::audio_mapping_changed, this, _1));
226         _start->Changed.connect          (boost::bind (&FilmEditor::start_changed, this));
227         _length->Changed.connect         (boost::bind (&FilmEditor::length_changed, this));
228 }
229
230 void
231 FilmEditor::make_video_panel ()
232 {
233         _video_panel = new wxPanel (_content_notebook);
234         wxBoxSizer* 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, _("Left crop"), wxGBPosition (r, 0));
242         _left_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
243         grid->Add (_left_crop, wxGBPosition (r, 1));
244         ++r;
245
246         add_label_to_grid_bag_sizer (grid, _video_panel, _("Right crop"), wxGBPosition (r, 0));
247         _right_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
248         grid->Add (_right_crop, wxGBPosition (r, 1));
249         ++r;
250         
251         add_label_to_grid_bag_sizer (grid, _video_panel, _("Top crop"), wxGBPosition (r, 0));
252         _top_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
253         grid->Add (_top_crop, wxGBPosition (r, 1));
254         ++r;
255         
256         add_label_to_grid_bag_sizer (grid, _video_panel, _("Bottom crop"), wxGBPosition (r, 0));
257         _bottom_crop = new wxSpinCtrl (_video_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
258         grid->Add (_bottom_crop, wxGBPosition (r, 1));
259         ++r;
260
261         add_label_to_grid_bag_sizer (grid, _video_panel, _("Scale to"), wxGBPosition (r, 0));
262         _format = new wxChoice (_video_panel, wxID_ANY);
263         grid->Add (_format, wxGBPosition (r, 1));
264         ++r;
265
266         _scaling_description = new wxStaticText (_video_panel, wxID_ANY, wxT ("\n \n \n \n"), wxDefaultPosition, wxDefaultSize);
267         grid->Add (_scaling_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
268         wxFont font = _scaling_description->GetFont();
269         font.SetStyle(wxFONTSTYLE_ITALIC);
270         font.SetPointSize(font.GetPointSize() - 1);
271         _scaling_description->SetFont(font);
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, _("Colour look-up table"), wxGBPosition (r, 0));
287         _colour_lut = new wxChoice (_video_panel, wxID_ANY);
288         for (int i = 0; i < 2; ++i) {
289                 _colour_lut->Append (std_to_wx (colour_lut_index_to_name (i)));
290         }
291         _colour_lut->SetSelection (0);
292         grid->Add (_colour_lut, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
293         ++r;
294
295         _left_crop->SetRange (0, 1024);
296         _top_crop->SetRange (0, 1024);
297         _right_crop->SetRange (0, 1024);
298         _bottom_crop->SetRange (0, 1024);
299 }
300
301 void
302 FilmEditor::make_content_panel ()
303 {
304         _content_panel = new wxPanel (_main_notebook);
305         _content_sizer = new wxBoxSizer (wxVERTICAL);
306         _content_panel->SetSizer (_content_sizer);
307
308         {
309                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
310                 
311                 _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_SINGLE_SEL);
312                 s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6);
313
314                 _content->InsertColumn (0, wxT(""));
315                 _content->SetColumnWidth (0, 512);
316
317                 wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
318                 _content_add = new wxButton (_content_panel, wxID_ANY, _("Add..."));
319                 b->Add (_content_add, 1, wxEXPAND | wxLEFT | wxRIGHT);
320                 _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
321                 b->Add (_content_remove, 1, wxEXPAND | wxLEFT | wxRIGHT);
322                 _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline..."));
323                 b->Add (_content_timeline, 1, wxEXPAND | wxLEFT | wxRIGHT);
324
325                 s->Add (b, 0, wxALL, 4);
326
327                 _content_sizer->Add (s, 0.75, wxEXPAND | wxALL, 6);
328         }
329
330         wxBoxSizer* h = new wxBoxSizer (wxHORIZONTAL);
331         _loop_content = new wxCheckBox (_content_panel, wxID_ANY, _("Loop everything"));
332         h->Add (_loop_content, 0, wxALL, 6);
333         _loop_count = new wxSpinCtrl (_content_panel, wxID_ANY);
334         h->Add (_loop_count, 0, wxALL, 6);
335         add_label_to_sizer (h, _content_panel, _("times"));
336         _content_sizer->Add (h, 0, wxALL, 6);
337
338         _content_notebook = new wxNotebook (_content_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_LEFT);
339         _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6);
340
341         make_video_panel ();
342         _content_notebook->AddPage (_video_panel, _("Video"), false);
343         make_audio_panel ();
344         _content_notebook->AddPage (_audio_panel, _("Audio"), false);
345         make_subtitle_panel ();
346         _content_notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
347         make_timing_panel ();
348         _content_notebook->AddPage (_timing_panel, _("Timing"), false);
349
350         _loop_count->SetRange (2, 1024);
351 }
352
353 void
354 FilmEditor::make_audio_panel ()
355 {
356         _audio_panel = new wxPanel (_content_notebook);
357         wxBoxSizer* audio_sizer = new wxBoxSizer (wxVERTICAL);
358         _audio_panel->SetSizer (audio_sizer);
359         
360         wxFlexGridSizer* grid = new wxFlexGridSizer (3, 4, 4);
361         audio_sizer->Add (grid, 0, wxALL, 8);
362
363         _show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio..."));
364         grid->Add (_show_audio, 1);
365         grid->AddSpacer (0);
366         grid->AddSpacer (0);
367
368         add_label_to_sizer (grid, _audio_panel, _("Audio Gain"));
369         {
370                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
371                 _audio_gain = new wxSpinCtrl (_audio_panel);
372                 s->Add (_audio_gain, 1);
373                 add_label_to_sizer (s, _audio_panel, _("dB"));
374                 grid->Add (s, 1);
375         }
376         
377         _audio_gain_calculate_button = new wxButton (_audio_panel, wxID_ANY, _("Calculate..."));
378         grid->Add (_audio_gain_calculate_button);
379
380         add_label_to_sizer (grid, _audio_panel, _("Audio Delay"));
381         {
382                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
383                 _audio_delay = new wxSpinCtrl (_audio_panel);
384                 s->Add (_audio_delay, 1);
385                 /// TRANSLATORS: this is an abbreviation for milliseconds, the unit of time
386                 add_label_to_sizer (s, _audio_panel, _("ms"));
387                 grid->Add (s);
388         }
389
390         grid->AddSpacer (0);
391
392         add_label_to_sizer (grid, _audio_panel, _("Audio Stream"));
393         _audio_stream = new wxChoice (_audio_panel, wxID_ANY);
394         grid->Add (_audio_stream, 1);
395         _audio_description = new wxStaticText (_audio_panel, wxID_ANY, wxT (""));
396         grid->AddSpacer (0);
397         
398         grid->Add (_audio_description, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
399         grid->AddSpacer (0);
400         grid->AddSpacer (0);
401         
402         _audio_mapping = new AudioMappingView (_audio_panel);
403         audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6);
404
405         _audio_gain->SetRange (-60, 60);
406         _audio_delay->SetRange (-1000, 1000);
407 }
408
409 void
410 FilmEditor::make_subtitle_panel ()
411 {
412         _subtitle_panel = new wxPanel (_content_notebook);
413         wxBoxSizer* subtitle_sizer = new wxBoxSizer (wxVERTICAL);
414         _subtitle_panel->SetSizer (subtitle_sizer);
415         wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
416         subtitle_sizer->Add (grid, 0, wxALL, 8);
417
418         _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles"));
419         grid->Add (_with_subtitles, 1);
420         grid->AddSpacer (0);
421         
422         {
423                 add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Offset"));
424                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
425                 _subtitle_offset = new wxSpinCtrl (_subtitle_panel);
426                 s->Add (_subtitle_offset);
427                 add_label_to_sizer (s, _subtitle_panel, _("pixels"));
428                 grid->Add (s);
429         }
430
431         {
432                 add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Scale"));
433                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
434                 _subtitle_scale = new wxSpinCtrl (_subtitle_panel);
435                 s->Add (_subtitle_scale);
436                 add_label_to_sizer (s, _subtitle_panel, _("%"));
437                 grid->Add (s);
438         }
439
440         add_label_to_sizer (grid, _subtitle_panel, _("Subtitle Stream"));
441         _subtitle_stream = new wxChoice (_subtitle_panel, wxID_ANY);
442         grid->Add (_subtitle_stream, 1, wxEXPAND | wxALL, 6);
443         grid->AddSpacer (0);
444         
445         _subtitle_offset->SetRange (-1024, 1024);
446         _subtitle_scale->SetRange (1, 1000);
447 }
448
449 void
450 FilmEditor::make_timing_panel ()
451 {
452         _timing_panel = new wxPanel (_content_notebook);
453         wxBoxSizer* timing_sizer = new wxBoxSizer (wxVERTICAL);
454         _timing_panel->SetSizer (timing_sizer);
455         wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
456         timing_sizer->Add (grid, 0, wxALL, 8);
457
458         add_label_to_sizer (grid, _timing_panel, _("Start time"));
459         _start = new Timecode (_timing_panel);
460         grid->Add (_start);
461         add_label_to_sizer (grid, _timing_panel, _("Length"));
462         _length = new Timecode (_timing_panel);
463         grid->Add (_length);
464 }
465
466
467 /** Called when the left crop widget has been changed */
468 void
469 FilmEditor::left_crop_changed (wxCommandEvent &)
470 {
471         shared_ptr<VideoContent> c = selected_video_content ();
472         if (!c) {
473                 return;
474         }
475
476         c->set_left_crop (_left_crop->GetValue ());
477 }
478
479 /** Called when the right crop widget has been changed */
480 void
481 FilmEditor::right_crop_changed (wxCommandEvent &)
482 {
483         shared_ptr<VideoContent> c = selected_video_content ();
484         if (!c) {
485                 return;
486         }
487
488         c->set_right_crop (_right_crop->GetValue ());
489 }
490
491 /** Called when the top crop widget has been changed */
492 void
493 FilmEditor::top_crop_changed (wxCommandEvent &)
494 {
495         shared_ptr<VideoContent> c = selected_video_content ();
496         if (!c) {
497                 return;
498         }
499
500         c->set_top_crop (_top_crop->GetValue ());
501 }
502
503 /** Called when the bottom crop value has been changed */
504 void
505 FilmEditor::bottom_crop_changed (wxCommandEvent &)
506 {
507         shared_ptr<VideoContent> c = selected_video_content ();
508         if (!c) {
509                 return;
510         }
511
512         c->set_bottom_crop (_bottom_crop->GetValue ());
513 }
514
515 /** Called when the name widget has been changed */
516 void
517 FilmEditor::name_changed (wxCommandEvent &)
518 {
519         if (!_film) {
520                 return;
521         }
522
523         _film->set_name (string (_name->GetValue().mb_str()));
524 }
525
526 void
527 FilmEditor::subtitle_offset_changed (wxCommandEvent &)
528 {
529         if (!_film) {
530                 return;
531         }
532
533         _film->set_subtitle_offset (_subtitle_offset->GetValue ());
534 }
535
536 void
537 FilmEditor::subtitle_scale_changed (wxCommandEvent &)
538 {
539         if (!_film) {
540                 return;
541         }
542
543         _film->set_subtitle_scale (_subtitle_scale->GetValue() / 100.0);
544 }
545
546 void
547 FilmEditor::colour_lut_changed (wxCommandEvent &)
548 {
549         if (!_film) {
550                 return;
551         }
552         
553         _film->set_colour_lut (_colour_lut->GetSelection ());
554 }
555
556 void
557 FilmEditor::j2k_bandwidth_changed (wxCommandEvent &)
558 {
559         if (!_film) {
560                 return;
561         }
562         
563         _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1e6);
564 }
565
566 void
567 FilmEditor::dcp_frame_rate_changed (wxCommandEvent &)
568 {
569         if (!_film) {
570                 return;
571         }
572
573         _film->set_dcp_video_frame_rate (
574                 boost::lexical_cast<int> (
575                         wx_to_std (_dcp_frame_rate->GetString (_dcp_frame_rate->GetSelection ()))
576                         )
577                 );
578 }
579
580
581 /** Called when the metadata stored in the Film object has changed;
582  *  so that we can update the GUI.
583  *  @param p Property of the Film that has changed.
584  */
585 void
586 FilmEditor::film_changed (Film::Property p)
587 {
588         ensure_ui_thread ();
589         
590         if (!_film) {
591                 return;
592         }
593
594         stringstream s;
595                 
596         switch (p) {
597         case Film::NONE:
598                 break;
599         case Film::CONTENT:
600                 setup_content ();
601                 setup_formats ();
602 //              setup_format ();
603                 setup_subtitle_control_sensitivity ();
604                 setup_show_audio_sensitivity ();
605                 break;
606         case Film::LOOP:
607                 checked_set (_loop_content, _film->loop() > 1);
608                 checked_set (_loop_count, _film->loop());
609                 setup_loop_sensitivity ();
610                 break;
611         case Film::CONTAINER:
612                 setup_container ();
613                 break;
614         case Film::NAME:
615                 checked_set (_name, _film->name());
616                 setup_dcp_name ();
617                 break;
618         case Film::DCP_CONTENT_TYPE:
619                 checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
620                 setup_dcp_name ();
621                 break;
622         case Film::SCALER:
623                 checked_set (_scaler, Scaler::as_index (_film->scaler ()));
624                 break;
625         case Film::WITH_SUBTITLES:
626                 checked_set (_with_subtitles, _film->with_subtitles ());
627                 setup_subtitle_control_sensitivity ();
628                 setup_dcp_name ();
629                 break;
630         case Film::SUBTITLE_OFFSET:
631                 checked_set (_subtitle_offset, _film->subtitle_offset ());
632                 break;
633         case Film::SUBTITLE_SCALE:
634                 checked_set (_subtitle_scale, _film->subtitle_scale() * 100);
635                 break;
636         case Film::COLOUR_LUT:
637                 checked_set (_colour_lut, _film->colour_lut ());
638                 break;
639         case Film::J2K_BANDWIDTH:
640                 checked_set (_j2k_bandwidth, double (_film->j2k_bandwidth()) / 1e6);
641                 break;
642         case Film::USE_DCI_NAME:
643                 checked_set (_use_dci_name, _film->use_dci_name ());
644                 setup_dcp_name ();
645                 break;
646         case Film::DCI_METADATA:
647                 setup_dcp_name ();
648                 break;
649         case Film::DCP_VIDEO_FRAME_RATE:
650         {
651                 bool done = false;
652                 for (unsigned int i = 0; i < _dcp_frame_rate->GetCount(); ++i) {
653                         if (wx_to_std (_dcp_frame_rate->GetString(i)) == boost::lexical_cast<string> (_film->dcp_video_frame_rate())) {
654                                 checked_set (_dcp_frame_rate, i);
655                                 done = true;
656                                 break;
657                         }
658                 }
659
660                 if (!done) {
661                         checked_set (_dcp_frame_rate, -1);
662                 }
663
664                 _best_dcp_frame_rate->Enable (_film->best_dcp_video_frame_rate () != _film->dcp_video_frame_rate ());
665                 break;
666         }
667         }
668 }
669
670 void
671 FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property)
672 {
673         if (!_film) {
674                 /* We call this method ourselves (as well as using it as a signal handler)
675                    so _film can be 0.
676                 */
677                 return;
678         }
679
680         shared_ptr<Content> content = weak_content.lock ();
681         shared_ptr<VideoContent> video_content;
682         shared_ptr<AudioContent> audio_content;
683         shared_ptr<FFmpegContent> ffmpeg_content;
684         if (content) {
685                 video_content = dynamic_pointer_cast<VideoContent> (content);
686                 audio_content = dynamic_pointer_cast<AudioContent> (content);
687                 ffmpeg_content = dynamic_pointer_cast<FFmpegContent> (content);
688         }
689
690         /* We can't use case {} here */
691         
692         if (property == ContentProperty::START) {
693                 if (content) {
694                         _start->set (content->start (), _film->dcp_video_frame_rate ());
695                 } else {
696                         _start->set (0, 24);
697                 }
698         } else if (property == ContentProperty::LENGTH) {
699                 if (content) {
700                         _length->set (content->length (), _film->dcp_video_frame_rate ());
701                 } else {
702                         _length->set (0, 24);
703                 }
704         } else if (property == VideoContentProperty::VIDEO_CROP) {
705                 checked_set (_left_crop,   video_content ? video_content->crop().left :   0);
706                 checked_set (_right_crop,  video_content ? video_content->crop().right :  0);
707                 checked_set (_top_crop,    video_content ? video_content->crop().top :    0);
708                 checked_set (_bottom_crop, video_content ? video_content->crop().bottom : 0);
709                 setup_scaling_description ();
710         } else if (property == AudioContentProperty::AUDIO_GAIN) {
711                 checked_set (_audio_gain, audio_content ? audio_content->audio_gain() : 0);
712         } else if (property == AudioContentProperty::AUDIO_DELAY) {
713                 checked_set (_audio_delay, audio_content ? audio_content->audio_delay() : 0);
714         } else if (property == AudioContentProperty::AUDIO_MAPPING) {
715                 _audio_mapping->set (audio_content ? audio_content->audio_mapping () : AudioMapping ());
716         } else if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
717                 _subtitle_stream->Clear ();
718                 if (ffmpeg_content) {
719                         vector<shared_ptr<FFmpegSubtitleStream> > s = ffmpeg_content->subtitle_streams ();
720                         if (s.empty ()) {
721                                 _subtitle_stream->Enable (false);
722                         }
723                         for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
724                                 _subtitle_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
725                         }
726                         
727                         if (ffmpeg_content->subtitle_stream()) {
728                                 checked_set (_subtitle_stream, lexical_cast<string> (ffmpeg_content->subtitle_stream()->id));
729                         } else {
730                                 _subtitle_stream->SetSelection (wxNOT_FOUND);
731                         }
732                 }
733                 setup_subtitle_control_sensitivity ();
734         } else if (property == FFmpegContentProperty::AUDIO_STREAMS) {
735                 _audio_stream->Clear ();
736                 if (ffmpeg_content) {
737                         vector<shared_ptr<FFmpegAudioStream> > a = ffmpeg_content->audio_streams ();
738                         for (vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin(); i != a.end(); ++i) {
739                                 _audio_stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx (lexical_cast<string> ((*i)->id))));
740                         }
741                         
742                         if (ffmpeg_content->audio_stream()) {
743                                 checked_set (_audio_stream, lexical_cast<string> (ffmpeg_content->audio_stream()->id));
744                         }
745                 }
746                 setup_show_audio_sensitivity ();
747         } else if (property == FFmpegContentProperty::AUDIO_STREAM) {
748                 setup_dcp_name ();
749                 setup_show_audio_sensitivity ();
750         } else if (property == FFmpegContentProperty::FILTERS) {
751                 if (ffmpeg_content) {
752                         pair<string, string> p = Filter::ffmpeg_strings (ffmpeg_content->filters ());
753                         if (p.first.empty () && p.second.empty ()) {
754                                 _filters->SetLabel (_("None"));
755                         } else {
756                                 string const b = p.first + " " + p.second;
757                                 _filters->SetLabel (std_to_wx (b));
758                         }
759                         _dcp_sizer->Layout ();
760                 }
761         }
762 }
763
764 void
765 FilmEditor::setup_container ()
766 {
767         int n = 0;
768         vector<Container const *> containers = Container::all ();
769         vector<Container const *>::iterator i = containers.begin ();
770         while (i != containers.end() && *i != _film->container ()) {
771                 ++i;
772                 ++n;
773         }
774         
775         if (i == containers.end()) {
776                 checked_set (_container, -1);
777         } else {
778                 checked_set (_container, n);
779         }
780         
781         setup_dcp_name ();
782         setup_scaling_description ();
783 }       
784
785 /** Called when the container widget has been changed */
786 void
787 FilmEditor::container_changed (wxCommandEvent &)
788 {
789         if (!_film) {
790                 return;
791         }
792
793         int const n = _container->GetSelection ();
794         if (n >= 0) {
795                 vector<Container const *> containers = Container::all ();
796                 assert (n < int (containers.size()));
797                 _film->set_container (containers[n]);
798         }
799 }
800
801 /** Called when the DCP content type widget has been changed */
802 void
803 FilmEditor::dcp_content_type_changed (wxCommandEvent &)
804 {
805         if (!_film) {
806                 return;
807         }
808
809         int const n = _dcp_content_type->GetSelection ();
810         if (n != wxNOT_FOUND) {
811                 _film->set_dcp_content_type (DCPContentType::from_index (n));
812         }
813 }
814
815 /** Sets the Film that we are editing */
816 void
817 FilmEditor::set_film (shared_ptr<Film> f)
818 {
819         set_things_sensitive (f != 0);
820
821         if (_film == f) {
822                 return;
823         }
824         
825         _film = f;
826
827         if (_film) {
828                 _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1));
829                 _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _1, _2));
830         }
831
832         if (_film) {
833                 FileChanged (_film->directory ());
834         } else {
835                 FileChanged ("");
836         }
837
838         if (_audio_dialog) {
839                 _audio_dialog->set_film (_film);
840         }
841         
842         film_changed (Film::NAME);
843         film_changed (Film::USE_DCI_NAME);
844         film_changed (Film::CONTENT);
845         film_changed (Film::LOOP);
846         film_changed (Film::DCP_CONTENT_TYPE);
847         film_changed (Film::CONTAINER);
848         film_changed (Film::SCALER);
849         film_changed (Film::WITH_SUBTITLES);
850         film_changed (Film::SUBTITLE_OFFSET);
851         film_changed (Film::SUBTITLE_SCALE);
852         film_changed (Film::COLOUR_LUT);
853         film_changed (Film::J2K_BANDWIDTH);
854         film_changed (Film::DCI_METADATA);
855         film_changed (Film::DCP_VIDEO_FRAME_RATE);
856
857         film_content_changed (boost::shared_ptr<Content> (), ContentProperty::START);
858         film_content_changed (boost::shared_ptr<Content> (), ContentProperty::LENGTH);
859         film_content_changed (boost::shared_ptr<Content> (), VideoContentProperty::VIDEO_CROP);
860         film_content_changed (boost::shared_ptr<Content> (), AudioContentProperty::AUDIO_GAIN);
861         film_content_changed (boost::shared_ptr<Content> (), AudioContentProperty::AUDIO_DELAY);
862         film_content_changed (boost::shared_ptr<Content> (), AudioContentProperty::AUDIO_MAPPING);
863         film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAMS);
864         film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::SUBTITLE_STREAM);
865         film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAMS);
866         film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::AUDIO_STREAM);
867         film_content_changed (boost::shared_ptr<Content> (), FFmpegContentProperty::FILTERS);
868 }
869
870 /** Updates the sensitivity of lots of widgets to a given value.
871  *  @param s true to make sensitive, false to make insensitive.
872  */
873 void
874 FilmEditor::set_things_sensitive (bool s)
875 {
876         _generally_sensitive = s;
877         
878         _name->Enable (s);
879         _use_dci_name->Enable (s);
880         _edit_dci_button->Enable (s);
881         _format->Enable (s);
882         _content->Enable (s);
883         _left_crop->Enable (s);
884         _right_crop->Enable (s);
885         _top_crop->Enable (s);
886         _bottom_crop->Enable (s);
887         _filters_button->Enable (s);
888         _scaler->Enable (s);
889         _dcp_content_type->Enable (s);
890         _best_dcp_frame_rate->Enable (s);
891         _dcp_frame_rate->Enable (s);
892         _colour_lut->Enable (s);
893         _j2k_bandwidth->Enable (s);
894         _audio_gain->Enable (s);
895         _audio_gain_calculate_button->Enable (s);
896         _show_audio->Enable (s);
897         _audio_delay->Enable (s);
898
899         setup_subtitle_control_sensitivity ();
900         setup_show_audio_sensitivity ();
901         setup_content_sensitivity ();
902 }
903
904 /** Called when the `Edit filters' button has been clicked */
905 void
906 FilmEditor::edit_filters_clicked (wxCommandEvent &)
907 {
908         shared_ptr<Content> c = selected_content ();
909         if (!c) {
910                 return;
911         }
912
913         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
914         if (!fc) {
915                 return;
916         }
917         
918         FilterDialog* d = new FilterDialog (this, fc->filters());
919         d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, fc, _1));
920         d->ShowModal ();
921         d->Destroy ();
922 }
923
924 /** Called when the scaler widget has been changed */
925 void
926 FilmEditor::scaler_changed (wxCommandEvent &)
927 {
928         if (!_film) {
929                 return;
930         }
931
932         int const n = _scaler->GetSelection ();
933         if (n >= 0) {
934                 _film->set_scaler (Scaler::from_index (n));
935         }
936 }
937
938 void
939 FilmEditor::audio_gain_changed (wxCommandEvent &)
940 {
941         shared_ptr<AudioContent> ac = selected_audio_content ();
942         if (!ac) {
943                 return;
944         }
945
946         ac->set_audio_gain (_audio_gain->GetValue ());
947 }
948
949 void
950 FilmEditor::audio_delay_changed (wxCommandEvent &)
951 {
952         shared_ptr<AudioContent> ac = selected_audio_content ();
953         if (!ac) {
954                 return;
955         }
956
957         ac->set_audio_delay (_audio_delay->GetValue ());
958 }
959
960 void
961 FilmEditor::setup_main_notebook_size ()
962 {
963         _main_notebook->InvalidateBestSize ();
964
965         _content_sizer->Layout ();
966         _content_sizer->SetSizeHints (_content_panel);
967         _dcp_sizer->Layout ();
968         _dcp_sizer->SetSizeHints (_dcp_panel);
969
970         _main_notebook->Fit ();
971         Fit ();
972 }
973
974 void
975 FilmEditor::audio_gain_calculate_button_clicked (wxCommandEvent &)
976 {
977         GainCalculatorDialog* d = new GainCalculatorDialog (this);
978         d->ShowModal ();
979
980         if (d->wanted_fader() == 0 || d->actual_fader() == 0) {
981                 d->Destroy ();
982                 return;
983         }
984         
985         _audio_gain->SetValue (
986                 Config::instance()->sound_processor()->db_for_fader_change (
987                         d->wanted_fader (),
988                         d->actual_fader ()
989                         )
990                 );
991
992         /* This appears to be necessary, as the change is not signalled,
993            I think.
994         */
995         wxCommandEvent dummy;
996         audio_gain_changed (dummy);
997         
998         d->Destroy ();
999 }
1000
1001 void
1002 FilmEditor::setup_formats ()
1003 {
1004         _formats = Format::all ();
1005
1006         _format->Clear ();
1007         for (vector<Format const *>::iterator i = _formats.begin(); i != _formats.end(); ++i) {
1008                 _format->Append (std_to_wx ((*i)->name ()));
1009         }
1010
1011         _dcp_sizer->Layout ();
1012 }
1013
1014 void
1015 FilmEditor::with_subtitles_toggled (wxCommandEvent &)
1016 {
1017         if (!_film) {
1018                 return;
1019         }
1020
1021         _film->set_with_subtitles (_with_subtitles->GetValue ());
1022 }
1023
1024 void
1025 FilmEditor::setup_subtitle_control_sensitivity ()
1026 {
1027         bool h = false;
1028         if (_generally_sensitive && _film) {
1029                 h = _film->has_subtitles ();
1030         }
1031         
1032         _with_subtitles->Enable (h);
1033
1034         bool j = false;
1035         if (_film) {
1036                 j = _film->with_subtitles ();
1037         }
1038         
1039         _subtitle_offset->Enable (j);
1040         _subtitle_scale->Enable (j);
1041 }
1042
1043 void
1044 FilmEditor::use_dci_name_toggled (wxCommandEvent &)
1045 {
1046         if (!_film) {
1047                 return;
1048         }
1049
1050         _film->set_use_dci_name (_use_dci_name->GetValue ());
1051 }
1052
1053 void
1054 FilmEditor::edit_dci_button_clicked (wxCommandEvent &)
1055 {
1056         if (!_film) {
1057                 return;
1058         }
1059
1060         DCIMetadataDialog* d = new DCIMetadataDialog (this, _film->dci_metadata ());
1061         d->ShowModal ();
1062         _film->set_dci_metadata (d->dci_metadata ());
1063         d->Destroy ();
1064 }
1065
1066 void
1067 FilmEditor::active_jobs_changed (bool a)
1068 {
1069         set_things_sensitive (!a);
1070 }
1071
1072 void
1073 FilmEditor::setup_dcp_name ()
1074 {
1075         string s = _film->dcp_name (true);
1076         if (s.length() > 28) {
1077                 _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
1078                 _dcp_name->SetToolTip (std_to_wx (s));
1079         } else {
1080                 _dcp_name->SetLabel (std_to_wx (s));
1081         }
1082 }
1083
1084 void
1085 FilmEditor::show_audio_clicked (wxCommandEvent &)
1086 {
1087         if (_audio_dialog) {
1088                 _audio_dialog->Destroy ();
1089                 _audio_dialog = 0;
1090         }
1091         
1092         _audio_dialog = new AudioDialog (this);
1093         _audio_dialog->Show ();
1094         _audio_dialog->set_film (_film);
1095 }
1096
1097 void
1098 FilmEditor::best_dcp_frame_rate_clicked (wxCommandEvent &)
1099 {
1100         if (!_film) {
1101                 return;
1102         }
1103         
1104         _film->set_dcp_video_frame_rate (_film->best_dcp_video_frame_rate ());
1105 }
1106
1107 void
1108 FilmEditor::setup_show_audio_sensitivity ()
1109 {
1110         _show_audio->Enable (_film);
1111 }
1112
1113 void
1114 FilmEditor::setup_content ()
1115 {
1116         string selected_summary;
1117         int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1118         if (s != -1) {
1119                 selected_summary = wx_to_std (_content->GetItemText (s));
1120         }
1121         
1122         _content->DeleteAllItems ();
1123
1124         Playlist::ContentList content = _film->content ();
1125         for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
1126                 int const t = _content->GetItemCount ();
1127                 _content->InsertItem (t, std_to_wx ((*i)->summary ()));
1128                 if ((*i)->summary() == selected_summary) {
1129                         _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1130                 }
1131         }
1132
1133         if (selected_summary.empty () && !content.empty ()) {
1134                 /* Select the item of content if non was selected before */
1135                 _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1136         }
1137 }
1138
1139 void
1140 FilmEditor::content_add_clicked (wxCommandEvent &)
1141 {
1142         wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE);
1143         int const r = d->ShowModal ();
1144         d->Destroy ();
1145
1146         if (r != wxID_OK) {
1147                 return;
1148         }
1149
1150         wxArrayString paths;
1151         d->GetPaths (paths);
1152
1153         for (unsigned int i = 0; i < paths.GetCount(); ++i) {
1154                 boost::filesystem::path p (wx_to_std (paths[i]));
1155
1156                 shared_ptr<Content> c;
1157
1158                 if (ImageMagickContent::valid_file (p)) {
1159                         c.reset (new ImageMagickContent (_film, p));
1160                 } else if (SndfileContent::valid_file (p)) {
1161                         c.reset (new SndfileContent (_film, p));
1162                 } else {
1163                         c.reset (new FFmpegContent (_film, p));
1164                 }
1165
1166                 _film->examine_and_add_content (c);
1167         }
1168 }
1169
1170 void
1171 FilmEditor::content_remove_clicked (wxCommandEvent &)
1172 {
1173         shared_ptr<Content> c = selected_content ();
1174         if (c) {
1175                 _film->remove_content (c);
1176         }
1177 }
1178
1179 void
1180 FilmEditor::content_selection_changed (wxListEvent &)
1181 {
1182         setup_content_sensitivity ();
1183         shared_ptr<Content> s = selected_content ();
1184         film_content_changed (s, ContentProperty::START);
1185         film_content_changed (s, ContentProperty::LENGTH);
1186         film_content_changed (s, VideoContentProperty::VIDEO_CROP);
1187         film_content_changed (s, AudioContentProperty::AUDIO_GAIN);
1188         film_content_changed (s, AudioContentProperty::AUDIO_DELAY);
1189         film_content_changed (s, AudioContentProperty::AUDIO_MAPPING);
1190         film_content_changed (s, FFmpegContentProperty::AUDIO_STREAM);
1191         film_content_changed (s, FFmpegContentProperty::AUDIO_STREAMS);
1192         film_content_changed (s, FFmpegContentProperty::SUBTITLE_STREAM);
1193 }
1194
1195 void
1196 FilmEditor::setup_content_sensitivity ()
1197 {
1198         _content_add->Enable (_generally_sensitive);
1199
1200         shared_ptr<Content> selection = selected_content ();
1201
1202         _content_remove->Enable (selection && _generally_sensitive);
1203         _content_timeline->Enable (_generally_sensitive);
1204
1205         _video_panel->Enable    (selection && dynamic_pointer_cast<VideoContent>  (selection) && _generally_sensitive);
1206         _audio_panel->Enable    (selection && dynamic_pointer_cast<AudioContent>  (selection) && _generally_sensitive);
1207         _subtitle_panel->Enable (selection && dynamic_pointer_cast<FFmpegContent> (selection) && _generally_sensitive);
1208 }
1209
1210 shared_ptr<Content>
1211 FilmEditor::selected_content ()
1212 {
1213         int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
1214         if (s == -1) {
1215                 return shared_ptr<Content> ();
1216         }
1217
1218         Playlist::ContentList c = _film->content ();
1219         if (s < 0 || size_t (s) >= c.size ()) {
1220                 return shared_ptr<Content> ();
1221         }
1222         
1223         return c[s];
1224 }
1225
1226 shared_ptr<VideoContent>
1227 FilmEditor::selected_video_content ()
1228 {
1229         shared_ptr<Content> c = selected_content ();
1230         if (!c) {
1231                 return shared_ptr<VideoContent> ();
1232         }
1233
1234         return dynamic_pointer_cast<VideoContent> (c);
1235 }
1236
1237 shared_ptr<AudioContent>
1238 FilmEditor::selected_audio_content ()
1239 {
1240         shared_ptr<Content> c = selected_content ();
1241         if (!c) {
1242                 return shared_ptr<AudioContent> ();
1243         }
1244
1245         return dynamic_pointer_cast<AudioContent> (c);
1246 }
1247
1248 void
1249 FilmEditor::setup_scaling_description ()
1250 {
1251         wxString d;
1252
1253 #if 0   
1254 XXX
1255         int lines = 0;
1256
1257         if (_film->video_size().width && _film->video_size().height) {
1258                 d << wxString::Format (
1259                         _("Original video is %dx%d (%.2f:1)\n"),
1260                         _film->video_size().width, _film->video_size().height,
1261                         float (_film->video_size().width) / _film->video_size().height
1262                         );
1263                 ++lines;
1264         }
1265
1266         Crop const crop = _film->crop ();
1267         if (crop.left || crop.right || crop.top || crop.bottom) {
1268                 libdcp::Size const cropped = _film->cropped_size (_film->video_size ());
1269                 d << wxString::Format (
1270                         _("Cropped to %dx%d (%.2f:1)\n"),
1271                         cropped.width, cropped.height,
1272                         float (cropped.width) / cropped.height
1273                         );
1274                 ++lines;
1275         }
1276
1277         Format const * format = _film->format ();
1278         if (format) {
1279                 int const padding = format->dcp_padding (_film);
1280                 libdcp::Size scaled = format->dcp_size ();
1281                 scaled.width -= padding * 2;
1282                 d << wxString::Format (
1283                         _("Scaled to %dx%d (%.2f:1)\n"),
1284                         scaled.width, scaled.height,
1285                         float (scaled.width) / scaled.height
1286                         );
1287                 ++lines;
1288
1289                 if (padding) {
1290                         d << wxString::Format (
1291                                 _("Padded with black to %dx%d (%.2f:1)\n"),
1292                                 format->dcp_size().width, format->dcp_size().height,
1293                                 float (format->dcp_size().width) / format->dcp_size().height
1294                                 );
1295                         ++lines;
1296                 }
1297         }
1298
1299         for (int i = lines; i < 4; ++i) {
1300                 d << wxT ("\n ");
1301         }
1302
1303 #endif  
1304         _scaling_description->SetLabel (d);
1305 }
1306
1307 void
1308 FilmEditor::loop_content_toggled (wxCommandEvent &)
1309 {
1310         if (_loop_content->GetValue ()) {
1311                 _film->set_loop (_loop_count->GetValue ());
1312         } else {
1313                 _film->set_loop (1);
1314         }
1315                 
1316         setup_loop_sensitivity ();
1317 }
1318
1319 void
1320 FilmEditor::loop_count_changed (wxCommandEvent &)
1321 {
1322         _film->set_loop (_loop_count->GetValue ());
1323 }
1324
1325 void
1326 FilmEditor::setup_loop_sensitivity ()
1327 {
1328         _loop_count->Enable (_loop_content->GetValue ());
1329 }
1330
1331 void
1332 FilmEditor::content_timeline_clicked (wxCommandEvent &)
1333 {
1334         if (_timeline_dialog) {
1335                 _timeline_dialog->Destroy ();
1336                 _timeline_dialog = 0;
1337         }
1338         
1339         _timeline_dialog = new TimelineDialog (this, _film);
1340         _timeline_dialog->Show ();
1341 }
1342
1343 void
1344 FilmEditor::audio_stream_changed (wxCommandEvent &)
1345 {
1346         shared_ptr<Content> c = selected_content ();
1347         if (!c) {
1348                 return;
1349         }
1350         
1351         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
1352         if (!fc) {
1353                 return;
1354         }
1355         
1356         vector<shared_ptr<FFmpegAudioStream> > a = fc->audio_streams ();
1357         vector<shared_ptr<FFmpegAudioStream> >::iterator i = a.begin ();
1358         string const s = string_client_data (_audio_stream->GetClientObject (_audio_stream->GetSelection ()));
1359         while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
1360                 ++i;
1361         }
1362
1363         if (i != a.end ()) {
1364                 fc->set_audio_stream (*i);
1365         }
1366
1367         if (!fc->audio_stream ()) {
1368                 _audio_description->SetLabel (wxT (""));
1369         } else {
1370                 wxString s;
1371                 if (fc->audio_channels() == 1) {
1372                         s << _("1 channel");
1373                 } else {
1374                         s << fc->audio_channels() << wxT (" ") << _("channels");
1375                 }
1376                 s << wxT (", ") << fc->content_audio_frame_rate() << _("Hz");
1377                 _audio_description->SetLabel (s);
1378         }
1379 }
1380
1381
1382
1383 void
1384 FilmEditor::subtitle_stream_changed (wxCommandEvent &)
1385 {
1386         shared_ptr<Content> c = selected_content ();
1387         if (!c) {
1388                 return;
1389         }
1390         
1391         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
1392         if (!fc) {
1393                 return;
1394         }
1395         
1396         vector<shared_ptr<FFmpegSubtitleStream> > a = fc->subtitle_streams ();
1397         vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = a.begin ();
1398         string const s = string_client_data (_subtitle_stream->GetClientObject (_subtitle_stream->GetSelection ()));
1399         while (i != a.end() && lexical_cast<string> ((*i)->id) != s) {
1400                 ++i;
1401         }
1402
1403         if (i != a.end ()) {
1404                 fc->set_subtitle_stream (*i);
1405         }
1406 }
1407
1408 void
1409 FilmEditor::audio_mapping_changed (AudioMapping m)
1410 {
1411         shared_ptr<Content> c = selected_content ();
1412         if (!c) {
1413                 return;
1414         }
1415         
1416         shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (c);
1417         if (!ac) {
1418                 return;
1419         }
1420
1421         ac->set_audio_mapping (m);
1422 }
1423
1424 void
1425 FilmEditor::start_changed ()
1426 {
1427         shared_ptr<Content> c = selected_content ();
1428         if (!c) {
1429                 return;
1430         }
1431
1432         c->set_start (_start->get (_film->dcp_video_frame_rate ()));
1433 }
1434
1435 void
1436 FilmEditor::length_changed ()
1437 {
1438         shared_ptr<Content> c = selected_content ();
1439         if (!c) {
1440                 return;
1441         }
1442
1443         shared_ptr<ImageMagickContent> ic = dynamic_pointer_cast<ImageMagickContent> (c);
1444         if (ic) {
1445                 ic->set_video_length (_length->get(_film->dcp_video_frame_rate()) * ic->video_frame_rate() / TIME_HZ);
1446         }
1447 }
1448
1449 void
1450 FilmEditor::set_selection (weak_ptr<Content> wc)
1451 {
1452         Playlist::ContentList content = _film->content ();
1453         for (size_t i = 0; i < content.size(); ++i) {
1454                 if (content[i] == wc.lock ()) {
1455                         _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1456                 } else {
1457                         _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
1458                 }
1459         }
1460 }