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), ui_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 (&EditorSummary::set_dirty, this), gui_context());
79 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::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 = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
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)
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 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
188 cairo_set_line_width (cr, _track_height - 2);
189 cairo_move_to (cr, 0, y + _track_height / 2);
190 cairo_line_to (cr, get_width(), y + _track_height / 2);
193 StreamView* s = (*i)->view ();
196 cairo_set_line_width (cr, _track_height * 0.6);
198 s->foreach_regionview (sigc::bind (
199 sigc::mem_fun (*this, &EditorSummary::render_region),
201 y + _track_height / 2
208 /* start and end markers */
210 cairo_set_line_width (cr, 1);
211 cairo_set_source_rgb (cr, 1, 1, 0);
213 double const p = (_session->current_start_frame() - _start) * _x_scale;
214 cairo_move_to (cr, p, 0);
215 cairo_line_to (cr, p, get_height());
218 double const q = (_session->current_end_frame() - _start) * _x_scale;
219 cairo_move_to (cr, q, 0);
220 cairo_line_to (cr, q, get_height());
224 /** Render a region for the summary.
225 * @param r Region view.
226 * @param cr Cairo context.
227 * @param y y coordinate to render at.
230 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
232 uint32_t const c = r->get_fill_color ();
233 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
235 if (r->region()->position() > _start) {
236 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
238 cairo_move_to (cr, 0, y);
241 if ((r->region()->position() + r->region()->length()) > _start) {
242 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
244 cairo_line_to (cr, 0, y);
250 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
252 EditorSummary::set_overlays_dirty ()
254 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
258 /** Handle a size request.
259 * @param req GTK requisition
262 EditorSummary::on_size_request (Gtk::Requisition *req)
264 /* Use a dummy, small width and the actual height that we want */
271 EditorSummary::centre_on_click (GdkEventButton* ev)
273 pair<double, double> xr;
274 pair<double, double> yr;
275 get_editor (&xr, &yr);
277 double const w = xr.second - xr.first;
278 double ex = ev->x - w / 2;
281 } else if ((ex + w) > get_width()) {
282 ex = get_width() - w;
285 double const h = yr.second - yr.first;
286 double ey = ev->y - h / 2;
289 } else if ((ey + h) > get_height()) {
290 ey = get_height() - h;
296 /** Handle a button press.
297 * @param ev GTK event.
300 EditorSummary::on_button_press_event (GdkEventButton* ev)
302 if (ev->button == 1) {
304 pair<double, double> xr;
305 pair<double, double> yr;
306 get_editor (&xr, &yr);
308 _start_editor_x = xr;
309 _start_editor_y = yr;
310 _start_mouse_x = ev->x;
311 _start_mouse_y = ev->y;
312 _start_position = get_position (ev->x, ev->y);
314 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
315 _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
318 /* start a zoom drag */
320 _zoom_position = get_position (ev->x, ev->y);
321 _zoom_dragging = true;
322 _editor->_dragging_playhead = true;
323 _old_follow_playhead = _editor->follow_playhead ();
324 _editor->set_follow_playhead (false);
326 if (suspending_editor_updates ()) {
327 get_editor (&_pending_editor_x, &_pending_editor_y);
328 _pending_editor_changed = false;
331 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
333 /* secondary-modifier-click: locate playhead */
335 _session->request_locate (ev->x / _x_scale + _start);
338 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
340 centre_on_click (ev);
344 /* start a move drag */
346 /* get the editor's state in case we are suspending updates */
347 get_editor (&_pending_editor_x, &_pending_editor_y);
348 _pending_editor_changed = false;
350 _move_dragging = true;
352 _editor->_dragging_playhead = true;
353 _old_follow_playhead = _editor->follow_playhead ();
354 _editor->set_follow_playhead (false);
361 /** @return true if we are currently suspending updates to the editor's viewport,
362 * which we do if configured to do so, and if in a drag of some kind.
365 EditorSummary::suspending_editor_updates () const
367 return (!Config->get_update_editor_during_summary_drag () && (_zoom_dragging || _move_dragging));
370 /** Fill in x and y with the editor's current viewable area in summary coordinates */
372 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
377 if (suspending_editor_updates ()) {
379 /* We are dragging, and configured not to update the editor window during drags,
380 so just return where the editor will be when the drag finishes.
383 *x = _pending_editor_x;
384 *y = _pending_editor_y;
388 /* Otherwise query the editor for its actual position */
390 x->first = (_editor->leftmost_position () - _start) * _x_scale;
391 x->second = x->first + _editor->current_page_frames() * _x_scale;
393 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
394 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
398 /** Get an expression of the position of a point with respect to the view rectangle */
399 EditorSummary::Position
400 EditorSummary::get_position (double x, double y) const
402 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
405 int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
406 x_edge_size = min (x_edge_size, 8);
407 x_edge_size = max (x_edge_size, 1);
409 int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
410 y_edge_size = min (y_edge_size, 8);
411 y_edge_size = max (y_edge_size, 1);
413 bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
414 bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
415 bool const near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
416 bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_edge_size);
417 bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
418 bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
420 if (near_left && near_top) {
422 } else if (near_left && near_bottom) {
424 } else if (near_right && near_top) {
426 } else if (near_right && near_bottom) {
428 } else if (near_left && within_y) {
430 } else if (near_right && within_y) {
432 } else if (near_top && within_x) {
434 } else if (near_bottom && within_x) {
436 } else if (within_x && within_y) {
438 } else if (within_x) {
439 return BELOW_OR_ABOVE;
440 } else if (within_y) {
441 return TO_LEFT_OR_RIGHT;
443 return OTHERWISE_OUTSIDE;
448 EditorSummary::set_cursor (Position p)
452 get_window()->set_cursor (*_editor->_cursors->resize_left);
455 get_window()->set_cursor (*_editor->_cursors->resize_top_left);
458 get_window()->set_cursor (*_editor->_cursors->resize_top);
461 get_window()->set_cursor (*_editor->_cursors->resize_top_right);
464 get_window()->set_cursor (*_editor->_cursors->resize_right);
467 get_window()->set_cursor (*_editor->_cursors->resize_bottom_right);
470 get_window()->set_cursor (*_editor->_cursors->resize_bottom);
473 get_window()->set_cursor (*_editor->_cursors->resize_bottom_left);
476 get_window()->set_cursor (*_editor->_cursors->move);
478 case TO_LEFT_OR_RIGHT:
479 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
482 get_window()->set_cursor (*_editor->_cursors->expand_up_down);
485 get_window()->set_cursor ();
491 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
493 pair<double, double> xr = _start_editor_x;
494 pair<double, double> yr = _start_editor_y;
495 double x = _start_editor_x.first;
496 double y = _start_editor_y.first;
498 if (_move_dragging) {
502 /* don't alter x if we clicked outside and above or below the viewbox */
503 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
504 x += ev->x - _start_mouse_x;
507 /* don't alter y if we clicked outside and to the left or right of the viewbox */
508 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
509 y += ev->y - _start_mouse_y;
521 set_cursor (_start_position);
523 } else if (_zoom_dragging) {
525 double const dx = ev->x - _start_mouse_x;
526 double const dy = ev->y - _start_mouse_y;
528 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
530 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
534 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
536 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
540 set_overlays_dirty ();
541 set_cursor (_zoom_position);
546 set_cursor (get_position (ev->x, ev->y));
554 EditorSummary::on_button_release_event (GdkEventButton*)
556 bool const was_suspended = suspending_editor_updates ();
558 _move_dragging = false;
559 _zoom_dragging = false;
560 _editor->_dragging_playhead = false;
561 _editor->set_follow_playhead (_old_follow_playhead, false);
563 if (was_suspended && _pending_editor_changed) {
564 set_editor (_pending_editor_x, _pending_editor_y);
571 EditorSummary::on_scroll_event (GdkEventScroll* ev)
575 pair<double, double> xr;
576 pair<double, double> yr;
577 get_editor (&xr, &yr);
583 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
585 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
589 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
591 /* secondary-wheel == left-right scrolling */
593 if (ev->direction == GDK_SCROLL_UP) {
595 } else if (ev->direction == GDK_SCROLL_DOWN) {
599 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
601 /* primary-wheel == zoom */
603 if (ev->direction == GDK_SCROLL_UP) {
604 _editor->temporal_zoom_step (false);
606 _editor->temporal_zoom_step (true);
611 if (ev->direction == GDK_SCROLL_DOWN) {
613 } else if (ev->direction == GDK_SCROLL_UP) {
615 } else if (ev->direction == GDK_SCROLL_LEFT) {
617 } else if (ev->direction == GDK_SCROLL_RIGHT) {
626 /** Set the editor to display a x range with the left at a given position
627 * and a y range with the top at a given position.
628 * x and y parameters are specified in summary coordinates.
629 * Zoom is not changed in either direction.
632 EditorSummary::set_editor (double const x, double const y)
634 if (_editor->pending_visual_change.idle_handler_id >= 0) {
636 /* As a side-effect, the Editor's visual change idle handler processes
637 pending GTK events. Hence this motion notify handler can be called
638 in the middle of a visual change idle handler, and if this happens,
639 the queue_visual_change calls below modify the variables that the
640 idle handler is working with. This causes problems. Hence this
641 check. It ensures that we won't modify the pending visual change
642 while a visual change idle handler is in progress. It's not perfect,
643 as it also means that we won't change these variables if an idle handler
644 is merely pending but not executing. But c'est la vie.
654 /** Set the editor to display a given x range and a y range with the top at a given position.
655 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
656 * x and y parameters are specified in summary coordinates.
659 EditorSummary::set_editor (pair<double,double> const x, double const y)
661 if (_editor->pending_visual_change.idle_handler_id >= 0) {
662 /* see comment in other set_editor () */
670 /** Set the editor to display given x and y ranges. x zoom and track heights are
671 * adjusted if necessary.
672 * x and y parameters are specified in summary coordinates.
675 EditorSummary::set_editor (pair<double,double> const x, pair<double, double> const y)
677 if (_editor->pending_visual_change.idle_handler_id >= 0) {
678 /* see comment in other set_editor () */
686 /** Set the left of the x range visible in the editor.
687 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
688 * @param x new x left position in summary coordinates.
691 EditorSummary::set_editor_x (double x)
697 if (suspending_editor_updates ()) {
698 double const w = _pending_editor_x.second - _pending_editor_x.first;
699 _pending_editor_x.first = x;
700 _pending_editor_x.second = x + w;
701 _pending_editor_changed = true;
704 _editor->reset_x_origin (x / _x_scale + _start);
708 /** Set the x range visible in the editor.
709 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
710 * @param x new x range in summary coordinates.
713 EditorSummary::set_editor_x (pair<double, double> x)
720 x.second = x.first + 1;
723 if (suspending_editor_updates ()) {
724 _pending_editor_x = x;
725 _pending_editor_changed = true;
728 _editor->reset_x_origin (x.first / _x_scale + _start);
731 ((x.second - x.first) / _x_scale) /
732 _editor->frame_to_unit (_editor->current_page_frames())
735 if (nx != _editor->get_current_zoom ()) {
736 _editor->reset_zoom (nx);
741 /** Set the top of the y range visible in the editor.
742 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
743 * @param y new editor top in summary coodinates.
746 EditorSummary::set_editor_y (double const y)
748 double y1 = summary_y_to_editor (y);
749 double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
752 double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
754 if (y2 > full_editor_height) {
755 y1 -= y2 - full_editor_height;
762 if (suspending_editor_updates ()) {
763 double const h = _pending_editor_y.second - _pending_editor_y.first;
764 _pending_editor_y.first = y;
765 _pending_editor_y.second = y + h;
766 _pending_editor_changed = true;
769 _editor->reset_y_origin (y1);
773 /** Set the y range visible in the editor. This is achieved by scaling track heights,
775 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
776 * @param y new editor range in summary coodinates.
779 EditorSummary::set_editor_y (pair<double, double> const y)
781 if (suspending_editor_updates ()) {
782 _pending_editor_y = y;
783 _pending_editor_changed = true;
788 /* Compute current height of tracks between y.first and y.second. We add up
789 the total height into `total_height' and the height of complete tracks into
793 /* Copy of target range for use below */
794 pair<double, double> yc = y;
795 /* Total height of all tracks */
796 double total_height = 0;
797 /* Height of any parts of tracks that aren't fully in the desired range */
798 double partial_height = 0;
799 /* Height of any tracks that are fully in the desired range */
800 double scale_height = 0;
802 _editor->_routes->suspend_redisplay ();
804 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
806 if ((*i)->hidden()) {
810 double const h = (*i)->effective_height ();
813 if (yc.first > 0 && yc.first < _track_height) {
814 partial_height += (_track_height - yc.first) * h / _track_height;
815 } else if (yc.first <= 0 && yc.second >= _track_height) {
817 } else if (yc.second > 0 && yc.second < _track_height) {
818 partial_height += yc.second * h / _track_height;
822 yc.first -= _track_height;
823 yc.second -= _track_height;
826 /* Height that we will use for scaling; use the whole editor height unless there are not
827 enough tracks to fill it.
829 double const ch = min (total_height, _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
831 /* hence required scale factor of the complete tracks to fit the required y range;
832 the amount of space they should take up divided by the amount they currently take up.
834 double const scale = (ch - partial_height) / scale_height;
838 /* Scale complete tracks within the range to make it fit */
840 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
842 if ((*i)->hidden()) {
846 if (yc.first <= 0 && yc.second >= _track_height) {
847 (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
850 yc.first -= _track_height;
851 yc.second -= _track_height;
854 _editor->_routes->resume_redisplay ();
856 set_editor_y (y.first);
860 EditorSummary::playhead_position_changed (framepos_t p)
862 if (_session && int (p * _x_scale) != int (_last_playhead)) {
863 set_overlays_dirty ();
868 EditorSummary::summary_y_to_editor (double y) const
871 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
873 if ((*i)->hidden()) {
877 double const h = (*i)->effective_height ();
878 if (y < _track_height) {
880 return ey + y * h / _track_height;
891 EditorSummary::editor_y_to_summary (double y) const
894 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
896 if ((*i)->hidden()) {
900 double const h = (*i)->effective_height ();
903 return sy + y * _track_height / h;
914 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
916 /* Connect to gui_changed() on the routes so that we know when their colour has changed */
917 for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
918 (*i)->route()->gui_changed.connect (*this, invalidator (*this), ui_bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
925 EditorSummary::route_gui_changed (string c)