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