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