2 Copyright (C) 2009 Paul Davis
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.
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.
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.
20 #include "ardour/session.h"
21 #include "time_axis_view.h"
22 #include "streamview.h"
23 #include "editor_summary.h"
24 #include "gui_thread.h"
26 #include "region_view.h"
27 #include "rgb_macros.h"
29 #include "editor_routes.h"
30 #include "editor_cursors.h"
31 #include "mouse_cursors.h"
32 #include "route_time_axis.h"
35 using namespace ARDOUR;
36 using Gtkmm2ext::Keyboard;
38 /** Construct an EditorSummary.
39 * @param e Editor to represent.
41 EditorSummary::EditorSummary (Editor* e)
42 : EditorComponent (e),
45 _overhang_fraction (0.1),
49 _move_dragging (false),
51 _view_rectangle_x (0, 0),
52 _view_rectangle_y (0, 0),
53 _zoom_dragging (false),
54 _old_follow_playhead (false)
56 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
57 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), boost::bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
59 add_events (Gdk::POINTER_MOTION_MASK);
62 /** Connect to a session.
66 EditorSummary::set_session (Session* s)
68 SessionHandlePtr::set_session (s);
72 /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
73 * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
74 * emitted when a cut region is added to the `cutlist' playlist.
78 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
79 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
83 /** Handle an expose event.
84 * @param event Event from GTK.
87 EditorSummary::on_expose_event (GdkEventExpose* event)
89 CairoWidget::on_expose_event (event);
95 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
97 /* Render the view rectangle. If there is an editor visual pending, don't update
98 the view rectangle now --- wait until the expose event that we'll get after
99 the visual change. This prevents a flicker.
102 if (_editor->pending_visual_change.idle_handler_id < 0) {
103 get_editor (&_view_rectangle_x, &_view_rectangle_y);
106 cairo_move_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
107 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.first);
108 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.second);
109 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.second);
110 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
111 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
112 cairo_fill_preserve (cr);
113 cairo_set_line_width (cr, 1);
114 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
119 cairo_set_line_width (cr, 1);
120 /* XXX: colour should be set from configuration file */
121 cairo_set_source_rgba (cr, 1, 0, 0, 1);
123 double const p = playhead_frame_to_position (_editor->playhead_cursor->current_frame);
124 cairo_move_to (cr, p, 0);
125 cairo_line_to (cr, p, get_height());
134 /** Render the required regions to a cairo context.
138 EditorSummary::render (cairo_t* cr)
140 /* background (really just the dividing lines between tracks */
142 cairo_set_source_rgb (cr, 0, 0, 0);
143 cairo_rectangle (cr, 0, 0, get_width(), get_height());
150 /* compute start and end points for the summary */
152 framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
153 double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
154 _start = theoretical_start > 0 ? theoretical_start : 0;
155 _end = _session->current_end_frame() + session_length * _overhang_fraction;
157 /* compute track height */
159 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
160 if (!(*i)->hidden()) {
168 _track_height = (double) get_height() / N;
171 /* calculate x scale */
172 if (_end != _start) {
173 _x_scale = static_cast<double> (get_width()) / (_end - _start);
178 /* render tracks and regions */
181 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
183 if ((*i)->hidden()) {
187 /* paint a non-bg colored strip to represent the track itself */
189 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
190 cairo_set_line_width (cr, _track_height - 1);
191 cairo_move_to (cr, 0, y + _track_height / 2);
192 cairo_line_to (cr, get_width(), y + _track_height / 2);
195 StreamView* s = (*i)->view ();
198 cairo_set_line_width (cr, _track_height * 0.6);
200 s->foreach_regionview (sigc::bind (
201 sigc::mem_fun (*this, &EditorSummary::render_region),
203 y + _track_height / 2
210 /* start and end markers */
212 cairo_set_line_width (cr, 1);
213 cairo_set_source_rgb (cr, 1, 1, 0);
215 double const p = (_session->current_start_frame() - _start) * _x_scale;
216 cairo_move_to (cr, p, 0);
217 cairo_line_to (cr, p, get_height());
220 double const q = (_session->current_end_frame() - _start) * _x_scale;
221 cairo_move_to (cr, q, 0);
222 cairo_line_to (cr, q, get_height());
226 /** Render a region for the summary.
227 * @param r Region view.
228 * @param cr Cairo context.
229 * @param y y coordinate to render at.
232 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
234 uint32_t const c = r->get_fill_color ();
235 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
237 if (r->region()->position() > _start) {
238 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
240 cairo_move_to (cr, 0, y);
243 if ((r->region()->position() + r->region()->length()) > _start) {
244 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
246 cairo_line_to (cr, 0, y);
252 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
254 EditorSummary::set_overlays_dirty ()
256 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
260 /** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
262 EditorSummary::set_overlays_dirty (int x, int y, int w, int h)
264 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
265 queue_draw_area (x, y, w, h);
269 /** Handle a size request.
270 * @param req GTK requisition
273 EditorSummary::on_size_request (Gtk::Requisition *req)
275 /* Use a dummy, small width and the actual height that we want */
282 EditorSummary::centre_on_click (GdkEventButton* ev)
284 pair<double, double> xr;
285 pair<double, double> yr;
286 get_editor (&xr, &yr);
288 double const w = xr.second - xr.first;
289 double ex = ev->x - w / 2;
292 } else if ((ex + w) > get_width()) {
293 ex = get_width() - w;
296 double const h = yr.second - yr.first;
297 double ey = ev->y - h / 2;
300 } else if ((ey + h) > get_height()) {
301 ey = get_height() - h;
307 /** Handle a button press.
308 * @param ev GTK event.
311 EditorSummary::on_button_press_event (GdkEventButton* ev)
313 if (ev->button == 1) {
315 pair<double, double> xr;
316 pair<double, double> yr;
317 get_editor (&xr, &yr);
319 _start_editor_x = xr;
320 _start_editor_y = yr;
321 _start_mouse_x = ev->x;
322 _start_mouse_y = ev->y;
323 _start_position = get_position (ev->x, ev->y);
325 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
326 _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
329 /* start a zoom drag */
331 _zoom_position = get_position (ev->x, ev->y);
332 _zoom_dragging = true;
333 _editor->_dragging_playhead = true;
334 _old_follow_playhead = _editor->follow_playhead ();
335 _editor->set_follow_playhead (false);
337 if (suspending_editor_updates ()) {
338 get_editor (&_pending_editor_x, &_pending_editor_y);
339 _pending_editor_changed = false;
342 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
344 /* secondary-modifier-click: locate playhead */
346 _session->request_locate (ev->x / _x_scale + _start);
349 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
351 centre_on_click (ev);
355 /* start a move drag */
357 /* get the editor's state in case we are suspending updates */
358 get_editor (&_pending_editor_x, &_pending_editor_y);
359 _pending_editor_changed = false;
361 _move_dragging = true;
363 _editor->_dragging_playhead = true;
364 _old_follow_playhead = _editor->follow_playhead ();
365 _editor->set_follow_playhead (false);
372 /** @return true if we are currently suspending updates to the editor's viewport,
373 * which we do if configured to do so, and if in a drag of some kind.
376 EditorSummary::suspending_editor_updates () const
378 return (!Config->get_update_editor_during_summary_drag () && (_zoom_dragging || _move_dragging));
381 /** Fill in x and y with the editor's current viewable area in summary coordinates */
383 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
388 if (suspending_editor_updates ()) {
390 /* We are dragging, and configured not to update the editor window during drags,
391 so just return where the editor will be when the drag finishes.
394 *x = _pending_editor_x;
395 *y = _pending_editor_y;
399 /* Otherwise query the editor for its actual position */
401 x->first = (_editor->leftmost_position () - _start) * _x_scale;
402 x->second = x->first + _editor->current_page_frames() * _x_scale;
404 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
405 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
409 /** Get an expression of the position of a point with respect to the view rectangle */
410 EditorSummary::Position
411 EditorSummary::get_position (double x, double y) const
413 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
416 int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
417 x_edge_size = min (x_edge_size, 8);
418 x_edge_size = max (x_edge_size, 1);
420 int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
421 y_edge_size = min (y_edge_size, 8);
422 y_edge_size = max (y_edge_size, 1);
424 bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
425 bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
426 bool const near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
427 bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_edge_size);
428 bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
429 bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
431 if (near_left && near_top) {
433 } else if (near_left && near_bottom) {
435 } else if (near_right && near_top) {
437 } else if (near_right && near_bottom) {
439 } else if (near_left && within_y) {
441 } else if (near_right && within_y) {
443 } else if (near_top && within_x) {
445 } else if (near_bottom && within_x) {
447 } else if (within_x && within_y) {
449 } else if (within_x) {
450 return BELOW_OR_ABOVE;
451 } else if (within_y) {
452 return TO_LEFT_OR_RIGHT;
454 return OTHERWISE_OUTSIDE;
459 EditorSummary::set_cursor (Position p)
463 get_window()->set_cursor (*_editor->_cursors->resize_left);
466 get_window()->set_cursor (*_editor->_cursors->resize_top_left);
469 get_window()->set_cursor (*_editor->_cursors->resize_top);
472 get_window()->set_cursor (*_editor->_cursors->resize_top_right);
475 get_window()->set_cursor (*_editor->_cursors->resize_right);
478 get_window()->set_cursor (*_editor->_cursors->resize_bottom_right);
481 get_window()->set_cursor (*_editor->_cursors->resize_bottom);
484 get_window()->set_cursor (*_editor->_cursors->resize_bottom_left);
487 get_window()->set_cursor (*_editor->_cursors->move);
489 case TO_LEFT_OR_RIGHT:
490 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
493 get_window()->set_cursor (*_editor->_cursors->expand_up_down);
496 get_window()->set_cursor ();
502 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
504 pair<double, double> xr = _start_editor_x;
505 pair<double, double> yr = _start_editor_y;
506 double x = _start_editor_x.first;
507 double y = _start_editor_y.first;
509 if (_move_dragging) {
513 /* don't alter x if we clicked outside and above or below the viewbox */
514 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
515 x += ev->x - _start_mouse_x;
518 /* don't alter y if we clicked outside and to the left or right of the viewbox */
519 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
520 y += ev->y - _start_mouse_y;
532 set_cursor (_start_position);
534 } else if (_zoom_dragging) {
536 double const dx = ev->x - _start_mouse_x;
537 double const dy = ev->y - _start_mouse_y;
539 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
541 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
545 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
547 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
551 set_overlays_dirty ();
552 set_cursor (_zoom_position);
557 set_cursor (get_position (ev->x, ev->y));
565 EditorSummary::on_button_release_event (GdkEventButton*)
567 bool const was_suspended = suspending_editor_updates ();
569 _move_dragging = false;
570 _zoom_dragging = false;
571 _editor->_dragging_playhead = false;
572 _editor->set_follow_playhead (_old_follow_playhead, false);
574 if (was_suspended && _pending_editor_changed) {
575 set_editor (_pending_editor_x, _pending_editor_y);
582 EditorSummary::on_scroll_event (GdkEventScroll* ev)
586 pair<double, double> xr;
587 pair<double, double> yr;
588 get_editor (&xr, &yr);
594 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
596 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
600 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
602 /* secondary-wheel == left-right scrolling */
604 if (ev->direction == GDK_SCROLL_UP) {
606 } else if (ev->direction == GDK_SCROLL_DOWN) {
610 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
612 /* primary-wheel == zoom */
614 if (ev->direction == GDK_SCROLL_UP) {
615 _editor->temporal_zoom_step (false);
617 _editor->temporal_zoom_step (true);
622 if (ev->direction == GDK_SCROLL_DOWN) {
624 } else if (ev->direction == GDK_SCROLL_UP) {
626 } else if (ev->direction == GDK_SCROLL_LEFT) {
628 } else if (ev->direction == GDK_SCROLL_RIGHT) {
637 /** Set the editor to display a x range with the left at a given position
638 * and a y range with the top at a given position.
639 * x and y parameters are specified in summary coordinates.
640 * Zoom is not changed in either direction.
643 EditorSummary::set_editor (double const x, double const y)
645 if (_editor->pending_visual_change.idle_handler_id >= 0) {
647 /* As a side-effect, the Editor's visual change idle handler processes
648 pending GTK events. Hence this motion notify handler can be called
649 in the middle of a visual change idle handler, and if this happens,
650 the queue_visual_change calls below modify the variables that the
651 idle handler is working with. This causes problems. Hence this
652 check. It ensures that we won't modify the pending visual change
653 while a visual change idle handler is in progress. It's not perfect,
654 as it also means that we won't change these variables if an idle handler
655 is merely pending but not executing. But c'est la vie.
665 /** Set the editor to display a given x range and a y range with the top at a given position.
666 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
667 * x and y parameters are specified in summary coordinates.
670 EditorSummary::set_editor (pair<double,double> const x, double const y)
672 if (_editor->pending_visual_change.idle_handler_id >= 0) {
673 /* see comment in other set_editor () */
681 /** Set the editor to display given x and y ranges. x zoom and track heights are
682 * adjusted if necessary.
683 * x and y parameters are specified in summary coordinates.
686 EditorSummary::set_editor (pair<double,double> const x, pair<double, double> const y)
688 if (_editor->pending_visual_change.idle_handler_id >= 0) {
689 /* see comment in other set_editor () */
697 /** Set the left of the x range visible in the editor.
698 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
699 * @param x new x left position in summary coordinates.
702 EditorSummary::set_editor_x (double x)
708 if (suspending_editor_updates ()) {
709 double const w = _pending_editor_x.second - _pending_editor_x.first;
710 _pending_editor_x.first = x;
711 _pending_editor_x.second = x + w;
712 _pending_editor_changed = true;
715 _editor->reset_x_origin (x / _x_scale + _start);
719 /** Set the x range visible in the editor.
720 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
721 * @param x new x range in summary coordinates.
724 EditorSummary::set_editor_x (pair<double, double> x)
731 x.second = x.first + 1;
734 if (suspending_editor_updates ()) {
735 _pending_editor_x = x;
736 _pending_editor_changed = true;
739 _editor->reset_x_origin (x.first / _x_scale + _start);
742 ((x.second - x.first) / _x_scale) /
743 _editor->frame_to_unit (_editor->current_page_frames())
746 if (nx != _editor->get_current_zoom ()) {
747 _editor->reset_zoom (nx);
752 /** Set the top of the y range visible in the editor.
753 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
754 * @param y new editor top in summary coodinates.
757 EditorSummary::set_editor_y (double const y)
759 double y1 = summary_y_to_editor (y);
760 double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
763 double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
765 if (y2 > full_editor_height) {
766 y1 -= y2 - full_editor_height;
773 if (suspending_editor_updates ()) {
774 double const h = _pending_editor_y.second - _pending_editor_y.first;
775 _pending_editor_y.first = y;
776 _pending_editor_y.second = y + h;
777 _pending_editor_changed = true;
780 _editor->reset_y_origin (y1);
784 /** Set the y range visible in the editor. This is achieved by scaling track heights,
786 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
787 * @param y new editor range in summary coodinates.
790 EditorSummary::set_editor_y (pair<double, double> const y)
792 if (suspending_editor_updates ()) {
793 _pending_editor_y = y;
794 _pending_editor_changed = true;
799 /* Compute current height of tracks between y.first and y.second. We add up
800 the total height into `total_height' and the height of complete tracks into
804 /* Copy of target range for use below */
805 pair<double, double> yc = y;
806 /* Total height of all tracks */
807 double total_height = 0;
808 /* Height of any parts of tracks that aren't fully in the desired range */
809 double partial_height = 0;
810 /* Height of any tracks that are fully in the desired range */
811 double scale_height = 0;
813 _editor->_routes->suspend_redisplay ();
815 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
817 if ((*i)->hidden()) {
821 double const h = (*i)->effective_height ();
824 if (yc.first > 0 && yc.first < _track_height) {
825 partial_height += (_track_height - yc.first) * h / _track_height;
826 } else if (yc.first <= 0 && yc.second >= _track_height) {
828 } else if (yc.second > 0 && yc.second < _track_height) {
829 partial_height += yc.second * h / _track_height;
833 yc.first -= _track_height;
834 yc.second -= _track_height;
837 /* Height that we will use for scaling; use the whole editor height unless there are not
838 enough tracks to fill it.
840 double const ch = min (total_height, _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
842 /* hence required scale factor of the complete tracks to fit the required y range;
843 the amount of space they should take up divided by the amount they currently take up.
845 double const scale = (ch - partial_height) / scale_height;
849 /* Scale complete tracks within the range to make it fit */
851 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
853 if ((*i)->hidden()) {
857 if (yc.first <= 0 && yc.second >= _track_height) {
858 (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
861 yc.first -= _track_height;
862 yc.second -= _track_height;
865 _editor->_routes->resume_redisplay ();
867 set_editor_y (y.first);
871 EditorSummary::playhead_position_changed (framepos_t p)
873 int const o = int (_last_playhead);
874 int const n = int (playhead_frame_to_position (p));
875 if (_session && o != n) {
878 set_overlays_dirty (a - 1, 0, b + 1, get_height ());
883 EditorSummary::summary_y_to_editor (double y) const
886 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
888 if ((*i)->hidden()) {
892 double const h = (*i)->effective_height ();
893 if (y < _track_height) {
895 return ey + y * h / _track_height;
906 EditorSummary::editor_y_to_summary (double y) const
909 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
911 if ((*i)->hidden()) {
915 double const h = (*i)->effective_height ();
918 return sy + y * _track_height / h;
929 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
931 for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
932 /* Connect to gui_changed() on the route so that we know when their colour has changed */
933 (*i)->route()->gui_changed.connect (*this, invalidator (*this), boost::bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
934 boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> ((*i)->route ());
936 tr->PlaylistChanged.connect (*this, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context ());
944 EditorSummary::route_gui_changed (string c)
952 EditorSummary::playhead_frame_to_position (framepos_t t) const
954 return (t - _start) * _x_scale;