Put video on 0, subtitles on 1, audio on 2 and up.
[dcpomatic.git] / src / wx / timeline.cc
1 /*
2     Copyright (C) 2013-2016 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 #include "film_editor.h"
21 #include "timeline.h"
22 #include "timeline_time_axis_view.h"
23 #include "timeline_reels_view.h"
24 #include "timeline_video_content_view.h"
25 #include "timeline_audio_content_view.h"
26 #include "timeline_subtitle_content_view.h"
27 #include "content_panel.h"
28 #include "wx_util.h"
29 #include "lib/film.h"
30 #include "lib/playlist.h"
31 #include "lib/image_content.h"
32 #include "lib/timer.h"
33 #include "lib/audio_content.h"
34 #include "lib/subtitle_content.h"
35 #include <wx/graphics.h>
36 #include <boost/weak_ptr.hpp>
37 #include <boost/foreach.hpp>
38 #include <list>
39 #include <iostream>
40
41 using std::list;
42 using std::cout;
43 using std::max;
44 using boost::shared_ptr;
45 using boost::weak_ptr;
46 using boost::dynamic_pointer_cast;
47 using boost::bind;
48 using boost::optional;
49
50 Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film)
51         : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
52         , _content_panel (cp)
53         , _film (film)
54         , _time_axis_view (new TimelineTimeAxisView (*this, 64))
55         , _reels_view (new TimelineReelsView (*this, 32))
56         , _tracks (0)
57         , _left_down (false)
58         , _down_view_position (0)
59         , _first_move (false)
60         , _menu (this)
61         , _snap (true)
62 {
63 #ifndef __WXOSX__
64         SetDoubleBuffered (true);
65 #endif
66
67         Bind (wxEVT_PAINT,      boost::bind (&Timeline::paint,       this));
68         Bind (wxEVT_LEFT_DOWN,  boost::bind (&Timeline::left_down,   this, _1));
69         Bind (wxEVT_LEFT_UP,    boost::bind (&Timeline::left_up,     this, _1));
70         Bind (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down,  this, _1));
71         Bind (wxEVT_MOTION,     boost::bind (&Timeline::mouse_moved, this, _1));
72         Bind (wxEVT_SIZE,       boost::bind (&Timeline::resized,     this));
73
74         film_changed (Film::CONTENT);
75
76         SetMinSize (wxSize (640, tracks() * track_height() + 96));
77
78         _film_changed_connection = film->Changed.connect (bind (&Timeline::film_changed, this, _1));
79         _film_content_changed_connection = film->ContentChanged.connect (bind (&Timeline::film_content_changed, this, _2, _3));
80 }
81
82 void
83 Timeline::paint ()
84 {
85         wxPaintDC dc (this);
86
87         wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
88         if (!gc) {
89                 return;
90         }
91
92         for (TimelineViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
93                 (*i)->paint (gc);
94         }
95
96         delete gc;
97 }
98
99 void
100 Timeline::film_changed (Film::Property p)
101 {
102         if (p == Film::CONTENT || p == Film::REEL_TYPE || p == Film::REEL_LENGTH) {
103                 ensure_ui_thread ();
104                 recreate_views ();
105         } else if (p == Film::CONTENT_ORDER) {
106                 Refresh ();
107         }
108 }
109
110 void
111 Timeline::recreate_views ()
112 {
113         shared_ptr<const Film> film = _film.lock ();
114         if (!film) {
115                 return;
116         }
117
118         _views.clear ();
119         _views.push_back (_time_axis_view);
120         _views.push_back (_reels_view);
121
122         BOOST_FOREACH (shared_ptr<Content> i, film->content ()) {
123                 if (dynamic_pointer_cast<VideoContent> (i)) {
124                         _views.push_back (shared_ptr<TimelineView> (new TimelineVideoContentView (*this, i)));
125                 }
126
127                 shared_ptr<AudioContent> ac = dynamic_pointer_cast<AudioContent> (i);
128                 if (ac && !ac->audio_mapping().mapped_output_channels().empty ()) {
129                         _views.push_back (shared_ptr<TimelineView> (new TimelineAudioContentView (*this, i)));
130                 }
131
132                 shared_ptr<SubtitleContent> sc = dynamic_pointer_cast<SubtitleContent> (i);
133                 if (sc && sc->has_subtitles ()) {
134                         _views.push_back (shared_ptr<TimelineView> (new TimelineSubtitleContentView (*this, sc)));
135                 }
136         }
137
138         assign_tracks ();
139         setup_pixels_per_second ();
140         Refresh ();
141 }
142
143 void
144 Timeline::film_content_changed (int property, bool frequent)
145 {
146         ensure_ui_thread ();
147
148         if (property == AudioContentProperty::AUDIO_STREAMS) {
149                 recreate_views ();
150         } else if (!frequent) {
151                 setup_pixels_per_second ();
152                 Refresh ();
153         }
154 }
155
156 void
157 Timeline::assign_tracks ()
158 {
159         for (TimelineViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
160                 shared_ptr<TimelineContentView> c = dynamic_pointer_cast<TimelineContentView> (*i);
161                 if (c) {
162                         c->unset_track ();
163                 }
164         }
165
166         for (TimelineViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
167                 shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (*i);
168                 if (!cv) {
169                         continue;
170                 }
171
172                 shared_ptr<Content> content = cv->content();
173
174                 if (dynamic_pointer_cast<VideoContent> (content)) {
175                         /* Video on track 0 */
176                         cv->set_track (0);
177                         _tracks = max (_tracks, 1);
178                         continue;
179                 } else if (dynamic_pointer_cast<SubtitleContent> (content)) {
180                         /* Subtitles on track 1 */
181                         cv->set_track (1);
182                         _tracks = max (_tracks, 2);
183                         continue;
184                 }
185
186                 /* Audio on tracks 2 and up */
187                 int t = 2;
188
189                 DCPTimePeriod content_period (content->position(), content->end());
190
191                 while (true) {
192                         TimelineViewList::iterator j = _views.begin();
193                         while (j != _views.end()) {
194                                 shared_ptr<TimelineAudioContentView> test = dynamic_pointer_cast<TimelineAudioContentView> (*j);
195                                 if (!test) {
196                                         ++j;
197                                         continue;
198                                 }
199
200                                 shared_ptr<Content> test_content = test->content();
201
202                                 if (test && test->track() && test->track().get() == t) {
203                                         if (content_period.overlaps (DCPTimePeriod(test_content->position(), test_content->end()))) {
204                                                 /* we have an overlap on track `t' */
205                                                 ++t;
206                                                 break;
207                                         }
208                                 }
209
210                                 ++j;
211                         }
212
213                         if (j == _views.end ()) {
214                                 /* no overlap on `t' */
215                                 break;
216                         }
217                 }
218
219                 cv->set_track (t);
220                 _tracks = max (_tracks, t + 1);
221         }
222
223         _time_axis_view->set_y (tracks() * track_height() + 64);
224         _reels_view->set_y (tracks() * track_height() + 32);
225 }
226
227 int
228 Timeline::tracks () const
229 {
230         return _tracks;
231 }
232
233 void
234 Timeline::setup_pixels_per_second ()
235 {
236         shared_ptr<const Film> film = _film.lock ();
237         if (!film || film->length() == DCPTime ()) {
238                 return;
239         }
240
241         _pixels_per_second = static_cast<double>(width() - x_offset() * 2) / film->length().seconds ();
242 }
243
244 shared_ptr<TimelineView>
245 Timeline::event_to_view (wxMouseEvent& ev)
246 {
247         /* Search backwards through views so that we find the uppermost one first */
248         TimelineViewList::reverse_iterator i = _views.rbegin();
249         Position<int> const p (ev.GetX(), ev.GetY());
250         while (i != _views.rend() && !(*i)->bbox().contains (p)) {
251                 shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (*i);
252                 ++i;
253         }
254
255         if (i == _views.rend ()) {
256                 return shared_ptr<TimelineView> ();
257         }
258
259         return *i;
260 }
261
262 void
263 Timeline::left_down (wxMouseEvent& ev)
264 {
265         shared_ptr<TimelineView> view = event_to_view (ev);
266         shared_ptr<TimelineContentView> content_view = dynamic_pointer_cast<TimelineContentView> (view);
267
268         _down_view.reset ();
269
270         if (content_view) {
271                 _down_view = content_view;
272                 _down_view_position = content_view->content()->position ();
273         }
274
275         for (TimelineViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
276                 shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (*i);
277                 if (!cv) {
278                         continue;
279                 }
280
281                 if (!ev.ShiftDown ()) {
282                         cv->set_selected (view == *i);
283                 }
284         }
285
286         if (content_view && ev.ShiftDown ()) {
287                 content_view->set_selected (!content_view->selected ());
288         }
289
290         _left_down = true;
291         _down_point = ev.GetPosition ();
292         _first_move = false;
293
294         if (_down_view) {
295                 /* Pre-compute the points that we might snap to */
296                 for (TimelineViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
297                         shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (*i);
298                         if (!cv || cv == _down_view || cv->content() == _down_view->content()) {
299                                 continue;
300                         }
301
302                         _start_snaps.push_back (cv->content()->position());
303                         _end_snaps.push_back (cv->content()->position());
304                         _start_snaps.push_back (cv->content()->end());
305                         _end_snaps.push_back (cv->content()->end());
306
307                         BOOST_FOREACH (DCPTime i, cv->content()->reel_split_points()) {
308                                 _start_snaps.push_back (i);
309                         }
310                 }
311
312                 /* Tell everyone that things might change frequently during the drag */
313                 _down_view->content()->set_change_signals_frequent (true);
314         }
315 }
316
317 void
318 Timeline::left_up (wxMouseEvent& ev)
319 {
320         _left_down = false;
321
322         if (_down_view) {
323                 _down_view->content()->set_change_signals_frequent (false);
324                 _content_panel->set_selection (_down_view->content ());
325         }
326
327         set_position_from_event (ev);
328
329         /* Clear up up the stuff we don't do during drag */
330         assign_tracks ();
331         setup_pixels_per_second ();
332         Refresh ();
333
334         _start_snaps.clear ();
335         _end_snaps.clear ();
336 }
337
338 void
339 Timeline::mouse_moved (wxMouseEvent& ev)
340 {
341         if (!_left_down) {
342                 return;
343         }
344
345         set_position_from_event (ev);
346 }
347
348 void
349 Timeline::right_down (wxMouseEvent& ev)
350 {
351         shared_ptr<TimelineView> view = event_to_view (ev);
352         shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (view);
353         if (!cv) {
354                 return;
355         }
356
357         if (!cv->selected ()) {
358                 clear_selection ();
359                 cv->set_selected (true);
360         }
361
362         _menu.popup (_film, selected_content (), selected_views (), ev.GetPosition ());
363 }
364
365 void
366 Timeline::maybe_snap (DCPTime a, DCPTime b, optional<DCPTime>& nearest_distance) const
367 {
368         DCPTime const d = a - b;
369         if (!nearest_distance || d.abs() < nearest_distance.get().abs()) {
370                 nearest_distance = d;
371         }
372 }
373
374 void
375 Timeline::set_position_from_event (wxMouseEvent& ev)
376 {
377         if (!_pixels_per_second) {
378                 return;
379         }
380
381         double const pps = _pixels_per_second.get ();
382
383         wxPoint const p = ev.GetPosition();
384
385         if (!_first_move) {
386                 /* We haven't moved yet; in that case, we must move the mouse some reasonable distance
387                    before the drag is considered to have started.
388                 */
389                 int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
390                 if (dist < 8) {
391                         return;
392                 }
393                 _first_move = true;
394         }
395
396         if (!_down_view) {
397                 return;
398         }
399
400         DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps);
401
402         if (_snap) {
403
404                 DCPTime const new_end = new_position + _down_view->content()->length_after_trim();
405                 /* Signed `distance' to nearest thing (i.e. negative is left on the timeline,
406                    positive is right).
407                 */
408                 optional<DCPTime> nearest_distance;
409
410                 /* Find the nearest snap point */
411
412                 BOOST_FOREACH (DCPTime i, _start_snaps) {
413                         maybe_snap (i, new_position, nearest_distance);
414                 }
415
416                 BOOST_FOREACH (DCPTime i, _end_snaps) {
417                         maybe_snap (i, new_end, nearest_distance);
418                 }
419
420                 if (nearest_distance) {
421                         /* Snap if it's close; `close' means within a proportion of the time on the timeline */
422                         if (nearest_distance.get().abs() < DCPTime::from_seconds ((width() / pps) / 64)) {
423                                 new_position += nearest_distance.get ();
424                         }
425                 }
426         }
427
428         if (new_position < DCPTime ()) {
429                 new_position = DCPTime ();
430         }
431
432         _down_view->content()->set_position (new_position);
433
434         shared_ptr<Film> film = _film.lock ();
435         DCPOMATIC_ASSERT (film);
436         film->set_sequence (false);
437 }
438
439 void
440 Timeline::force_redraw (dcpomatic::Rect<int> const & r)
441 {
442         RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
443 }
444
445 shared_ptr<const Film>
446 Timeline::film () const
447 {
448         return _film.lock ();
449 }
450
451 void
452 Timeline::resized ()
453 {
454         setup_pixels_per_second ();
455 }
456
457 void
458 Timeline::clear_selection ()
459 {
460         for (TimelineViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
461                 shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (*i);
462                 if (cv) {
463                         cv->set_selected (false);
464                 }
465         }
466 }
467
468 TimelineContentViewList
469 Timeline::selected_views () const
470 {
471         TimelineContentViewList sel;
472
473         for (TimelineViewList::const_iterator i = _views.begin(); i != _views.end(); ++i) {
474                 shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (*i);
475                 if (cv && cv->selected()) {
476                         sel.push_back (cv);
477                 }
478         }
479
480         return sel;
481 }
482
483 ContentList
484 Timeline::selected_content () const
485 {
486         ContentList sel;
487         TimelineContentViewList views = selected_views ();
488
489         for (TimelineContentViewList::const_iterator i = views.begin(); i != views.end(); ++i) {
490                 sel.push_back ((*i)->content ());
491         }
492
493         return sel;
494 }
495
496 void
497 Timeline::set_selection (ContentList selection)
498 {
499         for (TimelineViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
500                 shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (*i);
501                 if (cv) {
502                         cv->set_selected (find (selection.begin(), selection.end(), cv->content ()) != selection.end ());
503                 }
504         }
505 }