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