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