43c3cd3571c016b37459aa8d9a185ae5ec3e99df
[ardour.git] / gtk2_ardour / editor_summary.cc
1 #include "ardour/session.h"
2 #include "time_axis_view.h"
3 #include "streamview.h"
4 #include "editor_summary.h"
5 #include "gui_thread.h"
6 #include "editor.h"
7 #include "region_view.h"
8
9 using namespace std;
10 using namespace sigc;
11 using namespace ARDOUR;
12
13 /** Construct an EditorSummary.
14  *  @param e Editor to represent.
15  */
16 EditorSummary::EditorSummary (Editor* e)
17         : _editor (e),
18           _session (0),
19           _pixmap (0),
20           _regions_dirty (true),
21           _width (512),
22           _height (64),
23           _pixels_per_frame (1),
24           _vertical_scale (1),
25           _dragging (false),
26           _moved (false)
27           
28 {
29         
30 }
31
32 /** Set the session.
33  *  @param s Session.
34  */
35 void
36 EditorSummary::set_session (Session* s)
37 {
38         _session = s;
39
40         Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
41
42         _session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
43         _session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
44         _session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
45
46         set_dirty ();
47 }
48
49 /** Destroy */
50 EditorSummary::~EditorSummary ()
51 {
52         if (_pixmap) {
53                 gdk_pixmap_unref (_pixmap);
54         }
55 }
56
57 /** Handle an expose event.
58  *  @param event Event from GTK.
59  */
60 bool
61 EditorSummary::on_expose_event (GdkEventExpose* event)
62 {
63         /* Render the regions pixmap */
64         
65         Gdk::Rectangle const exposure (
66                 event->area.x, event->area.y, event->area.width, event->area.height
67                 );
68
69         Gdk::Rectangle r = exposure;
70         Gdk::Rectangle content (0, 0, _width, _height);
71         bool intersects;
72         r.intersect (content, intersects);
73         
74         if (intersects) {
75
76                 GdkPixmap* p = get_pixmap (get_window()->gobj ());
77
78                 gdk_draw_drawable (
79                         get_window()->gobj(),
80                         get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
81                         p,
82                         r.get_x(),
83                         r.get_y(),
84                         r.get_x(),
85                         r.get_y(),
86                         r.get_width(),
87                         r.get_height()
88                         );
89         }
90
91         /* Render the view rectangle */
92         
93         pair<double, double> x;
94         pair<double, double> y;
95         editor_view (&x, &y);
96         
97         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
98
99         cairo_set_source_rgba (cr, 0, 1, 0, 0.5);
100
101         cairo_move_to (cr, x.first, y.first);
102         cairo_line_to (cr, x.second, y.first);
103         cairo_line_to (cr, x.second, y.second);
104         cairo_line_to (cr, x.first, y.second);
105         cairo_line_to (cr, x.first, y.first);
106         cairo_stroke (cr);
107
108         cairo_destroy (cr);
109         
110         return true;
111 }
112
113 /** @param drawable GDK drawable.
114  *  @return pixmap for the regions.
115  */
116 GdkPixmap *
117 EditorSummary::get_pixmap (GdkDrawable* drawable)
118 {
119         if (_regions_dirty) {
120
121                 if (_pixmap) {
122                         gdk_pixmap_unref (_pixmap);
123                 }
124                 _pixmap = gdk_pixmap_new (drawable, _width, _height, -1);
125
126                 cairo_t* cr = gdk_cairo_create (_pixmap);
127                 render (cr);
128                 cairo_destroy (cr);
129
130                 _regions_dirty = false;
131         }
132
133         return _pixmap;
134 }
135
136 /** Render the required regions to a cairo context.
137  *  @param cr Context.
138  */
139 void
140 EditorSummary::render (cairo_t* cr)
141 {
142         if (_session == 0) {
143                 return;
144         }
145
146         /* background */
147         
148         cairo_set_source_rgb (cr, 0, 0, 0);
149         cairo_rectangle (cr, 0, 0, _width, _height);
150         cairo_fill (cr);
151
152         /* compute total height of all tracks */
153         
154         int h = 0;
155         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
156                 h += (*i)->effective_height ();
157         }
158
159         nframes_t const start = _session->current_start_frame ();
160         _pixels_per_frame = static_cast<double> (_width) / (_session->current_end_frame() - start);
161         _vertical_scale = static_cast<double> (_height) / h;
162
163         /* render regions */
164
165         int n = 0;
166         double y = 0;
167         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
168                 StreamView* s = (*i)->view ();
169
170                 if (s) {
171                         double const h = (*i)->effective_height () * _vertical_scale;
172                         cairo_set_line_width (cr, h);
173                         
174                         double const v = ((n % 2) == 0) ? 1 : 0.5;
175                         cairo_set_source_rgb (cr, v, v, v);
176                         
177                         s->foreach_regionview (bind (
178                                                        mem_fun (*this, &EditorSummary::render_region),
179                                                        cr,
180                                                        start,
181                                                        y + h / 2
182                                                        ));
183                         ++n;
184                         y += h;
185                 }
186         }
187
188 }
189
190 /** Render a region for the summary.
191  *  @param r Region view.
192  *  @param cr Cairo context.
193  *  @param start Frame offset that the summary starts at.
194  *  @param y y coordinate to render at.
195  */
196 void
197 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
198 {
199         cairo_move_to (cr, (r->region()->position() - start) * _pixels_per_frame, y);
200         cairo_line_to (cr, ((r->region()->position() - start + r->region()->length())) * _pixels_per_frame, y);
201         cairo_stroke (cr);
202 }
203
204 /** Set the summary so that the whole thing will be re-rendered next time it is required */
205 void
206 EditorSummary::set_dirty ()
207 {
208         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_dirty));
209
210         _regions_dirty = true;
211         queue_draw ();
212 }
213
214 /** Set the summary so that just the view boundary markers will be re-rendered */
215 void
216 EditorSummary::set_bounds_dirty ()
217 {
218         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_bounds_dirty));
219         queue_draw ();
220 }
221
222 /** Handle a size request.
223  *  @param req GTK requisition
224  */
225 void
226 EditorSummary::on_size_request (Gtk::Requisition *req)
227 {
228         /* Use a dummy, small width and the actual height that we want */
229         req->width = 64;
230         req->height = 64;
231 }
232
233 /** Handle a size allocation.
234  *  @param alloc GTK allocation.
235  */
236 void
237 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
238 {
239         Gtk::EventBox::on_size_allocate (alloc);
240
241         _width = alloc.get_width ();
242         _height = alloc.get_height ();
243
244         set_dirty ();
245 }
246
247 void
248 EditorSummary::centre_on_click (GdkEventButton* ev)
249 {
250         nframes_t x = (ev->x / _pixels_per_frame) + _session->current_start_frame();
251         nframes_t const xh = _editor->current_page_frames () / 2;
252         if (x > xh) {
253                 x -= xh;
254         } else {
255                 x = 0;
256         }
257         
258         _editor->reset_x_origin (x);
259         
260         double y = ev->y / _vertical_scale;
261         double const yh = _editor->canvas_height () / 2;
262         if (y > yh) {
263                 y -= yh;
264         } else {
265                 y = 0;
266         }
267         
268         _editor->reset_y_origin (y);
269 }
270
271 /** Handle a button press.
272  *  @param ev GTK event.
273  */
274 bool
275 EditorSummary::on_button_press_event (GdkEventButton* ev)
276 {
277         if (ev->button == 1) {
278
279                 pair<double, double> xr;
280                 pair<double, double> yr;
281                 editor_view (&xr, &yr);
282
283                 if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
284
285                         /* click inside the view rectangle: drag it */
286                         _dragging = true;
287                         _moved = false;
288                         _x_offset = ev->x - xr.first;
289                         _y_offset = ev->y - yr.first;
290                         _editor->_dragging_playhead = true;
291                         
292                 } else {
293                         
294                         /* click outside the view rectangle: centre the view around the mouse click */
295                         centre_on_click (ev);
296                 }
297         }
298
299         return true;
300 }
301
302 void
303 EditorSummary::editor_view (pair<double, double>* x, pair<double, double>* y) const
304 {
305         x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _pixels_per_frame;
306         x->second = x->first + _editor->current_page_frames() * _pixels_per_frame;
307
308         y->first = _editor->get_trackview_group_vertical_offset () * _vertical_scale;
309         y->second = y->first + _editor->canvas_height () * _vertical_scale;
310 }
311
312 bool
313 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
314 {
315         if (!_dragging) {
316                 return false;
317         }
318
319         _moved = true;
320
321         _editor->reset_x_origin (((ev->x - _x_offset) / _pixels_per_frame) + _session->current_start_frame ());
322         _editor->reset_y_origin ((ev->y - _y_offset) / _vertical_scale);
323
324         return true;
325 }
326
327 bool
328 EditorSummary::on_button_release_event (GdkEventButton* ev)
329 {
330         if (_dragging && !_moved) {
331                 centre_on_click (ev);
332         }
333         
334         _dragging = false;
335         _editor->_dragging_playhead = false;
336         return true;
337 }