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