error/debug output when dropping MIDI event due to timing now shows MIDI bytes
[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
37 using namespace std;
38 using namespace ARDOUR;
39 using Gtkmm2ext::Keyboard;
40
41 /** Construct an EditorSummary.
42  *  @param e Editor to represent.
43  */
44 EditorSummary::EditorSummary (Editor* e)
45         : EditorComponent (e),
46           _start (0),
47           _end (1),
48           _overhang_fraction (0.1),
49           _x_scale (1),
50           _track_height (16),
51           _last_playhead (-1),
52           _move_dragging (false),
53           _moved (false),
54           _view_rectangle_x (0, 0),
55           _view_rectangle_y (0, 0),
56           _zoom_dragging (false),
57           _old_follow_playhead (false),
58           _image (0),
59           _background_dirty (true)
60 {
61         add_events (Gdk::POINTER_MOTION_MASK|Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
62         set_flags (get_flags() | Gtk::CAN_FOCUS);
63 }
64
65 EditorSummary::~EditorSummary ()
66 {
67         cairo_surface_destroy (_image);
68 }
69
70 /** Handle a size allocation.
71  *  @param alloc GTK allocation.
72  */
73 void
74 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
75 {
76         Gtk::EventBox::on_size_allocate (alloc);
77         _background_dirty = true;
78         set_dirty ();
79 }
80
81
82 /** Connect to a session.
83  *  @param s Session.
84  */
85 void
86 EditorSummary::set_session (Session* s)
87 {
88         SessionHandlePtr::set_session (s);
89
90         set_dirty ();
91
92         /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
93          * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
94          * emitted when a cut region is added to the `cutlist' playlist.
95          */
96
97         if (_session) {
98                 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
99                 Route::RemoteControlIDChange.connect (route_ctrl_id_connection, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
100                 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), boost::bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
101                 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
102                 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
103                 _editor->selection->RegionsChanged.connect (sigc::mem_fun(*this, &EditorSummary::set_background_dirty));
104         }
105 }
106
107 void
108 EditorSummary::render_background_image ()
109 {
110         cairo_surface_destroy (_image); // passing NULL is safe
111         _image = cairo_image_surface_create (CAIRO_FORMAT_RGB24, get_width (), get_height ());
112
113         cairo_t* cr = cairo_create (_image);
114
115        /* background (really just the dividing lines between tracks */
116
117         cairo_set_source_rgb (cr, 0, 0, 0);
118         cairo_rectangle (cr, 0, 0, get_width(), get_height());
119         cairo_fill (cr);
120
121         /* compute start and end points for the summary */
122
123         framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
124         double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
125         _start = theoretical_start > 0 ? theoretical_start : 0;
126         _end = _session->current_end_frame() + session_length * _overhang_fraction;
127
128         /* compute track height */
129         int N = 0;
130         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
131                 if (!(*i)->hidden()) {
132                         ++N;
133                 }
134         }
135
136         if (N == 0) {
137                 _track_height = 16;
138         } else {
139                 _track_height = (double) get_height() / N;
140         }
141
142         /* calculate x scale */
143         if (_end != _start) {
144                 _x_scale = static_cast<double> (get_width()) / (_end - _start);
145         } else {
146                 _x_scale = 1;
147         }
148
149         /* render tracks and regions */
150
151         double y = 0;
152         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
153
154                 if ((*i)->hidden()) {
155                         continue;
156                 }
157
158                 /* paint a non-bg colored strip to represent the track itself */
159
160                 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
161                 cairo_set_line_width (cr, _track_height - 1);
162                 cairo_move_to (cr, 0, y + _track_height / 2);
163                 cairo_line_to (cr, get_width(), y + _track_height / 2);
164                 cairo_stroke (cr);
165
166                 StreamView* s = (*i)->view ();
167
168                 if (s) {
169                         cairo_set_line_width (cr, _track_height * 0.6);
170
171                         s->foreach_regionview (sigc::bind (
172                                                        sigc::mem_fun (*this, &EditorSummary::render_region),
173                                                        cr,
174                                                        y + _track_height / 2
175                                                        ));
176                 }
177
178                 y += _track_height;
179         }
180
181         /* start and end markers */
182
183         cairo_set_line_width (cr, 1);
184         cairo_set_source_rgb (cr, 1, 1, 0);
185
186         const double p = (_session->current_start_frame() - _start) * _x_scale;
187         cairo_move_to (cr, p, 0);
188         cairo_line_to (cr, p, get_height());
189
190         double const q = (_session->current_end_frame() - _start) * _x_scale;
191         cairo_move_to (cr, q, 0);
192         cairo_line_to (cr, q, get_height());
193         cairo_stroke (cr);
194
195         cairo_destroy (cr);
196 }
197
198 /** Render the required regions to a cairo context.
199  *  @param cr Context.
200  */
201 void
202 EditorSummary::render (cairo_t* cr, cairo_rectangle_t*)
203 {
204
205         if (_session == 0) {
206                 return;
207         }
208
209         if (!_image || _background_dirty) {
210                 render_background_image ();
211                 _background_dirty = false;
212         }
213
214         cairo_push_group (cr);
215         
216         /* Fill with the background image */
217
218         cairo_rectangle (cr, 0, 0, get_width(), get_height());
219         cairo_set_source_surface (cr, _image, 0, 0);
220         cairo_fill (cr);
221
222         /* Render the view rectangle.  If there is an editor visual pending, don't update
223            the view rectangle now --- wait until the expose event that we'll get after
224            the visual change.  This prevents a flicker.
225         */
226
227         if (_editor->pending_visual_change.idle_handler_id < 0) {
228                 get_editor (&_view_rectangle_x, &_view_rectangle_y);
229         }
230
231         int32_t width = _view_rectangle_x.second - _view_rectangle_x.first;
232         int32_t height = _view_rectangle_y.second - _view_rectangle_y.first;
233         cairo_rectangle (cr, _view_rectangle_x.first, _view_rectangle_y.first, width, height); 
234         cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
235         cairo_fill_preserve (cr);
236         cairo_set_line_width (cr, 1);
237         cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
238         cairo_stroke (cr);
239
240         /* Playhead */
241
242         cairo_set_line_width (cr, 1);
243         /* XXX: colour should be set from configuration file */
244         cairo_set_source_rgba (cr, 1, 0, 0, 1);
245
246         const double ph= playhead_frame_to_position (_editor->playhead_cursor->current_frame());
247         cairo_move_to (cr, ph, 0);
248         cairo_line_to (cr, ph, get_height());
249         cairo_stroke (cr);
250         cairo_pop_group_to_source (cr);
251         cairo_paint (cr);
252         _last_playhead = ph;
253
254 }
255
256 /** Render a region for the summary.
257  *  @param r Region view.
258  *  @param cr Cairo context.
259  *  @param y y coordinate to render at.
260  */
261 void
262 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
263 {
264         uint32_t const c = r->get_fill_color ();
265         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
266
267         if (r->region()->position() > _start) {
268                 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
269         } else {
270                 cairo_move_to (cr, 0, y);
271         }
272
273         if ((r->region()->position() + r->region()->length()) > _start) {
274                 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
275         } else {
276                 cairo_line_to (cr, 0, y);
277         }
278
279         cairo_stroke (cr);
280 }
281
282 void
283 EditorSummary::set_background_dirty ()
284 {
285         _background_dirty = true;
286         set_dirty ();
287 }
288
289 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
290 void
291 EditorSummary::set_overlays_dirty ()
292 {
293         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
294         queue_draw ();
295 }
296
297 /** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
298 void
299 EditorSummary::set_overlays_dirty (int x, int y, int w, int h)
300 {
301         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
302         queue_draw_area (x, y, w, h);
303 }
304
305
306 /** Handle a size request.
307  *  @param req GTK requisition
308  */
309 void
310 EditorSummary::on_size_request (Gtk::Requisition *req)
311 {
312         /* Use a dummy, small width and the actual height that we want */
313         req->width = 64;
314         req->height = 32;
315 }
316
317
318 void
319 EditorSummary::centre_on_click (GdkEventButton* ev)
320 {
321         pair<double, double> xr;
322         pair<double, double> yr;
323         get_editor (&xr, &yr);
324
325         double const w = xr.second - xr.first;
326         double ex = ev->x - w / 2;
327         if (ex < 0) {
328                 ex = 0;
329         } else if ((ex + w) > get_width()) {
330                 ex = get_width() - w;
331         }
332
333         double const h = yr.second - yr.first;
334         double ey = ev->y - h / 2;
335         if (ey < 0) {
336                 ey = 0;
337         } else if ((ey + h) > get_height()) {
338                 ey = get_height() - h;
339         }
340
341         set_editor (ex, ey);
342 }
343
344 bool 
345 EditorSummary::on_enter_notify_event (GdkEventCrossing*)
346 {
347         grab_focus ();
348         Keyboard::magic_widget_grab_focus ();
349         return false;
350 }
351
352 bool 
353 EditorSummary::on_leave_notify_event (GdkEventCrossing*)
354 {
355         /* there are no inferior/child windows, so any leave event means that
356            we're gone.
357         */
358         Keyboard::magic_widget_drop_focus ();
359         return false;
360 }
361
362 bool
363 EditorSummary::on_key_press_event (GdkEventKey* key)
364 {
365         gint x, y;
366         GtkAccelKey set_playhead_accel;
367         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
368                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
369                         if (_session) {
370                                 get_pointer (x, y);
371                                 _session->request_locate (_start + (framepos_t) x / _x_scale, _session->transport_rolling());
372                                 return true;
373                         }
374                 }
375         }
376
377         return false;
378 }
379
380 bool
381 EditorSummary::on_key_release_event (GdkEventKey* key)
382 {
383
384         GtkAccelKey set_playhead_accel;
385         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
386                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
387                         return true;
388                 }
389         }
390         return false;
391 }
392
393 /** Handle a button press.
394  *  @param ev GTK event.
395  */
396 bool
397 EditorSummary::on_button_press_event (GdkEventButton* ev)
398 {
399         _old_follow_playhead = _editor->follow_playhead ();
400         
401         if (ev->button == 1) {
402
403                 pair<double, double> xr;
404                 pair<double, double> yr;
405                 get_editor (&xr, &yr);
406
407                 _start_editor_x = xr;
408                 _start_editor_y = yr;
409                 _start_mouse_x = ev->x;
410                 _start_mouse_y = ev->y;
411                 _start_position = get_position (ev->x, ev->y);
412
413                 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
414                     _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
415                         ) {
416
417                         /* start a zoom drag */
418
419                         _zoom_position = get_position (ev->x, ev->y);
420                         _zoom_dragging = true;
421                         _editor->_dragging_playhead = true;
422                         _editor->set_follow_playhead (false);
423
424                         if (suspending_editor_updates ()) {
425                                 get_editor (&_pending_editor_x, &_pending_editor_y);
426                                 _pending_editor_changed = false;
427                         }
428                         
429                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
430
431                         /* secondary-modifier-click: locate playhead */
432                         if (_session) {
433                                 _session->request_locate (ev->x / _x_scale + _start);
434                         }
435
436                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
437
438                         centre_on_click (ev);
439
440                 } else {
441
442                         /* start a move drag */
443
444                         /* get the editor's state in case we are suspending updates */
445                         get_editor (&_pending_editor_x, &_pending_editor_y);
446                         _pending_editor_changed = false;
447
448                         _move_dragging = true;
449                         _moved = false;
450                         _editor->_dragging_playhead = true;
451                         _editor->set_follow_playhead (false);
452
453                         ArdourCanvas::checkpoint ("sum", "------------------ summary move drag starts.\n");
454                 }
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 (!Config->get_update_editor_during_summary_drag () && (_zoom_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         assert (y);
475
476         if (suspending_editor_updates ()) {
477
478                 /* We are dragging, and configured not to update the editor window during drags,
479                    so just return where the editor will be when the drag finishes.
480                 */
481                    
482                 *x = _pending_editor_x;
483                 *y = _pending_editor_y;
484
485         } else {
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                 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
493                 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->visible_canvas_height());
494         }
495 }
496
497 /** Get an expression of the position of a point with respect to the view rectangle */
498 EditorSummary::Position
499 EditorSummary::get_position (double x, double y) const
500 {
501         /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
502            in pixels */
503
504         int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
505         x_edge_size = min (x_edge_size, 8);
506         x_edge_size = max (x_edge_size, 1);
507
508         int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
509         y_edge_size = min (y_edge_size, 8);
510         y_edge_size = max (y_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 near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
515         bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_edge_size);
516         bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
517         bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
518
519         if (near_left && near_top) {
520                 return LEFT_TOP;
521         } else if (near_left && near_bottom) {
522                 return LEFT_BOTTOM;
523         } else if (near_right && near_top) {
524                 return RIGHT_TOP;
525         } else if (near_right && near_bottom) {
526                 return RIGHT_BOTTOM;
527         } else if (near_left && within_y) {
528                 return LEFT;
529         } else if (near_right && within_y) {
530                 return RIGHT;
531         } else if (near_top && within_x) {
532                 return TOP;
533         } else if (near_bottom && within_x) {
534                 return BOTTOM;
535         } else if (within_x && within_y) {
536                 return INSIDE;
537         } else if (within_x) {
538                 return BELOW_OR_ABOVE;
539         } else if (within_y) {
540                 return TO_LEFT_OR_RIGHT;
541         } else {
542                 return OTHERWISE_OUTSIDE;
543         }
544 }
545
546 void
547 EditorSummary::set_cursor (Position p)
548 {
549         switch (p) {
550         case LEFT:
551                 get_window()->set_cursor (*_editor->_cursors->resize_left);
552                 break;
553         case LEFT_TOP:
554                 get_window()->set_cursor (*_editor->_cursors->resize_top_left);
555                 break;
556         case TOP:
557                 get_window()->set_cursor (*_editor->_cursors->resize_top);
558                 break;
559         case RIGHT_TOP:
560                 get_window()->set_cursor (*_editor->_cursors->resize_top_right);
561                 break;
562         case RIGHT:
563                 get_window()->set_cursor (*_editor->_cursors->resize_right);
564                 break;
565         case RIGHT_BOTTOM:
566                 get_window()->set_cursor (*_editor->_cursors->resize_bottom_right);
567                 break;
568         case BOTTOM:
569                 get_window()->set_cursor (*_editor->_cursors->resize_bottom);
570                 break;
571         case LEFT_BOTTOM:
572                 get_window()->set_cursor (*_editor->_cursors->resize_bottom_left);
573                 break;
574         case INSIDE:
575                 get_window()->set_cursor (*_editor->_cursors->move);
576                 break;
577         case TO_LEFT_OR_RIGHT:
578                 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
579                 break;
580         case BELOW_OR_ABOVE:
581                 get_window()->set_cursor (*_editor->_cursors->expand_up_down);
582                 break;
583         default:
584                 get_window()->set_cursor ();
585                 break;
586         }
587 }
588
589 bool
590 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
591 {
592         pair<double, double> xr = _start_editor_x;
593         pair<double, double> yr = _start_editor_y;
594         double x = _start_editor_x.first;
595         double y = _start_editor_y.first;
596
597         if (_move_dragging) {
598
599                 _moved = true;
600
601                 /* don't alter x if we clicked outside and above or below the viewbox */
602                 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
603                         x += ev->x - _start_mouse_x;
604                 }
605
606                 /* don't alter y if we clicked outside and to the left or right of the viewbox */
607                 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
608                         y += ev->y - _start_mouse_y;
609                 }
610
611                 if (x < 0) {
612                         x = 0;
613                 }
614
615                 if (y < 0) {
616                         y = 0;
617                 }
618
619                 set_editor (x, y);
620                 // set_cursor (_start_position);
621
622         } else if (_zoom_dragging) {
623
624                 double const dx = ev->x - _start_mouse_x;
625                 double const dy = ev->y - _start_mouse_y;
626
627                 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
628                         xr.first += dx;
629                 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
630                         xr.second += dx;
631                 }
632
633                 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
634                         yr.first += dy;
635                 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
636                         yr.second += dy;
637                 }
638
639                 set_overlays_dirty ();
640                 set_cursor (_zoom_position);
641                 set_editor (xr, yr);
642
643         } else {
644
645                 set_cursor (get_position (ev->x, ev->y));
646
647         }
648
649         return true;
650 }
651
652 bool
653 EditorSummary::on_button_release_event (GdkEventButton*)
654 {
655         bool const was_suspended = suspending_editor_updates ();
656         
657         _move_dragging = false;
658         _zoom_dragging = false;
659         _editor->_dragging_playhead = false;
660         _editor->set_follow_playhead (_old_follow_playhead, false);
661
662         if (was_suspended && _pending_editor_changed) {
663                 set_editor (_pending_editor_x, _pending_editor_y);
664         }
665                 
666         return true;
667 }
668
669 bool
670 EditorSummary::on_scroll_event (GdkEventScroll* ev)
671 {
672         /* mouse wheel */
673
674         pair<double, double> xr;
675         pair<double, double> yr;
676         get_editor (&xr, &yr);
677         double x = xr.first;
678         double y = yr.first;
679
680         switch (ev->direction) {
681                 case GDK_SCROLL_UP:
682                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollHorizontalModifier)) {
683                                 x -= 64;
684                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
685                                 _editor->temporal_zoom_step (false);
686                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
687                                 yr.first  += 4;
688                                 yr.second -= 4;
689                                 set_editor (xr, yr);
690                                 return true;
691                         } else {
692                                 y -= 8;
693                         }
694                         break;
695                 case GDK_SCROLL_DOWN:
696                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollHorizontalModifier)) {
697                                 x += 64;
698                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
699                                 _editor->temporal_zoom_step (true);
700                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
701                                 yr.first  -= 4;
702                                 yr.second += 4;
703                                 set_editor (xr, yr);
704                                 return true;
705                         } else {
706                                 y += 8;
707                         }
708                         break;
709                 case GDK_SCROLL_LEFT:
710                         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
711                                 x -= 64;
712                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
713                                 x -= 1;
714                         } else {
715                                 x -= 8;
716                         }
717                         break;
718                 case GDK_SCROLL_RIGHT:
719                         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
720                                 x += 64;
721                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
722                                 x += 1;
723                         } else {
724                                 x += 8;
725                         }
726                         break;
727                 default:
728                         break;
729         }
730
731         set_editor (x, y);
732         return true;
733 }
734
735 /** Set the editor to display a x range with the left at a given position
736  *  and a y range with the top at a given position.
737  *  x and y parameters are specified in summary coordinates.
738  *  Zoom is not changed in either direction.
739  */
740 void
741 EditorSummary::set_editor (double const x, double const y)
742 {
743         if (_editor->pending_visual_change.idle_handler_id >= 0 && _editor->pending_visual_change.being_handled == true) {
744
745                 /* As a side-effect, the Editor's visual change idle handler processes
746                    pending GTK events.  Hence this motion notify handler can be called
747                    in the middle of a visual change idle handler, and if this happens,
748                    the queue_visual_change calls below modify the variables that the
749                    idle handler is working with.  This causes problems.  Hence this
750                    check.  It ensures that we won't modify the pending visual change
751                    while a visual change idle handler is in progress.  It's not perfect,
752                    as it also means that we won't change these variables if an idle handler
753                    is merely pending but not executing.  But c'est la vie.
754                 */
755                 
756                 return;
757         }
758
759         set_editor_x (x);
760         set_editor_y (y);
761 }
762
763 /** Set the editor to display a given x range and a y range with the top at a given position.
764  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
765  *  x and y parameters are specified in summary coordinates.
766  */
767 void
768 EditorSummary::set_editor (pair<double,double> const x, double const y)
769 {
770         if (_editor->pending_visual_change.idle_handler_id >= 0) {
771                 /* see comment in other set_editor () */
772                 return;
773         }
774
775         set_editor_x (x);
776         set_editor_y (y);
777 }
778
779 /** Set the editor to display given x and y ranges.  x zoom and track heights are
780  *  adjusted if necessary.
781  *  x and y parameters are specified in summary coordinates.
782  */
783 void
784 EditorSummary::set_editor (pair<double,double> const x, pair<double, double> const y)
785 {
786         if (_editor->pending_visual_change.idle_handler_id >= 0) {
787                 /* see comment in other set_editor () */
788                 return;
789         }
790
791         set_editor_x (x);
792         set_editor_y (y);
793 }
794
795 /** Set the left of the x range visible in the editor.
796  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
797  *  @param x new x left position in summary coordinates.
798  */
799 void
800 EditorSummary::set_editor_x (double x)
801 {
802         if (x < 0) {
803                 x = 0;
804         }
805
806         if (suspending_editor_updates ()) {
807                 double const w = _pending_editor_x.second - _pending_editor_x.first;
808                 _pending_editor_x.first = x;
809                 _pending_editor_x.second = x + w;
810                 _pending_editor_changed = true;
811                 set_dirty ();
812         } else {
813                 _editor->reset_x_origin (x / _x_scale + _start);
814         }
815 }
816
817 /** Set the x range visible in the editor.
818  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
819  *  @param x new x range in summary coordinates.
820  */
821 void
822 EditorSummary::set_editor_x (pair<double, double> x)
823 {
824         if (x.first < 0) {
825                 x.first = 0;
826         }
827
828         if (x.second < 0) {
829                 x.second = x.first + 1;
830         }
831
832         if (suspending_editor_updates ()) {
833                 _pending_editor_x = x;
834                 _pending_editor_changed = true;
835                 set_dirty ();
836         } else {
837                 _editor->reset_x_origin (x.first / _x_scale + _start);
838                 
839                 double const nx = (
840                         ((x.second - x.first) / _x_scale) /
841                         _editor->sample_to_pixel (_editor->current_page_samples())
842                         );
843                 
844                 if (nx != _editor->get_current_zoom ()) {
845                         _editor->reset_zoom (nx);
846                 }
847         }
848 }
849
850 /** Set the top of the y range visible in the editor.
851  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
852  *  @param y new editor top in summary coodinates.
853  */
854 void
855 EditorSummary::set_editor_y (double const y)
856 {
857         double y1 = summary_y_to_editor (y);
858         double const eh = _editor->visible_canvas_height();
859         double y2 = y1 + eh;
860
861         double const full_editor_height = _editor->_full_canvas_height;
862
863         if (y2 > full_editor_height) {
864                 y1 -= y2 - full_editor_height;
865         }
866
867         if (y1 < 0) {
868                 y1 = 0;
869         }
870
871         if (suspending_editor_updates ()) {
872                 double const h = _pending_editor_y.second - _pending_editor_y.first;
873                 _pending_editor_y.first = y;
874                 _pending_editor_y.second = y + h;
875                 _pending_editor_changed = true;
876                 set_dirty ();
877         } else {
878                 _editor->reset_y_origin (y1);
879         }
880 }
881
882 /** Set the y range visible in the editor.  This is achieved by scaling track heights,
883  *  if necessary.
884  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
885  *  @param y new editor range in summary coodinates.
886  */
887 void
888 EditorSummary::set_editor_y (pair<double, double> const y)
889 {
890         if (suspending_editor_updates ()) {
891                 _pending_editor_y = y;
892                 _pending_editor_changed = true;
893                 set_dirty ();
894                 return;
895         }
896         
897         /* Compute current height of tracks between y.first and y.second.  We add up
898            the total height into `total_height' and the height of complete tracks into
899            `scale height'.
900         */
901
902         /* Copy of target range for use below */
903         pair<double, double> yc = y;
904         /* Total height of all tracks */
905         double total_height = 0;
906         /* Height of any parts of tracks that aren't fully in the desired range */
907         double partial_height = 0;
908         /* Height of any tracks that are fully in the desired range */
909         double scale_height = 0;
910
911         _editor->_routes->suspend_redisplay ();
912
913         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
914
915                 if ((*i)->hidden()) {
916                         continue;
917                 }
918
919                 double const h = (*i)->effective_height ();
920                 total_height += h;
921
922                 if (yc.first > 0 && yc.first < _track_height) {
923                         partial_height += (_track_height - yc.first) * h / _track_height;
924                 } else if (yc.first <= 0 && yc.second >= _track_height) {
925                         scale_height += h;
926                 } else if (yc.second > 0 && yc.second < _track_height) {
927                         partial_height += yc.second * h / _track_height;
928                         break;
929                 }
930
931                 yc.first -= _track_height;
932                 yc.second -= _track_height;
933         }
934
935         /* Height that we will use for scaling; use the whole editor height unless there are not
936            enough tracks to fill it.
937         */
938         double const ch = min (total_height, _editor->visible_canvas_height());
939
940         /* hence required scale factor of the complete tracks to fit the required y range;
941            the amount of space they should take up divided by the amount they currently take up.
942         */
943         double const scale = (ch - partial_height) / scale_height;
944
945         yc = y;
946
947         /* Scale complete tracks within the range to make it fit */
948
949         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
950
951                 if ((*i)->hidden()) {
952                         continue;
953                 }
954
955                 if (yc.first <= 0 && yc.second >= _track_height) {
956                         (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
957                 }
958
959                 yc.first -= _track_height;
960                 yc.second -= _track_height;
961         }
962
963         _editor->_routes->resume_redisplay ();
964
965         set_editor_y (y.first);
966 }
967
968 void
969 EditorSummary::playhead_position_changed (framepos_t p)
970 {
971         int const o = int (_last_playhead);
972         int const n = int (playhead_frame_to_position (p));
973         if (_session && o != n) {
974                 int a = max(2, min (o, n));
975                 int b = max (o, n);
976                 set_overlays_dirty (a - 2, 0, b + 2, get_height ());
977         }
978 }
979
980 double
981 EditorSummary::summary_y_to_editor (double y) const
982 {
983         double ey = 0;
984         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
985
986                 if ((*i)->hidden()) {
987                         continue;
988                 }
989
990                 double const h = (*i)->effective_height ();
991                 if (y < _track_height) {
992                         /* in this track */
993                         return ey + y * h / _track_height;
994                 }
995
996                 ey += h;
997                 y -= _track_height;
998         }
999
1000         return ey;
1001 }
1002
1003 double
1004 EditorSummary::editor_y_to_summary (double y) const
1005 {
1006         double sy = 0;
1007         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
1008
1009                 if ((*i)->hidden()) {
1010                         continue;
1011                 }
1012
1013                 double const h = (*i)->effective_height ();
1014                 if (y < h) {
1015                         /* in this track */
1016                         return sy + y * _track_height / h;
1017                 }
1018
1019                 sy += _track_height;
1020                 y -= h;
1021         }
1022
1023         return sy;
1024 }
1025
1026 void
1027 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
1028 {
1029         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
1030                 /* Connect to gui_changed() on the route so that we know when their colour has changed */
1031                 (*i)->route()->gui_changed.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
1032                 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
1033                 if (tr) {
1034                         tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context ());
1035                 }
1036         }
1037
1038         _background_dirty = true;
1039         set_dirty ();
1040 }
1041
1042 void
1043 EditorSummary::route_gui_changed (string c)
1044 {
1045         if (c == "color") {
1046                 _background_dirty = true;
1047                 set_dirty ();
1048         }
1049 }
1050
1051 double
1052 EditorSummary::playhead_frame_to_position (framepos_t t) const
1053 {
1054         return (t - _start) * _x_scale;
1055 }
1056
1057 framepos_t
1058 EditorSummary::position_to_playhead_frame_to_position (double pos) const
1059 {
1060         return _start  + (pos * _x_scale);
1061 }