Remove some debugging.
[dcpomatic.git] / src / wx / timeline.cc
1 /*
2     Copyright (C) 2013 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 <list>
21 #include <wx/graphics.h>
22 #include <boost/weak_ptr.hpp>
23 #include "lib/film.h"
24 #include "lib/playlist.h"
25 #include "film_editor.h"
26 #include "timeline.h"
27 #include "wx_util.h"
28
29 using std::list;
30 using std::cout;
31 using std::max;
32 using boost::shared_ptr;
33 using boost::weak_ptr;
34 using boost::dynamic_pointer_cast;
35 using boost::bind;
36
37 class View : public boost::noncopyable
38 {
39 public:
40         View (Timeline& t)
41                 : _timeline (t)
42         {
43
44         }
45                 
46         void paint (wxGraphicsContext* g)
47         {
48                 _last_paint_bbox = bbox ();
49                 do_paint (g);
50         }
51         
52         void force_redraw ()
53         {
54                 _timeline.force_redraw (_last_paint_bbox);
55                 _timeline.force_redraw (bbox ());
56         }
57
58         virtual dcpomatic::Rect<int> bbox () const = 0;
59
60 protected:
61         virtual void do_paint (wxGraphicsContext *) = 0;
62         
63         int time_x (Time t) const
64         {
65                 return _timeline.tracks_position().x + t * _timeline.pixels_per_time_unit();
66         }
67         
68         Timeline& _timeline;
69
70 private:
71         dcpomatic::Rect<int> _last_paint_bbox;
72 };
73
74 class ContentView : public View
75 {
76 public:
77         ContentView (Timeline& tl, shared_ptr<Content> c, int t)
78                 : View (tl)
79                 , _content (c)
80                 , _track (t)
81                 , _selected (false)
82         {
83                 _content_connection = c->Changed.connect (bind (&ContentView::content_changed, this, _2));
84         }
85
86         dcpomatic::Rect<int> bbox () const
87         {
88                 shared_ptr<const Film> film = _timeline.film ();
89                 if (!film) {
90                         return dcpomatic::Rect<int> ();
91                 }
92                 
93                 return dcpomatic::Rect<int> (
94                         time_x (_content->start ()) - 8,
95                         y_pos (_track) - 8,
96                         _content->length () * _timeline.pixels_per_time_unit() + 16,
97                         _timeline.track_height() + 16
98                         );
99         }
100
101         void set_selected (bool s) {
102                 _selected = s;
103                 force_redraw ();
104         }
105         
106         bool selected () const {
107                 return _selected;
108         }
109
110         shared_ptr<Content> content () const {
111                 return _content;
112         }
113
114         void set_track (int t) {
115                 _track = t;
116         }
117
118         int track () const {
119                 return _track;
120         }
121
122         virtual wxString type () const = 0;
123         virtual wxColour colour () const = 0;
124         
125 private:
126
127         void do_paint (wxGraphicsContext* gc)
128         {
129                 shared_ptr<const Film> film = _timeline.film ();
130                 if (!film) {
131                         return;
132                 }
133
134                 Time const start = _content->start ();
135                 Time const len = _content->length ();
136
137                 wxColour selected (colour().Red() / 2, colour().Green() / 2, colour().Blue() / 2);
138
139                 gc->SetPen (*wxBLACK_PEN);
140                 
141                 gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxPENSTYLE_SOLID));
142                 if (_selected) {
143                         gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (selected, wxBRUSHSTYLE_SOLID));
144                 } else {
145                         gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (colour(), wxBRUSHSTYLE_SOLID));
146                 }
147
148                 wxGraphicsPath path = gc->CreatePath ();
149                 path.MoveToPoint    (time_x (start),       y_pos (_track) + 4);
150                 path.AddLineToPoint (time_x (start + len), y_pos (_track) + 4);
151                 path.AddLineToPoint (time_x (start + len), y_pos (_track + 1) - 4);
152                 path.AddLineToPoint (time_x (start),       y_pos (_track + 1) - 4);
153                 path.AddLineToPoint (time_x (start),       y_pos (_track) + 4);
154                 gc->StrokePath (path);
155                 gc->FillPath (path);
156
157                 wxString name = wxString::Format (wxT ("%s [%s]"), std_to_wx (_content->file().filename().string()).data(), type().data());
158                 wxDouble name_width;
159                 wxDouble name_height;
160                 wxDouble name_descent;
161                 wxDouble name_leading;
162                 gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
163                 
164                 gc->Clip (wxRegion (time_x (start), y_pos (_track), len * _timeline.pixels_per_time_unit(), _timeline.track_height()));
165                 gc->DrawText (name, time_x (start) + 12, y_pos (_track + 1) - name_height - 4);
166                 gc->ResetClip ();
167         }
168
169         int y_pos (int t) const
170         {
171                 return _timeline.tracks_position().y + t * _timeline.track_height();
172         }
173
174         void content_changed (int p)
175         {
176                 if (p == ContentProperty::START || p == ContentProperty::LENGTH) {
177                         force_redraw ();
178                 }
179         }
180
181         /* This must be a shared_ptr, not a weak_ptr, as in the looped case this
182            will be the only remaining pointer to the looped content that we get
183            from the playlist.
184         */
185         boost::shared_ptr<Content> _content;
186         int _track;
187         bool _selected;
188
189         boost::signals2::scoped_connection _content_connection;
190 };
191
192 class AudioContentView : public ContentView
193 {
194 public:
195         AudioContentView (Timeline& tl, shared_ptr<Content> c, int t)
196                 : ContentView (tl, c, t)
197         {}
198         
199 private:
200         wxString type () const
201         {
202                 return _("audio");
203         }
204
205         wxColour colour () const
206         {
207                 return wxColour (149, 121, 232, 255);
208         }
209 };
210
211 class VideoContentView : public ContentView
212 {
213 public:
214         VideoContentView (Timeline& tl, shared_ptr<Content> c, int t)
215                 : ContentView (tl, c, t)
216         {}
217
218 private:        
219
220         wxString type () const
221         {
222                 return _("video");
223         }
224
225         wxColour colour () const
226         {
227                 return wxColour (242, 92, 120, 255);
228         }
229 };
230
231 class TimeAxisView : public View
232 {
233 public:
234         TimeAxisView (Timeline& tl, int y)
235                 : View (tl)
236                 , _y (y)
237         {}
238         
239         dcpomatic::Rect<int> bbox () const
240         {
241                 return dcpomatic::Rect<int> (0, _y - 4, _timeline.width(), 24);
242         }
243
244         void set_y (int y)
245         {
246                 _y = y;
247                 force_redraw ();
248         }
249
250 private:        
251
252         void do_paint (wxGraphicsContext* gc)
253         {
254                 gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
255                 
256                 int mark_interval = rint (128 / (TIME_HZ * _timeline.pixels_per_time_unit ()));
257                 if (mark_interval > 5) {
258                         mark_interval -= mark_interval % 5;
259                 }
260                 if (mark_interval > 10) {
261                         mark_interval -= mark_interval % 10;
262                 }
263                 if (mark_interval > 60) {
264                         mark_interval -= mark_interval % 60;
265                 }
266                 if (mark_interval > 3600) {
267                         mark_interval -= mark_interval % 3600;
268                 }
269                 
270                 if (mark_interval < 1) {
271                         mark_interval = 1;
272                 }
273
274                 wxGraphicsPath path = gc->CreatePath ();
275                 path.MoveToPoint (_timeline.x_offset(), _y);
276                 path.AddLineToPoint (_timeline.width(), _y);
277                 gc->StrokePath (path);
278
279                 Time t = 0;
280                 while ((t * _timeline.pixels_per_time_unit()) < _timeline.width()) {
281                         wxGraphicsPath path = gc->CreatePath ();
282                         path.MoveToPoint (time_x (t), _y - 4);
283                         path.AddLineToPoint (time_x (t), _y + 4);
284                         gc->StrokePath (path);
285
286                         int tc = t / TIME_HZ;
287                         int const h = tc / 3600;
288                         tc -= h * 3600;
289                         int const m = tc / 60;
290                         tc -= m * 60;
291                         int const s = tc;
292                         
293                         wxString str = wxString::Format (wxT ("%02d:%02d:%02d"), h, m, s);
294                         wxDouble str_width;
295                         wxDouble str_height;
296                         wxDouble str_descent;
297                         wxDouble str_leading;
298                         gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
299                         
300                         int const tx = _timeline.x_offset() + t * _timeline.pixels_per_time_unit();
301                         if ((tx + str_width) < _timeline.width()) {
302                                 gc->DrawText (str, time_x (t), _y + 16);
303                         }
304                         
305                         t += mark_interval * TIME_HZ;
306                 }
307         }
308
309 private:
310         int _y;
311 };
312
313 Timeline::Timeline (wxWindow* parent, FilmEditor* ed, shared_ptr<Film> film)
314         : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
315         , _film_editor (ed)
316         , _film (film)
317         , _time_axis_view (new TimeAxisView (*this, 32))
318         , _tracks (0)
319         , _pixels_per_time_unit (0)
320         , _left_down (false)
321         , _down_view_start (0)
322         , _first_move (false)
323 {
324 #ifndef __WXOSX__
325         SetDoubleBuffered (true);
326 #endif  
327
328         setup_pixels_per_time_unit ();
329         
330         Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (Timeline::paint), 0, this);
331         Connect (wxID_ANY, wxEVT_LEFT_DOWN, wxMouseEventHandler (Timeline::left_down), 0, this);
332         Connect (wxID_ANY, wxEVT_LEFT_UP, wxMouseEventHandler (Timeline::left_up), 0, this);
333         Connect (wxID_ANY, wxEVT_MOTION, wxMouseEventHandler (Timeline::mouse_moved), 0, this);
334         Connect (wxID_ANY, wxEVT_SIZE, wxSizeEventHandler (Timeline::resized), 0, this);
335
336         playlist_changed ();
337
338         SetMinSize (wxSize (640, tracks() * track_height() + 96));
339
340         _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
341
342         _views.push_back (_time_axis_view);
343 }
344
345 void
346 Timeline::paint (wxPaintEvent &)
347 {
348         wxPaintDC dc (this);
349
350         wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
351         if (!gc) {
352                 return;
353         }
354
355         gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
356
357         for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
358                 (*i)->paint (gc);
359         }
360
361         delete gc;
362 }
363
364 void
365 Timeline::playlist_changed ()
366 {
367         shared_ptr<const Film> fl = _film.lock ();
368         if (!fl) {
369                 return;
370         }
371
372         _views.clear ();
373
374         Playlist::ContentList content = fl->playlist()->content_with_loop ();
375
376         for (Playlist::ContentList::iterator i = content.begin(); i != content.end(); ++i) {
377                 if (dynamic_pointer_cast<VideoContent> (*i)) {
378                         _views.push_back (shared_ptr<View> (new VideoContentView (*this, *i, 0)));
379                 }
380                 if (dynamic_pointer_cast<AudioContent> (*i)) {
381                         _views.push_back (shared_ptr<View> (new AudioContentView (*this, *i, 0)));
382                 }
383         }
384
385         assign_tracks ();
386         Refresh ();
387 }
388
389 void
390 Timeline::assign_tracks ()
391 {
392         for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
393                 shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
394                 if (cv) {
395                         cv->set_track (0);
396                         _tracks = 1;
397                 }
398         }
399
400         for (list<shared_ptr<View> >::iterator i = _views.begin(); i != _views.end(); ++i) {
401                 shared_ptr<AudioContentView> acv = dynamic_pointer_cast<AudioContentView> (*i);
402                 if (!acv) {
403                         continue;
404                 }
405         
406                 shared_ptr<Content> acv_content = acv->content();
407                 
408                 int t = 1;
409                 while (1) {
410                         list<shared_ptr<View> >::iterator j = _views.begin();
411                         while (j != _views.end()) {
412                                 shared_ptr<AudioContentView> test = dynamic_pointer_cast<AudioContentView> (*j);
413                                 if (!test) {
414                                         ++j;
415                                         continue;
416                                 }
417                                 
418                                 shared_ptr<Content> test_content = test->content();
419                                         
420                                 if (test && test->track() == t) {
421                                         if ((acv_content->start() < test_content->start() && test_content->start() < acv_content->end()) ||
422                                             (acv_content->start() < test_content->end()   && test_content->end()   < acv_content->end())) {
423                                                 /* we have an overlap on track `t' */
424                                                 ++t;
425                                                 break;
426                                         }
427                                 }
428                                 
429                                 ++j;
430                         }
431
432                         if (j == _views.end ()) {
433                                 /* no overlap on `t' */
434                                 break;
435                         }
436                 }
437
438                 acv->set_track (t);
439                 _tracks = max (_tracks, t + 1);
440         }
441
442         _time_axis_view->set_y (tracks() * track_height() + 32);
443 }
444
445 int
446 Timeline::tracks () const
447 {
448         return _tracks;
449 }
450
451 void
452 Timeline::setup_pixels_per_time_unit ()
453 {
454         shared_ptr<const Film> film = _film.lock ();
455         if (!film) {
456                 return;
457         }
458
459         _pixels_per_time_unit = static_cast<double>(width() - x_offset() * 2) / film->length_with_loop();
460 }
461
462 void
463 Timeline::left_down (wxMouseEvent& ev)
464 {
465         list<shared_ptr<View> >::iterator i = _views.begin();
466         Position<int> const p (ev.GetX(), ev.GetY());
467         while (i != _views.end() && !(*i)->bbox().contains (p)) {
468                 ++i;
469         }
470
471         _down_view.reset ();
472
473         if (i != _views.end ()) {
474                 shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*i);
475                 if (cv) {
476                         _down_view = cv;
477                         _down_view_start = cv->content()->start ();
478                 }
479         }
480
481         for (list<shared_ptr<View> >::iterator j = _views.begin(); j != _views.end(); ++j) {
482                 shared_ptr<ContentView> cv = dynamic_pointer_cast<ContentView> (*j);
483                 if (cv) {
484                         cv->set_selected (i == j);
485                         if (i == j) {
486                                 _film_editor->set_selection (cv->content ());
487                         }
488                 }
489         }
490
491         _left_down = true;
492         _down_point = ev.GetPosition ();
493         _first_move = false;
494
495         if (_down_view) {
496                 _down_view->content()->set_change_signals_frequent (true);
497         }
498 }
499
500 void
501 Timeline::left_up (wxMouseEvent& ev)
502 {
503         _left_down = false;
504
505         if (_down_view) {
506                 _down_view->content()->set_change_signals_frequent (false);
507         }
508
509         set_start_from_event (ev);
510 }
511
512 void
513 Timeline::mouse_moved (wxMouseEvent& ev)
514 {
515         if (!_left_down) {
516                 return;
517         }
518
519         set_start_from_event (ev);
520 }
521
522 void
523 Timeline::set_start_from_event (wxMouseEvent& ev)
524 {
525         wxPoint const p = ev.GetPosition();
526
527         if (!_first_move) {
528                 int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
529                 if (dist < 8) {
530                         return;
531                 }
532                 _first_move = true;
533         }
534
535         Time const time_diff = (p.x - _down_point.x) / _pixels_per_time_unit;
536         if (_down_view) {
537                 _down_view->content()->set_start (max (static_cast<Time> (0), _down_view_start + time_diff));
538
539                 shared_ptr<Film> film = _film.lock ();
540                 assert (film);
541                 film->set_sequence_video (false);
542         }
543 }
544
545 void
546 Timeline::force_redraw (dcpomatic::Rect<int> const & r)
547 {
548         RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
549 }
550
551 shared_ptr<const Film>
552 Timeline::film () const
553 {
554         return _film.lock ();
555 }
556
557 void
558 Timeline::resized (wxSizeEvent &)
559 {
560         setup_pixels_per_time_unit ();
561 }