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