more changes flowing from a persistent MonitorSection object
[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         /*NOTE:  you can optimize this operation by coalescing adjacent regions into a single line stroke.
311          * In a session with a single track ~1,000 regions, this reduced render time from 14ms to 11 ms.
312          * However, you lose a lot of visual information.  The current method preserves a sense of separation between regions.
313          * The current method shows the current selection (red regions), which needs to be preserved if this is optimized.
314          * I think it's not worth it for now,  but we might choose to revisit this someday.
315          */ 
316
317         uint32_t const c = r->get_fill_color ();
318         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
319
320         if (r->region()->position() > _start) {
321                 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
322         } else {
323                 cairo_move_to (cr, 0, y);
324         }
325
326         if ((r->region()->position() + r->region()->length()) > _start) {
327                 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
328         } else {
329                 cairo_line_to (cr, 0, y);
330         }
331
332         cairo_stroke (cr);
333 }
334
335 void
336 EditorSummary::set_background_dirty ()
337 {
338         if (!_background_dirty) {
339                 _background_dirty = true;
340                 set_dirty ();
341         }
342 }
343
344 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
345 void
346 EditorSummary::set_overlays_dirty ()
347 {
348         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
349         queue_draw ();
350 }
351
352 /** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
353 void
354 EditorSummary::set_overlays_dirty_rect (int x, int y, int w, int h)
355 {
356         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty_rect);
357         queue_draw_area (x, y, w, h);
358 }
359
360
361 /** Handle a size request.
362  *  @param req GTK requisition
363  */
364 void
365 EditorSummary::on_size_request (Gtk::Requisition *req)
366 {
367         /* The left/right buttons will determine our height */
368         req->width = -1;
369         req->height = -1;
370 }
371
372
373 void
374 EditorSummary::centre_on_click (GdkEventButton* ev)
375 {
376         pair<double, double> xr;
377         get_editor (&xr);
378
379         double const w = xr.second - xr.first;
380         double ex = ev->x - w / 2;
381         if (ex < 0) {
382                 ex = 0;
383         } else if ((ex + w) > get_width()) {
384                 ex = get_width() - w;
385         }
386
387         set_editor (ex);
388 }
389
390 bool
391 EditorSummary::on_enter_notify_event (GdkEventCrossing*)
392 {
393         grab_focus ();
394         Keyboard::magic_widget_grab_focus ();
395         return false;
396 }
397
398 bool
399 EditorSummary::on_leave_notify_event (GdkEventCrossing*)
400 {
401         /* there are no inferior/child windows, so any leave event means that
402            we're gone.
403         */
404         Keyboard::magic_widget_drop_focus ();
405         return false;
406 }
407
408 bool
409 EditorSummary::on_key_press_event (GdkEventKey* key)
410 {
411         gint x, y;
412         GtkAccelKey set_playhead_accel;
413         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
414                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
415                         if (_session) {
416                                 get_pointer (x, y);
417                                 _session->request_locate (_start + (samplepos_t) x / _x_scale, _session->transport_rolling());
418                                 return true;
419                         }
420                 }
421         }
422
423         return false;
424 }
425
426 bool
427 EditorSummary::on_key_release_event (GdkEventKey* key)
428 {
429
430         GtkAccelKey set_playhead_accel;
431         if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
432                 if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
433                         return true;
434                 }
435         }
436         return false;
437 }
438
439 #include "gtkmm2ext/utils.h"
440
441 /** Handle a button press.
442  *  @param ev GTK event.
443  */
444 bool
445 EditorSummary::on_button_press_event (GdkEventButton* ev)
446 {
447         _old_follow_playhead = _editor->follow_playhead ();
448
449         if (ev->button == 3) { // right-click:  show the reset menu action
450                 using namespace Gtk::Menu_Helpers;
451                 Gtk::Menu* m = manage (new Gtk::Menu);
452                 MenuList& items = m->items ();
453                 items.push_back(MenuElem(_("Reset Summary to Extents"),
454                         sigc::mem_fun(*this, &EditorSummary::reset_to_extents)));
455                 m->popup (ev->button, ev->time);
456                 return true;
457         }
458
459         if (ev->button != 1) {
460                 return true;
461         }
462
463         pair<double, double> xr;
464         get_editor (&xr);
465
466         _start_editor_x = xr;
467         _start_mouse_x = ev->x;
468         _start_mouse_y = ev->y;
469         _start_position = get_position (ev->x, ev->y);
470
471         if (_start_position != INSIDE && _start_position != TO_LEFT_OR_RIGHT) {
472
473                 /* start a zoom_trim drag */
474
475                 _zoom_trim_position = get_position (ev->x, ev->y);
476                 _zoom_trim_dragging = true;
477                 _editor->_dragging_playhead = true;
478                 _editor->set_follow_playhead (false);
479
480                 if (suspending_editor_updates ()) {
481                         get_editor (&_pending_editor_x, &_pending_editor_y);
482                         _pending_editor_changed = false;
483                 }
484
485         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
486
487                 /* secondary-modifier-click: locate playhead */
488                 if (_session) {
489                         _session->request_locate (ev->x / _x_scale + _start);
490                 }
491
492         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
493
494                 centre_on_click (ev);
495
496         } else {
497
498                 /* start a move+zoom drag */
499                 get_editor (&_pending_editor_x, &_pending_editor_y);
500                 _pending_editor_changed = false;
501                 _editor->_dragging_playhead = true;
502                 _editor->set_follow_playhead (false);
503
504                 _move_dragging = true;
505
506                 _last_mx = ev->x;
507                 _last_my = ev->y;
508                 _last_dx = 0;
509                 _last_dy = 0;
510                 _last_y_delta = 0;
511
512                 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
513
514         }
515
516         return true;
517 }
518
519 /** @return true if we are currently suspending updates to the editor's viewport,
520  *  which we do if configured to do so, and if in a drag of some kind.
521  */
522 bool
523 EditorSummary::suspending_editor_updates () const
524 {
525         return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_trim_dragging || _move_dragging));
526 }
527
528 /** Fill in x and y with the editor's current viewable area in summary coordinates */
529 void
530 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
531 {
532         assert (x);
533         if (suspending_editor_updates ()) {
534
535                 /* We are dragging, and configured not to update the editor window during drags,
536                  * so just return where the editor will be when the drag finishes.
537                 */
538
539                 *x = _pending_editor_x;
540                 if (y) {
541                         *y = _pending_editor_y;
542                 }
543                 return;
544         }
545
546         /* Otherwise query the editor for its actual position */
547
548         x->first = (_editor->leftmost_sample () - _start) * _x_scale;
549         x->second = x->first + _editor->current_page_samples() * _x_scale;
550
551         if (y) {
552                 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
553                 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->visible_canvas_height() - _editor->get_trackview_group()->canvas_origin().y);
554         }
555 }
556
557 /** Get an expression of the position of a point with respect to the view rectangle */
558 EditorSummary::Position
559 EditorSummary::get_position (double x, double y) const
560 {
561         /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
562            in pixels */
563
564         int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
565         x_edge_size = min (x_edge_size, 8);
566         x_edge_size = max (x_edge_size, 1);
567
568         bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
569         bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
570         bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
571
572         if (near_left) {
573                 return LEFT;
574         } else if (near_right) {
575                 return RIGHT;
576         } else if (within_x) {
577                 return INSIDE;
578         } else {
579                 return TO_LEFT_OR_RIGHT;
580         }
581 }
582
583 void
584 EditorSummary::reset_to_extents()
585 {
586         /* reset as if the user never went anywhere outside the extents */
587         _leftmost = max_samplepos;
588         _rightmost = 0;
589
590         _editor->temporal_zoom_extents ();
591         set_background_dirty ();
592 }
593
594
595 void
596 EditorSummary::set_cursor (Position p)
597 {
598         switch (p) {
599         case LEFT:
600                 get_window()->set_cursor (*_editor->_cursors->resize_left);
601                 break;
602         case RIGHT:
603                 get_window()->set_cursor (*_editor->_cursors->resize_right);
604                 break;
605         case INSIDE:
606                 get_window()->set_cursor (*_editor->_cursors->move);
607                 break;
608         case TO_LEFT_OR_RIGHT:
609                 get_window()->set_cursor (*_editor->_cursors->move);
610                 break;
611         default:
612                 assert (0);
613                 get_window()->set_cursor ();
614                 break;
615         }
616 }
617
618 void
619 EditorSummary::summary_zoom_step (int steps /* positive steps to zoom "out" , negative steps to zoom "in" */  )
620 {
621         pair<double, double> xn;
622
623         get_editor (&xn);
624
625         xn.first -= steps;
626         xn.second += steps;
627
628         /* for now, disallow really close zooming-in from the scroomer. (Currently it
629          * causes the start-offset to 'walk' because of integer limitations.
630          * To fix this, probably need to maintain float throught the get/set_editor() path.)
631          */
632         if (steps<0) {
633       if ((xn.second - xn.first) < 2)
634                 return;
635         }
636
637         set_overlays_dirty ();
638         set_editor_x (xn);
639 }
640
641
642 bool
643 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
644 {
645         if (_move_dragging) {
646
647                 /* To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
648                  * we use screen coordinates for this, not canvas-based grab_x */
649                 double mx = ev->x;
650                 double dx = mx - _last_mx;
651                 double my = ev->y;
652                 double dy = my - _last_my;
653
654                 /* do zooming in windowed "steps" so it feels more reversible ? */
655                 const int stepsize = 2;
656                 int y_delta = _start_mouse_y - my;
657                 y_delta = y_delta / stepsize;
658
659                 /* do the zoom? */
660                 const float zscale = 3;
661                 if ((dx == 0) && (_last_dx == 0) && (y_delta != _last_y_delta)) {
662
663                         summary_zoom_step (dy * zscale);
664
665                         /* after the zoom we must re-calculate x-pos grabs */
666                         pair<double, double> xr;
667                         get_editor (&xr);
668                         _start_editor_x = xr;
669                         _start_mouse_x = ev->x;
670
671                         _last_y_delta = y_delta;
672                 }
673
674                 /* always track horizontal movement, if any */
675                 if (dx != 0) {
676
677                         double x = _start_editor_x.first;
678                         x += ev->x - _start_mouse_x;
679
680                         if (x < 0) {
681                                 x = 0;
682                         }
683
684                         /* zoom-behavior-tweaks: protect the right edge from expanding beyond the end */
685                         pair<double, double> xr;
686                         get_editor (&xr);
687                         double w = xr.second - xr.first;
688                         if (x + w < get_width()) {
689                                 set_editor (x);
690                         }
691                 }
692
693                 _last_my = my;
694                 _last_mx = mx;
695                 _last_dx = dx;
696                 _last_dy = dy;
697
698         } else if (_zoom_trim_dragging) {
699
700                 pair<double, double> xr = _start_editor_x;
701
702                 double const dx = ev->x - _start_mouse_x;
703
704                 if (_zoom_trim_position == LEFT) {
705                         xr.first += dx;
706                 } else if (_zoom_trim_position == RIGHT) {
707
708                         /* zoom-behavior-tweaks: protect the right edge from expanding beyond the edge */
709                         if ((xr.second + dx) < get_width()) {
710                                 xr.second += dx;
711                         }
712
713                 } else {
714                         assert (0);
715                         xr.first = -1; /* do not change */
716                 }
717
718                 set_overlays_dirty ();
719                 set_cursor (_zoom_trim_position);
720                 set_editor (xr);
721
722         } else {
723                 set_cursor (get_position (ev->x, ev->y));
724         }
725
726         return true;
727 }
728
729 bool
730 EditorSummary::on_button_release_event (GdkEventButton*)
731 {
732         bool const was_suspended = suspending_editor_updates ();
733
734         _move_dragging = false;
735         _zoom_trim_dragging = false;
736         _editor->_dragging_playhead = false;
737         _editor->set_follow_playhead (_old_follow_playhead, false);
738
739         if (was_suspended && _pending_editor_changed) {
740                 set_editor (_pending_editor_x);
741         }
742
743         return true;
744 }
745
746 bool
747 EditorSummary::on_scroll_event (GdkEventScroll* ev)
748 {
749         /* mouse wheel */
750         pair<double, double> xr;
751         get_editor (&xr);
752         double x = xr.first;
753
754         switch (ev->direction) {
755                 case GDK_SCROLL_UP: {
756
757                         summary_zoom_step (-4);
758
759                         return true;
760                 } break;
761
762                 case GDK_SCROLL_DOWN: {
763
764                         summary_zoom_step (4);
765
766                         return true;
767                 } break;
768
769                 case GDK_SCROLL_LEFT:
770                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
771                                 _editor->temporal_zoom_step (false);
772                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
773                                 x -= 64;
774                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
775                                 x -= 1;
776                         } else {
777                                 _editor->scroll_left_half_page ();
778                                 return true;
779                         }
780                         break;
781                 case GDK_SCROLL_RIGHT:
782                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
783                                 _editor->temporal_zoom_step (true);
784                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
785                                 x += 64;
786                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
787                                 x += 1;
788                         } else {
789                                 _editor->scroll_right_half_page ();
790                                 return true;
791                         }
792                         break;
793                 default:
794                         break;
795         }
796
797         set_editor (x);
798         return true;
799 }
800
801 /** Set the editor to display a x range with the left at a given position
802  *  and a y range with the top at a given position.
803  *  x and y parameters are specified in summary coordinates.
804  *  Zoom is not changed in either direction.
805  */
806 void
807 EditorSummary::set_editor (double const x)
808 {
809         if (_editor->pending_visual_change.idle_handler_id >= 0 && _editor->pending_visual_change.being_handled == true) {
810
811                 /* As a side-effect, the Editor's visual change idle handler processes
812                    pending GTK events.  Hence this motion notify handler can be called
813                    in the middle of a visual change idle handler, and if this happens,
814                    the queue_visual_change calls below modify the variables that the
815                    idle handler is working with.  This causes problems.  Hence this
816                    check.  It ensures that we won't modify the pending visual change
817                    while a visual change idle handler is in progress.  It's not perfect,
818                    as it also means that we won't change these variables if an idle handler
819                    is merely pending but not executing.  But c'est la vie.
820                 */
821
822                 return;
823         }
824
825         set_editor_x (x);
826 }
827
828 /** Set the editor to display a given x range and a y range with the top at a given position.
829  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
830  *  x and y parameters are specified in summary coordinates.
831  */
832 void
833 EditorSummary::set_editor (pair<double,double> const x)
834 {
835         if (_editor->pending_visual_change.idle_handler_id >= 0) {
836                 /* see comment in other set_editor () */
837                 return;
838         }
839
840         if (x.first >= 0) {
841                 set_editor_x (x);
842         }
843 }
844
845 /** Set the left of the x range visible in the editor.
846  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
847  *  @param x new x left position in summary coordinates.
848  */
849 void
850 EditorSummary::set_editor_x (double x)
851 {
852         if (x < 0) {
853                 x = 0;
854         }
855
856         if (suspending_editor_updates ()) {
857                 double const w = _pending_editor_x.second - _pending_editor_x.first;
858                 _pending_editor_x.first = x;
859                 _pending_editor_x.second = x + w;
860                 _pending_editor_changed = true;
861                 set_dirty ();
862         } else {
863                 _editor->reset_x_origin (x / _x_scale + _start);
864         }
865 }
866
867 /** Set the x range visible in the editor.
868  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
869  *  @param x new x range in summary coordinates.
870  */
871 void
872 EditorSummary::set_editor_x (pair<double, double> x)
873 {
874         if (x.first < 0) {
875                 x.first = 0;
876         }
877
878         if (x.second < 0) {
879                 x.second = x.first + 1;
880         }
881
882         if (suspending_editor_updates ()) {
883                 _pending_editor_x = x;
884                 _pending_editor_changed = true;
885                 set_dirty ();
886         } else {
887                 _editor->reset_x_origin (x.first / _x_scale + _start);
888
889                 double const nx = (
890                         ((x.second - x.first) / _x_scale) /
891                         _editor->sample_to_pixel (_editor->current_page_samples())
892                         );
893
894                 if (nx != _editor->get_current_zoom ()) {
895                         _editor->reset_zoom (nx);
896                 }
897         }
898 }
899
900 void
901 EditorSummary::playhead_position_changed (samplepos_t p)
902 {
903         int const o = int (_last_playhead);
904         int const n = int (playhead_sample_to_position (p));
905         if (_session && o != n) {
906                 int a = max(2, min (o, n));
907                 int b = max (o, n);
908                 set_overlays_dirty_rect (a - 2, 0, b + 2, get_height ());
909         }
910 }
911
912 double
913 EditorSummary::editor_y_to_summary (double y) const
914 {
915         double sy = 0;
916         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
917
918                 if ((*i)->hidden()) {
919                         continue;
920                 }
921
922                 double const h = (*i)->effective_height ();
923                 if (y < h) {
924                         /* in this track */
925                         return sy + y * _track_height / h;
926                 }
927
928                 sy += _track_height;
929                 y -= h;
930         }
931
932         return sy;
933 }
934
935 void
936 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
937 {
938         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
939                 /* Connect to the relevant signal for the route so that we know when its colour has changed */
940                 (*i)->route()->presentation_info().PropertyChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
941                 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
942                 if (tr) {
943                         tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context ());
944                 }
945         }
946
947         set_background_dirty ();
948 }
949
950 void
951 EditorSummary::route_gui_changed (PBD::PropertyChange const& what_changed)
952 {
953         if (what_changed.contains (Properties::color)) {
954                 set_background_dirty ();
955         }
956 }
957
958 double
959 EditorSummary::playhead_sample_to_position (samplepos_t t) const
960 {
961         return (t - _start) * _x_scale;
962 }
963
964 samplepos_t
965 EditorSummary::position_to_playhead_sample_to_position (double pos) const
966 {
967         return _start  + (pos * _x_scale);
968 }