Further Freesound import tweaks.
[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         switch (ev->direction) {
627                 case GDK_SCROLL_UP:
628                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollHorizontalModifier)) {
629                                 x -= 64;
630                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
631                                 _editor->temporal_zoom_step (false);
632                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
633                                 yr.first  += 4;
634                                 yr.second -= 4;
635                                 set_editor (xr, yr);
636                                 return true;
637                         } else {
638                                 y -= 8;
639                         }
640                         break;
641                 case GDK_SCROLL_DOWN:
642                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollHorizontalModifier)) {
643                                 x += 64;
644                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
645                                 _editor->temporal_zoom_step (true);
646                         } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomVerticalModifier)) {
647                                 yr.first  -= 4;
648                                 yr.second += 4;
649                                 set_editor (xr, yr);
650                                 return true;
651                         } else {
652                                 y += 8;
653                         }
654                         break;
655                 case GDK_SCROLL_LEFT:
656                         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
657                                 x -= 64;
658                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
659                                 x -= 1;
660                         } else {
661                                 x -= 8;
662                         }
663                         break;
664                 case GDK_SCROLL_RIGHT:
665                         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
666                                 x += 64;
667                         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
668                                 x += 1;
669                         } else {
670                                 x += 8;
671                         }
672                         break;
673                 default:
674                         break;
675         }
676
677         set_editor (x, y);
678         return true;
679 }
680
681 /** Set the editor to display a x range with the left at a given position
682  *  and a y range with the top at a given position.
683  *  x and y parameters are specified in summary coordinates.
684  *  Zoom is not changed in either direction.
685  */
686 void
687 EditorSummary::set_editor (double const x, double const y)
688 {
689         if (_editor->pending_visual_change.idle_handler_id >= 0) {
690
691                 /* As a side-effect, the Editor's visual change idle handler processes
692                    pending GTK events.  Hence this motion notify handler can be called
693                    in the middle of a visual change idle handler, and if this happens,
694                    the queue_visual_change calls below modify the variables that the
695                    idle handler is working with.  This causes problems.  Hence this
696                    check.  It ensures that we won't modify the pending visual change
697                    while a visual change idle handler is in progress.  It's not perfect,
698                    as it also means that we won't change these variables if an idle handler
699                    is merely pending but not executing.  But c'est la vie.
700                 */
701                 
702                 return;
703         }
704
705         set_editor_x (x);
706         set_editor_y (y);
707 }
708
709 /** Set the editor to display a given x range and a y range with the top at a given position.
710  *  The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
711  *  x and y parameters are specified in summary coordinates.
712  */
713 void
714 EditorSummary::set_editor (pair<double,double> const x, double const y)
715 {
716         if (_editor->pending_visual_change.idle_handler_id >= 0) {
717                 /* see comment in other set_editor () */
718                 return;
719         }
720
721         set_editor_x (x);
722         set_editor_y (y);
723 }
724
725 /** Set the editor to display given x and y ranges.  x zoom and track heights are
726  *  adjusted if necessary.
727  *  x and y parameters are specified in summary coordinates.
728  */
729 void
730 EditorSummary::set_editor (pair<double,double> const x, pair<double, double> const y)
731 {
732         if (_editor->pending_visual_change.idle_handler_id >= 0) {
733                 /* see comment in other set_editor () */
734                 return;
735         }
736
737         set_editor_x (x);
738         set_editor_y (y);
739 }
740
741 /** Set the left of the x range visible in the editor.
742  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
743  *  @param x new x left position in summary coordinates.
744  */
745 void
746 EditorSummary::set_editor_x (double x)
747 {
748         if (x < 0) {
749                 x = 0;
750         }
751
752         if (suspending_editor_updates ()) {
753                 double const w = _pending_editor_x.second - _pending_editor_x.first;
754                 _pending_editor_x.first = x;
755                 _pending_editor_x.second = x + w;
756                 _pending_editor_changed = true;
757                 set_dirty ();
758         } else {
759                 _editor->reset_x_origin (x / _x_scale + _start);
760         }
761 }
762
763 /** Set the x range visible in the editor.
764  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
765  *  @param x new x range in summary coordinates.
766  */
767 void
768 EditorSummary::set_editor_x (pair<double, double> x)
769 {
770         if (x.first < 0) {
771                 x.first = 0;
772         }
773
774         if (x.second < 0) {
775                 x.second = x.first + 1;
776         }
777
778         if (suspending_editor_updates ()) {
779                 _pending_editor_x = x;
780                 _pending_editor_changed = true;
781                 set_dirty ();
782         } else {
783                 _editor->reset_x_origin (x.first / _x_scale + _start);
784                 
785                 double const nx = (
786                         ((x.second - x.first) / _x_scale) /
787                         _editor->frame_to_unit (_editor->current_page_frames())
788                         );
789                 
790                 if (nx != _editor->get_current_zoom ()) {
791                         _editor->reset_zoom (nx);
792                 }
793         }
794 }
795
796 /** Set the top of the y range visible in the editor.
797  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
798  *  @param y new editor top in summary coodinates.
799  */
800 void
801 EditorSummary::set_editor_y (double const y)
802 {
803         double y1 = summary_y_to_editor (y);
804         double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
805         double y2 = y1 + eh;
806
807         double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
808
809         if (y2 > full_editor_height) {
810                 y1 -= y2 - full_editor_height;
811         }
812
813         if (y1 < 0) {
814                 y1 = 0;
815         }
816
817         if (suspending_editor_updates ()) {
818                 double const h = _pending_editor_y.second - _pending_editor_y.first;
819                 _pending_editor_y.first = y;
820                 _pending_editor_y.second = y + h;
821                 _pending_editor_changed = true;
822                 set_dirty ();
823         } else {
824                 _editor->reset_y_origin (y1);
825         }
826 }
827
828 /** Set the y range visible in the editor.  This is achieved by scaling track heights,
829  *  if necessary.
830  *  Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
831  *  @param y new editor range in summary coodinates.
832  */
833 void
834 EditorSummary::set_editor_y (pair<double, double> const y)
835 {
836         if (suspending_editor_updates ()) {
837                 _pending_editor_y = y;
838                 _pending_editor_changed = true;
839                 set_dirty ();
840                 return;
841         }
842         
843         /* Compute current height of tracks between y.first and y.second.  We add up
844            the total height into `total_height' and the height of complete tracks into
845            `scale height'.
846         */
847
848         /* Copy of target range for use below */
849         pair<double, double> yc = y;
850         /* Total height of all tracks */
851         double total_height = 0;
852         /* Height of any parts of tracks that aren't fully in the desired range */
853         double partial_height = 0;
854         /* Height of any tracks that are fully in the desired range */
855         double scale_height = 0;
856
857         _editor->_routes->suspend_redisplay ();
858
859         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
860
861                 if ((*i)->hidden()) {
862                         continue;
863                 }
864
865                 double const h = (*i)->effective_height ();
866                 total_height += h;
867
868                 if (yc.first > 0 && yc.first < _track_height) {
869                         partial_height += (_track_height - yc.first) * h / _track_height;
870                 } else if (yc.first <= 0 && yc.second >= _track_height) {
871                         scale_height += h;
872                 } else if (yc.second > 0 && yc.second < _track_height) {
873                         partial_height += yc.second * h / _track_height;
874                         break;
875                 }
876
877                 yc.first -= _track_height;
878                 yc.second -= _track_height;
879         }
880
881         /* Height that we will use for scaling; use the whole editor height unless there are not
882            enough tracks to fill it.
883         */
884         double const ch = min (total_height, _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
885
886         /* hence required scale factor of the complete tracks to fit the required y range;
887            the amount of space they should take up divided by the amount they currently take up.
888         */
889         double const scale = (ch - partial_height) / scale_height;
890
891         yc = y;
892
893         /* Scale complete tracks within the range to make it fit */
894
895         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
896
897                 if ((*i)->hidden()) {
898                         continue;
899                 }
900
901                 if (yc.first <= 0 && yc.second >= _track_height) {
902                         (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
903                 }
904
905                 yc.first -= _track_height;
906                 yc.second -= _track_height;
907         }
908
909         _editor->_routes->resume_redisplay ();
910
911         set_editor_y (y.first);
912 }
913
914 void
915 EditorSummary::playhead_position_changed (framepos_t p)
916 {
917         int const o = int (_last_playhead);
918         int const n = int (playhead_frame_to_position (p));
919         if (_session && o != n) {
920                 int a = max(2, min (o, n));
921                 int b = max (o, n);
922                 set_overlays_dirty (a - 2, 0, b + 2, get_height ());
923         }
924 }
925
926 double
927 EditorSummary::summary_y_to_editor (double y) const
928 {
929         double ey = 0;
930         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
931
932                 if ((*i)->hidden()) {
933                         continue;
934                 }
935
936                 double const h = (*i)->effective_height ();
937                 if (y < _track_height) {
938                         /* in this track */
939                         return ey + y * h / _track_height;
940                 }
941
942                 ey += h;
943                 y -= _track_height;
944         }
945
946         return ey;
947 }
948
949 double
950 EditorSummary::editor_y_to_summary (double y) const
951 {
952         double sy = 0;
953         for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
954
955                 if ((*i)->hidden()) {
956                         continue;
957                 }
958
959                 double const h = (*i)->effective_height ();
960                 if (y < h) {
961                         /* in this track */
962                         return sy + y * _track_height / h;
963                 }
964
965                 sy += _track_height;
966                 y -= h;
967         }
968
969         return sy;
970 }
971
972 void
973 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
974 {
975         for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
976                 /* Connect to gui_changed() on the route so that we know when their colour has changed */
977                 (*i)->route()->gui_changed.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
978                 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
979                 if (tr) {
980                         tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context ());
981                 }
982         }
983
984         set_dirty ();
985 }
986
987 void
988 EditorSummary::route_gui_changed (string c)
989 {
990         if (c == "color") {
991                 set_dirty ();
992         }
993 }
994
995 double
996 EditorSummary::playhead_frame_to_position (framepos_t t) const
997 {
998         return (t - _start) * _x_scale;
999 }
1000
1001 framepos_t
1002 EditorSummary::position_to_playhead_frame_to_position (double pos) const
1003 {
1004         return _start  + (pos * _x_scale);
1005 }