Secondary-modifier-click in the summary locates the playhead to the click.
[ardour.git] / gtk2_ardour / editor_summary.cc
1 /*
2     Copyright (C) 2009 Paul Davis 
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 "ardour/session.h"
21 #include "time_axis_view.h"
22 #include "streamview.h"
23 #include "editor_summary.h"
24 #include "gui_thread.h"
25 #include "editor.h"
26 #include "region_view.h"
27 #include "rgb_macros.h"
28 #include "keyboard.h"
29
30 using namespace std;
31 using namespace sigc;
32 using namespace ARDOUR;
33
34 /** Construct an EditorSummary.
35  *  @param e Editor to represent.
36  */
37 EditorSummary::EditorSummary (Editor* e)
38         : _editor (e),
39           _session (0),
40           _pixmap (0),
41           _regions_dirty (true),
42           _width (512),
43           _height (32),
44           _x_scale (1),
45           _y_scale (1),
46           _last_playhead (-1),
47           _move_dragging (false),
48           _moved (false),
49           _zoom_dragging (false)
50           
51 {
52         
53 }
54
55 /** Set the session.
56  *  @param s Session.
57  */
58 void
59 EditorSummary::set_session (Session* s)
60 {
61         _session = s;
62
63         Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
64
65         _session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
66         _session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
67         _session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
68         _editor->playhead_cursor->PositionChanged.connect (mem_fun (*this, &EditorSummary::playhead_position_changed));
69
70         set_dirty ();
71 }
72
73 /** Destroy */
74 EditorSummary::~EditorSummary ()
75 {
76         if (_pixmap) {
77                 gdk_pixmap_unref (_pixmap);
78         }
79 }
80
81 /** Handle an expose event.
82  *  @param event Event from GTK.
83  */
84 bool
85 EditorSummary::on_expose_event (GdkEventExpose* event)
86 {
87         /* Render the regions pixmap */
88         
89         Gdk::Rectangle const exposure (
90                 event->area.x, event->area.y, event->area.width, event->area.height
91                 );
92
93         Gdk::Rectangle r = exposure;
94         Gdk::Rectangle content (0, 0, _width, _height);
95         bool intersects;
96         r.intersect (content, intersects);
97         
98         if (intersects) {
99
100                 GdkPixmap* p = get_pixmap (get_window()->gobj ());
101
102                 gdk_draw_drawable (
103                         get_window()->gobj(),
104                         get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
105                         p,
106                         r.get_x(),
107                         r.get_y(),
108                         r.get_x(),
109                         r.get_y(),
110                         r.get_width(),
111                         r.get_height()
112                         );
113         }
114
115         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
116
117         /* Render the view rectangle */
118         
119         pair<double, double> x;
120         pair<double, double> y;
121         get_editor (&x, &y);
122         
123         cairo_move_to (cr, x.first, y.first);
124         cairo_line_to (cr, x.second, y.first);
125         cairo_line_to (cr, x.second, y.second);
126         cairo_line_to (cr, x.first, y.second);
127         cairo_line_to (cr, x.first, y.first);
128         cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
129         cairo_fill_preserve (cr);
130         cairo_set_line_width (cr, 1);
131         cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
132         cairo_stroke (cr);
133
134         /* Playhead */
135
136         cairo_set_line_width (cr, 1);
137         /* XXX: colour should be set from configuration file */
138         cairo_set_source_rgba (cr, 1, 0, 0, 1);
139
140         double const p = _editor->playhead_cursor->current_frame * _x_scale;
141         cairo_move_to (cr, p, 0);
142         cairo_line_to (cr, p, _height);
143         cairo_stroke (cr);
144         _last_playhead = p;
145
146         cairo_destroy (cr);
147         
148         return true;
149 }
150
151 /** @param drawable GDK drawable.
152  *  @return pixmap for the regions.
153  */
154 GdkPixmap *
155 EditorSummary::get_pixmap (GdkDrawable* drawable)
156 {
157         if (_regions_dirty) {
158
159                 if (_pixmap) {
160                         gdk_pixmap_unref (_pixmap);
161                 }
162                 _pixmap = gdk_pixmap_new (drawable, _width, _height, -1);
163
164                 cairo_t* cr = gdk_cairo_create (_pixmap);
165                 render (cr);
166                 cairo_destroy (cr);
167
168                 _regions_dirty = false;
169         }
170
171         return _pixmap;
172 }
173
174 /** Render the required regions to a cairo context.
175  *  @param cr Context.
176  */
177 void
178 EditorSummary::render (cairo_t* cr)
179 {
180         if (_session == 0) {
181                 return;
182         }
183
184         /* background */
185         
186         cairo_set_source_rgb (cr, 0, 0, 0);
187         cairo_rectangle (cr, 0, 0, _width, _height);
188         cairo_fill (cr);
189
190         /* compute total height of all tracks */
191         
192         int h = 0;
193         int max_height = 0;
194         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
195                 int const t = (*i)->effective_height ();
196                 h += t;
197                 max_height = max (max_height, t);
198         }
199
200         nframes_t const start = _session->current_start_frame ();
201         _x_scale = static_cast<double> (_width) / (_session->current_end_frame() - start);
202         _y_scale = static_cast<double> (_height) / h;
203
204         /* tallest a region should ever be in the summary, in pixels */
205         int const tallest_region_pixels = 4;
206
207         if (max_height * _y_scale > tallest_region_pixels) {
208                 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
209         }
210
211         /* render regions */
212
213         double y = 0;
214         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
215                 StreamView* s = (*i)->view ();
216
217                 if (s) {
218                         double const h = (*i)->effective_height () * _y_scale;
219                         cairo_set_line_width (cr, h);
220
221                         s->foreach_regionview (bind (
222                                                        mem_fun (*this, &EditorSummary::render_region),
223                                                        cr,
224                                                        start,
225                                                        y + h / 2
226                                                        ));
227                         y += h;
228                 }
229         }
230
231 }
232
233 /** Render a region for the summary.
234  *  @param r Region view.
235  *  @param cr Cairo context.
236  *  @param start Frame offset that the summary starts at.
237  *  @param y y coordinate to render at.
238  */
239 void
240 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
241 {
242         uint32_t const c = r->get_fill_color ();
243         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
244                         
245         cairo_move_to (cr, (r->region()->position() - start) * _x_scale, y);
246         cairo_line_to (cr, ((r->region()->position() - start + r->region()->length())) * _x_scale, y);
247         cairo_stroke (cr);
248 }
249
250 /** Set the summary so that the whole thing will be re-rendered next time it is required */
251 void
252 EditorSummary::set_dirty ()
253 {
254         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_dirty));
255
256         _regions_dirty = true;
257         queue_draw ();
258 }
259
260 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
261 void
262 EditorSummary::set_overlays_dirty ()
263 {
264         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_overlays_dirty));
265         queue_draw ();
266 }
267
268 /** Handle a size request.
269  *  @param req GTK requisition
270  */
271 void
272 EditorSummary::on_size_request (Gtk::Requisition *req)
273 {
274         /* Use a dummy, small width and the actual height that we want */
275         req->width = 64;
276         req->height = _height;
277 }
278
279 /** Handle a size allocation.
280  *  @param alloc GTK allocation.
281  */
282 void
283 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
284 {
285         Gtk::EventBox::on_size_allocate (alloc);
286
287         _width = alloc.get_width ();
288         _height = alloc.get_height ();
289
290         set_dirty ();
291 }
292
293 void
294 EditorSummary::centre_on_click (GdkEventButton* ev)
295 {
296         pair<double, double> xr;
297         pair<double, double> yr;
298         get_editor (&xr, &yr);
299
300         double const w = xr.second - xr.first;
301         double const h = yr.second - yr.first;
302         
303         xr.first = ev->x - w / 2;
304         xr.second = ev->x + w / 2;
305         yr.first = ev->y - h / 2;
306         yr.second = ev->y + h / 2;
307
308         if (xr.first < 0) {
309                 xr.first = 0;
310                 xr.second = w;
311         } else if (xr.second > _width) {
312                 xr.second = _width;
313                 xr.first = _width - w;
314         }
315
316         if (yr.first < 0) {
317                 yr.first = 0;
318                 yr.second = h;
319         } else if (yr.second > _height) {
320                 yr.second = _height;
321                 yr.first = _height - h;
322         }
323
324         set_editor (xr, yr);
325 }
326
327 /** Handle a button press.
328  *  @param ev GTK event.
329  */
330 bool
331 EditorSummary::on_button_press_event (GdkEventButton* ev)
332 {
333         if (ev->button == 1) {
334
335                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
336
337                         /* secondary-modifier-click: locate playhead */
338                         if (_session) {
339                                 _session->request_locate (ev->x / _x_scale + _session->current_start_frame());
340                         }
341
342                 } else {
343                 
344                         pair<double, double> xr;
345                         pair<double, double> yr;
346                         get_editor (&xr, &yr);
347                         
348                         if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
349                                 
350                                 _start_editor_x = xr;
351                                 _start_editor_y = yr;
352                                 _start_mouse_x = ev->x;
353                                 _start_mouse_y = ev->y;
354                                 
355                                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
356                                         
357                                         /* modifier-click inside the view rectangle: start a zoom drag */
358                                         
359                                         double const hx = (xr.first + xr.second) * 0.5;
360                                         _zoom_left = ev->x < hx;
361                                         _zoom_dragging = true;
362                                         _editor->_dragging_playhead = true;
363                                         
364                                         /* In theory, we could support vertical dragging, which logically
365                                            might scale track heights in order to make the editor reflect
366                                            the dragged viewbox.  However, having tried this:
367                                            a) it's hard to do
368                                            b) it's quite slow
369                                            c) it doesn't seem particularly useful, especially with the
370                                            limited height of the summary
371                                            
372                                            So at the moment we don't support that...
373                                         */
374                                         
375                                 } else {
376                                         
377                                         /* ordinary click inside the view rectangle: start a move drag */
378                                         
379                                         _move_dragging = true;
380                                         _moved = false;
381                                         _editor->_dragging_playhead = true;
382                                 }
383                                 
384                         } else {
385                         
386                                 /* click outside the view rectangle: centre the view around the mouse click */
387                                 centre_on_click (ev);
388                         }
389                 }
390         }
391         
392         return true;
393 }
394
395 void
396 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
397 {
398         x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
399         x->second = x->first + _editor->current_page_frames() * _x_scale;
400
401         y->first = _editor->vertical_adjustment.get_value() * _y_scale;
402         y->second = y->first + _editor->canvas_height () * _y_scale;
403 }
404
405 bool
406 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
407 {
408         pair<double, double> xr = _start_editor_x;
409         pair<double, double> yr = _start_editor_y;
410         
411         if (_move_dragging) {
412
413                 _moved = true;
414
415                 xr.first += ev->x - _start_mouse_x;
416                 xr.second += ev->x - _start_mouse_x;
417                 yr.first += ev->y - _start_mouse_y;
418                 yr.second += ev->y - _start_mouse_y;
419                 
420                 set_editor (xr, yr);
421
422         } else if (_zoom_dragging) {
423
424                 double const dx = ev->x - _start_mouse_x;
425
426                 if (_zoom_left) {
427                         xr.first += dx;
428                 } else {
429                         xr.second += dx;
430                 }
431
432                 set_editor (xr, yr);
433         }
434                 
435         return true;
436 }
437
438 bool
439 EditorSummary::on_button_release_event (GdkEventButton* ev)
440 {
441         if (_move_dragging && !_moved) {
442                 centre_on_click (ev);
443         }
444
445         _move_dragging = false;
446         _zoom_dragging = false;
447         _editor->_dragging_playhead = false;
448         return true;
449 }
450
451 bool
452 EditorSummary::on_scroll_event (GdkEventScroll* ev)
453 {
454         /* mouse wheel */
455         
456         pair<double, double> xr;
457         pair<double, double> yr;
458         get_editor (&xr, &yr);
459
460         double const amount = 8;
461                 
462         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
463                 
464                 if (ev->direction == GDK_SCROLL_UP) {
465                         xr.first += amount;
466                         xr.second += amount;
467                 } else {
468                         xr.first -= amount;
469                         xr.second -= amount;
470                 }
471
472         } else {
473                 
474                 if (ev->direction == GDK_SCROLL_DOWN) {
475                         yr.first += amount;
476                         yr.second += amount;
477                 } else {
478                         yr.first -= amount;
479                         yr.second -= amount;
480                 }
481                 
482         }
483         
484         set_editor (xr, yr);
485         return true;
486 }
487
488 void
489 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
490 {
491         if (_editor->pending_visual_change.idle_handler_id < 0) {
492
493                 /* As a side-effect, the Editor's visual change idle handler processes
494                    pending GTK events.  Hence this motion notify handler can be called
495                    in the middle of a visual change idle handler, and if this happens,
496                    the queue_visual_change calls below modify the variables that the
497                    idle handler is working with.  This causes problems.  Hence the
498                    check above.  It ensures that we won't modify the pending visual change
499                    while a visual change idle handler is in progress.  It's not perfect,
500                    as it also means that we won't change these variables if an idle handler
501                    is merely pending but not executing.  But c'est la vie.
502                 */
503
504                 _editor->reset_x_origin (x.first / _x_scale);
505                 _editor->reset_y_origin (y.first / _y_scale);
506
507                 double const nx = (
508                         ((x.second - x.first) / _x_scale) /
509                         _editor->frame_to_unit (_editor->current_page_frames())
510                         );
511
512                 if (nx != _editor->get_current_zoom ()) {
513                         _editor->reset_zoom (nx);
514                 }
515         }
516 }
517
518 void
519 EditorSummary::playhead_position_changed (nframes64_t p)
520 {
521         if (int (p * _x_scale) != int (_last_playhead)) {
522                 set_overlays_dirty ();
523         }
524 }
525
526