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