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