Add basic timeline window.
[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 "timeline.h"
23 #include "wx_util.h"
24 #include "playlist.h"
25
26 using std::list;
27 using std::cout;
28 using std::max;
29 using boost::shared_ptr;
30
31 int const Timeline::_track_height = 64;
32
33 Timeline::Timeline (wxWindow* parent, shared_ptr<Playlist> pl)
34         : wxPanel (parent)
35         , _playlist (pl)
36 {
37         SetDoubleBuffered (true);
38         
39         Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (Timeline::paint), 0, this);
40
41         if (pl->audio_from() == Playlist::AUDIO_FFMPEG) {
42                 SetMinSize (wxSize (640, _track_height * 2 + 96));
43         } else {
44                 SetMinSize (wxSize (640, _track_height * (max (1UL, pl->audio().size()) + 1) + 96));
45         }
46 }
47
48 template <class T>
49 int
50 plot_content_list (
51         list<shared_ptr<const T> > content, wxGraphicsContext* gc, int x, int y, double pixels_per_second, int track_height, wxString type, bool consecutive
52         )
53 {
54         Time t = 0;
55         for (typename list<shared_ptr<const T> >::iterator i = content.begin(); i != content.end(); ++i) {
56                 Time const len = (*i)->temporal_length ();
57                 wxGraphicsPath path = gc->CreatePath ();
58                 path.MoveToPoint (x + t * pixels_per_second, y);
59                 path.AddLineToPoint (x + (t + len) * pixels_per_second, y);
60                 path.AddLineToPoint (x + (t + len) * pixels_per_second, y + track_height);
61                 path.AddLineToPoint (x + t * pixels_per_second, y + track_height);
62                 path.AddLineToPoint (x + t * pixels_per_second, y);
63                 gc->StrokePath (path);
64                 gc->FillPath (path);
65
66                 wxString name = wxString::Format ("%s [%s]", std_to_wx ((*i)->file().filename().string()), type);
67                 wxDouble name_width;
68                 wxDouble name_height;
69                 wxDouble name_descent;
70                 wxDouble name_leading;
71                 gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
72                 
73                 gc->Clip (wxRegion (x + t * pixels_per_second, y, len * pixels_per_second, track_height));
74                 gc->DrawText (name, t * pixels_per_second + 12, y + track_height - name_height - 4);
75                 gc->ResetClip ();
76
77                 if (consecutive) {
78                         t += len;
79                 } else {
80                         y += track_height;
81                 }
82         }
83
84         if (consecutive) {
85                 y += track_height;
86         }
87
88         return y;
89 }
90
91 void
92 Timeline::paint (wxPaintEvent &)
93 {
94         wxPaintDC dc (this);
95
96         shared_ptr<Playlist> pl = _playlist.lock ();
97         if (!pl) {
98                 return;
99         }
100
101         wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
102         if (!gc) {
103                 return;
104         }
105
106         int const x_offset = 8;
107         int y = 8;
108         int const width = GetSize().GetWidth();
109         double const pixels_per_second = (width - x_offset * 2) / (pl->content_length() / pl->video_frame_rate());
110
111         gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
112
113         gc->SetPen (*wxBLACK_PEN);
114 #if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
115         gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxPENSTYLE_SOLID));
116         gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (149, 121, 232, 255), wxBRUSHSTYLE_SOLID));
117 #else                   
118         gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, SOLID));
119         gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (149, 121, 232, 255), SOLID));
120 #endif
121         y = plot_content_list (pl->video (), gc, x_offset, y, pixels_per_second, _track_height, _("video"), true);
122         
123 #if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
124         gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (242, 92, 120, 255), wxBRUSHSTYLE_SOLID));
125 #else                   
126         gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (242, 92, 120, 255), SOLID));
127 #endif
128         y = plot_content_list (pl->audio (), gc, x_offset, y, pixels_per_second, _track_height, _("audio"), pl->audio_from() == Playlist::AUDIO_FFMPEG);
129
130         /* Time axis */
131
132 #if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
133         gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
134 #else               
135         gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, SOLID));
136 #endif              
137                     
138         int mark_interval = rint (128 / pixels_per_second);
139         if (mark_interval > 5) {
140                 mark_interval -= mark_interval % 5;
141         }
142         if (mark_interval > 10) {
143                 mark_interval -= mark_interval % 10;
144         }
145         if (mark_interval > 60) {
146                 mark_interval -= mark_interval % 60;
147         }
148         if (mark_interval > 3600) {
149                 mark_interval -= mark_interval % 3600;
150         }
151
152         if (mark_interval < 1) {
153                 mark_interval = 1;
154         }
155
156         wxGraphicsPath path = gc->CreatePath ();
157         path.MoveToPoint (x_offset, y + 40);
158         path.AddLineToPoint (width, y + 40);
159         gc->StrokePath (path);
160
161         double t = 0;
162         while ((t * pixels_per_second) < width) {
163                 wxGraphicsPath path = gc->CreatePath ();
164                 path.MoveToPoint (x_offset + t * pixels_per_second, y + 36);
165                 path.AddLineToPoint (x_offset + t * pixels_per_second, y + 44);
166                 gc->StrokePath (path);
167
168                 int tc = t;
169                 int const h = tc / 3600;
170                 tc -= h * 3600;
171                 int const m = tc / 60;
172                 tc -= m * 60;
173                 int const s = tc;
174
175                 wxString str = wxString::Format ("%02d:%02d:%02d", h, m, s);
176                 wxDouble str_width;
177                 wxDouble str_height;
178                 wxDouble str_descent;
179                 wxDouble str_leading;
180                 gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
181
182                 int const tx = x_offset + t * pixels_per_second;
183                 if ((tx + str_width) < width) {
184                         gc->DrawText (str, x_offset + t * pixels_per_second, y + 60);
185                 }
186                 t += mark_interval;
187         }
188
189         delete gc;
190 }
191