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