Remove unused code.
[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::SecondaryModifier)) {
590
591                 /* secondary-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 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
600
601                 /* primary-wheel == zoom */
602                 
603                 if (ev->direction == GDK_SCROLL_UP) {
604                         _editor->temporal_zoom_step (false);
605                 } else {
606                         _editor->temporal_zoom_step (true);
607                 }
608
609         } else {
610
611                 if (ev->direction == GDK_SCROLL_DOWN) {
612                         y += amount;
613                 } else if (ev->direction == GDK_SCROLL_UP) {
614                         y -= amount;
615                 } else if (ev->direction == GDK_SCROLL_LEFT) {
616                         x -= amount;
617                 } else if (ev->direction == GDK_SCROLL_RIGHT) {
618                         x += amount;
619                 }
620         }
621
622         set_editor (x, y);
623         return true;
624 }
625
626 /** Set the editor to display a x range with the left at a given position
627  *  and a y range with the top at a given position.
628  *  x and y parameters are specified in summary coordinates.
629  *  Zoom is not changed in either direction.
630  */
631 void
632 EditorSummary::set_editor (double const x, double const y)
633 {
634         if (_editor->pending_visual_change.idle_handler_id >= 0) {
635
636                 /* As a side-effect, the Editor's visual change idle handler processes
637                    pending GTK events.  Hence this motion notify handler can be called
638                    in the middle of a visual change idle handler, and if this happens,
639                    the queue_visual_change calls below modify the variables that the
640                    idle handler is working with.  This causes problems.  Hence this
641                    check.  It ensures that we won't modify the pending visual change
642                    while a visual change idle handler is in progress.  It's not perfect,
643                    as it also means that we won't change these variables if an idle handler
644                    is merely pending but not executing.  But c'est la vie.
645                 */
646
647                 return;
648         }
649
650         set_editor_x (x);
651         set_editor_y (y);
652 }
653
654 /** Set the editor to display a given x range and a y range with the top at a given position.
655  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
656  *  x and y parameters are specified in summary coordinates.
657  */
658 void
659 EditorSummary::set_editor (pair<double,double> const x, double const y)
660 {
661         if (_editor->pending_visual_change.idle_handler_id >= 0) {
662                 /* see comment in other set_editor () */
663                 return;
664         }
665
666         set_editor_x (x);
667         set_editor_y (y);
668 }
669
670 /** Set the editor to display given x and y ranges.  x zoom and track heights are
671  *  adjusted if necessary.
672  *  x and y parameters are specified in summary coordinates.
673  */
674 void
675 EditorSummary::set_editor (pair<double,double> const x, pair<double, double> const y)
676 {
677         if (_editor->pending_visual_change.idle_handler_id >= 0) {
678                 /* see comment in other set_editor () */
679                 return;
680         }
681
682         set_editor_x (x);
683         set_editor_y (y);
684 }
685
686 /** Set the left of the x range visible in the editor.
687  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
688  *  @param x new x left position in summary coordinates.
689  */
690 void
691 EditorSummary::set_editor_x (double x)
692 {
693         if (x < 0) {
694                 x = 0;
695         }
696
697         if (suspending_editor_updates ()) {
698                 double const w = _pending_editor_x.second - _pending_editor_x.first;
699                 _pending_editor_x.first = x;
700                 _pending_editor_x.second = x + w;
701                 _pending_editor_changed = true;
702                 set_dirty ();
703         } else {
704                 _editor->reset_x_origin (x / _x_scale + _start);
705         }
706 }
707
708 /** Set the x range visible in the editor.
709  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
710  *  @param x new x range in summary coordinates.
711  */
712 void
713 EditorSummary::set_editor_x (pair<double, double> x)
714 {
715         if (x.first < 0) {
716                 x.first = 0;
717         }
718
719         if (x.second < 0) {
720                 x.second = x.first + 1;
721         }
722
723         if (suspending_editor_updates ()) {
724                 _pending_editor_x = x;
725                 _pending_editor_changed = true;
726                 set_dirty ();
727         } else {
728                 _editor->reset_x_origin (x.first / _x_scale + _start);
729                 
730                 double const nx = (
731                         ((x.second - x.first) / _x_scale) /
732                         _editor->frame_to_unit (_editor->current_page_frames())
733                         );
734                 
735                 if (nx != _editor->get_current_zoom ()) {
736                         _editor->reset_zoom (nx);
737                 }
738         }
739 }
740
741 /** Set the top of the y range visible in the editor.
742  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
743  *  @param y new editor top in summary coodinates.
744  */
745 void
746 EditorSummary::set_editor_y (double const y)
747 {
748         double y1 = summary_y_to_editor (y);
749         double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
750         double y2 = y1 + eh;
751
752         double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
753
754         if (y2 > full_editor_height) {
755                 y1 -= y2 - full_editor_height;
756         }
757
758         if (y1 < 0) {
759                 y1 = 0;
760         }
761
762         if (suspending_editor_updates ()) {
763                 double const h = _pending_editor_y.second - _pending_editor_y.first;
764                 _pending_editor_y.first = y;
765                 _pending_editor_y.second = y + h;
766                 _pending_editor_changed = true;
767                 set_dirty ();
768         } else {
769                 _editor->reset_y_origin (y1);
770         }
771 }
772
773 /** Set the y range visible in the editor.  This is achieved by scaling track heights,
774  *  if necessary.
775  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
776  *  @param y new editor range in summary coodinates.
777  */
778 void
779 EditorSummary::set_editor_y (pair<double, double> const y)
780 {
781         if (suspending_editor_updates ()) {
782                 _pending_editor_y = y;
783                 _pending_editor_changed = true;
784                 set_dirty ();
785                 return;
786         }
787         
788         /* Compute current height of tracks between y.first and y.second.  We add up
789            the total height into `total_height' and the height of complete tracks into
790            `scale height'.
791         */
792
793         /* Copy of target range for use below */
794         pair<double, double> yc = y;
795         /* Total height of all tracks */
796         double total_height = 0;
797         /* Height of any parts of tracks that aren't fully in the desired range */
798         double partial_height = 0;
799         /* Height of any tracks that are fully in the desired range */
800         double scale_height = 0;
801
802         _editor->_routes->suspend_redisplay ();
803
804         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
805
806                 if ((*i)->hidden()) {
807                         continue;
808                 }
809
810                 double const h = (*i)->effective_height ();
811                 total_height += h;
812
813                 if (yc.first > 0 && yc.first < _track_height) {
814                         partial_height += (_track_height - yc.first) * h / _track_height;
815                 } else if (yc.first <= 0 && yc.second >= _track_height) {
816                         scale_height += h;
817                 } else if (yc.second > 0 && yc.second < _track_height) {
818                         partial_height += yc.second * h / _track_height;
819                         break;
820                 }
821
822                 yc.first -= _track_height;
823                 yc.second -= _track_height;
824         }
825
826         /* Height that we will use for scaling; use the whole editor height unless there are not
827            enough tracks to fill it.
828         */
829         double const ch = min (total_height, _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
830
831         /* hence required scale factor of the complete tracks to fit the required y range;
832            the amount of space they should take up divided by the amount they currently take up.
833         */
834         double const scale = (ch - partial_height) / scale_height;
835
836         yc = y;
837
838         /* Scale complete tracks within the range to make it fit */
839
840         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
841
842                 if ((*i)->hidden()) {
843                         continue;
844                 }
845
846                 if (yc.first <= 0 && yc.second >= _track_height) {
847                         (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
848                 }
849
850                 yc.first -= _track_height;
851                 yc.second -= _track_height;
852         }
853
854         _editor->_routes->resume_redisplay ();
855
856         set_editor_y (y.first);
857 }
858
859 void
860 EditorSummary::playhead_position_changed (framepos_t p)
861 {
862         if (_session && int (p * _x_scale) != int (_last_playhead)) {
863                 set_overlays_dirty ();
864         }
865 }
866
867 double
868 EditorSummary::summary_y_to_editor (double y) const
869 {
870         double ey = 0;
871         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
872
873                 if ((*i)->hidden()) {
874                         continue;
875                 }
876
877                 double const h = (*i)->effective_height ();
878                 if (y < _track_height) {
879                         /* in this track */
880                         return ey + y * h / _track_height;
881                 }
882
883                 ey += h;
884                 y -= _track_height;
885         }
886
887         return ey;
888 }
889
890 double
891 EditorSummary::editor_y_to_summary (double y) const
892 {
893         double sy = 0;
894         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
895
896                 if ((*i)->hidden()) {
897                         continue;
898                 }
899
900                 double const h = (*i)->effective_height ();
901                 if (y < h) {
902                         /* in this track */
903                         return sy + y * _track_height / h;
904                 }
905
906                 sy += _track_height;
907                 y -= h;
908         }
909
910         return sy;
911 }
912
913 void
914 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
915 {
916         /* Connect to gui_changed() on the routes so that we know when their colour has changed */
917         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
918                 (*i)->route()->gui_changed.connect (*this, invalidator (*this), ui_bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
919         }
920
921         set_dirty ();
922 }
923
924 void
925 EditorSummary::route_gui_changed (string c)
926 {
927         if (c == "color") {
928                 set_dirty ();
929         }
930 }