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