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