Fix autoscroll fudge calculations. Remove some unused
[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, get_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, get_width(), get_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) get_height() / N;
169         }
170
171         /* calculate x scale */
172         if (_end != _start) {
173                 _x_scale = static_cast<double> (get_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, get_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, get_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, get_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         double ex = ev->x - w / 2;
279         if (ex < 0) {
280                 ex = 0;
281         } else if ((ex + w) > get_width()) {
282                 ex = get_width() - w;
283         }
284
285         double const h = yr.second - yr.first;
286         double ey = ev->y - h / 2;
287         if (ey < 0) {
288                 ey = 0;
289         } else if ((ey + h) > get_height()) {
290                 ey = get_height() - h;
291         }
292
293         set_editor (ex, ey);
294 }
295
296 /** Handle a button press.
297  *  @param ev GTK event.
298  */
299 bool
300 EditorSummary::on_button_press_event (GdkEventButton* ev)
301 {
302         if (ev->button == 1) {
303
304                 pair<double, double> xr;
305                 pair<double, double> yr;
306                 get_editor (&xr, &yr);
307
308                 _start_editor_x = xr;
309                 _start_editor_y = yr;
310                 _start_mouse_x = ev->x;
311                 _start_mouse_y = ev->y;
312                 _start_position = get_position (ev->x, ev->y);
313
314                 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
315                     _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
316                         ) {
317
318                         /* start a zoom drag */
319
320                         _zoom_position = get_position (ev->x, ev->y);
321                         _zoom_dragging = true;
322                         _editor->_dragging_playhead = true;
323                         _old_follow_playhead = _editor->follow_playhead ();
324                         _editor->set_follow_playhead (false);
325
326                         if (suspending_editor_updates ()) {
327                                 get_editor (&_pending_editor_x, &_pending_editor_y);
328                                 _pending_editor_changed = false;
329                         }
330                         
331                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
332
333                         /* secondary-modifier-click: locate playhead */
334                         if (_session) {
335                                 _session->request_locate (ev->x / _x_scale + _start);
336                         }
337
338                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
339
340                         centre_on_click (ev);
341
342                 } else {
343
344                         /* start a move drag */
345
346                         /* get the editor's state in case we are suspending updates */
347                         get_editor (&_pending_editor_x, &_pending_editor_y);
348                         _pending_editor_changed = false;
349
350                         _move_dragging = true;
351                         _moved = false;
352                         _editor->_dragging_playhead = true;
353                         _old_follow_playhead = _editor->follow_playhead ();
354                         _editor->set_follow_playhead (false);
355                 }
356         }
357
358         return true;
359 }
360
361 /** @return true if we are currently suspending updates to the editor's viewport,
362  *  which we do if configured to do so, and if in a drag of some kind.
363  */
364 bool
365 EditorSummary::suspending_editor_updates () const
366 {
367         return (!Config->get_update_editor_during_summary_drag () && (_zoom_dragging || _move_dragging));
368 }
369
370 /** Fill in x and y with the editor's current viewable area in summary coordinates */
371 void
372 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
373 {
374         assert (x);
375         assert (y);
376
377         if (suspending_editor_updates ()) {
378
379                 /* We are dragging, and configured not to update the editor window during drags,
380                    so just return where the editor will be when the drag finishes.
381                 */
382                    
383                 *x = _pending_editor_x;
384                 *y = _pending_editor_y;
385
386         } else {
387
388                 /* Otherwise query the editor for its actual position */
389
390                 x->first = (_editor->leftmost_position () - _start) * _x_scale;
391                 x->second = x->first + _editor->current_page_frames() * _x_scale;
392                 
393                 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
394                 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
395         }
396 }
397
398 /** Get an expression of the position of a point with respect to the view rectangle */
399 EditorSummary::Position
400 EditorSummary::get_position (double x, double y) const
401 {
402         /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
403            in pixels */
404
405         int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
406         x_edge_size = min (x_edge_size, 8);
407         x_edge_size = max (x_edge_size, 1);
408
409         int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
410         y_edge_size = min (y_edge_size, 8);
411         y_edge_size = max (y_edge_size, 1);
412
413         bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
414         bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
415         bool const near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
416         bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_edge_size);
417         bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
418         bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
419
420         if (near_left && near_top) {
421                 return LEFT_TOP;
422         } else if (near_left && near_bottom) {
423                 return LEFT_BOTTOM;
424         } else if (near_right && near_top) {
425                 return RIGHT_TOP;
426         } else if (near_right && near_bottom) {
427                 return RIGHT_BOTTOM;
428         } else if (near_left && within_y) {
429                 return LEFT;
430         } else if (near_right && within_y) {
431                 return RIGHT;
432         } else if (near_top && within_x) {
433                 return TOP;
434         } else if (near_bottom && within_x) {
435                 return BOTTOM;
436         } else if (within_x && within_y) {
437                 return INSIDE;
438         } else if (within_x) {
439                 return BELOW_OR_ABOVE;
440         } else if (within_y) {
441                 return TO_LEFT_OR_RIGHT;
442         } else {
443                 return OTHERWISE_OUTSIDE;
444         }
445 }
446
447 void
448 EditorSummary::set_cursor (Position p)
449 {
450         switch (p) {
451         case LEFT:
452                 get_window()->set_cursor (*_editor->_cursors->resize_left);
453                 break;
454         case LEFT_TOP:
455                 get_window()->set_cursor (*_editor->_cursors->resize_top_left);
456                 break;
457         case TOP:
458                 get_window()->set_cursor (*_editor->_cursors->resize_top);
459                 break;
460         case RIGHT_TOP:
461                 get_window()->set_cursor (*_editor->_cursors->resize_top_right);
462                 break;
463         case RIGHT:
464                 get_window()->set_cursor (*_editor->_cursors->resize_right);
465                 break;
466         case RIGHT_BOTTOM:
467                 get_window()->set_cursor (*_editor->_cursors->resize_bottom_right);
468                 break;
469         case BOTTOM:
470                 get_window()->set_cursor (*_editor->_cursors->resize_bottom);
471                 break;
472         case LEFT_BOTTOM:
473                 get_window()->set_cursor (*_editor->_cursors->resize_bottom_left);
474                 break;
475         case INSIDE:
476                 get_window()->set_cursor (*_editor->_cursors->move);
477                 break;
478         case TO_LEFT_OR_RIGHT:
479                 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
480                 break;
481         case BELOW_OR_ABOVE:
482                 get_window()->set_cursor (*_editor->_cursors->expand_up_down);
483                 break;
484         default:
485                 get_window()->set_cursor ();
486                 break;
487         }
488 }
489
490 bool
491 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
492 {
493         pair<double, double> xr = _start_editor_x;
494         pair<double, double> yr = _start_editor_y;
495         double x = _start_editor_x.first;
496         double y = _start_editor_y.first;
497
498         if (_move_dragging) {
499
500                 _moved = true;
501
502                 /* don't alter x if we clicked outside and above or below the viewbox */
503                 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
504                         x += ev->x - _start_mouse_x;
505                 }
506
507                 /* don't alter y if we clicked outside and to the left or right of the viewbox */
508                 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
509                         y += ev->y - _start_mouse_y;
510                 }
511
512                 if (x < 0) {
513                         x = 0;
514                 }
515
516                 if (y < 0) {
517                         y = 0;
518                 }
519
520                 set_editor (x, y);
521                 set_cursor (_start_position);
522
523         } else if (_zoom_dragging) {
524
525                 double const dx = ev->x - _start_mouse_x;
526                 double const dy = ev->y - _start_mouse_y;
527
528                 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
529                         xr.first += dx;
530                 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
531                         xr.second += dx;
532                 }
533
534                 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
535                         yr.first += dy;
536                 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
537                         yr.second += dy;
538                 }
539
540                 set_overlays_dirty ();
541                 set_cursor (_zoom_position);
542                 set_editor (xr, yr);
543
544         } else {
545
546                 set_cursor (get_position (ev->x, ev->y));
547
548         }
549
550         return true;
551 }
552
553 bool
554 EditorSummary::on_button_release_event (GdkEventButton*)
555 {
556         bool const was_suspended = suspending_editor_updates ();
557         
558         _move_dragging = false;
559         _zoom_dragging = false;
560         _editor->_dragging_playhead = false;
561         _editor->set_follow_playhead (_old_follow_playhead, false);
562
563         if (was_suspended && _pending_editor_changed) {
564                 set_editor (_pending_editor_x, _pending_editor_y);
565         }
566                 
567         return true;
568 }
569
570 bool
571 EditorSummary::on_scroll_event (GdkEventScroll* ev)
572 {
573         /* mouse wheel */
574
575         pair<double, double> xr;
576         pair<double, double> yr;
577         get_editor (&xr, &yr);
578         double x = xr.first;
579         double y = yr.first;
580
581         double amount = 8;
582
583         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
584                 amount = 64;
585         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
586                 amount = 1;
587         }
588
589         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
590
591                 /* primary-wheel == left-right scrolling */
592
593                 if (ev->direction == GDK_SCROLL_UP) {
594                         x += amount;
595                 } else if (ev->direction == GDK_SCROLL_DOWN) {
596                         x -= amount;
597                 }
598
599         } else {
600
601                 if (ev->direction == GDK_SCROLL_DOWN) {
602                         y += amount;
603                 } else if (ev->direction == GDK_SCROLL_UP) {
604                         y -= amount;
605                 } else if (ev->direction == GDK_SCROLL_LEFT) {
606                         x -= amount;
607                 } else if (ev->direction == GDK_SCROLL_RIGHT) {
608                         x += amount;
609                 }
610         }
611
612         set_editor (x, y);
613         return true;
614 }
615
616 /** Set the editor to display a x range with the left at a given position
617  *  and a y range with the top at a given position.
618  *  x and y parameters are specified in summary coordinates.
619  *  Zoom is not changed in either direction.
620  */
621 void
622 EditorSummary::set_editor (double const x, double const y)
623 {
624         if (_editor->pending_visual_change.idle_handler_id >= 0) {
625
626                 /* As a side-effect, the Editor's visual change idle handler processes
627                    pending GTK events.  Hence this motion notify handler can be called
628                    in the middle of a visual change idle handler, and if this happens,
629                    the queue_visual_change calls below modify the variables that the
630                    idle handler is working with.  This causes problems.  Hence this
631                    check.  It ensures that we won't modify the pending visual change
632                    while a visual change idle handler is in progress.  It's not perfect,
633                    as it also means that we won't change these variables if an idle handler
634                    is merely pending but not executing.  But c'est la vie.
635                 */
636
637                 return;
638         }
639
640         set_editor_x (x);
641         set_editor_y (y);
642 }
643
644 /** Set the editor to display a given x range and a y range with the top at a given position.
645  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
646  *  x and y parameters are specified in summary coordinates.
647  */
648 void
649 EditorSummary::set_editor (pair<double,double> const x, double const y)
650 {
651         if (_editor->pending_visual_change.idle_handler_id >= 0) {
652                 /* see comment in other set_editor () */
653                 return;
654         }
655
656         set_editor_x (x);
657         set_editor_y (y);
658 }
659
660 /** Set the editor to display given x and y ranges.  x zoom and track heights are
661  *  adjusted if necessary.
662  *  x and y parameters are specified in summary coordinates.
663  */
664 void
665 EditorSummary::set_editor (pair<double,double> const x, pair<double, double> const y)
666 {
667         if (_editor->pending_visual_change.idle_handler_id >= 0) {
668                 /* see comment in other set_editor () */
669                 return;
670         }
671
672         set_editor_x (x);
673         set_editor_y (y);
674 }
675
676 /** Set the left of the x range visible in the editor.
677  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
678  *  @param x new x left position in summary coordinates.
679  */
680 void
681 EditorSummary::set_editor_x (double x)
682 {
683         if (x < 0) {
684                 x = 0;
685         }
686
687         if (suspending_editor_updates ()) {
688                 double const w = _pending_editor_x.second - _pending_editor_x.first;
689                 _pending_editor_x.first = x;
690                 _pending_editor_x.second = x + w;
691                 _pending_editor_changed = true;
692                 set_dirty ();
693         } else {
694                 _editor->reset_x_origin (x / _x_scale + _start);
695         }
696 }
697
698 /** Set the x range visible in the editor.
699  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
700  *  @param x new x range in summary coordinates.
701  */
702 void
703 EditorSummary::set_editor_x (pair<double, double> x)
704 {
705         if (x.first < 0) {
706                 x.first = 0;
707         }
708
709         if (x.second < 0) {
710                 x.second = x.first + 1;
711         }
712
713         if (suspending_editor_updates ()) {
714                 _pending_editor_x = x;
715                 _pending_editor_changed = true;
716                 set_dirty ();
717         } else {
718                 _editor->reset_x_origin (x.first / _x_scale + _start);
719                 
720                 double const nx = (
721                         ((x.second - x.first) / _x_scale) /
722                         _editor->frame_to_unit (_editor->current_page_frames())
723                         );
724                 
725                 if (nx != _editor->get_current_zoom ()) {
726                         _editor->reset_zoom (nx);
727                 }
728         }
729 }
730
731 /** Set the top of the y range visible in the editor.
732  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
733  *  @param y new editor top in summary coodinates.
734  */
735 void
736 EditorSummary::set_editor_y (double const y)
737 {
738         double y1 = summary_y_to_editor (y);
739         double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
740         double y2 = y1 + eh;
741
742         double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
743
744         if (y2 > full_editor_height) {
745                 y1 -= y2 - full_editor_height;
746         }
747
748         if (y1 < 0) {
749                 y1 = 0;
750         }
751
752         if (suspending_editor_updates ()) {
753                 double const h = _pending_editor_y.second - _pending_editor_y.first;
754                 _pending_editor_y.first = y;
755                 _pending_editor_y.second = y + h;
756                 _pending_editor_changed = true;
757                 set_dirty ();
758         } else {
759                 _editor->reset_y_origin (y1);
760         }
761 }
762
763 /** Set the y range visible in the editor.  This is achieved by scaling track heights,
764  *  if necessary.
765  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
766  *  @param y new editor range in summary coodinates.
767  */
768 void
769 EditorSummary::set_editor_y (pair<double, double> const y)
770 {
771         if (suspending_editor_updates ()) {
772                 _pending_editor_y = y;
773                 _pending_editor_changed = true;
774                 set_dirty ();
775                 return;
776         }
777         
778         /* Compute current height of tracks between y.first and y.second.  We add up
779            the total height into `total_height' and the height of complete tracks into
780            `scale height'.
781         */
782
783         /* Copy of target range for use below */
784         pair<double, double> yc = y;
785         /* Total height of all tracks */
786         double total_height = 0;
787         /* Height of any parts of tracks that aren't fully in the desired range */
788         double partial_height = 0;
789         /* Height of any tracks that are fully in the desired range */
790         double scale_height = 0;
791
792         _editor->_routes->suspend_redisplay ();
793
794         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
795
796                 if ((*i)->hidden()) {
797                         continue;
798                 }
799
800                 double const h = (*i)->effective_height ();
801                 total_height += h;
802
803                 if (yc.first > 0 && yc.first < _track_height) {
804                         partial_height += (_track_height - yc.first) * h / _track_height;
805                 } else if (yc.first <= 0 && yc.second >= _track_height) {
806                         scale_height += h;
807                 } else if (yc.second > 0 && yc.second < _track_height) {
808                         partial_height += yc.second * h / _track_height;
809                         break;
810                 }
811
812                 yc.first -= _track_height;
813                 yc.second -= _track_height;
814         }
815
816         /* Height that we will use for scaling; use the whole editor height unless there are not
817            enough tracks to fill it.
818         */
819         double const ch = min (total_height, _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
820
821         /* hence required scale factor of the complete tracks to fit the required y range;
822            the amount of space they should take up divided by the amount they currently take up.
823         */
824         double const scale = (ch - partial_height) / scale_height;
825
826         yc = y;
827
828         /* Scale complete tracks within the range to make it fit */
829
830         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
831
832                 if ((*i)->hidden()) {
833                         continue;
834                 }
835
836                 if (yc.first <= 0 && yc.second >= _track_height) {
837                         (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
838                 }
839
840                 yc.first -= _track_height;
841                 yc.second -= _track_height;
842         }
843
844         _editor->_routes->resume_redisplay ();
845
846         set_editor_y (y.first);
847 }
848
849 void
850 EditorSummary::playhead_position_changed (framepos_t p)
851 {
852         if (_session && int (p * _x_scale) != int (_last_playhead)) {
853                 set_overlays_dirty ();
854         }
855 }
856
857 double
858 EditorSummary::summary_y_to_editor (double y) const
859 {
860         double ey = 0;
861         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
862
863                 if ((*i)->hidden()) {
864                         continue;
865                 }
866
867                 double const h = (*i)->effective_height ();
868                 if (y < _track_height) {
869                         /* in this track */
870                         return ey + y * h / _track_height;
871                 }
872
873                 ey += h;
874                 y -= _track_height;
875         }
876
877         return ey;
878 }
879
880 double
881 EditorSummary::editor_y_to_summary (double y) const
882 {
883         double sy = 0;
884         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
885
886                 if ((*i)->hidden()) {
887                         continue;
888                 }
889
890                 double const h = (*i)->effective_height ();
891                 if (y < h) {
892                         /* in this track */
893                         return sy + y * _track_height / h;
894                 }
895
896                 sy += _track_height;
897                 y -= h;
898         }
899
900         return sy;
901 }
902
903 void
904 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
905 {
906         /* Connect to gui_changed() on the routes so that we know when their colour has changed */
907         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
908                 (*i)->route()->gui_changed.connect (*this, invalidator (*this), ui_bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
909         }
910
911         set_dirty ();
912 }
913
914 void
915 EditorSummary::route_gui_changed (string c)
916 {
917         if (c == "color") {
918                 set_dirty ();
919         }
920 }