Editor Summary: implement summary_zoom_step() to lessen redundant code.
[ardour.git] / gtk2_ardour / editor_summary.cc
1 /*
2     Copyright (C) 2009 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include "ardour/session.h"
21
22 #include "canvas/debug.h"
23
24 #include "time_axis_view.h"
25 #include "streamview.h"
26 #include "editor_summary.h"
27 #include "gui_thread.h"
28 #include "editor.h"
29 #include "region_view.h"
30 #include "rgb_macros.h"
31 #include "keyboard.h"
32 #include "editor_routes.h"
33 #include "editor_cursors.h"
34 #include "mouse_cursors.h"
35 #include "route_time_axis.h"
36 #include "ui_config.h"
37
38 using namespace std;
39 using namespace ARDOUR;
40 using Gtkmm2ext::Keyboard;
41
42 /** Construct an EditorSummary.
43  *  @param e Editor to represent.
44  */
45 EditorSummary::EditorSummary (Editor* e)
46         : EditorComponent (e),
47           _start (0),
48           _end (1),
49           _overhang_fraction (0.1),
50           _x_scale (1),
51           _track_height (16),
52           _last_playhead (-1),
53           _begin_dragging (false),
54           _move_dragging (false),
55           _moved (false),
56           _view_rectangle_x (0, 0),
57           _view_rectangle_y (0, 0),
58           _zoom_trim_dragging (false),
59           _zoom_dragging (false),
60           _old_follow_playhead (false),
61           _image (0),
62           _background_dirty (true)
63 {
64         CairoWidget::use_nsglview ();
65         add_events (Gdk::POINTER_MOTION_MASK|Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK|Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
66         set_flags (get_flags() | Gtk::CAN_FOCUS);
67
68         UIConfiguration::instance().ParameterChanged.connect (sigc::mem_fun (*this, &EditorSummary::parameter_changed));
69 }
70
71 EditorSummary::~EditorSummary ()
72 {
73         cairo_surface_destroy (_image);
74 }
75
76 void
77 EditorSummary::parameter_changed (string p)
78 {
79
80         if (p == "color-regions-using-track-color") {
81                 set_background_dirty ();
82         }
83 }
84
85 /** Handle a size allocation.
86  *  @param alloc GTK allocation.
87  */
88 void
89 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
90 {
91         CairoWidget::on_size_allocate (alloc);
92         set_background_dirty ();
93 }
94
95
96 /** Connect to a session.
97  *  @param s Session.
98  */
99 void
100 EditorSummary::set_session (Session* s)
101 {
102         SessionHandlePtr::set_session (s);
103
104         set_dirty ();
105
106         /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
107          * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
108          * emitted when a cut region is added to the `cutlist' playlist.
109          */
110
111         if (_session) {
112                 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
113                 PresentationInfo::Change.connect (route_ctrl_id_connection, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
114                 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), boost::bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
115                 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
116                 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
117                 _editor->selection->RegionsChanged.connect (sigc::mem_fun(*this, &EditorSummary::set_background_dirty));
118         }
119 }
120
121 void
122 EditorSummary::render_background_image ()
123 {
124         cairo_surface_destroy (_image); // passing NULL is safe
125         _image = cairo_image_surface_create (CAIRO_FORMAT_RGB24, get_width (), get_height ());
126
127         cairo_t* cr = cairo_create (_image);
128
129         /* background (really just the dividing lines between tracks */
130
131         cairo_set_source_rgb (cr, 0, 0, 0);
132         cairo_rectangle (cr, 0, 0, get_width(), get_height());
133         cairo_fill (cr);
134
135         /* compute start and end points for the summary */
136
137         framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
138         double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
139         _start = theoretical_start > 0 ? theoretical_start : 0;
140         _end = _session->current_end_frame() + session_length * _overhang_fraction;
141
142         /* compute track height */
143         int N = 0;
144         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
145                 if (!(*i)->hidden()) {
146                         ++N;
147                 }
148         }
149
150         if (N == 0) {
151                 _track_height = 16;
152         } else {
153                 _track_height = (double) get_height() / N;
154         }
155
156         /* calculate x scale */
157         if (_end != _start) {
158                 _x_scale = static_cast<double> (get_width()) / (_end - _start);
159         } else {
160                 _x_scale = 1;
161         }
162
163         /* render tracks and regions */
164
165         double y = 0;
166         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
167
168                 if ((*i)->hidden()) {
169                         continue;
170                 }
171
172                 /* paint a non-bg colored strip to represent the track itself */
173
174                 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
175                 cairo_set_line_width (cr, _track_height - 1);
176                 cairo_move_to (cr, 0, y + _track_height / 2);
177                 cairo_line_to (cr, get_width(), y + _track_height / 2);
178                 cairo_stroke (cr);
179
180                 StreamView* s = (*i)->view ();
181
182                 if (s) {
183                         cairo_set_line_width (cr, _track_height * 0.8);
184
185                         s->foreach_regionview (sigc::bind (
186                                                        sigc::mem_fun (*this, &EditorSummary::render_region),
187                                                        cr,
188                                                        y + _track_height / 2
189                                                        ));
190                 }
191
192                 y += _track_height;
193         }
194
195         /* start and end markers */
196
197         cairo_set_line_width (cr, 1);
198         cairo_set_source_rgb (cr, 1, 1, 0);
199
200         const double p = (_session->current_start_frame() - _start) * _x_scale;
201         cairo_move_to (cr, p, 0);
202         cairo_line_to (cr, p, get_height());
203
204         double const q = (_session->current_end_frame() - _start) * _x_scale;
205         cairo_move_to (cr, q, 0);
206         cairo_line_to (cr, q, get_height());
207         cairo_stroke (cr);
208
209         cairo_destroy (cr);
210 }
211
212 /** Render the required regions to a cairo context.
213  *  @param cr Context.
214  */
215 void
216 EditorSummary::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t*)
217 {
218         cairo_t* cr = ctx->cobj();
219
220         if (_session == 0) {
221                 return;
222         }
223
224         if (!_image || _background_dirty) {
225                 render_background_image ();
226                 _background_dirty = false;
227         }
228
229         cairo_push_group (cr);
230
231         /* Fill with the background image */
232
233         cairo_rectangle (cr, 0, 0, get_width(), get_height());
234         cairo_set_source_surface (cr, _image, 0, 0);
235         cairo_fill (cr);
236
237         /* Render the view rectangle.  If there is an editor visual pending, don't update
238          * the view rectangle now --- wait until the expose event that we'll get after
239          * the visual change.  This prevents a flicker.
240          */
241
242         if (_editor->pending_visual_change.idle_handler_id < 0) {
243                 get_editor (&_view_rectangle_x, &_view_rectangle_y);
244         }
245
246         int32_t width = _view_rectangle_x.second - _view_rectangle_x.first;
247         std::min(8, width);
248         int32_t height = _view_rectangle_y.second - _view_rectangle_y.first;
249         cairo_rectangle (cr, _view_rectangle_x.first, 0, width, get_height ());
250         cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
251         cairo_fill (cr);
252
253         /* horiz zoom */
254         cairo_rectangle (cr, _view_rectangle_x.first, 0, width, get_height ());
255         cairo_set_line_width (cr, 1);
256         cairo_set_source_rgba (cr, 1, 1, 1, 0.9);
257         cairo_stroke (cr);
258
259         /* Playhead */
260
261         cairo_set_line_width (cr, 1);
262         /* XXX: colour should be set from configuration file */
263         cairo_set_source_rgba (cr, 1, 0, 0, 1);
264
265         const double ph= playhead_frame_to_position (_editor->playhead_cursor->current_frame());
266         cairo_move_to (cr, ph, 0);
267         cairo_line_to (cr, ph, get_height());
268         cairo_stroke (cr);
269         cairo_pop_group_to_source (cr);
270         cairo_paint (cr);
271         _last_playhead = ph;
272
273 }
274
275 /** Render a region for the summary.
276  *  @param r Region view.
277  *  @param cr Cairo context.
278  *  @param y y coordinate to render at.
279  */
280 void
281 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
282 {
283         uint32_t const c = r->get_fill_color ();
284         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
285
286         if (r->region()->position() > _start) {
287                 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
288         } else {
289                 cairo_move_to (cr, 0, y);
290         }
291
292         if ((r->region()->position() + r->region()->length()) > _start) {
293                 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
294         } else {
295                 cairo_line_to (cr, 0, y);
296         }
297
298         cairo_stroke (cr);
299 }
300
301 void
302 EditorSummary::set_background_dirty ()
303 {
304         if (!_background_dirty) {
305                 _background_dirty = true;
306                 set_dirty ();
307         }
308 }
309
310 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
311 void
312 EditorSummary::set_overlays_dirty ()
313 {
314         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
315         queue_draw ();
316 }
317
318 /** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
319 void
320 EditorSummary::set_overlays_dirty (int x, int y, int w, int h)
321 {
322         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
323         queue_draw_area (x, y, w, h);
324 }
325
326
327 /** Handle a size request.
328  *  @param req GTK requisition
329  */
330 void
331 EditorSummary::on_size_request (Gtk::Requisition *req)
332 {
333         /* The left/right buttons will determine our height */
334         req->width = -1;
335         req->height = -1;
336 }
337
338
339 void
340 EditorSummary::centre_on_click (GdkEventButton* ev)
341 {
342         pair<double, double> xr;
343         get_editor (&xr);
344
345         double const w = xr.second - xr.first;
346         double ex = ev->x - w / 2;
347         if (ex < 0) {
348                 ex = 0;
349         } else if ((ex + w) > get_width()) {
350                 ex = get_width() - w;
351         }
352
353         set_editor (ex);
354 }
355
356 bool
357 EditorSummary::on_enter_notify_event (GdkEventCrossing*)
358 {
359         grab_focus ();
360         Keyboard::magic_widget_grab_focus ();
361         return false;
362 }
363
364 bool
365 EditorSummary::on_leave_notify_event (GdkEventCrossing*)
366 {
367         /* there are no inferior/child windows, so any leave event means that
368            we're gone.
369         */
370         Keyboard::magic_widget_drop_focus ();
371         return false;
372 }
373
374 bool
375 EditorSummary::on_key_press_event (GdkEventKey* key)
376 {
377         gint x, y;
378         GtkAccelKey set_playhead_accel;
379         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
380                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
381                         if (_session) {
382                                 get_pointer (x, y);
383                                 _session->request_locate (_start + (framepos_t) x / _x_scale, _session->transport_rolling());
384                                 return true;
385                         }
386                 }
387         }
388
389         return false;
390 }
391
392 bool
393 EditorSummary::on_key_release_event (GdkEventKey* key)
394 {
395
396         GtkAccelKey set_playhead_accel;
397         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
398                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
399                         return true;
400                 }
401         }
402         return false;
403 }
404
405 /** Handle a button press.
406  *  @param ev GTK event.
407  */
408 bool
409 EditorSummary::on_button_press_event (GdkEventButton* ev)
410 {
411         _old_follow_playhead = _editor->follow_playhead ();
412
413         if (ev->button != 1) {
414                 return true;
415         }
416
417         pair<double, double> xr;
418         get_editor (&xr);
419
420         _start_editor_x = xr;
421         _start_mouse_x = ev->x;
422         _start_mouse_y = ev->y;
423         _start_position = get_position (ev->x, ev->y);
424
425         if (_start_position != INSIDE && _start_position != TO_LEFT_OR_RIGHT) {
426
427                 /* start a zoom_trim drag */
428
429                 _zoom_trim_position = get_position (ev->x, ev->y);
430                 _zoom_trim_dragging = true;
431                 _editor->_dragging_playhead = true;
432                 _editor->set_follow_playhead (false);
433
434                 if (suspending_editor_updates ()) {
435                         get_editor (&_pending_editor_x, &_pending_editor_y);
436                         _pending_editor_changed = false;
437                 }
438
439         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
440
441                 /* secondary-modifier-click: locate playhead */
442                 if (_session) {
443                         _session->request_locate (ev->x / _x_scale + _start);
444                 }
445
446         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
447
448                 centre_on_click (ev);
449
450         } else {
451
452                 /* start a move or zoom drag */
453                 /* won't know which one until the mouse moves */
454                 _begin_dragging = true;
455         }
456
457         return true;
458 }
459
460 /** @return true if we are currently suspending updates to the editor's viewport,
461  *  which we do if configured to do so, and if in a drag of some kind.
462  */
463 bool
464 EditorSummary::suspending_editor_updates () const
465 {
466         return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_dragging || _zoom_trim_dragging || _move_dragging));
467 }
468
469 /** Fill in x and y with the editor's current viewable area in summary coordinates */
470 void
471 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
472 {
473         assert (x);
474         if (suspending_editor_updates ()) {
475
476                 /* We are dragging, and configured not to update the editor window during drags,
477                  * so just return where the editor will be when the drag finishes.
478                 */
479
480                 *x = _pending_editor_x;
481                 if (y) {
482                         *y = _pending_editor_y;
483                 }
484                 return;
485         }
486
487         /* Otherwise query the editor for its actual position */
488
489         x->first = (_editor->leftmost_sample () - _start) * _x_scale;
490         x->second = x->first + _editor->current_page_samples() * _x_scale;
491
492         if (y) {
493                 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
494                 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y);
495         }
496 }
497
498 /** Get an expression of the position of a point with respect to the view rectangle */
499 EditorSummary::Position
500 EditorSummary::get_position (double x, double y) const
501 {
502         /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
503            in pixels */
504
505         int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
506         x_edge_size = min (x_edge_size, 8);
507         x_edge_size = max (x_edge_size, 1);
508
509         bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
510         bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
511         bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
512
513         if (near_left) {
514                 return LEFT;
515         } else if (near_right) {
516                 return RIGHT;
517         } else if (within_x) {
518                 return INSIDE;
519         } else {
520                 return TO_LEFT_OR_RIGHT;
521         }
522 }
523
524 void
525 EditorSummary::set_cursor (Position p)
526 {
527         switch (p) {
528         case LEFT:
529                 get_window()->set_cursor (*_editor->_cursors->resize_left);
530                 break;
531         case RIGHT:
532                 get_window()->set_cursor (*_editor->_cursors->resize_right);
533                 break;
534         case INSIDE:
535                 get_window()->set_cursor (*_editor->_cursors->move);
536                 break;
537         case TO_LEFT_OR_RIGHT:
538                 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
539                 break;
540         default:
541                 assert (0);
542                 get_window()->set_cursor ();
543                 break;
544         }
545 }
546
547 void
548 EditorSummary::summary_zoom_step ( int steps /* negative steps to zoom "out" , positive steps to zoom "in" */  )
549 {
550         pair<double, double> xn;
551
552         get_editor (&xn);
553 //      {
554 //              xn.first = (_editor->leftmost_sample () - _start) * _x_scale;
555 //              xn.second = xn.first + _editor->current_page_samples() * _x_scale;
556 //      }
557
558         xn.first += steps;
559         xn.second -= steps;
560
561         set_overlays_dirty ();
562         set_editor_x (xn);
563 }
564
565
566 bool
567 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
568 {
569         pair<double, double> xr = _start_editor_x;
570         double x = _start_editor_x.first;
571
572         if (_move_dragging) {
573
574                 _moved = true;
575
576                 assert (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT);
577                 x += ev->x - _start_mouse_x;
578
579                 if (x < 0) {
580                         x = 0;
581                 }
582
583                 set_editor (x);
584
585         } else if (_zoom_dragging) {
586
587                 //ToDo: refactor into summary_zoom_in/out(
588                 //ToDo:  protect the case where the editor position is small, and results in offsetting the position
589
590                 double const dy = ev->y - _zoom_last_y;
591                 
592                 summary_zoom_step( dy );
593
594                 _zoom_last_y = ev->y;
595                         
596         } else if (_zoom_trim_dragging) {
597
598                 double const dx = ev->x - _start_mouse_x;
599
600                 if (_zoom_trim_position == LEFT) {
601                         xr.first += dx;
602                 } else if (_zoom_trim_position == RIGHT) {
603                         xr.second += dx;
604                 } else {
605                         assert (0);
606                         xr.first = -1; /* do not change */
607                 }
608
609                 set_overlays_dirty ();
610                 set_cursor (_zoom_trim_position);
611                 set_editor (xr);
612
613         } else if (_begin_dragging) {
614
615                 double const dx = ev->x - _start_mouse_x;
616                 double const dy = ev->y - _start_mouse_y;
617
618                 if ( fabs(dx) > fabs(dy) ) {
619                         
620                         /* initiate a move drag */
621
622                         /* get the editor's state in case we are suspending updates */
623                         get_editor (&_pending_editor_x, &_pending_editor_y);
624                         _pending_editor_changed = false;
625
626                         _move_dragging = true;
627                         _moved = false;
628                         _editor->_dragging_playhead = true;
629                         _editor->set_follow_playhead (false);
630
631                         get_window()->set_cursor (*_editor->_cursors->expand_left_right);
632
633                         _begin_dragging = false;
634                 
635                 } else if ( fabs(dy) > fabs(dx) ) {
636                 
637                         /* initiate a zoom drag */
638
639                         /* get the editor's state in case we are suspending updates */
640                         get_editor (&_pending_editor_x, &_pending_editor_y);
641                         _pending_editor_changed = false;
642
643                         //_zoom_position = get_position (ev->x, ev->y);
644                         _zoom_dragging = true;
645                         _zoom_last_y = ev->y;
646                         _editor->_dragging_playhead = true;
647                         _editor->set_follow_playhead (false);
648
649                         get_window()->set_cursor (*_editor->_cursors->expand_up_down);
650
651                         _begin_dragging = false;
652                 }
653                 
654         } else {
655                 set_cursor ( INSIDE );
656         }
657
658         return true;
659 }
660
661 bool
662 EditorSummary::on_button_release_event (GdkEventButton*)
663 {
664         bool const was_suspended = suspending_editor_updates ();
665
666         _begin_dragging = false;
667         _move_dragging = false;
668         _zoom_trim_dragging = false;
669         _zoom_dragging = false;
670         _editor->_dragging_playhead = false;
671         _editor->set_follow_playhead (_old_follow_playhead, false);
672
673         if (was_suspended && _pending_editor_changed) {
674                 set_editor (_pending_editor_x);
675         }
676
677         return true;
678 }
679
680 bool
681 EditorSummary::on_scroll_event (GdkEventScroll* ev)
682 {
683         /* mouse wheel */
684         pair<double, double> xr;
685         get_editor (&xr);
686         double x = xr.first;
687
688         switch (ev->direction) {
689                 case GDK_SCROLL_UP: {
690                         
691                         summary_zoom_step( -2 );
692                 
693                         return true;
694                 } break;
695                 
696                 case GDK_SCROLL_DOWN: {
697                         
698                         summary_zoom_step( 2 );
699                 
700                         return true;
701                 } break;
702                 
703                 case GDK_SCROLL_LEFT:
704                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
705                                 _editor->temporal_zoom_step (false);
706                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
707                                 x -= 64;
708                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
709                                 x -= 1;
710                         } else {
711                                 _editor->scroll_left_half_page ();
712                                 return true;
713                         }
714                         break;
715                 case GDK_SCROLL_RIGHT:
716                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
717                                 _editor->temporal_zoom_step (true);
718                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
719                                 x += 64;
720                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
721                                 x += 1;
722                         } else {
723                                 _editor->scroll_right_half_page ();
724                                 return true;
725                         }
726                         break;
727                 default:
728                         break;
729         }
730
731         set_editor (x);
732         return true;
733 }
734
735 /** Set the editor to display a x range with the left at a given position
736  *  and a y range with the top at a given position.
737  *  x and y parameters are specified in summary coordinates.
738  *  Zoom is not changed in either direction.
739  */
740 void
741 EditorSummary::set_editor (double const x)
742 {
743         if (_editor->pending_visual_change.idle_handler_id >= 0 && _editor->pending_visual_change.being_handled == true) {
744
745                 /* As a side-effect, the Editor's visual change idle handler processes
746                    pending GTK events.  Hence this motion notify handler can be called
747                    in the middle of a visual change idle handler, and if this happens,
748                    the queue_visual_change calls below modify the variables that the
749                    idle handler is working with.  This causes problems.  Hence this
750                    check.  It ensures that we won't modify the pending visual change
751                    while a visual change idle handler is in progress.  It's not perfect,
752                    as it also means that we won't change these variables if an idle handler
753                    is merely pending but not executing.  But c'est la vie.
754                 */
755
756                 return;
757         }
758
759         set_editor_x (x);
760 }
761
762 /** Set the editor to display a given x range and a y range with the top at a given position.
763  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
764  *  x and y parameters are specified in summary coordinates.
765  */
766 void
767 EditorSummary::set_editor (pair<double,double> const x)
768 {
769         if (_editor->pending_visual_change.idle_handler_id >= 0) {
770                 /* see comment in other set_editor () */
771                 return;
772         }
773
774         if (x.first >= 0) {
775                 set_editor_x (x);
776         }
777 }
778
779 /** Set the left of the x range visible in the editor.
780  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
781  *  @param x new x left position in summary coordinates.
782  */
783 void
784 EditorSummary::set_editor_x (double x)
785 {
786         if (x < 0) {
787                 x = 0;
788         }
789
790         if (suspending_editor_updates ()) {
791                 double const w = _pending_editor_x.second - _pending_editor_x.first;
792                 _pending_editor_x.first = x;
793                 _pending_editor_x.second = x + w;
794                 _pending_editor_changed = true;
795                 set_dirty ();
796         } else {
797                 _editor->reset_x_origin (x / _x_scale + _start);
798         }
799 }
800
801 /** Set the x range visible in the editor.
802  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
803  *  @param x new x range in summary coordinates.
804  */
805 void
806 EditorSummary::set_editor_x (pair<double, double> x)
807 {
808         if (x.first < 0) {
809                 x.first = 0;
810         }
811
812         if (x.second < 0) {
813                 x.second = x.first + 1;
814         }
815
816         if (suspending_editor_updates ()) {
817                 _pending_editor_x = x;
818                 _pending_editor_changed = true;
819                 set_dirty ();
820         } else {
821                 _editor->reset_x_origin (x.first / _x_scale + _start);
822
823                 double const nx = (
824                         ((x.second - x.first) / _x_scale) /
825                         _editor->sample_to_pixel (_editor->current_page_samples())
826                         );
827
828                 if (nx != _editor->get_current_zoom ()) {
829                         _editor->reset_zoom (nx);
830                 }
831         }
832 }
833
834 void
835 EditorSummary::playhead_position_changed (framepos_t p)
836 {
837         int const o = int (_last_playhead);
838         int const n = int (playhead_frame_to_position (p));
839         if (_session && o != n) {
840                 int a = max(2, min (o, n));
841                 int b = max (o, n);
842                 set_overlays_dirty (a - 2, 0, b + 2, get_height ());
843         }
844 }
845
846 double
847 EditorSummary::editor_y_to_summary (double y) const
848 {
849         double sy = 0;
850         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
851
852                 if ((*i)->hidden()) {
853                         continue;
854                 }
855
856                 double const h = (*i)->effective_height ();
857                 if (y < h) {
858                         /* in this track */
859                         return sy + y * _track_height / h;
860                 }
861
862                 sy += _track_height;
863                 y -= h;
864         }
865
866         return sy;
867 }
868
869 void
870 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
871 {
872         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
873                 /* Connect to the relevant signal for the route so that we know when its colour has changed */
874                 (*i)->route()->presentation_info().PropertyChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
875                 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
876                 if (tr) {
877                         tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context ());
878                 }
879         }
880
881         set_background_dirty ();
882 }
883
884 void
885 EditorSummary::route_gui_changed (PBD::PropertyChange const& what_changed)
886 {
887         if (what_changed.contains (Properties::color)) {
888                 set_background_dirty ();
889         }
890 }
891
892 double
893 EditorSummary::playhead_frame_to_position (framepos_t t) const
894 {
895         return (t - _start) * _x_scale;
896 }
897
898 framepos_t
899 EditorSummary::position_to_playhead_frame_to_position (double pos) const
900 {
901         return _start  + (pos * _x_scale);
902 }