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