Tweak group tabs class hierarchy and offer the same menu in both editor and mixer...
[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           _track_height (16),
44           _last_playhead (-1),
45           _move_dragging (false),
46           _moved (false),
47           _view_rectangle_x (0, 0),
48           _view_rectangle_y (0, 0),
49           _zoom_dragging (false)
50 {
51         Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
52         _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
53
54         add_events (Gdk::POINTER_MOTION_MASK);  
55 }
56
57 /** Connect to a session.
58  *  @param s Session.
59  */
60 void
61 EditorSummary::set_session (Session* s)
62 {
63         SessionHandlePtr::set_session (s);
64
65         set_dirty ();
66
67         /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
68          * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
69          * emitted when a cut region is added to the `cutlist' playlist.
70          */
71
72         if (_session) {
73                 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
74                 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
75         }
76 }
77
78 /** Handle an expose event.
79  *  @param event Event from GTK.
80  */
81 bool
82 EditorSummary::on_expose_event (GdkEventExpose* event)
83 {
84         CairoWidget::on_expose_event (event);
85
86         if (_session == 0) {
87                 return false;
88         }
89
90         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
91
92         /* Render the view rectangle.  If there is an editor visual pending, don't update
93            the view rectangle now --- wait until the expose event that we'll get after
94            the visual change.  This prevents a flicker.
95         */
96
97         if (_editor->pending_visual_change.idle_handler_id < 0) {
98                 get_editor (&_view_rectangle_x, &_view_rectangle_y);
99         }
100
101         cairo_move_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
102         cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.first);
103         cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.second);
104         cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.second);
105         cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
106         cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
107         cairo_fill_preserve (cr);
108         cairo_set_line_width (cr, 1);
109         cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
110         cairo_stroke (cr);
111
112         /* Playhead */
113
114         cairo_set_line_width (cr, 1);
115         /* XXX: colour should be set from configuration file */
116         cairo_set_source_rgba (cr, 1, 0, 0, 1);
117
118         double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
119         cairo_move_to (cr, p, 0);
120         cairo_line_to (cr, p, _height);
121         cairo_stroke (cr);
122         _last_playhead = p;
123
124         cairo_destroy (cr);
125
126         return true;
127 }
128
129 /** Render the required regions to a cairo context.
130  *  @param cr Context.
131  */
132 void
133 EditorSummary::render (cairo_t* cr)
134 {
135         /* background */
136
137         cairo_set_source_rgb (cr, 0, 0, 0);
138         cairo_rectangle (cr, 0, 0, _width, _height);
139         cairo_fill (cr);
140
141         if (_session == 0) {
142                 return;
143         }
144
145         /* compute start and end points for the summary */
146         
147         nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
148         double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
149         _start = theoretical_start > 0 ? theoretical_start : 0;
150         _end = _session->current_end_frame() + session_length * _overhang_fraction;
151
152         /* compute track height */
153         int N = 0;
154         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
155                 if (!(*i)->hidden()) {
156                         ++N;
157                 }
158         }
159         
160         if (N == 0) {
161                 _track_height = 16;
162         } else {
163                 _track_height = (double) _height / N;
164         }
165
166         /* calculate x scale */
167         if (_end != _start) {
168                 _x_scale = static_cast<double> (_width) / (_end - _start);
169         } else {
170                 _x_scale = 1;
171         }
172
173         /* render tracks and regions */
174
175         double y = 0;
176         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
177
178                 if ((*i)->hidden()) {
179                         continue;
180                 }
181
182                 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
183                 cairo_set_line_width (cr, _track_height - 2);
184                 cairo_move_to (cr, 0, y + _track_height / 2);
185                 cairo_line_to (cr, _width, y + _track_height / 2);
186                 cairo_stroke (cr);
187                 
188                 StreamView* s = (*i)->view ();
189
190                 if (s) {
191                         cairo_set_line_width (cr, _track_height * 0.6);
192
193                         s->foreach_regionview (sigc::bind (
194                                                        sigc::mem_fun (*this, &EditorSummary::render_region),
195                                                        cr,
196                                                        y + _track_height / 2
197                                                        ));
198                 }
199                 
200                 y += _track_height;
201         }
202
203         /* start and end markers */
204
205         cairo_set_line_width (cr, 1);
206         cairo_set_source_rgb (cr, 1, 1, 0);
207
208         double const p = (_session->current_start_frame() - _start) * _x_scale;
209         cairo_move_to (cr, p, 0);
210         cairo_line_to (cr, p, _height);
211         cairo_stroke (cr);
212
213         double const q = (_session->current_end_frame() - _start) * _x_scale;
214         cairo_move_to (cr, q, 0);
215         cairo_line_to (cr, q, _height);
216         cairo_stroke (cr);
217 }
218
219 /** Render a region for the summary.
220  *  @param r Region view.
221  *  @param cr Cairo context.
222  *  @param y y coordinate to render at.
223  */
224 void
225 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
226 {
227         uint32_t const c = r->get_fill_color ();
228         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
229
230         if (r->region()->position() > _start) {
231                 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
232         } else {
233                 cairo_move_to (cr, 0, y);
234         }
235
236         if ((r->region()->position() + r->region()->length()) > _start) {
237                 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
238         } else {
239                 cairo_line_to (cr, 0, y);
240         }
241
242         cairo_stroke (cr);
243 }
244
245 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
246 void
247 EditorSummary::set_overlays_dirty ()
248 {
249         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
250         queue_draw ();
251 }
252
253 /** Handle a size request.
254  *  @param req GTK requisition
255  */
256 void
257 EditorSummary::on_size_request (Gtk::Requisition *req)
258 {
259         /* Use a dummy, small width and the actual height that we want */
260         req->width = 64;
261         req->height = 32;
262 }
263
264
265 void
266 EditorSummary::centre_on_click (GdkEventButton* ev)
267 {
268         pair<double, double> xr;
269         pair<double, double> yr;
270         get_editor (&xr, &yr);
271
272         double const w = xr.second - xr.first;
273
274         xr.first = ev->x - w / 2;
275         xr.second = ev->x + w / 2;
276
277         if (xr.first < 0) {
278                 xr.first = 0;
279                 xr.second = w;
280         } else if (xr.second > _width) {
281                 xr.second = _width;
282                 xr.first = _width - w;
283         }
284
285         double ey = summary_y_to_editor (ev->y);
286         ey -= (_editor->canvas_height() - _editor->get_canvas_timebars_vsize ()) / 2;
287         if (ey < 0) {
288                 ey = 0;
289         }
290         
291         set_editor (xr, editor_y_to_summary (ey));
292 }
293
294 /** Handle a button press.
295  *  @param ev GTK event.
296  */
297 bool
298 EditorSummary::on_button_press_event (GdkEventButton* ev)
299 {
300         if (ev->button == 1) {
301
302                 pair<double, double> xr;
303                 pair<double, double> yr;
304                 get_editor (&xr, &yr);
305
306                 _start_editor_x = xr;
307                 _start_editor_y = yr;
308                 _start_mouse_x = ev->x;
309                 _start_mouse_y = ev->y;
310                 _start_position = get_position (ev->x, ev->y);
311
312                 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
313                     _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
314                         ) {
315
316                         /* start a zoom drag */
317
318                         _zoom_position = get_position (ev->x, ev->y);
319                         _zoom_dragging = true;
320                         _editor->_dragging_playhead = true;
321
322                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
323
324                         /* secondary-modifier-click: locate playhead */
325                         if (_session) {
326                                 _session->request_locate (ev->x / _x_scale + _start);
327                         }
328
329                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
330
331                         centre_on_click (ev);
332
333                 } else {
334
335                         /* start a move drag */
336
337                         _move_dragging = true;
338                         _moved = false;
339                         _editor->_dragging_playhead = true;
340                 }
341         }
342
343         return true;
344 }
345
346 /** Fill in x and y with the editor's current viewable area in summary coordinates */
347 void
348 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
349 {
350         assert (x);
351         assert (y);
352         
353         x->first = (_editor->leftmost_position () - _start) * _x_scale;
354         x->second = x->first + _editor->current_page_frames() * _x_scale;
355
356         y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
357         y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
358 }
359
360 /** Get an expression of the position of a point with respect to the view rectangle */
361 EditorSummary::Position
362 EditorSummary::get_position (double x, double y) const
363 {
364         /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
365            in pixels */
366         int const edge_size = 8;
367         
368         bool const near_left = (std::abs (x - _view_rectangle_x.first) < edge_size);
369         bool const near_right = (std::abs (x - _view_rectangle_x.second) < edge_size);
370         bool const near_top = (std::abs (y - _view_rectangle_y.first) < edge_size);
371         bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < edge_size);
372         bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
373         bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
374
375         if (near_left && near_top) {
376                 return LEFT_TOP;
377         } else if (near_left && near_bottom) {
378                 return LEFT_BOTTOM;
379         } else if (near_right && near_top) {
380                 return RIGHT_TOP;
381         } else if (near_right && near_bottom) {
382                 return RIGHT_BOTTOM;
383         } else if (near_left && within_y) {
384                 return LEFT;
385         } else if (near_right && within_y) {
386                 return RIGHT;
387         } else if (near_top && within_x) {
388                 return TOP;
389         } else if (near_bottom && within_x) {
390                 return BOTTOM;
391         } else if (within_x && within_y) {
392                 return INSIDE;
393         } else if (within_x) {
394                 return BELOW_OR_ABOVE;
395         } else if (within_y) {
396                 return TO_LEFT_OR_RIGHT;
397         } else {
398                 return OTHERWISE_OUTSIDE;
399         }
400 }
401
402 void
403 EditorSummary::set_cursor (Position p)
404 {
405         switch (p) {
406         case LEFT:
407                 get_window()->set_cursor (*_editor->left_side_trim_cursor);
408                 break;
409         case LEFT_TOP:
410                 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_LEFT_CORNER));
411                 break;
412         case TOP:
413                 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_SIDE));
414                 break;
415         case RIGHT_TOP:
416                 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_RIGHT_CORNER));
417                 break;
418         case RIGHT:
419                 get_window()->set_cursor (*_editor->right_side_trim_cursor);
420                 break;
421         case RIGHT_BOTTOM:
422                 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_RIGHT_CORNER));
423                 break;
424         case BOTTOM:
425                 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_SIDE));
426                 break;
427         case LEFT_BOTTOM:
428                 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_LEFT_CORNER));
429                 break;
430         case INSIDE:
431                 get_window()->set_cursor (Gdk::Cursor (Gdk::FLEUR));
432                 break;
433         default:
434                 get_window()->set_cursor ();
435                 break;
436         }
437 }
438
439 bool
440 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
441 {
442         pair<double, double> xr = _start_editor_x;
443         pair<double, double> yr = _start_editor_y;
444         double y = _start_editor_y.first;
445
446         if (_move_dragging) {
447
448                 _moved = true;
449
450                 /* don't alter x if we clicked outside and above or below the viewbox */
451                 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
452                         xr.first += ev->x - _start_mouse_x;
453                         xr.second += ev->x - _start_mouse_x;
454                 }
455
456                 /* don't alter y if we clicked outside and to the left or right of the viewbox */
457                 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
458                         y += ev->y - _start_mouse_y;
459                 }
460
461                 if (xr.first < 0) {
462                         xr.second -= xr.first;
463                         xr.first = 0;
464                 }
465
466                 if (y < 0) {
467                         y = 0;
468                 }
469
470                 set_editor (xr, y);
471                 set_cursor (INSIDE);
472
473         } else if (_zoom_dragging) {
474
475                 double const dx = ev->x - _start_mouse_x;
476                 double const dy = ev->y - _start_mouse_y;
477
478                 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
479                         xr.first += dx;
480                 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
481                         xr.second += dx;
482                 }
483
484                 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
485                         yr.first += dy;
486                 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
487                         yr.second += dy;
488                 }
489
490                 set_overlays_dirty ();
491                 set_cursor (_zoom_position);
492                 set_editor (xr, yr);
493
494         } else {
495
496                 set_cursor (get_position (ev->x, ev->y));
497
498         }
499
500         return true;
501 }
502
503 bool
504 EditorSummary::on_button_release_event (GdkEventButton*)
505 {
506         _move_dragging = false;
507         _zoom_dragging = false;
508         _editor->_dragging_playhead = false;
509         return true;
510 }
511
512 bool
513 EditorSummary::on_scroll_event (GdkEventScroll* ev)
514 {
515         /* mouse wheel */
516
517         pair<double, double> xr;
518         pair<double, double> yr;
519         get_editor (&xr, &yr);
520         double y = yr.first;
521
522         double amount = 8;
523
524         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
525                 amount = 64;
526         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
527                 amount = 1;
528         }
529
530         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
531
532                 /* primary-wheel == left-right scrolling */
533
534                 if (ev->direction == GDK_SCROLL_UP) {
535                         xr.first += amount;
536                         xr.second += amount;
537                 } else if (ev->direction == GDK_SCROLL_DOWN) {
538                         xr.first -= amount;
539                         xr.second -= amount;
540                 }
541
542         } else {
543
544                 if (ev->direction == GDK_SCROLL_DOWN) {
545                         y += amount;
546                 } else if (ev->direction == GDK_SCROLL_UP) {
547                         y -= amount;
548                 } else if (ev->direction == GDK_SCROLL_LEFT) {
549                         xr.first -= amount;
550                         xr.second -= amount;
551                 } else if (ev->direction == GDK_SCROLL_RIGHT) {
552                         xr.first += amount;
553                         xr.second += amount;
554                 }
555         }
556
557         set_editor (xr, y);
558         return true;
559 }
560
561 /** Set the editor to display a given x range and a y range with the top at a given position.
562  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
563  *  x and y parameters are specified in summary coordinates.
564  */
565 void
566 EditorSummary::set_editor (pair<double,double> const & x, double const y)
567 {
568         if (_editor->pending_visual_change.idle_handler_id >= 0) {
569
570                 /* As a side-effect, the Editor's visual change idle handler processes
571                    pending GTK events.  Hence this motion notify handler can be called
572                    in the middle of a visual change idle handler, and if this happens,
573                    the queue_visual_change calls below modify the variables that the
574                    idle handler is working with.  This causes problems.  Hence this
575                    check.  It ensures that we won't modify the pending visual change
576                    while a visual change idle handler is in progress.  It's not perfect,
577                    as it also means that we won't change these variables if an idle handler
578                    is merely pending but not executing.  But c'est la vie.
579                 */
580
581                 return;
582         }
583         
584         set_editor_x (x);
585         set_editor_y (y);
586 }
587
588 /** Set the editor to display given x and y ranges.  x zoom and track heights are
589  *  adjusted if necessary.
590  *  x and y parameters are specified in summary coordinates.
591  */
592 void
593 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
594 {
595         if (_editor->pending_visual_change.idle_handler_id >= 0) {
596                 /* see comment in other set_editor () */
597                 return;
598         }
599         
600         set_editor_x (x);
601         set_editor_y (y);
602 }
603
604 /** Set the x range visible in the editor.
605  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
606  *  @param x new x range in summary coordinates.
607  */
608 void
609 EditorSummary::set_editor_x (pair<double, double> const & x)
610 {
611         _editor->reset_x_origin (x.first / _x_scale + _start);
612
613         double const nx = (
614                 ((x.second - x.first) / _x_scale) /
615                 _editor->frame_to_unit (_editor->current_page_frames())
616                 );
617         
618         if (nx != _editor->get_current_zoom ()) {
619                 _editor->reset_zoom (nx);
620         }       
621 }
622
623 /** Set the top of the y range visible in the editor.
624  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
625  *  @param y new editor top in summary coodinates.
626  */
627 void
628 EditorSummary::set_editor_y (double const y)
629 {
630         double y1 = summary_y_to_editor (y);
631         double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
632         double y2 = y1 + eh;
633         
634         double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
635
636         if (y2 > full_editor_height) {
637                 y1 -= y2 - full_editor_height;
638         }
639         
640         if (y1 < 0) {
641                 y1 = 0;
642         }
643
644         _editor->reset_y_origin (y1);
645 }
646
647 /** Set the y range visible in the editor.  This is achieved by scaling track heights,
648  *  if necessary.
649  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
650  *  @param y new editor range in summary coodinates.
651  */
652 void
653 EditorSummary::set_editor_y (pair<double, double> const & y)
654 {
655         /* Compute current height of tracks between y.first and y.second.  We add up
656            the total height into `total_height' and the height of complete tracks into
657            `scale height'.
658         */
659         pair<double, double> yc = y;
660         double total_height = 0;
661         double scale_height = 0;
662         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
663
664                 if ((*i)->hidden()) {
665                         continue;
666                 }
667
668                 double const h = (*i)->effective_height ();
669
670                 if (yc.first >= 0 && yc.first < _track_height) {
671                         total_height += (_track_height - yc.first) * h / _track_height;
672                 } else if (yc.first < 0 && yc.second > _track_height) {
673                         total_height += h;
674                         scale_height += h;
675                 } else if (yc.second >= 0 && yc.second < _track_height) {
676                         total_height += yc.second * h / _track_height;
677                         break;
678                 }
679
680                 yc.first -= _track_height;
681                 yc.second -= _track_height;
682         }
683
684         /* hence required scale factor of the complete tracks to fit the required y range */
685         double const scale = ((_editor->canvas_height() - _editor->get_canvas_timebars_vsize()) - (total_height - scale_height)) / scale_height;
686
687         yc = y;
688
689         /* Scale complete tracks within the range to make it fit */
690         
691         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
692
693                 if ((*i)->hidden()) {
694                         continue;
695                 }
696
697                 if (yc.first < 0 && yc.second > _track_height) {
698                         (*i)->set_height ((*i)->effective_height() * scale);
699                 }
700
701                 yc.first -= _track_height;
702                 yc.second -= _track_height;
703         }
704
705         set_editor_y (y.first);
706 }
707
708 void
709 EditorSummary::playhead_position_changed (nframes64_t p)
710 {
711         if (_session && int (p * _x_scale) != int (_last_playhead)) {
712                 set_overlays_dirty ();
713         }
714 }
715
716 double
717 EditorSummary::summary_y_to_editor (double y) const
718 {
719         double ey = 0;
720         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
721                 
722                 if ((*i)->hidden()) {
723                         continue;
724                 }
725                 
726                 double const h = (*i)->effective_height ();
727                 if (y < _track_height) {
728                         /* in this track */
729                         return ey + y * h / _track_height;
730                 }
731
732                 ey += h;
733                 y -= _track_height;
734         }
735
736         return ey;
737 }
738
739 double
740 EditorSummary::editor_y_to_summary (double y) const
741 {
742         double sy = 0;
743         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
744                 
745                 if ((*i)->hidden()) {
746                         continue;
747                 }
748
749                 double const h = (*i)->effective_height ();
750                 if (y < h) {
751                         /* in this track */
752                         return sy + y * _track_height / h;
753                 }
754
755                 sy += _track_height;
756                 y -= h;
757         }
758
759         return sy;
760 }