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