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