first vaguely working version using PresentationInfo
[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                 Stripable::PresentationInfoChange.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                                 _editor->scroll_left_half_page ();
699                                 return true;
700                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
701                                 _editor->temporal_zoom_step (false);
702                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
703                                 yr.first  += 4;
704                                 yr.second -= 4;
705                                 set_editor (xr, yr);
706                                 return true;
707                         } else {
708                                 y -= 8;
709                         }
710                         break;
711                 case GDK_SCROLL_DOWN:
712                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollHorizontalModifier)) {
713                                 _editor->scroll_right_half_page ();
714                                 return true;
715                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
716                                 _editor->temporal_zoom_step (true);
717                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
718                                 yr.first  -= 4;
719                                 yr.second += 4;
720                                 set_editor (xr, yr);
721                                 return true;
722                         } else {
723                                 y += 8;
724                         }
725                         break;
726                 case GDK_SCROLL_LEFT:
727                         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
728                                 x -= 64;
729                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
730                                 x -= 1;
731                         } else {
732                                 _editor->scroll_left_half_page ();
733                                 return true;
734                         }
735                         break;
736                 case GDK_SCROLL_RIGHT:
737                         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
738                                 x += 64;
739                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
740                                 x += 1;
741                         } else {
742                                 _editor->scroll_right_half_page ();
743                                 return true;
744                         }
745                         break;
746                 default:
747                         break;
748         }
749
750         set_editor (x, y);
751         return true;
752 }
753
754 /** Set the editor to display a x range with the left at a given position
755  *  and a y range with the top at a given position.
756  *  x and y parameters are specified in summary coordinates.
757  *  Zoom is not changed in either direction.
758  */
759 void
760 EditorSummary::set_editor (double const x, double const y)
761 {
762         if (_editor->pending_visual_change.idle_handler_id >= 0 && _editor->pending_visual_change.being_handled == true) {
763
764                 /* As a side-effect, the Editor's visual change idle handler processes
765                    pending GTK events.  Hence this motion notify handler can be called
766                    in the middle of a visual change idle handler, and if this happens,
767                    the queue_visual_change calls below modify the variables that the
768                    idle handler is working with.  This causes problems.  Hence this
769                    check.  It ensures that we won't modify the pending visual change
770                    while a visual change idle handler is in progress.  It's not perfect,
771                    as it also means that we won't change these variables if an idle handler
772                    is merely pending but not executing.  But c'est la vie.
773                 */
774
775                 return;
776         }
777
778         set_editor_x (x);
779         set_editor_y (y);
780 }
781
782 /** Set the editor to display a given x range and a y range with the top at a given position.
783  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
784  *  x and y parameters are specified in summary coordinates.
785  */
786 void
787 EditorSummary::set_editor (pair<double,double> const x, double const y)
788 {
789         if (_editor->pending_visual_change.idle_handler_id >= 0) {
790                 /* see comment in other set_editor () */
791                 return;
792         }
793
794         set_editor_x (x);
795         set_editor_y (y);
796 }
797
798 /** Set the editor to display given x and y ranges.  x zoom and track heights are
799  *  adjusted if necessary.
800  *  x and y parameters are specified in summary coordinates.
801  */
802 void
803 EditorSummary::set_editor (pair<double,double> const x, pair<double, double> const y)
804 {
805         if (_editor->pending_visual_change.idle_handler_id >= 0) {
806                 /* see comment in other set_editor () */
807                 return;
808         }
809
810         if (x.first >= 0) {
811                 set_editor_x (x);
812         }
813         if (y.first >= 0) {
814                 set_editor_y (y);
815         }
816 }
817
818 /** Set the left of the x range visible in the editor.
819  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
820  *  @param x new x left position in summary coordinates.
821  */
822 void
823 EditorSummary::set_editor_x (double x)
824 {
825         if (x < 0) {
826                 x = 0;
827         }
828
829         if (suspending_editor_updates ()) {
830                 double const w = _pending_editor_x.second - _pending_editor_x.first;
831                 _pending_editor_x.first = x;
832                 _pending_editor_x.second = x + w;
833                 _pending_editor_changed = true;
834                 set_dirty ();
835         } else {
836                 _editor->reset_x_origin (x / _x_scale + _start);
837         }
838 }
839
840 /** Set the x range visible in the editor.
841  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
842  *  @param x new x range in summary coordinates.
843  */
844 void
845 EditorSummary::set_editor_x (pair<double, double> x)
846 {
847         if (x.first < 0) {
848                 x.first = 0;
849         }
850
851         if (x.second < 0) {
852                 x.second = x.first + 1;
853         }
854
855         if (suspending_editor_updates ()) {
856                 _pending_editor_x = x;
857                 _pending_editor_changed = true;
858                 set_dirty ();
859         } else {
860                 _editor->reset_x_origin (x.first / _x_scale + _start);
861
862                 double const nx = (
863                         ((x.second - x.first) / _x_scale) /
864                         _editor->sample_to_pixel (_editor->current_page_samples())
865                         );
866
867                 if (nx != _editor->get_current_zoom ()) {
868                         _editor->reset_zoom (nx);
869                 }
870         }
871 }
872
873 /** Set the top of the y range visible in the editor.
874  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
875  *  @param y new editor top in summary coodinates.
876  */
877 void
878 EditorSummary::set_editor_y (double const y)
879 {
880         double y1 = summary_y_to_editor (y);
881         double const eh = _editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y;
882         double y2 = y1 + eh;
883
884         double const full_editor_height = _editor->_full_canvas_height;
885
886         if (y2 > full_editor_height) {
887                 y1 -= y2 - full_editor_height;
888         }
889
890         if (y1 < 0) {
891                 y1 = 0;
892         }
893
894         if (suspending_editor_updates ()) {
895                 double const h = _pending_editor_y.second - _pending_editor_y.first;
896                 _pending_editor_y.first = y;
897                 _pending_editor_y.second = y + h;
898                 _pending_editor_changed = true;
899                 set_dirty ();
900         } else {
901                 _editor->reset_y_origin (y1);
902         }
903 }
904
905 /** Set the y range visible in the editor.  This is achieved by scaling track heights,
906  *  if necessary.
907  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
908  *  @param y new editor range in summary coodinates.
909  */
910 void
911 EditorSummary::set_editor_y (pair<double, double> const y)
912 {
913         if (suspending_editor_updates ()) {
914                 _pending_editor_y = y;
915                 _pending_editor_changed = true;
916                 set_dirty ();
917                 return;
918         }
919
920         /* Compute current height of tracks between y.first and y.second.  We add up
921            the total height into `total_height' and the height of complete tracks into
922            `scale height'.
923         */
924
925         /* Copy of target range for use below */
926         pair<double, double> yc = y;
927         /* Total height of all tracks */
928         double total_height = 0;
929         /* Height of any parts of tracks that aren't fully in the desired range */
930         double partial_height = 0;
931         /* Height of any tracks that are fully in the desired range */
932         double scale_height = 0;
933
934         _editor->_routes->suspend_redisplay ();
935
936         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
937
938                 if ((*i)->hidden()) {
939                         continue;
940                 }
941
942                 double const h = (*i)->effective_height ();
943                 total_height += h;
944
945                 if (yc.first > 0 && yc.first < _track_height) {
946                         partial_height += (_track_height - yc.first) * h / _track_height;
947                 } else if (yc.first <= 0 && yc.second >= _track_height) {
948                         scale_height += h;
949                 } else if (yc.second > 0 && yc.second < _track_height) {
950                         partial_height += yc.second * h / _track_height;
951                         break;
952                 }
953
954                 yc.first -= _track_height;
955                 yc.second -= _track_height;
956         }
957
958         /* Height that we will use for scaling; use the whole editor height unless there are not
959            enough tracks to fill it.
960         */
961         double const ch = min (total_height, (_editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y));
962
963         /* hence required scale factor of the complete tracks to fit the required y range;
964            the amount of space they should take up divided by the amount they currently take up.
965         */
966         double const scale = (ch - partial_height) / scale_height;
967
968         yc = y;
969
970         /* Scale complete tracks within the range to make it fit */
971
972         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
973
974                 if ((*i)->hidden()) {
975                         continue;
976                 }
977
978                 if (yc.first <= 0 && yc.second >= _track_height) {
979                         (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)), TimeAxisView::TotalHeight);
980                 }
981
982                 yc.first -= _track_height;
983                 yc.second -= _track_height;
984         }
985
986         _editor->_routes->resume_redisplay ();
987
988         set_editor_y (y.first);
989 }
990
991 void
992 EditorSummary::playhead_position_changed (framepos_t p)
993 {
994         int const o = int (_last_playhead);
995         int const n = int (playhead_frame_to_position (p));
996         if (_session && o != n) {
997                 int a = max(2, min (o, n));
998                 int b = max (o, n);
999                 set_overlays_dirty (a - 2, 0, b + 2, get_height ());
1000         }
1001 }
1002
1003 double
1004 EditorSummary::summary_y_to_editor (double y) const
1005 {
1006         double ey = 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 < _track_height) {
1015                         /* in this track */
1016                         return ey + y * h / _track_height;
1017                 }
1018
1019                 ey += h;
1020                 y -= _track_height;
1021         }
1022
1023         return ey;
1024 }
1025
1026 double
1027 EditorSummary::editor_y_to_summary (double y) const
1028 {
1029         double sy = 0;
1030         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
1031
1032                 if ((*i)->hidden()) {
1033                         continue;
1034                 }
1035
1036                 double const h = (*i)->effective_height ();
1037                 if (y < h) {
1038                         /* in this track */
1039                         return sy + y * _track_height / h;
1040                 }
1041
1042                 sy += _track_height;
1043                 y -= h;
1044         }
1045
1046         return sy;
1047 }
1048
1049 void
1050 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
1051 {
1052         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
1053                 /* Connect to gui_changed() on the route so that we know when their colour has changed */
1054                 (*i)->route()->gui_changed.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
1055                 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
1056                 if (tr) {
1057                         tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context ());
1058                 }
1059         }
1060
1061         set_background_dirty ();
1062 }
1063
1064 void
1065 EditorSummary::route_gui_changed (string c)
1066 {
1067         if (c == "color") {
1068                 set_background_dirty ();
1069         }
1070 }
1071
1072 double
1073 EditorSummary::playhead_frame_to_position (framepos_t t) const
1074 {
1075         return (t - _start) * _x_scale;
1076 }
1077
1078 framepos_t
1079 EditorSummary::position_to_playhead_frame_to_position (double pos) const
1080 {
1081         return _start  + (pos * _x_scale);
1082 }