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