Fix session-open after selecting new, template, then back
[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
22 #include "canvas/debug.h"
23
24 #include "time_axis_view.h"
25 #include "streamview.h"
26 #include "editor_summary.h"
27 #include "gui_thread.h"
28 #include "editor.h"
29 #include "region_view.h"
30 #include "rgb_macros.h"
31 #include "keyboard.h"
32 #include "editor_routes.h"
33 #include "editor_cursors.h"
34 #include "mouse_cursors.h"
35 #include "route_time_axis.h"
36 #include "ui_config.h"
37
38 using namespace std;
39 using namespace ARDOUR;
40 using Gtkmm2ext::Keyboard;
41
42 /** Construct an EditorSummary.
43  *  @param e Editor to represent.
44  */
45 EditorSummary::EditorSummary (Editor* e)
46         : EditorComponent (e),
47           _start (0),
48           _end (1),
49           _overhang_fraction (0.02),
50           _x_scale (1),
51           _track_height (16),
52           _last_playhead (-1),
53           _move_dragging (false),
54           _view_rectangle_x (0, 0),
55           _view_rectangle_y (0, 0),
56           _zoom_trim_dragging (false),
57           _old_follow_playhead (false),
58           _image (0),
59           _background_dirty (true)
60 {
61         CairoWidget::use_nsglview ();
62         add_events (Gdk::POINTER_MOTION_MASK|Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
63         set_flags (get_flags() | Gtk::CAN_FOCUS);
64
65         UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &EditorSummary::parameter_changed));
66 }
67
68 EditorSummary::~EditorSummary ()
69 {
70         cairo_surface_destroy (_image);
71 }
72
73 void
74 EditorSummary::parameter_changed (string p)
75 {
76
77         if (p == "color-regions-using-track-color") {
78                 set_background_dirty ();
79         }
80 }
81
82 /** Handle a size allocation.
83  *  @param alloc GTK allocation.
84  */
85 void
86 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
87 {
88         CairoWidget::on_size_allocate (alloc);
89         set_background_dirty ();
90 }
91
92
93 /** Connect to a session.
94  *  @param s Session.
95  */
96 void
97 EditorSummary::set_session (Session* s)
98 {
99         SessionHandlePtr::set_session (s);
100
101         set_dirty ();
102
103         /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
104          * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
105          * emitted when a cut region is added to the `cutlist' playlist.
106          */
107
108         if (_session) {
109                 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
110                 PresentationInfo::Change.connect (route_ctrl_id_connection, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
111                 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), boost::bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
112                 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
113                 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
114                 _editor->selection->RegionsChanged.connect (sigc::mem_fun(*this, &EditorSummary::set_background_dirty));
115         
116                 _leftmost = _session->current_start_frame();
117                 _rightmost = min (_session->nominal_frame_rate()*60*2, _session->current_end_frame() );  //always show at least 2 minutes
118         }
119 }
120
121 void
122 EditorSummary::render_background_image ()
123 {
124         cairo_surface_destroy (_image); // passing NULL is safe
125         _image = cairo_image_surface_create (CAIRO_FORMAT_RGB24, get_width (), get_height ());
126
127         cairo_t* cr = cairo_create (_image);
128
129         /* background (really just the dividing lines between tracks */
130
131         cairo_set_source_rgb (cr, 0, 0, 0);
132         cairo_rectangle (cr, 0, 0, get_width(), get_height());
133         cairo_fill (cr);
134
135         /* compute start and end points for the summary */
136
137         framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
138         double theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
139         double theoretical_end = _session->current_end_frame();
140
141         /* the summary should encompass the full extent of everywhere we've visited since the session was opened */
142         if ( _leftmost < theoretical_start)
143                 theoretical_start = _leftmost;
144         if ( _rightmost > theoretical_end )
145                 theoretical_end = _rightmost;
146
147         /* range-check */
148         _start = theoretical_start > 0 ? theoretical_start : 0;
149         _end = theoretical_end + session_length * _overhang_fraction;
150
151         /* calculate x scale */
152         if (_end != _start) {
153                 _x_scale = static_cast<double> (get_width()) / (_end - _start);
154         } else {
155                 _x_scale = 1;
156         }
157
158         /* compute track height */
159         int N = 0;
160         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
161                 if (!(*i)->hidden()) {
162                         ++N;
163                 }
164         }
165
166         if (N == 0) {
167                 _track_height = 16;
168         } else {
169                 _track_height = (double) get_height() / N;
170         }
171
172         /* render tracks and regions */
173
174         double y = 0;
175         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
176
177                 if ((*i)->hidden()) {
178                         continue;
179                 }
180
181                 /* paint a non-bg colored strip to represent the track itself */
182
183                 if ( _track_height > 4 ) {
184                         cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
185                         cairo_set_line_width (cr, _track_height - 1);
186                         cairo_move_to (cr, 0, y + _track_height / 2);
187                         cairo_line_to (cr, get_width(), y + _track_height / 2);
188                         cairo_stroke (cr);
189                 }
190                 
191                 StreamView* s = (*i)->view ();
192
193                 if (s) {
194                         cairo_set_line_width (cr, _track_height * 0.8);
195
196                         s->foreach_regionview (sigc::bind (
197                                                        sigc::mem_fun (*this, &EditorSummary::render_region),
198                                                        cr,
199                                                        y + _track_height / 2
200                                                        ));
201                 }
202
203                 y += _track_height;
204         }
205
206         /* start and end markers */
207
208         cairo_set_line_width (cr, 1);
209         cairo_set_source_rgb (cr, 1, 1, 0);
210
211         const double p = (_session->current_start_frame() - _start) * _x_scale;
212         cairo_move_to (cr, p, 0);
213         cairo_line_to (cr, p, get_height());
214
215         double const q = (_session->current_end_frame() - _start) * _x_scale;
216         cairo_move_to (cr, q, 0);
217         cairo_line_to (cr, q, get_height());
218         cairo_stroke (cr);
219
220         cairo_destroy (cr);
221 }
222
223 /** Render the required regions to a cairo context.
224  *  @param cr Context.
225  */
226 void
227 EditorSummary::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t*)
228 {
229         cairo_t* cr = ctx->cobj();
230
231         if (_session == 0) {
232                 return;
233         }
234
235         /* maintain the leftmost and rightmost locations that we've ever reached */
236         framecnt_t const leftmost = _editor->leftmost_sample ();
237         if ( leftmost < _leftmost) {
238                 _leftmost = leftmost;
239                 _background_dirty = true;
240         }
241         framecnt_t const rightmost = leftmost + _editor->current_page_samples();
242         if ( rightmost > _rightmost) {
243                 _rightmost = rightmost;
244                 _background_dirty = true;
245         }
246
247         //draw the background (regions, markers, etc ) if they've changed
248         if (!_image || _background_dirty) {
249                 render_background_image ();
250                 _background_dirty = false;
251         }
252
253         cairo_push_group (cr);
254
255         /* Fill with the background image */
256
257         cairo_rectangle (cr, 0, 0, get_width(), get_height());
258         cairo_set_source_surface (cr, _image, 0, 0);
259         cairo_fill (cr);
260
261         /* Render the view rectangle.  If there is an editor visual pending, don't update
262          * the view rectangle now --- wait until the expose event that we'll get after
263          * the visual change.  This prevents a flicker.
264          */
265
266         if (_editor->pending_visual_change.idle_handler_id < 0) {
267                 get_editor (&_view_rectangle_x, &_view_rectangle_y);
268         }
269
270         int32_t width = _view_rectangle_x.second - _view_rectangle_x.first;
271         std::min(8, width);
272         cairo_rectangle (cr, _view_rectangle_x.first, 0, width, get_height ());
273         cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
274         cairo_fill (cr);
275
276         /* horiz zoom */
277         cairo_rectangle (cr, _view_rectangle_x.first, 0, width, get_height ());
278         cairo_set_line_width (cr, 1);
279         cairo_set_source_rgba (cr, 1, 1, 1, 0.9);
280         cairo_stroke (cr);
281
282         /* Playhead */
283
284         cairo_set_line_width (cr, 1);
285         /* XXX: colour should be set from configuration file */
286         cairo_set_source_rgba (cr, 1, 0, 0, 1);
287
288         const double ph= playhead_frame_to_position (_editor->playhead_cursor->current_frame());
289         cairo_move_to (cr, ph, 0);
290         cairo_line_to (cr, ph, get_height());
291         cairo_stroke (cr);
292         cairo_pop_group_to_source (cr);
293         cairo_paint (cr);
294         _last_playhead = ph;
295
296 }
297
298 /** Render a region for the summary.
299  *  @param r Region view.
300  *  @param cr Cairo context.
301  *  @param y y coordinate to render at.
302  */
303 void
304 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
305 {
306         uint32_t const c = r->get_fill_color ();
307         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
308
309         if (r->region()->position() > _start) {
310                 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
311         } else {
312                 cairo_move_to (cr, 0, y);
313         }
314
315         if ((r->region()->position() + r->region()->length()) > _start) {
316                 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
317         } else {
318                 cairo_line_to (cr, 0, y);
319         }
320
321         cairo_stroke (cr);
322 }
323
324 void
325 EditorSummary::set_background_dirty ()
326 {
327         if (!_background_dirty) {
328                 _background_dirty = true;
329                 set_dirty ();
330         }
331 }
332
333 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
334 void
335 EditorSummary::set_overlays_dirty ()
336 {
337         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
338         queue_draw ();
339 }
340
341 /** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
342 void
343 EditorSummary::set_overlays_dirty (int x, int y, int w, int h)
344 {
345         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
346         queue_draw_area (x, y, w, h);
347 }
348
349
350 /** Handle a size request.
351  *  @param req GTK requisition
352  */
353 void
354 EditorSummary::on_size_request (Gtk::Requisition *req)
355 {
356         /* The left/right buttons will determine our height */
357         req->width = -1;
358         req->height = -1;
359 }
360
361
362 void
363 EditorSummary::centre_on_click (GdkEventButton* ev)
364 {
365         pair<double, double> xr;
366         get_editor (&xr);
367
368         double const w = xr.second - xr.first;
369         double ex = ev->x - w / 2;
370         if (ex < 0) {
371                 ex = 0;
372         } else if ((ex + w) > get_width()) {
373                 ex = get_width() - w;
374         }
375
376         set_editor (ex);
377 }
378
379 bool
380 EditorSummary::on_enter_notify_event (GdkEventCrossing*)
381 {
382         grab_focus ();
383         Keyboard::magic_widget_grab_focus ();
384         return false;
385 }
386
387 bool
388 EditorSummary::on_leave_notify_event (GdkEventCrossing*)
389 {
390         /* there are no inferior/child windows, so any leave event means that
391            we're gone.
392         */
393         Keyboard::magic_widget_drop_focus ();
394         return false;
395 }
396
397 bool
398 EditorSummary::on_key_press_event (GdkEventKey* key)
399 {
400         gint x, y;
401         GtkAccelKey set_playhead_accel;
402         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
403                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
404                         if (_session) {
405                                 get_pointer (x, y);
406                                 _session->request_locate (_start + (framepos_t) x / _x_scale, _session->transport_rolling());
407                                 return true;
408                         }
409                 }
410         }
411
412         return false;
413 }
414
415 bool
416 EditorSummary::on_key_release_event (GdkEventKey* key)
417 {
418
419         GtkAccelKey set_playhead_accel;
420         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
421                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
422                         return true;
423                 }
424         }
425         return false;
426 }
427
428 /** Handle a button press.
429  *  @param ev GTK event.
430  */
431 bool
432 EditorSummary::on_button_press_event (GdkEventButton* ev)
433 {
434         _old_follow_playhead = _editor->follow_playhead ();
435
436         if (ev->button != 1) {
437                 return true;
438         }
439
440         pair<double, double> xr;
441         get_editor (&xr);
442
443         _start_editor_x = xr;
444         _start_mouse_x = ev->x;
445         _start_mouse_y = ev->y;
446         _start_position = get_position (ev->x, ev->y);
447
448         if (_start_position != INSIDE && _start_position != TO_LEFT_OR_RIGHT) {
449
450                 /* start a zoom_trim drag */
451
452                 _zoom_trim_position = get_position (ev->x, ev->y);
453                 _zoom_trim_dragging = true;
454                 _editor->_dragging_playhead = true;
455                 _editor->set_follow_playhead (false);
456
457                 if (suspending_editor_updates ()) {
458                         get_editor (&_pending_editor_x, &_pending_editor_y);
459                         _pending_editor_changed = false;
460                 }
461
462         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
463
464                 /* secondary-modifier-click: locate playhead */
465                 if (_session) {
466                         _session->request_locate (ev->x / _x_scale + _start);
467                 }
468
469         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
470
471                 centre_on_click (ev);
472
473         } else {
474
475                 /* start a move+zoom drag */
476                 get_editor (&_pending_editor_x, &_pending_editor_y);
477                 _pending_editor_changed = false;
478                 _editor->_dragging_playhead = true;
479                 _editor->set_follow_playhead (false);
480
481                 _move_dragging = true;
482                 
483                 _last_mx = ev->x;
484                 _last_my = ev->y;
485                 _last_dx = 0;
486                 _last_dy = 0;
487                 _last_y_delta = 0;
488
489                 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
490         
491         }
492
493         return true;
494 }
495
496 /** @return true if we are currently suspending updates to the editor's viewport,
497  *  which we do if configured to do so, and if in a drag of some kind.
498  */
499 bool
500 EditorSummary::suspending_editor_updates () const
501 {
502         return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_trim_dragging || _move_dragging));
503 }
504
505 /** Fill in x and y with the editor's current viewable area in summary coordinates */
506 void
507 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
508 {
509         assert (x);
510         if (suspending_editor_updates ()) {
511
512                 /* We are dragging, and configured not to update the editor window during drags,
513                  * so just return where the editor will be when the drag finishes.
514                 */
515
516                 *x = _pending_editor_x;
517                 if (y) {
518                         *y = _pending_editor_y;
519                 }
520                 return;
521         }
522
523         /* Otherwise query the editor for its actual position */
524
525         x->first = (_editor->leftmost_sample () - _start) * _x_scale;
526         x->second = x->first + _editor->current_page_samples() * _x_scale;
527
528         if (y) {
529                 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
530                 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y);
531         }
532 }
533
534 /** Get an expression of the position of a point with respect to the view rectangle */
535 EditorSummary::Position
536 EditorSummary::get_position (double x, double y) const
537 {
538         /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
539            in pixels */
540
541         int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
542         x_edge_size = min (x_edge_size, 8);
543         x_edge_size = max (x_edge_size, 1);
544
545         bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
546         bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
547         bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
548
549         if (near_left) {
550                 return LEFT;
551         } else if (near_right) {
552                 return RIGHT;
553         } else if (within_x) {
554                 return INSIDE;
555         } else {
556                 return TO_LEFT_OR_RIGHT;
557         }
558 }
559
560 void
561 EditorSummary::set_cursor (Position p)
562 {
563         switch (p) {
564         case LEFT:
565                 get_window()->set_cursor (*_editor->_cursors->resize_left);
566                 break;
567         case RIGHT:
568                 get_window()->set_cursor (*_editor->_cursors->resize_right);
569                 break;
570         case INSIDE:
571                 get_window()->set_cursor (*_editor->_cursors->move);
572                 break;
573         case TO_LEFT_OR_RIGHT:
574                 get_window()->set_cursor (*_editor->_cursors->move);
575                 break;
576         default:
577                 assert (0);
578                 get_window()->set_cursor ();
579                 break;
580         }
581 }
582
583 void
584 EditorSummary::summary_zoom_step ( int steps /* positive steps to zoom "out" , negative steps to zoom "in" */  )
585 {
586         pair<double, double> xn;
587
588         get_editor (&xn);
589
590         xn.first -= steps;
591         xn.second += steps;
592
593         //for now, disallow really close zooming-in from the scroomer. ( currently it causes the start-offset to 'walk' because of integer limitations.  to fix this, probably need to maintain float throught the get/set_editor() path )
594         if (steps<0) {
595       if ( (xn.second-xn.first) < 2)
596                 return;
597         }
598
599         set_overlays_dirty ();
600         set_editor_x (xn);
601 }
602
603
604 bool
605 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
606 {
607         if (_move_dragging) {
608
609                 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
610                 //we use screen coordinates for this, not canvas-based grab_x
611                 double mx = ev->x;
612                 double dx = mx - _last_mx;
613                 double my = ev->y;
614                 double dy = my - _last_my;
615
616                 //do zooming in windowed "steps" so it feels more reversible ?
617                 const int stepsize = 2;
618                 int y_delta = _start_mouse_y - my;
619                 y_delta = y_delta / stepsize;
620
621                 //do the zoom?
622                 const float zscale = 3;
623                 if ( (dx==0) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
624
625                         summary_zoom_step( dy * zscale );
626
627                         //after the zoom we must re-calculate x-pos grabs
628                         pair<double, double> xr;
629                         get_editor (&xr);
630                         _start_editor_x = xr;
631                         _start_mouse_x = ev->x;
632                         
633                         _last_y_delta = y_delta;
634                 }
635                 
636                 //always track horizontal movement, if any
637                 if ( dx != 0 ) {
638
639                         double x = _start_editor_x.first;
640                         x += ev->x - _start_mouse_x;
641                         if (x < 0) {
642                                 x = 0;
643                         }
644                         set_editor (x);
645                 }
646
647                 _last_my = my;
648                 _last_mx = mx;
649                 _last_dx = dx;
650                 _last_dy = dy;
651
652         } else if (_zoom_trim_dragging) {
653
654                 pair<double, double> xr = _start_editor_x;
655
656                 double const dx = ev->x - _start_mouse_x;
657
658                 if (_zoom_trim_position == LEFT) {
659                         xr.first += dx;
660                 } else if (_zoom_trim_position == RIGHT) {
661                         xr.second += dx;
662                 } else {
663                         assert (0);
664                         xr.first = -1; /* do not change */
665                 }
666
667                 set_overlays_dirty ();
668                 set_cursor (_zoom_trim_position);
669                 set_editor (xr);
670
671         } else {
672                 set_cursor ( get_position(ev->x, ev->y) );
673         }
674
675         return true;
676 }
677
678 bool
679 EditorSummary::on_button_release_event (GdkEventButton*)
680 {
681         bool const was_suspended = suspending_editor_updates ();
682
683         _move_dragging = false;
684         _zoom_trim_dragging = false;
685         _editor->_dragging_playhead = false;
686         _editor->set_follow_playhead (_old_follow_playhead, false);
687
688         if (was_suspended && _pending_editor_changed) {
689                 set_editor (_pending_editor_x);
690         }
691
692         return true;
693 }
694
695 bool
696 EditorSummary::on_scroll_event (GdkEventScroll* ev)
697 {
698         /* mouse wheel */
699         pair<double, double> xr;
700         get_editor (&xr);
701         double x = xr.first;
702
703         switch (ev->direction) {
704                 case GDK_SCROLL_UP: {
705                         
706                         summary_zoom_step( -4 );
707                 
708                         return true;
709                 } break;
710                 
711                 case GDK_SCROLL_DOWN: {
712                         
713                         summary_zoom_step( 4 );
714                 
715                         return true;
716                 } break;
717                 
718                 case GDK_SCROLL_LEFT:
719                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
720                                 _editor->temporal_zoom_step (false);
721                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
722                                 x -= 64;
723                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
724                                 x -= 1;
725                         } else {
726                                 _editor->scroll_left_half_page ();
727                                 return true;
728                         }
729                         break;
730                 case GDK_SCROLL_RIGHT:
731                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
732                                 _editor->temporal_zoom_step (true);
733                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
734                                 x += 64;
735                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
736                                 x += 1;
737                         } else {
738                                 _editor->scroll_right_half_page ();
739                                 return true;
740                         }
741                         break;
742                 default:
743                         break;
744         }
745
746         set_editor (x);
747         return true;
748 }
749
750 /** Set the editor to display a x range with the left at a given position
751  *  and a y range with the top at a given position.
752  *  x and y parameters are specified in summary coordinates.
753  *  Zoom is not changed in either direction.
754  */
755 void
756 EditorSummary::set_editor (double const x)
757 {
758         if (_editor->pending_visual_change.idle_handler_id >= 0 && _editor->pending_visual_change.being_handled == true) {
759
760                 /* As a side-effect, the Editor's visual change idle handler processes
761                    pending GTK events.  Hence this motion notify handler can be called
762                    in the middle of a visual change idle handler, and if this happens,
763                    the queue_visual_change calls below modify the variables that the
764                    idle handler is working with.  This causes problems.  Hence this
765                    check.  It ensures that we won't modify the pending visual change
766                    while a visual change idle handler is in progress.  It's not perfect,
767                    as it also means that we won't change these variables if an idle handler
768                    is merely pending but not executing.  But c'est la vie.
769                 */
770
771                 return;
772         }
773
774         set_editor_x (x);
775 }
776
777 /** Set the editor to display a given x range and a y range with the top at a given position.
778  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
779  *  x and y parameters are specified in summary coordinates.
780  */
781 void
782 EditorSummary::set_editor (pair<double,double> const x)
783 {
784         if (_editor->pending_visual_change.idle_handler_id >= 0) {
785                 /* see comment in other set_editor () */
786                 return;
787         }
788
789         if (x.first >= 0) {
790                 set_editor_x (x);
791         }
792 }
793
794 /** Set the left of the x range visible in the editor.
795  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
796  *  @param x new x left position in summary coordinates.
797  */
798 void
799 EditorSummary::set_editor_x (double x)
800 {
801         if (x < 0) {
802                 x = 0;
803         }
804
805         if (suspending_editor_updates ()) {
806                 double const w = _pending_editor_x.second - _pending_editor_x.first;
807                 _pending_editor_x.first = x;
808                 _pending_editor_x.second = x + w;
809                 _pending_editor_changed = true;
810                 set_dirty ();
811         } else {
812                 _editor->reset_x_origin (x / _x_scale + _start);
813         }
814 }
815
816 /** Set the x range visible in the editor.
817  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
818  *  @param x new x range in summary coordinates.
819  */
820 void
821 EditorSummary::set_editor_x (pair<double, double> x)
822 {
823         if (x.first < 0) {
824                 x.first = 0;
825         }
826
827         if (x.second < 0) {
828                 x.second = x.first + 1;
829         }
830
831         if (suspending_editor_updates ()) {
832                 _pending_editor_x = x;
833                 _pending_editor_changed = true;
834                 set_dirty ();
835         } else {
836                 _editor->reset_x_origin (x.first / _x_scale + _start);
837
838                 double const nx = (
839                         ((x.second - x.first) / _x_scale) /
840                         _editor->sample_to_pixel (_editor->current_page_samples())
841                         );
842
843                 if (nx != _editor->get_current_zoom ()) {
844                         _editor->reset_zoom (nx);
845                 }
846         }
847 }
848
849 void
850 EditorSummary::playhead_position_changed (framepos_t p)
851 {
852         int const o = int (_last_playhead);
853         int const n = int (playhead_frame_to_position (p));
854         if (_session && o != n) {
855                 int a = max(2, min (o, n));
856                 int b = max (o, n);
857                 set_overlays_dirty (a - 2, 0, b + 2, get_height ());
858         }
859 }
860
861 double
862 EditorSummary::editor_y_to_summary (double y) const
863 {
864         double sy = 0;
865         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
866
867                 if ((*i)->hidden()) {
868                         continue;
869                 }
870
871                 double const h = (*i)->effective_height ();
872                 if (y < h) {
873                         /* in this track */
874                         return sy + y * _track_height / h;
875                 }
876
877                 sy += _track_height;
878                 y -= h;
879         }
880
881         return sy;
882 }
883
884 void
885 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
886 {
887         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
888                 /* Connect to the relevant signal for the route so that we know when its colour has changed */
889                 (*i)->route()->presentation_info().PropertyChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
890                 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
891                 if (tr) {
892                         tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context ());
893                 }
894         }
895
896         set_background_dirty ();
897 }
898
899 void
900 EditorSummary::route_gui_changed (PBD::PropertyChange const& what_changed)
901 {
902         if (what_changed.contains (Properties::color)) {
903                 set_background_dirty ();
904         }
905 }
906
907 double
908 EditorSummary::playhead_frame_to_position (framepos_t t) const
909 {
910         return (t - _start) * _x_scale;
911 }
912
913 framepos_t
914 EditorSummary::position_to_playhead_frame_to_position (double pos) const
915 {
916         return _start  + (pos * _x_scale);
917 }