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