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