Make Dropdown menus at least as wide as the button
[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                 PresentationInfo::Change.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         if (!_background_dirty) {
297                 _background_dirty = true;
298                 set_dirty ();
299         }
300 }
301
302 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
303 void
304 EditorSummary::set_overlays_dirty ()
305 {
306         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
307         queue_draw ();
308 }
309
310 /** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
311 void
312 EditorSummary::set_overlays_dirty (int x, int y, int w, int h)
313 {
314         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
315         queue_draw_area (x, y, w, h);
316 }
317
318
319 /** Handle a size request.
320  *  @param req GTK requisition
321  */
322 void
323 EditorSummary::on_size_request (Gtk::Requisition *req)
324 {
325         /* Use a dummy, small width and the actual height that we want */
326         req->width = 64;
327         req->height = 32;
328 }
329
330
331 void
332 EditorSummary::centre_on_click (GdkEventButton* ev)
333 {
334         pair<double, double> xr;
335         pair<double, double> yr;
336         get_editor (&xr, &yr);
337
338         double const w = xr.second - xr.first;
339         double ex = ev->x - w / 2;
340         if (ex < 0) {
341                 ex = 0;
342         } else if ((ex + w) > get_width()) {
343                 ex = get_width() - w;
344         }
345
346         double const h = yr.second - yr.first;
347         double ey = ev->y - h / 2;
348         if (ey < 0) {
349                 ey = 0;
350         } else if ((ey + h) > get_height()) {
351                 ey = get_height() - h;
352         }
353
354         set_editor (ex, ey);
355 }
356
357 bool
358 EditorSummary::on_enter_notify_event (GdkEventCrossing*)
359 {
360         grab_focus ();
361         Keyboard::magic_widget_grab_focus ();
362         return false;
363 }
364
365 bool
366 EditorSummary::on_leave_notify_event (GdkEventCrossing*)
367 {
368         /* there are no inferior/child windows, so any leave event means that
369            we're gone.
370         */
371         Keyboard::magic_widget_drop_focus ();
372         return false;
373 }
374
375 bool
376 EditorSummary::on_key_press_event (GdkEventKey* key)
377 {
378         gint x, y;
379         GtkAccelKey set_playhead_accel;
380         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
381                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
382                         if (_session) {
383                                 get_pointer (x, y);
384                                 _session->request_locate (_start + (framepos_t) x / _x_scale, _session->transport_rolling());
385                                 return true;
386                         }
387                 }
388         }
389
390         return false;
391 }
392
393 bool
394 EditorSummary::on_key_release_event (GdkEventKey* key)
395 {
396
397         GtkAccelKey set_playhead_accel;
398         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
399                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
400                         return true;
401                 }
402         }
403         return false;
404 }
405
406 /** Handle a button press.
407  *  @param ev GTK event.
408  */
409 bool
410 EditorSummary::on_button_press_event (GdkEventButton* ev)
411 {
412         _old_follow_playhead = _editor->follow_playhead ();
413
414         if (ev->button == 1) {
415
416                 pair<double, double> xr;
417                 pair<double, double> yr;
418                 get_editor (&xr, &yr);
419
420                 _start_editor_x = xr;
421                 _start_editor_y = yr;
422                 _start_mouse_x = ev->x;
423                 _start_mouse_y = ev->y;
424                 _start_position = get_position (ev->x, ev->y);
425
426                 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
427                     _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
428                         ) {
429
430                         /* start a zoom drag */
431
432                         _zoom_position = get_position (ev->x, ev->y);
433                         _zoom_dragging = true;
434                         _editor->_dragging_playhead = true;
435                         _editor->set_follow_playhead (false);
436
437                         if (suspending_editor_updates ()) {
438                                 get_editor (&_pending_editor_x, &_pending_editor_y);
439                                 _pending_editor_changed = false;
440                         }
441
442                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
443
444                         /* secondary-modifier-click: locate playhead */
445                         if (_session) {
446                                 _session->request_locate (ev->x / _x_scale + _start);
447                         }
448
449                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
450
451                         centre_on_click (ev);
452
453                 } else {
454
455                         /* start a move drag */
456
457                         /* get the editor's state in case we are suspending updates */
458                         get_editor (&_pending_editor_x, &_pending_editor_y);
459                         _pending_editor_changed = false;
460
461                         _move_dragging = true;
462                         _moved = false;
463                         _editor->_dragging_playhead = true;
464                         _editor->set_follow_playhead (false);
465
466                         ArdourCanvas::checkpoint ("sum", "------------------ summary move drag starts.\n");
467                 }
468         }
469
470         return true;
471 }
472
473 /** @return true if we are currently suspending updates to the editor's viewport,
474  *  which we do if configured to do so, and if in a drag of some kind.
475  */
476 bool
477 EditorSummary::suspending_editor_updates () const
478 {
479         return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_dragging || _move_dragging));
480 }
481
482 /** Fill in x and y with the editor's current viewable area in summary coordinates */
483 void
484 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
485 {
486         assert (x);
487         assert (y);
488
489         if (suspending_editor_updates ()) {
490
491                 /* We are dragging, and configured not to update the editor window during drags,
492                    so just return where the editor will be when the drag finishes.
493                 */
494
495                 *x = _pending_editor_x;
496                 *y = _pending_editor_y;
497
498         } else {
499
500                 /* Otherwise query the editor for its actual position */
501
502                 x->first = (_editor->leftmost_sample () - _start) * _x_scale;
503                 x->second = x->first + _editor->current_page_samples() * _x_scale;
504
505                 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
506                 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y);
507         }
508 }
509
510 /** Get an expression of the position of a point with respect to the view rectangle */
511 EditorSummary::Position
512 EditorSummary::get_position (double x, double y) const
513 {
514         /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
515            in pixels */
516
517         int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
518         x_edge_size = min (x_edge_size, 8);
519         x_edge_size = max (x_edge_size, 1);
520
521         int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
522         y_edge_size = min (y_edge_size, 8);
523         y_edge_size = max (y_edge_size, 1);
524
525         bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
526         bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
527         bool const near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
528         bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_edge_size);
529         bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
530         bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
531
532         if (near_left && near_top) {
533                 return LEFT_TOP;
534         } else if (near_left && near_bottom) {
535                 return LEFT_BOTTOM;
536         } else if (near_right && near_top) {
537                 return RIGHT_TOP;
538         } else if (near_right && near_bottom) {
539                 return RIGHT_BOTTOM;
540         } else if (near_left && within_y) {
541                 return LEFT;
542         } else if (near_right && within_y) {
543                 return RIGHT;
544         } else if (near_top && within_x) {
545                 return TOP;
546         } else if (near_bottom && within_x) {
547                 return BOTTOM;
548         } else if (within_x && within_y) {
549                 return INSIDE;
550         } else if (within_x) {
551                 return BELOW_OR_ABOVE;
552         } else if (within_y) {
553                 return TO_LEFT_OR_RIGHT;
554         } else {
555                 return OTHERWISE_OUTSIDE;
556         }
557 }
558
559 void
560 EditorSummary::set_cursor (Position p)
561 {
562         switch (p) {
563         case LEFT:
564                 get_window()->set_cursor (*_editor->_cursors->resize_left);
565                 break;
566         case LEFT_TOP:
567                 get_window()->set_cursor (*_editor->_cursors->resize_top_left);
568                 break;
569         case TOP:
570                 get_window()->set_cursor (*_editor->_cursors->resize_top);
571                 break;
572         case RIGHT_TOP:
573                 get_window()->set_cursor (*_editor->_cursors->resize_top_right);
574                 break;
575         case RIGHT:
576                 get_window()->set_cursor (*_editor->_cursors->resize_right);
577                 break;
578         case RIGHT_BOTTOM:
579                 get_window()->set_cursor (*_editor->_cursors->resize_bottom_right);
580                 break;
581         case BOTTOM:
582                 get_window()->set_cursor (*_editor->_cursors->resize_bottom);
583                 break;
584         case LEFT_BOTTOM:
585                 get_window()->set_cursor (*_editor->_cursors->resize_bottom_left);
586                 break;
587         case INSIDE:
588                 get_window()->set_cursor (*_editor->_cursors->move);
589                 break;
590         case TO_LEFT_OR_RIGHT:
591                 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
592                 break;
593         case BELOW_OR_ABOVE:
594                 get_window()->set_cursor (*_editor->_cursors->expand_up_down);
595                 break;
596         default:
597                 get_window()->set_cursor ();
598                 break;
599         }
600 }
601
602 bool
603 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
604 {
605         pair<double, double> xr = _start_editor_x;
606         pair<double, double> yr = _start_editor_y;
607         double x = _start_editor_x.first;
608         double y = _start_editor_y.first;
609
610         if (_move_dragging) {
611
612                 _moved = true;
613
614                 /* don't alter x if we clicked outside and above or below the viewbox */
615                 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
616                         x += ev->x - _start_mouse_x;
617                 }
618
619                 /* don't alter y if we clicked outside and to the left or right of the viewbox */
620                 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
621                         y += ev->y - _start_mouse_y;
622                 }
623
624                 if (x < 0) {
625                         x = 0;
626                 }
627
628                 if (y < 0) {
629                         y = 0;
630                 }
631
632                 set_editor (x, y);
633                 // set_cursor (_start_position);
634
635         } else if (_zoom_dragging) {
636
637                 double const dx = ev->x - _start_mouse_x;
638                 double const dy = ev->y - _start_mouse_y;
639
640                 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
641                         xr.first += dx;
642                 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
643                         xr.second += dx;
644                 } else {
645                         xr.first = -1; /* do not change */
646                 }
647
648                 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
649                         yr.first += dy;
650                 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
651                         yr.second += dy;
652                 } else {
653                         yr.first = -1; /* do not change y */
654                 }
655
656                 set_overlays_dirty ();
657                 set_cursor (_zoom_position);
658                 set_editor (xr, yr);
659
660         } else {
661
662                 set_cursor (get_position (ev->x, ev->y));
663
664         }
665
666         return true;
667 }
668
669 bool
670 EditorSummary::on_button_release_event (GdkEventButton*)
671 {
672         bool const was_suspended = suspending_editor_updates ();
673
674         _move_dragging = false;
675         _zoom_dragging = false;
676         _editor->_dragging_playhead = false;
677         _editor->set_follow_playhead (_old_follow_playhead, false);
678
679         if (was_suspended && _pending_editor_changed) {
680                 set_editor (_pending_editor_x, _pending_editor_y);
681         }
682
683         return true;
684 }
685
686 bool
687 EditorSummary::on_scroll_event (GdkEventScroll* ev)
688 {
689         /* mouse wheel */
690
691         pair<double, double> xr;
692         pair<double, double> yr;
693         get_editor (&xr, &yr);
694         double x = xr.first;
695         double y = yr.first;
696
697         switch (ev->direction) {
698                 case GDK_SCROLL_UP:
699                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollHorizontalModifier)) {
700                                 _editor->scroll_left_half_page ();
701                                 return true;
702                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
703                                 _editor->temporal_zoom_step (false);
704                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
705                                 yr.first  += 4;
706                                 yr.second -= 4;
707                                 set_editor (xr, yr);
708                                 return true;
709                         } else {
710                                 y -= 8;
711                         }
712                         break;
713                 case GDK_SCROLL_DOWN:
714                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollHorizontalModifier)) {
715                                 _editor->scroll_right_half_page ();
716                                 return true;
717                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
718                                 _editor->temporal_zoom_step (true);
719                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
720                                 yr.first  -= 4;
721                                 yr.second += 4;
722                                 set_editor (xr, yr);
723                                 return true;
724                         } else {
725                                 y += 8;
726                         }
727                         break;
728                 case GDK_SCROLL_LEFT:
729                         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
730                                 x -= 64;
731                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
732                                 x -= 1;
733                         } else {
734                                 _editor->scroll_left_half_page ();
735                                 return true;
736                         }
737                         break;
738                 case GDK_SCROLL_RIGHT:
739                         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
740                                 x += 64;
741                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
742                                 x += 1;
743                         } else {
744                                 _editor->scroll_right_half_page ();
745                                 return true;
746                         }
747                         break;
748                 default:
749                         break;
750         }
751
752         set_editor (x, y);
753         return true;
754 }
755
756 /** Set the editor to display a x range with the left at a given position
757  *  and a y range with the top at a given position.
758  *  x and y parameters are specified in summary coordinates.
759  *  Zoom is not changed in either direction.
760  */
761 void
762 EditorSummary::set_editor (double const x, double const y)
763 {
764         if (_editor->pending_visual_change.idle_handler_id >= 0 && _editor->pending_visual_change.being_handled == true) {
765
766                 /* As a side-effect, the Editor's visual change idle handler processes
767                    pending GTK events.  Hence this motion notify handler can be called
768                    in the middle of a visual change idle handler, and if this happens,
769                    the queue_visual_change calls below modify the variables that the
770                    idle handler is working with.  This causes problems.  Hence this
771                    check.  It ensures that we won't modify the pending visual change
772                    while a visual change idle handler is in progress.  It's not perfect,
773                    as it also means that we won't change these variables if an idle handler
774                    is merely pending but not executing.  But c'est la vie.
775                 */
776
777                 return;
778         }
779
780         set_editor_x (x);
781         set_editor_y (y);
782 }
783
784 /** Set the editor to display a given x range and a y range with the top at a given position.
785  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
786  *  x and y parameters are specified in summary coordinates.
787  */
788 void
789 EditorSummary::set_editor (pair<double,double> const x, double const y)
790 {
791         if (_editor->pending_visual_change.idle_handler_id >= 0) {
792                 /* see comment in other set_editor () */
793                 return;
794         }
795
796         set_editor_x (x);
797         set_editor_y (y);
798 }
799
800 /** Set the editor to display given x and y ranges.  x zoom and track heights are
801  *  adjusted if necessary.
802  *  x and y parameters are specified in summary coordinates.
803  */
804 void
805 EditorSummary::set_editor (pair<double,double> const x, pair<double, double> const y)
806 {
807         if (_editor->pending_visual_change.idle_handler_id >= 0) {
808                 /* see comment in other set_editor () */
809                 return;
810         }
811
812         if (x.first >= 0) {
813                 set_editor_x (x);
814         }
815         if (y.first >= 0) {
816                 set_editor_y (y);
817         }
818 }
819
820 /** Set the left of the x range visible in the editor.
821  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
822  *  @param x new x left position in summary coordinates.
823  */
824 void
825 EditorSummary::set_editor_x (double x)
826 {
827         if (x < 0) {
828                 x = 0;
829         }
830
831         if (suspending_editor_updates ()) {
832                 double const w = _pending_editor_x.second - _pending_editor_x.first;
833                 _pending_editor_x.first = x;
834                 _pending_editor_x.second = x + w;
835                 _pending_editor_changed = true;
836                 set_dirty ();
837         } else {
838                 _editor->reset_x_origin (x / _x_scale + _start);
839         }
840 }
841
842 /** Set the x range visible in the editor.
843  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
844  *  @param x new x range in summary coordinates.
845  */
846 void
847 EditorSummary::set_editor_x (pair<double, double> x)
848 {
849         if (x.first < 0) {
850                 x.first = 0;
851         }
852
853         if (x.second < 0) {
854                 x.second = x.first + 1;
855         }
856
857         if (suspending_editor_updates ()) {
858                 _pending_editor_x = x;
859                 _pending_editor_changed = true;
860                 set_dirty ();
861         } else {
862                 _editor->reset_x_origin (x.first / _x_scale + _start);
863
864                 double const nx = (
865                         ((x.second - x.first) / _x_scale) /
866                         _editor->sample_to_pixel (_editor->current_page_samples())
867                         );
868
869                 if (nx != _editor->get_current_zoom ()) {
870                         _editor->reset_zoom (nx);
871                 }
872         }
873 }
874
875 /** Set the top of the y range visible in the editor.
876  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
877  *  @param y new editor top in summary coodinates.
878  */
879 void
880 EditorSummary::set_editor_y (double const y)
881 {
882         double y1 = summary_y_to_editor (y);
883         double const eh = _editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y;
884         double y2 = y1 + eh;
885
886         double const full_editor_height = _editor->_full_canvas_height;
887
888         if (y2 > full_editor_height) {
889                 y1 -= y2 - full_editor_height;
890         }
891
892         if (y1 < 0) {
893                 y1 = 0;
894         }
895
896         if (suspending_editor_updates ()) {
897                 double const h = _pending_editor_y.second - _pending_editor_y.first;
898                 _pending_editor_y.first = y;
899                 _pending_editor_y.second = y + h;
900                 _pending_editor_changed = true;
901                 set_dirty ();
902         } else {
903                 _editor->reset_y_origin (y1);
904         }
905 }
906
907 /** Set the y range visible in the editor.  This is achieved by scaling track heights,
908  *  if necessary.
909  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
910  *  @param y new editor range in summary coodinates.
911  */
912 void
913 EditorSummary::set_editor_y (pair<double, double> const y)
914 {
915         if (suspending_editor_updates ()) {
916                 _pending_editor_y = y;
917                 _pending_editor_changed = true;
918                 set_dirty ();
919                 return;
920         }
921
922         /* Compute current height of tracks between y.first and y.second.  We add up
923            the total height into `total_height' and the height of complete tracks into
924            `scale height'.
925         */
926
927         /* Copy of target range for use below */
928         pair<double, double> yc = y;
929         /* Total height of all tracks */
930         double total_height = 0;
931         /* Height of any parts of tracks that aren't fully in the desired range */
932         double partial_height = 0;
933         /* Height of any tracks that are fully in the desired range */
934         double scale_height = 0;
935
936         _editor->_routes->suspend_redisplay ();
937
938         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
939
940                 if ((*i)->hidden()) {
941                         continue;
942                 }
943
944                 double const h = (*i)->effective_height ();
945                 total_height += h;
946
947                 if (yc.first > 0 && yc.first < _track_height) {
948                         partial_height += (_track_height - yc.first) * h / _track_height;
949                 } else if (yc.first <= 0 && yc.second >= _track_height) {
950                         scale_height += h;
951                 } else if (yc.second > 0 && yc.second < _track_height) {
952                         partial_height += yc.second * h / _track_height;
953                         break;
954                 }
955
956                 yc.first -= _track_height;
957                 yc.second -= _track_height;
958         }
959
960         /* Height that we will use for scaling; use the whole editor height unless there are not
961            enough tracks to fill it.
962         */
963         double const ch = min (total_height, (_editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y));
964
965         /* hence required scale factor of the complete tracks to fit the required y range;
966            the amount of space they should take up divided by the amount they currently take up.
967         */
968         double const scale = (ch - partial_height) / scale_height;
969
970         yc = y;
971
972         /* Scale complete tracks within the range to make it fit */
973
974         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
975
976                 if ((*i)->hidden()) {
977                         continue;
978                 }
979
980                 if (yc.first <= 0 && yc.second >= _track_height) {
981                         (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)), TimeAxisView::TotalHeight);
982                 }
983
984                 yc.first -= _track_height;
985                 yc.second -= _track_height;
986         }
987
988         _editor->_routes->resume_redisplay ();
989
990         set_editor_y (y.first);
991 }
992
993 void
994 EditorSummary::playhead_position_changed (framepos_t p)
995 {
996         int const o = int (_last_playhead);
997         int const n = int (playhead_frame_to_position (p));
998         if (_session && o != n) {
999                 int a = max(2, min (o, n));
1000                 int b = max (o, n);
1001                 set_overlays_dirty (a - 2, 0, b + 2, get_height ());
1002         }
1003 }
1004
1005 double
1006 EditorSummary::summary_y_to_editor (double y) const
1007 {
1008         double ey = 0;
1009         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
1010
1011                 if ((*i)->hidden()) {
1012                         continue;
1013                 }
1014
1015                 double const h = (*i)->effective_height ();
1016                 if (y < _track_height) {
1017                         /* in this track */
1018                         return ey + y * h / _track_height;
1019                 }
1020
1021                 ey += h;
1022                 y -= _track_height;
1023         }
1024
1025         return ey;
1026 }
1027
1028 double
1029 EditorSummary::editor_y_to_summary (double y) const
1030 {
1031         double sy = 0;
1032         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
1033
1034                 if ((*i)->hidden()) {
1035                         continue;
1036                 }
1037
1038                 double const h = (*i)->effective_height ();
1039                 if (y < h) {
1040                         /* in this track */
1041                         return sy + y * _track_height / h;
1042                 }
1043
1044                 sy += _track_height;
1045                 y -= h;
1046         }
1047
1048         return sy;
1049 }
1050
1051 void
1052 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
1053 {
1054         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
1055                 /* Connect to the relevant signal for the route so that we know when its colour has changed */
1056                 (*i)->route()->presentation_info().PropertyChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
1057                 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
1058                 if (tr) {
1059                         tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context ());
1060                 }
1061         }
1062
1063         set_background_dirty ();
1064 }
1065
1066 void
1067 EditorSummary::route_gui_changed (PBD::PropertyChange const& what_changed)
1068 {
1069         if (what_changed.contains (Properties::color)) {
1070                 set_background_dirty ();
1071         }
1072 }
1073
1074 double
1075 EditorSummary::playhead_frame_to_position (framepos_t t) const
1076 {
1077         return (t - _start) * _x_scale;
1078 }
1079
1080 framepos_t
1081 EditorSummary::position_to_playhead_frame_to_position (double pos) const
1082 {
1083         return _start  + (pos * _x_scale);
1084 }