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