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