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