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)
55 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
56 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
58 add_events (Gdk::POINTER_MOTION_MASK);
61 /** Connect to a session.
65 EditorSummary::set_session (Session* s)
67 SessionHandlePtr::set_session (s);
71 /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
72 * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
73 * emitted when a cut region is added to the `cutlist' playlist.
77 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
78 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
82 /** Handle an expose event.
83 * @param event Event from GTK.
86 EditorSummary::on_expose_event (GdkEventExpose* event)
88 CairoWidget::on_expose_event (event);
94 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
96 /* Render the view rectangle. If there is an editor visual pending, don't update
97 the view rectangle now --- wait until the expose event that we'll get after
98 the visual change. This prevents a flicker.
101 if (_editor->pending_visual_change.idle_handler_id < 0) {
102 get_editor (&_view_rectangle_x, &_view_rectangle_y);
105 cairo_move_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
106 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.first);
107 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.second);
108 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.second);
109 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
110 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
111 cairo_fill_preserve (cr);
112 cairo_set_line_width (cr, 1);
113 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
118 cairo_set_line_width (cr, 1);
119 /* XXX: colour should be set from configuration file */
120 cairo_set_source_rgba (cr, 1, 0, 0, 1);
122 double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
123 cairo_move_to (cr, p, 0);
124 cairo_line_to (cr, p, _height);
133 /** Render the required regions to a cairo context.
137 EditorSummary::render (cairo_t* cr)
141 cairo_set_source_rgb (cr, 0, 0, 0);
142 cairo_rectangle (cr, 0, 0, _width, _height);
149 /* compute start and end points for the summary */
151 framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
152 double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
153 _start = theoretical_start > 0 ? theoretical_start : 0;
154 _end = _session->current_end_frame() + session_length * _overhang_fraction;
156 /* compute track height */
158 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
159 if (!(*i)->hidden()) {
167 _track_height = (double) _height / N;
170 /* calculate x scale */
171 if (_end != _start) {
172 _x_scale = static_cast<double> (_width) / (_end - _start);
177 /* render tracks and regions */
180 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
182 if ((*i)->hidden()) {
186 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
187 cairo_set_line_width (cr, _track_height - 2);
188 cairo_move_to (cr, 0, y + _track_height / 2);
189 cairo_line_to (cr, _width, y + _track_height / 2);
192 StreamView* s = (*i)->view ();
195 cairo_set_line_width (cr, _track_height * 0.6);
197 s->foreach_regionview (sigc::bind (
198 sigc::mem_fun (*this, &EditorSummary::render_region),
200 y + _track_height / 2
207 /* start and end markers */
209 cairo_set_line_width (cr, 1);
210 cairo_set_source_rgb (cr, 1, 1, 0);
212 double const p = (_session->current_start_frame() - _start) * _x_scale;
213 cairo_move_to (cr, p, 0);
214 cairo_line_to (cr, p, _height);
217 double const q = (_session->current_end_frame() - _start) * _x_scale;
218 cairo_move_to (cr, q, 0);
219 cairo_line_to (cr, q, _height);
223 /** Render a region for the summary.
224 * @param r Region view.
225 * @param cr Cairo context.
226 * @param y y coordinate to render at.
229 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
231 uint32_t const c = r->get_fill_color ();
232 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
234 if (r->region()->position() > _start) {
235 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
237 cairo_move_to (cr, 0, y);
240 if ((r->region()->position() + r->region()->length()) > _start) {
241 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
243 cairo_line_to (cr, 0, y);
249 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
251 EditorSummary::set_overlays_dirty ()
253 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
257 /** Handle a size request.
258 * @param req GTK requisition
261 EditorSummary::on_size_request (Gtk::Requisition *req)
263 /* Use a dummy, small width and the actual height that we want */
270 EditorSummary::centre_on_click (GdkEventButton* ev)
272 pair<double, double> xr;
273 pair<double, double> yr;
274 get_editor (&xr, &yr);
276 double const w = xr.second - xr.first;
278 xr.first = ev->x - w / 2;
279 xr.second = ev->x + w / 2;
284 } else if (xr.second > _width) {
286 xr.first = _width - w;
289 double ey = summary_y_to_editor (ev->y);
290 ey -= (_editor->canvas_height() - _editor->get_canvas_timebars_vsize ()) / 2;
295 set_editor (xr, editor_y_to_summary (ey));
298 /** Handle a button press.
299 * @param ev GTK event.
302 EditorSummary::on_button_press_event (GdkEventButton* ev)
304 if (ev->button == 1) {
306 pair<double, double> xr;
307 pair<double, double> yr;
308 get_editor (&xr, &yr);
310 _start_editor_x = xr;
311 _start_editor_y = yr;
312 _start_mouse_x = ev->x;
313 _start_mouse_y = ev->y;
314 _start_position = get_position (ev->x, ev->y);
316 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
317 _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
320 /* start a zoom drag */
322 _zoom_position = get_position (ev->x, ev->y);
323 _zoom_dragging = true;
324 _editor->_dragging_playhead = true;
326 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
328 /* secondary-modifier-click: locate playhead */
330 _session->request_locate (ev->x / _x_scale + _start);
333 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
335 centre_on_click (ev);
339 /* start a move drag */
341 _move_dragging = true;
343 _editor->_dragging_playhead = true;
350 /** Fill in x and y with the editor's current viewable area in summary coordinates */
352 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
357 x->first = (_editor->leftmost_position () - _start) * _x_scale;
358 x->second = x->first + _editor->current_page_frames() * _x_scale;
360 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
361 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
364 /** Get an expression of the position of a point with respect to the view rectangle */
365 EditorSummary::Position
366 EditorSummary::get_position (double x, double y) const
368 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
371 int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
372 x_edge_size = min (x_edge_size, 8);
373 x_edge_size = max (x_edge_size, 1);
375 int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
376 y_edge_size = min (y_edge_size, 8);
377 y_edge_size = max (y_edge_size, 1);
379 bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
380 bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
381 bool const near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
382 bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_edge_size);
383 bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
384 bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
386 if (near_left && near_top) {
388 } else if (near_left && near_bottom) {
390 } else if (near_right && near_top) {
392 } else if (near_right && near_bottom) {
394 } else if (near_left && within_y) {
396 } else if (near_right && within_y) {
398 } else if (near_top && within_x) {
400 } else if (near_bottom && within_x) {
402 } else if (within_x && within_y) {
404 } else if (within_x) {
405 return BELOW_OR_ABOVE;
406 } else if (within_y) {
407 return TO_LEFT_OR_RIGHT;
409 return OTHERWISE_OUTSIDE;
414 EditorSummary::set_cursor (Position p)
418 get_window()->set_cursor (*_editor->_cursors->resize_left);
421 get_window()->set_cursor (*_editor->_cursors->resize_top_left);
424 get_window()->set_cursor (*_editor->_cursors->resize_top);
427 get_window()->set_cursor (*_editor->_cursors->resize_top_right);
430 get_window()->set_cursor (*_editor->_cursors->resize_right);
433 get_window()->set_cursor (*_editor->_cursors->resize_bottom_right);
436 get_window()->set_cursor (*_editor->_cursors->resize_bottom);
439 get_window()->set_cursor (*_editor->_cursors->resize_bottom_left);
442 get_window()->set_cursor (*_editor->_cursors->move);
444 case TO_LEFT_OR_RIGHT:
445 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
448 get_window()->set_cursor (*_editor->_cursors->expand_up_down);
451 get_window()->set_cursor ();
457 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
459 pair<double, double> xr = _start_editor_x;
460 pair<double, double> yr = _start_editor_y;
461 double y = _start_editor_y.first;
463 if (_move_dragging) {
467 /* don't alter x if we clicked outside and above or below the viewbox */
468 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
469 xr.first += ev->x - _start_mouse_x;
470 xr.second += ev->x - _start_mouse_x;
473 /* don't alter y if we clicked outside and to the left or right of the viewbox */
474 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
475 y += ev->y - _start_mouse_y;
479 xr.second -= xr.first;
488 set_cursor (_start_position);
490 } else if (_zoom_dragging) {
492 double const dx = ev->x - _start_mouse_x;
493 double const dy = ev->y - _start_mouse_y;
495 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
497 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
501 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
503 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
507 set_overlays_dirty ();
508 set_cursor (_zoom_position);
513 set_cursor (get_position (ev->x, ev->y));
521 EditorSummary::on_button_release_event (GdkEventButton*)
523 _move_dragging = false;
524 _zoom_dragging = false;
525 _editor->_dragging_playhead = false;
530 EditorSummary::on_scroll_event (GdkEventScroll* ev)
534 pair<double, double> xr;
535 pair<double, double> yr;
536 get_editor (&xr, &yr);
541 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
543 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
547 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
549 /* primary-wheel == left-right scrolling */
551 if (ev->direction == GDK_SCROLL_UP) {
554 } else if (ev->direction == GDK_SCROLL_DOWN) {
561 if (ev->direction == GDK_SCROLL_DOWN) {
563 } else if (ev->direction == GDK_SCROLL_UP) {
565 } else if (ev->direction == GDK_SCROLL_LEFT) {
568 } else if (ev->direction == GDK_SCROLL_RIGHT) {
578 /** Set the editor to display a given x range and a y range with the top at a given position.
579 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
580 * x and y parameters are specified in summary coordinates.
583 EditorSummary::set_editor (pair<double,double> const & x, double const y)
585 if (_editor->pending_visual_change.idle_handler_id >= 0) {
587 /* As a side-effect, the Editor's visual change idle handler processes
588 pending GTK events. Hence this motion notify handler can be called
589 in the middle of a visual change idle handler, and if this happens,
590 the queue_visual_change calls below modify the variables that the
591 idle handler is working with. This causes problems. Hence this
592 check. It ensures that we won't modify the pending visual change
593 while a visual change idle handler is in progress. It's not perfect,
594 as it also means that we won't change these variables if an idle handler
595 is merely pending but not executing. But c'est la vie.
605 /** Set the editor to display given x and y ranges. x zoom and track heights are
606 * adjusted if necessary.
607 * x and y parameters are specified in summary coordinates.
610 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
612 if (_editor->pending_visual_change.idle_handler_id >= 0) {
613 /* see comment in other set_editor () */
621 /** Set the x range visible in the editor.
622 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
623 * @param x new x range in summary coordinates.
626 EditorSummary::set_editor_x (pair<double, double> const & x)
628 _editor->reset_x_origin (x.first / _x_scale + _start);
631 ((x.second - x.first) / _x_scale) /
632 _editor->frame_to_unit (_editor->current_page_frames())
635 if (nx != _editor->get_current_zoom ()) {
636 _editor->reset_zoom (nx);
640 /** Set the top of the y range visible in the editor.
641 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
642 * @param y new editor top in summary coodinates.
645 EditorSummary::set_editor_y (double const y)
647 double y1 = summary_y_to_editor (y);
648 double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
651 double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
653 if (y2 > full_editor_height) {
654 y1 -= y2 - full_editor_height;
661 _editor->reset_y_origin (y1);
664 /** Set the y range visible in the editor. This is achieved by scaling track heights,
666 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
667 * @param y new editor range in summary coodinates.
670 EditorSummary::set_editor_y (pair<double, double> const & y)
672 /* Compute current height of tracks between y.first and y.second. We add up
673 the total height into `total_height' and the height of complete tracks into
677 /* Copy of target range for use below */
678 pair<double, double> yc = y;
679 /* Total height of all tracks */
680 double total_height = 0;
681 /* Height of any parts of tracks that aren't fully in the desired range */
682 double partial_height = 0;
683 /* Height of any tracks that are fully in the desired range */
684 double scale_height = 0;
686 _editor->_routes->suspend_redisplay ();
688 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
690 if ((*i)->hidden()) {
694 double const h = (*i)->effective_height ();
697 if (yc.first > 0 && yc.first < _track_height) {
698 partial_height += (_track_height - yc.first) * h / _track_height;
699 } else if (yc.first <= 0 && yc.second >= _track_height) {
701 } else if (yc.second > 0 && yc.second < _track_height) {
702 partial_height += yc.second * h / _track_height;
706 yc.first -= _track_height;
707 yc.second -= _track_height;
710 /* Height that we will use for scaling; use the whole editor height unless there are not
711 enough tracks to fill it.
713 double const ch = min (total_height, _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
715 /* hence required scale factor of the complete tracks to fit the required y range;
716 the amount of space they should take up divided by the amount they currently take up.
718 double const scale = (ch - partial_height) / scale_height;
722 /* Scale complete tracks within the range to make it fit */
724 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
726 if ((*i)->hidden()) {
730 if (yc.first <= 0 && yc.second >= _track_height) {
731 (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
734 yc.first -= _track_height;
735 yc.second -= _track_height;
738 _editor->_routes->resume_redisplay ();
740 set_editor_y (y.first);
744 EditorSummary::playhead_position_changed (framepos_t p)
746 if (_session && int (p * _x_scale) != int (_last_playhead)) {
747 set_overlays_dirty ();
752 EditorSummary::summary_y_to_editor (double y) const
755 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
757 if ((*i)->hidden()) {
761 double const h = (*i)->effective_height ();
762 if (y < _track_height) {
764 return ey + y * h / _track_height;
775 EditorSummary::editor_y_to_summary (double y) const
778 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
780 if ((*i)->hidden()) {
784 double const h = (*i)->effective_height ();
787 return sy + y * _track_height / h;
798 EditorSummary::routes_added (list<RouteTimeAxisView*> const & r)
800 /* Connect to gui_changed() on the routes so that we know when their colour has changed */
801 for (list<RouteTimeAxisView*>::const_iterator i = r.begin(); i != r.end(); ++i) {
802 (*i)->route()->gui_changed.connect (*this, invalidator (*this), ui_bind (&EditorSummary::route_gui_changed, this, _1), gui_context ());
809 EditorSummary::route_gui_changed (string c)