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