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