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"
31 using namespace ARDOUR;
32 using Gtkmm2ext::Keyboard;
34 /** Construct an EditorSummary.
35 * @param e Editor to represent.
37 EditorSummary::EditorSummary (Editor* e)
38 : EditorComponent (e),
41 _overhang_fraction (0.1),
45 _move_dragging (false),
47 _view_rectangle_x (0, 0),
48 _view_rectangle_y (0, 0),
49 _zoom_dragging (false)
51 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
52 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
54 add_events (Gdk::POINTER_MOTION_MASK);
57 /** Connect to a session.
61 EditorSummary::set_session (Session* s)
63 EditorComponent::set_session (s);
67 /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
68 * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
69 * emitted when a cut region is added to the `cutlist' playlist.
73 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
74 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
78 /** Handle an expose event.
79 * @param event Event from GTK.
82 EditorSummary::on_expose_event (GdkEventExpose* event)
84 CairoWidget::on_expose_event (event);
90 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
92 /* Render the view rectangle. If there is an editor visual pending, don't update
93 the view rectangle now --- wait until the expose event that we'll get after
94 the visual change. This prevents a flicker.
97 if (_editor->pending_visual_change.idle_handler_id < 0) {
100 _view_rectangle_x = _pending_zoom_x;
101 _view_rectangle_y = _pending_zoom_y;
103 get_editor (&_view_rectangle_x, &_view_rectangle_y);
107 cairo_move_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
108 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.first);
109 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.second);
110 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.second);
111 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
112 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
113 cairo_fill_preserve (cr);
114 cairo_set_line_width (cr, 1);
115 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
120 cairo_set_line_width (cr, 1);
121 /* XXX: colour should be set from configuration file */
122 cairo_set_source_rgba (cr, 1, 0, 0, 1);
124 double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
125 cairo_move_to (cr, p, 0);
126 cairo_line_to (cr, p, _height);
135 /** Render the required regions to a cairo context.
139 EditorSummary::render (cairo_t* cr)
143 cairo_set_source_rgb (cr, 0, 0, 0);
144 cairo_rectangle (cr, 0, 0, _width, _height);
151 /* compute start and end points for the summary */
153 nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
154 double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
155 _start = theoretical_start > 0 ? theoretical_start : 0;
156 _end = _session->current_end_frame() + session_length * _overhang_fraction;
158 /* compute track height */
160 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
161 if (!(*i)->hidden()) {
169 _track_height = (double) _height / N;
172 /* calculate x scale */
173 if (_end != _start) {
174 _x_scale = static_cast<double> (_width) / (_end - _start);
179 /* render tracks and regions */
182 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
184 if ((*i)->hidden()) {
188 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
189 cairo_set_line_width (cr, _track_height - 2);
190 cairo_move_to (cr, 0, y + _track_height / 2);
191 cairo_line_to (cr, _width, y + _track_height / 2);
194 StreamView* s = (*i)->view ();
197 cairo_set_line_width (cr, _track_height * 0.6);
199 s->foreach_regionview (sigc::bind (
200 sigc::mem_fun (*this, &EditorSummary::render_region),
202 y + _track_height / 2
209 /* start and end markers */
211 cairo_set_line_width (cr, 1);
212 cairo_set_source_rgb (cr, 1, 1, 0);
214 double const p = (_session->current_start_frame() - _start) * _x_scale;
215 cairo_move_to (cr, p, 0);
216 cairo_line_to (cr, p, _height);
219 double const q = (_session->current_end_frame() - _start) * _x_scale;
220 cairo_move_to (cr, q, 0);
221 cairo_line_to (cr, q, _height);
225 /** Render a region for the summary.
226 * @param r Region view.
227 * @param cr Cairo context.
228 * @param y y coordinate to render at.
231 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
233 uint32_t const c = r->get_fill_color ();
234 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
236 if (r->region()->position() > _start) {
237 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
239 cairo_move_to (cr, 0, y);
242 if ((r->region()->position() + r->region()->length()) > _start) {
243 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
245 cairo_line_to (cr, 0, y);
251 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
253 EditorSummary::set_overlays_dirty ()
255 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
259 /** Handle a size request.
260 * @param req GTK requisition
263 EditorSummary::on_size_request (Gtk::Requisition *req)
265 /* Use a dummy, small width and the actual height that we want */
272 EditorSummary::centre_on_click (GdkEventButton* ev)
274 pair<double, double> xr;
275 pair<double, double> yr;
276 get_editor (&xr, &yr);
278 double const w = xr.second - xr.first;
280 xr.first = ev->x - w / 2;
281 xr.second = ev->x + w / 2;
286 } else if (xr.second > _width) {
288 xr.first = _width - w;
291 double ey = summary_y_to_editor (ev->y);
292 ey -= (_editor->canvas_height() - _editor->get_canvas_timebars_vsize ()) / 2;
297 set_editor (xr, editor_y_to_summary (ey));
300 /** Handle a button press.
301 * @param ev GTK event.
304 EditorSummary::on_button_press_event (GdkEventButton* ev)
306 if (ev->button == 1) {
308 pair<double, double> xr;
309 pair<double, double> yr;
310 get_editor (&xr, &yr);
312 _start_editor_x = xr;
313 _start_editor_y = yr;
314 _start_mouse_x = ev->x;
315 _start_mouse_y = ev->y;
316 _start_position = get_position (ev->x, ev->y);
318 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
319 _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
322 /* start a zoom drag */
324 _zoom_position = get_position (ev->x, ev->y);
325 _zoom_dragging = true;
326 _editor->_dragging_playhead = true;
327 _pending_zoom_x = xr;
328 _pending_zoom_y = yr;
330 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
332 /* secondary-modifier-click: locate playhead */
334 _session->request_locate (ev->x / _x_scale + _start);
337 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
339 centre_on_click (ev);
343 /* start a move drag */
345 _move_dragging = true;
347 _editor->_dragging_playhead = true;
354 /** Fill in x and y with the editor's current viewable area in summary coordinates */
356 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
361 x->first = (_editor->leftmost_position () - _start) * _x_scale;
362 x->second = x->first + _editor->current_page_frames() * _x_scale;
364 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
365 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
368 /** Get an expression of the position of a point with respect to the view rectangle */
369 EditorSummary::Position
370 EditorSummary::get_position (double x, double y) const
372 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
374 int const edge_size = 8;
376 bool const near_left = (std::abs (x - _view_rectangle_x.first) < edge_size);
377 bool const near_right = (std::abs (x - _view_rectangle_x.second) < edge_size);
378 bool const near_top = (std::abs (y - _view_rectangle_y.first) < edge_size);
379 bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < edge_size);
380 bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
381 bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
383 if (near_left && near_top) {
385 } else if (near_left && near_bottom) {
387 } else if (near_right && near_top) {
389 } else if (near_right && near_bottom) {
391 } else if (near_left && within_y) {
393 } else if (near_right && within_y) {
395 } else if (near_top && within_x) {
397 } else if (near_bottom && within_x) {
399 } else if (within_x && within_y) {
401 } else if (within_x) {
402 return BELOW_OR_ABOVE;
403 } else if (within_y) {
404 return TO_LEFT_OR_RIGHT;
406 return OTHERWISE_OUTSIDE;
411 EditorSummary::set_cursor (Position p)
415 get_window()->set_cursor (Gdk::Cursor (Gdk::LEFT_SIDE));
418 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_LEFT_CORNER));
421 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_SIDE));
424 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_RIGHT_CORNER));
427 get_window()->set_cursor (Gdk::Cursor (Gdk::RIGHT_SIDE));
430 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_RIGHT_CORNER));
433 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_SIDE));
436 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_LEFT_CORNER));
439 get_window()->set_cursor (Gdk::Cursor (Gdk::FLEUR));
442 get_window()->set_cursor ();
448 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
450 pair<double, double> xr = _start_editor_x;
451 pair<double, double> yr = _start_editor_y;
452 double y = _start_editor_y.first;
454 if (_move_dragging) {
458 /* don't alter x if we clicked outside and above or below the viewbox */
459 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT) {
460 xr.first += ev->x - _start_mouse_x;
461 xr.second += ev->x - _start_mouse_x;
464 /* don't alter y if we clicked outside and to the left or right of the viewbox */
465 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
466 y += ev->y - _start_mouse_y;
470 xr.second -= xr.first;
481 } else if (_zoom_dragging) {
483 double const dx = ev->x - _start_mouse_x;
484 double const dy = ev->y - _start_mouse_y;
486 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
487 _pending_zoom_x.first = _start_editor_x.first + dx;
488 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
489 _pending_zoom_x.second = _start_editor_x.second + dx;
492 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
493 _pending_zoom_y.first = _start_editor_y.first + dy;
494 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
495 _pending_zoom_y.second = _start_editor_y.second + dy;
498 set_overlays_dirty ();
499 set_cursor (_zoom_position);
503 set_cursor (get_position (ev->x, ev->y));
511 EditorSummary::on_button_release_event (GdkEventButton*)
513 if (_zoom_dragging) {
514 set_editor (_pending_zoom_x, _pending_zoom_y);
517 _move_dragging = false;
518 _zoom_dragging = false;
519 _editor->_dragging_playhead = false;
524 EditorSummary::on_scroll_event (GdkEventScroll* ev)
528 pair<double, double> xr;
529 pair<double, double> yr;
530 get_editor (&xr, &yr);
535 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
537 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
541 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
543 /* primary-wheel == left-right scrolling */
545 if (ev->direction == GDK_SCROLL_UP) {
548 } else if (ev->direction == GDK_SCROLL_DOWN) {
555 if (ev->direction == GDK_SCROLL_DOWN) {
557 } else if (ev->direction == GDK_SCROLL_UP) {
559 } else if (ev->direction == GDK_SCROLL_LEFT) {
562 } else if (ev->direction == GDK_SCROLL_RIGHT) {
572 /** Set the editor to display a given x range and a y range with the top at a given position.
573 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
574 * x and y parameters are specified in summary coordinates.
577 EditorSummary::set_editor (pair<double,double> const & x, double const y)
579 if (_editor->pending_visual_change.idle_handler_id >= 0) {
581 /* As a side-effect, the Editor's visual change idle handler processes
582 pending GTK events. Hence this motion notify handler can be called
583 in the middle of a visual change idle handler, and if this happens,
584 the queue_visual_change calls below modify the variables that the
585 idle handler is working with. This causes problems. Hence this
586 check. It ensures that we won't modify the pending visual change
587 while a visual change idle handler is in progress. It's not perfect,
588 as it also means that we won't change these variables if an idle handler
589 is merely pending but not executing. But c'est la vie.
599 /** Set the editor to display given x and y ranges. x zoom and track heights are
600 * adjusted if necessary.
601 * x and y parameters are specified in summary coordinates.
604 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
606 if (_editor->pending_visual_change.idle_handler_id >= 0) {
607 /* see comment in other set_editor () */
615 /** Set the x range visible in the editor.
616 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
617 * @param x new x range in summary coordinates.
620 EditorSummary::set_editor_x (pair<double, double> const & x)
622 _editor->reset_x_origin (x.first / _x_scale + _start);
625 ((x.second - x.first) / _x_scale) /
626 _editor->frame_to_unit (_editor->current_page_frames())
629 if (nx != _editor->get_current_zoom ()) {
630 _editor->reset_zoom (nx);
634 /** Set the top of the y range visible in the editor.
635 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
636 * @param y new editor top in summary coodinates.
639 EditorSummary::set_editor_y (double const y)
641 double y1 = summary_y_to_editor (y);
642 double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
645 double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
647 if (y2 > full_editor_height) {
648 y1 -= y2 - full_editor_height;
655 _editor->reset_y_origin (y1);
658 /** Set the y range visible in the editor. This is achieved by scaling track heights,
660 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
661 * @param y new editor range in summary coodinates.
664 EditorSummary::set_editor_y (pair<double, double> const & y)
666 /* Compute current height of tracks between y.first and y.second. We add up
667 the total height into `total_height' and the height of complete tracks into
670 pair<double, double> yc = y;
671 double total_height = 0;
672 double scale_height = 0;
673 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
675 if ((*i)->hidden()) {
679 double const h = (*i)->effective_height ();
681 if (yc.first >= 0 && yc.first < _track_height) {
682 total_height += (_track_height - yc.first) * h / _track_height;
683 } else if (yc.first < 0 && yc.second > _track_height) {
686 } else if (yc.second >= 0 && yc.second < _track_height) {
687 total_height += yc.second * h / _track_height;
691 yc.first -= _track_height;
692 yc.second -= _track_height;
695 /* hence required scale factor of the complete tracks to fit the required y range */
696 double const scale = ((_editor->canvas_height() - _editor->get_canvas_timebars_vsize()) - (total_height - scale_height)) / scale_height;
700 /* Scale complete tracks within the range to make it fit */
702 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
704 if ((*i)->hidden()) {
708 if (yc.first < 0 && yc.second > _track_height) {
709 (*i)->set_height ((*i)->effective_height() * scale);
712 yc.first -= _track_height;
713 yc.second -= _track_height;
716 set_editor_y (y.first);
720 EditorSummary::playhead_position_changed (nframes64_t p)
722 if (_session && int (p * _x_scale) != int (_last_playhead)) {
723 set_overlays_dirty ();
728 EditorSummary::summary_y_to_editor (double y) const
731 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
733 if ((*i)->hidden()) {
737 double const h = (*i)->effective_height ();
738 if (y < _track_height) {
740 return ey + y * h / _track_height;
751 EditorSummary::editor_y_to_summary (double y) const
754 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
756 if ((*i)->hidden()) {
760 double const h = (*i)->effective_height ();
763 return sy + y * _track_height / h;