Fix pasting automation at 0
[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         int32_t height = _view_rectangle_y.second - _view_rectangle_y.first;
273         cairo_rectangle (cr, _view_rectangle_x.first, 0, width, get_height ());
274         cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
275         cairo_fill (cr);
276
277         /* horiz zoom */
278         cairo_rectangle (cr, _view_rectangle_x.first, 0, width, get_height ());
279         cairo_set_line_width (cr, 1);
280         cairo_set_source_rgba (cr, 1, 1, 1, 0.9);
281         cairo_stroke (cr);
282
283         /* Playhead */
284
285         cairo_set_line_width (cr, 1);
286         /* XXX: colour should be set from configuration file */
287         cairo_set_source_rgba (cr, 1, 0, 0, 1);
288
289         const double ph= playhead_frame_to_position (_editor->playhead_cursor->current_frame());
290         cairo_move_to (cr, ph, 0);
291         cairo_line_to (cr, ph, get_height());
292         cairo_stroke (cr);
293         cairo_pop_group_to_source (cr);
294         cairo_paint (cr);
295         _last_playhead = ph;
296
297 }
298
299 /** Render a region for the summary.
300  *  @param r Region view.
301  *  @param cr Cairo context.
302  *  @param y y coordinate to render at.
303  */
304 void
305 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
306 {
307         uint32_t const c = r->get_fill_color ();
308         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
309
310         if (r->region()->position() > _start) {
311                 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
312         } else {
313                 cairo_move_to (cr, 0, y);
314         }
315
316         if ((r->region()->position() + r->region()->length()) > _start) {
317                 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
318         } else {
319                 cairo_line_to (cr, 0, y);
320         }
321
322         cairo_stroke (cr);
323 }
324
325 void
326 EditorSummary::set_background_dirty ()
327 {
328         if (!_background_dirty) {
329                 _background_dirty = true;
330                 set_dirty ();
331         }
332 }
333
334 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
335 void
336 EditorSummary::set_overlays_dirty ()
337 {
338         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
339         queue_draw ();
340 }
341
342 /** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
343 void
344 EditorSummary::set_overlays_dirty (int x, int y, int w, int h)
345 {
346         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
347         queue_draw_area (x, y, w, h);
348 }
349
350
351 /** Handle a size request.
352  *  @param req GTK requisition
353  */
354 void
355 EditorSummary::on_size_request (Gtk::Requisition *req)
356 {
357         /* The left/right buttons will determine our height */
358         req->width = -1;
359         req->height = -1;
360 }
361
362
363 void
364 EditorSummary::centre_on_click (GdkEventButton* ev)
365 {
366         pair<double, double> xr;
367         get_editor (&xr);
368
369         double const w = xr.second - xr.first;
370         double ex = ev->x - w / 2;
371         if (ex < 0) {
372                 ex = 0;
373         } else if ((ex + w) > get_width()) {
374                 ex = get_width() - w;
375         }
376
377         set_editor (ex);
378 }
379
380 bool
381 EditorSummary::on_enter_notify_event (GdkEventCrossing*)
382 {
383         grab_focus ();
384         Keyboard::magic_widget_grab_focus ();
385         return false;
386 }
387
388 bool
389 EditorSummary::on_leave_notify_event (GdkEventCrossing*)
390 {
391         /* there are no inferior/child windows, so any leave event means that
392            we're gone.
393         */
394         Keyboard::magic_widget_drop_focus ();
395         return false;
396 }
397
398 bool
399 EditorSummary::on_key_press_event (GdkEventKey* key)
400 {
401         gint x, y;
402         GtkAccelKey set_playhead_accel;
403         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
404                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
405                         if (_session) {
406                                 get_pointer (x, y);
407                                 _session->request_locate (_start + (framepos_t) x / _x_scale, _session->transport_rolling());
408                                 return true;
409                         }
410                 }
411         }
412
413         return false;
414 }
415
416 bool
417 EditorSummary::on_key_release_event (GdkEventKey* key)
418 {
419
420         GtkAccelKey set_playhead_accel;
421         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
422                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
423                         return true;
424                 }
425         }
426         return false;
427 }
428
429 /** Handle a button press.
430  *  @param ev GTK event.
431  */
432 bool
433 EditorSummary::on_button_press_event (GdkEventButton* ev)
434 {
435         _old_follow_playhead = _editor->follow_playhead ();
436
437         if (ev->button != 1) {
438                 return true;
439         }
440
441         pair<double, double> xr;
442         get_editor (&xr);
443
444         _start_editor_x = xr;
445         _start_mouse_x = ev->x;
446         _start_mouse_y = ev->y;
447         _start_position = get_position (ev->x, ev->y);
448
449         if (_start_position != INSIDE && _start_position != TO_LEFT_OR_RIGHT) {
450
451                 /* start a zoom_trim drag */
452
453                 _zoom_trim_position = get_position (ev->x, ev->y);
454                 _zoom_trim_dragging = true;
455                 _editor->_dragging_playhead = true;
456                 _editor->set_follow_playhead (false);
457
458                 if (suspending_editor_updates ()) {
459                         get_editor (&_pending_editor_x, &_pending_editor_y);
460                         _pending_editor_changed = false;
461                 }
462
463         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
464
465                 /* secondary-modifier-click: locate playhead */
466                 if (_session) {
467                         _session->request_locate (ev->x / _x_scale + _start);
468                 }
469
470         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
471
472                 centre_on_click (ev);
473
474         } else {
475
476                 /* start a move+zoom drag */
477                 get_editor (&_pending_editor_x, &_pending_editor_y);
478                 _pending_editor_changed = false;
479                 _editor->_dragging_playhead = true;
480                 _editor->set_follow_playhead (false);
481
482                 _move_dragging = true;
483                 
484                 _last_mx = ev->x;
485                 _last_my = ev->y;
486                 _last_dx = 0;
487                 _last_dy = 0;
488                 _last_y_delta = 0;
489
490                 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
491         
492         }
493
494         return true;
495 }
496
497 /** @return true if we are currently suspending updates to the editor's viewport,
498  *  which we do if configured to do so, and if in a drag of some kind.
499  */
500 bool
501 EditorSummary::suspending_editor_updates () const
502 {
503         return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_trim_dragging || _move_dragging));
504 }
505
506 /** Fill in x and y with the editor's current viewable area in summary coordinates */
507 void
508 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
509 {
510         assert (x);
511         if (suspending_editor_updates ()) {
512
513                 /* We are dragging, and configured not to update the editor window during drags,
514                  * so just return where the editor will be when the drag finishes.
515                 */
516
517                 *x = _pending_editor_x;
518                 if (y) {
519                         *y = _pending_editor_y;
520                 }
521                 return;
522         }
523
524         /* Otherwise query the editor for its actual position */
525
526         x->first = (_editor->leftmost_sample () - _start) * _x_scale;
527         x->second = x->first + _editor->current_page_samples() * _x_scale;
528
529         if (y) {
530                 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
531                 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y);
532         }
533 }
534
535 /** Get an expression of the position of a point with respect to the view rectangle */
536 EditorSummary::Position
537 EditorSummary::get_position (double x, double y) const
538 {
539         /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
540            in pixels */
541
542         int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
543         x_edge_size = min (x_edge_size, 8);
544         x_edge_size = max (x_edge_size, 1);
545
546         bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
547         bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
548         bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
549
550         if (near_left) {
551                 return LEFT;
552         } else if (near_right) {
553                 return RIGHT;
554         } else if (within_x) {
555                 return INSIDE;
556         } else {
557                 return TO_LEFT_OR_RIGHT;
558         }
559 }
560
561 void
562 EditorSummary::set_cursor (Position p)
563 {
564         switch (p) {
565         case LEFT:
566                 get_window()->set_cursor (*_editor->_cursors->resize_left);
567                 break;
568         case RIGHT:
569                 get_window()->set_cursor (*_editor->_cursors->resize_right);
570                 break;
571         case INSIDE:
572                 get_window()->set_cursor (*_editor->_cursors->move);
573                 break;
574         case TO_LEFT_OR_RIGHT:
575                 get_window()->set_cursor (*_editor->_cursors->move);
576                 break;
577         default:
578                 assert (0);
579                 get_window()->set_cursor ();
580                 break;
581         }
582 }
583
584 void
585 EditorSummary::summary_zoom_step ( int steps /* positive steps to zoom "out" , negative steps to zoom "in" */  )
586 {
587         pair<double, double> xn;
588
589         get_editor (&xn);
590
591         xn.first -= steps;
592         xn.second += steps;
593
594         //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 )
595         if (steps<0) {
596       if ( (xn.second-xn.first) < 2)
597                 return;
598         }
599
600         set_overlays_dirty ();
601         set_editor_x (xn);
602 }
603
604
605 bool
606 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
607 {
608         if (_move_dragging) {
609
610                 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
611                 //we use screen coordinates for this, not canvas-based grab_x
612                 double mx = ev->x;
613                 double dx = mx - _last_mx;
614                 double my = ev->y;
615                 double dy = my - _last_my;
616
617                 //do zooming in windowed "steps" so it feels more reversible ?
618                 const int stepsize = 2;
619                 int y_delta = _start_mouse_y - my;
620                 y_delta = y_delta / stepsize;
621
622                 //do the zoom?
623                 const float zscale = 3;
624                 if ( (dx==0) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
625
626                         summary_zoom_step( dy * zscale );
627
628                         //after the zoom we must re-calculate x-pos grabs
629                         pair<double, double> xr;
630                         get_editor (&xr);
631                         _start_editor_x = xr;
632                         _start_mouse_x = ev->x;
633                         
634                         _last_y_delta = y_delta;
635                 }
636                 
637                 //always track horizontal movement, if any
638                 if ( dx != 0 ) {
639
640                         double x = _start_editor_x.first;
641                         x += ev->x - _start_mouse_x;
642                         if (x < 0) {
643                                 x = 0;
644                         }
645                         set_editor (x);
646                 }
647
648                 _last_my = my;
649                 _last_mx = mx;
650                 _last_dx = dx;
651                 _last_dy = dy;
652
653         } else if (_zoom_trim_dragging) {
654
655                 pair<double, double> xr = _start_editor_x;
656
657                 double const dx = ev->x - _start_mouse_x;
658
659                 if (_zoom_trim_position == LEFT) {
660                         xr.first += dx;
661                 } else if (_zoom_trim_position == RIGHT) {
662                         xr.second += dx;
663                 } else {
664                         assert (0);
665                         xr.first = -1; /* do not change */
666                 }
667
668                 set_overlays_dirty ();
669                 set_cursor (_zoom_trim_position);
670                 set_editor (xr);
671
672         } else {
673                 set_cursor ( get_position(ev->x, ev->y) );
674         }
675
676         return true;
677 }
678
679 bool
680 EditorSummary::on_button_release_event (GdkEventButton*)
681 {
682         bool const was_suspended = suspending_editor_updates ();
683
684         _move_dragging = false;
685         _zoom_trim_dragging = false;
686         _editor->_dragging_playhead = false;
687         _editor->set_follow_playhead (_old_follow_playhead, false);
688
689         if (was_suspended && _pending_editor_changed) {
690                 set_editor (_pending_editor_x);
691         }
692
693         return true;
694 }
695
696 bool
697 EditorSummary::on_scroll_event (GdkEventScroll* ev)
698 {
699         /* mouse wheel */
700         pair<double, double> xr;
701         get_editor (&xr);
702         double x = xr.first;
703
704         switch (ev->direction) {
705                 case GDK_SCROLL_UP: {
706                         
707                         summary_zoom_step( -4 );
708                 
709                         return true;
710                 } break;
711                 
712                 case GDK_SCROLL_DOWN: {
713                         
714                         summary_zoom_step( 4 );
715                 
716                         return true;
717                 } break;
718                 
719                 case GDK_SCROLL_LEFT:
720                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
721                                 _editor->temporal_zoom_step (false);
722                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
723                                 x -= 64;
724                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
725                                 x -= 1;
726                         } else {
727                                 _editor->scroll_left_half_page ();
728                                 return true;
729                         }
730                         break;
731                 case GDK_SCROLL_RIGHT:
732                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
733                                 _editor->temporal_zoom_step (true);
734                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
735                                 x += 64;
736                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
737                                 x += 1;
738                         } else {
739                                 _editor->scroll_right_half_page ();
740                                 return true;
741                         }
742                         break;
743                 default:
744                         break;
745         }
746
747         set_editor (x);
748         return true;
749 }
750
751 /** Set the editor to display a x range with the left at a given position
752  *  and a y range with the top at a given position.
753  *  x and y parameters are specified in summary coordinates.
754  *  Zoom is not changed in either direction.
755  */
756 void
757 EditorSummary::set_editor (double const x)
758 {
759         if (_editor->pending_visual_change.idle_handler_id >= 0 && _editor->pending_visual_change.being_handled == true) {
760
761                 /* As a side-effect, the Editor's visual change idle handler processes
762                    pending GTK events.  Hence this motion notify handler can be called
763                    in the middle of a visual change idle handler, and if this happens,
764                    the queue_visual_change calls below modify the variables that the
765                    idle handler is working with.  This causes problems.  Hence this
766                    check.  It ensures that we won't modify the pending visual change
767                    while a visual change idle handler is in progress.  It's not perfect,
768                    as it also means that we won't change these variables if an idle handler
769                    is merely pending but not executing.  But c'est la vie.
770                 */
771
772                 return;
773         }
774
775         set_editor_x (x);
776 }
777
778 /** Set the editor to display a given x range and a y range with the top at a given position.
779  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
780  *  x and y parameters are specified in summary coordinates.
781  */
782 void
783 EditorSummary::set_editor (pair<double,double> const x)
784 {
785         if (_editor->pending_visual_change.idle_handler_id >= 0) {
786                 /* see comment in other set_editor () */
787                 return;
788         }
789
790         if (x.first >= 0) {
791                 set_editor_x (x);
792         }
793 }
794
795 /** Set the left of the x range visible in the editor.
796  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
797  *  @param x new x left position in summary coordinates.
798  */
799 void
800 EditorSummary::set_editor_x (double x)
801 {
802         if (x < 0) {
803                 x = 0;
804         }
805
806         if (suspending_editor_updates ()) {
807                 double const w = _pending_editor_x.second - _pending_editor_x.first;
808                 _pending_editor_x.first = x;
809                 _pending_editor_x.second = x + w;
810                 _pending_editor_changed = true;
811                 set_dirty ();
812         } else {
813                 _editor->reset_x_origin (x / _x_scale + _start);
814         }
815 }
816
817 /** Set the x range visible in the editor.
818  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
819  *  @param x new x range in summary coordinates.
820  */
821 void
822 EditorSummary::set_editor_x (pair<double, double> x)
823 {
824         if (x.first < 0) {
825                 x.first = 0;
826         }
827
828         if (x.second < 0) {
829                 x.second = x.first + 1;
830         }
831
832         if (suspending_editor_updates ()) {
833                 _pending_editor_x = x;
834                 _pending_editor_changed = true;
835                 set_dirty ();
836         } else {
837                 _editor->reset_x_origin (x.first / _x_scale + _start);
838
839                 double const nx = (
840                         ((x.second - x.first) / _x_scale) /
841                         _editor->sample_to_pixel (_editor->current_page_samples())
842                         );
843
844                 if (nx != _editor->get_current_zoom ()) {
845                         _editor->reset_zoom (nx);
846                 }
847         }
848 }
849
850 void
851 EditorSummary::playhead_position_changed (framepos_t p)
852 {
853         int const o = int (_last_playhead);
854         int const n = int (playhead_frame_to_position (p));
855         if (_session && o != n) {
856                 int a = max(2, min (o, n));
857                 int b = max (o, n);
858                 set_overlays_dirty (a - 2, 0, b + 2, get_height ());
859         }
860 }
861
862 double
863 EditorSummary::editor_y_to_summary (double y) const
864 {
865         double sy = 0;
866         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
867
868                 if ((*i)->hidden()) {
869                         continue;
870                 }
871
872                 double const h = (*i)->effective_height ();
873                 if (y < h) {
874                         /* in this track */
875                         return sy + y * _track_height / h;
876                 }
877
878                 sy += _track_height;
879                 y -= h;
880         }
881
882         return sy;
883 }
884
885 void
886 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
887 {
888         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
889                 /* Connect to the relevant signal for the route so that we know when its colour has changed */
890                 (*i)->route()->presentation_info().PropertyChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
891                 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
892                 if (tr) {
893                         tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context ());
894                 }
895         }
896
897         set_background_dirty ();
898 }
899
900 void
901 EditorSummary::route_gui_changed (PBD::PropertyChange const& what_changed)
902 {
903         if (what_changed.contains (Properties::color)) {
904                 set_background_dirty ();
905         }
906 }
907
908 double
909 EditorSummary::playhead_frame_to_position (framepos_t t) const
910 {
911         return (t - _start) * _x_scale;
912 }
913
914 framepos_t
915 EditorSummary::position_to_playhead_frame_to_position (double pos) const
916 {
917         return _start  + (pos * _x_scale);
918 }