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