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"
32 using namespace ARDOUR;
33 using Gtkmm2ext::Keyboard;
35 /** Construct an EditorSummary.
36 * @param e Editor to represent.
38 EditorSummary::EditorSummary (Editor* e)
39 : EditorComponent (e),
42 _overhang_fraction (0.1),
46 _move_dragging (false),
48 _view_rectangle_x (0, 0),
49 _view_rectangle_y (0, 0),
50 _zoom_dragging (false)
52 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
53 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
55 add_events (Gdk::POINTER_MOTION_MASK);
58 /** Connect to a session.
62 EditorSummary::set_session (Session* s)
64 SessionHandlePtr::set_session (s);
68 /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
69 * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
70 * emitted when a cut region is added to the `cutlist' playlist.
74 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
75 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
79 /** Handle an expose event.
80 * @param event Event from GTK.
83 EditorSummary::on_expose_event (GdkEventExpose* event)
85 CairoWidget::on_expose_event (event);
91 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
93 /* Render the view rectangle. If there is an editor visual pending, don't update
94 the view rectangle now --- wait until the expose event that we'll get after
95 the visual change. This prevents a flicker.
98 if (_editor->pending_visual_change.idle_handler_id < 0) {
99 get_editor (&_view_rectangle_x, &_view_rectangle_y);
102 cairo_move_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
103 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.first);
104 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.second);
105 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.second);
106 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
107 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
108 cairo_fill_preserve (cr);
109 cairo_set_line_width (cr, 1);
110 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
115 cairo_set_line_width (cr, 1);
116 /* XXX: colour should be set from configuration file */
117 cairo_set_source_rgba (cr, 1, 0, 0, 1);
119 double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
120 cairo_move_to (cr, p, 0);
121 cairo_line_to (cr, p, _height);
130 /** Render the required regions to a cairo context.
134 EditorSummary::render (cairo_t* cr)
138 cairo_set_source_rgb (cr, 0, 0, 0);
139 cairo_rectangle (cr, 0, 0, _width, _height);
146 /* compute start and end points for the summary */
148 nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
149 double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
150 _start = theoretical_start > 0 ? theoretical_start : 0;
151 _end = _session->current_end_frame() + session_length * _overhang_fraction;
153 /* compute track height */
155 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
156 if (!(*i)->hidden()) {
164 _track_height = (double) _height / N;
167 /* calculate x scale */
168 if (_end != _start) {
169 _x_scale = static_cast<double> (_width) / (_end - _start);
174 /* render tracks and regions */
177 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
179 if ((*i)->hidden()) {
183 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
184 cairo_set_line_width (cr, _track_height - 2);
185 cairo_move_to (cr, 0, y + _track_height / 2);
186 cairo_line_to (cr, _width, y + _track_height / 2);
189 StreamView* s = (*i)->view ();
192 cairo_set_line_width (cr, _track_height * 0.6);
194 s->foreach_regionview (sigc::bind (
195 sigc::mem_fun (*this, &EditorSummary::render_region),
197 y + _track_height / 2
204 /* start and end markers */
206 cairo_set_line_width (cr, 1);
207 cairo_set_source_rgb (cr, 1, 1, 0);
209 double const p = (_session->current_start_frame() - _start) * _x_scale;
210 cairo_move_to (cr, p, 0);
211 cairo_line_to (cr, p, _height);
214 double const q = (_session->current_end_frame() - _start) * _x_scale;
215 cairo_move_to (cr, q, 0);
216 cairo_line_to (cr, q, _height);
220 /** Render a region for the summary.
221 * @param r Region view.
222 * @param cr Cairo context.
223 * @param y y coordinate to render at.
226 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
228 uint32_t const c = r->get_fill_color ();
229 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
231 if (r->region()->position() > _start) {
232 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
234 cairo_move_to (cr, 0, y);
237 if ((r->region()->position() + r->region()->length()) > _start) {
238 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
240 cairo_line_to (cr, 0, y);
246 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
248 EditorSummary::set_overlays_dirty ()
250 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
254 /** Handle a size request.
255 * @param req GTK requisition
258 EditorSummary::on_size_request (Gtk::Requisition *req)
260 /* Use a dummy, small width and the actual height that we want */
267 EditorSummary::centre_on_click (GdkEventButton* ev)
269 pair<double, double> xr;
270 pair<double, double> yr;
271 get_editor (&xr, &yr);
273 double const w = xr.second - xr.first;
275 xr.first = ev->x - w / 2;
276 xr.second = ev->x + w / 2;
281 } else if (xr.second > _width) {
283 xr.first = _width - w;
286 double ey = summary_y_to_editor (ev->y);
287 ey -= (_editor->canvas_height() - _editor->get_canvas_timebars_vsize ()) / 2;
292 set_editor (xr, editor_y_to_summary (ey));
295 /** Handle a button press.
296 * @param ev GTK event.
299 EditorSummary::on_button_press_event (GdkEventButton* ev)
301 if (ev->button == 1) {
303 pair<double, double> xr;
304 pair<double, double> yr;
305 get_editor (&xr, &yr);
307 _start_editor_x = xr;
308 _start_editor_y = yr;
309 _start_mouse_x = ev->x;
310 _start_mouse_y = ev->y;
311 _start_position = get_position (ev->x, ev->y);
313 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
314 _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
317 /* start a zoom drag */
319 _zoom_position = get_position (ev->x, ev->y);
320 _zoom_dragging = true;
321 _editor->_dragging_playhead = true;
323 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
325 /* secondary-modifier-click: locate playhead */
327 _session->request_locate (ev->x / _x_scale + _start);
330 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
332 centre_on_click (ev);
336 /* start a move drag */
338 _move_dragging = true;
340 _editor->_dragging_playhead = true;
347 /** Fill in x and y with the editor's current viewable area in summary coordinates */
349 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
354 x->first = (_editor->leftmost_position () - _start) * _x_scale;
355 x->second = x->first + _editor->current_page_frames() * _x_scale;
357 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
358 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
361 /** Get an expression of the position of a point with respect to the view rectangle */
362 EditorSummary::Position
363 EditorSummary::get_position (double x, double y) const
365 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
368 int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
369 x_edge_size = min (x_edge_size, 8);
370 x_edge_size = max (x_edge_size, 1);
372 int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
373 y_edge_size = min (y_edge_size, 8);
374 y_edge_size = max (y_edge_size, 1);
376 bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
377 bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
378 bool const near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
379 bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_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 (*_editor->resize_left_cursor);
418 get_window()->set_cursor (*_editor->resize_top_left_cursor);
421 get_window()->set_cursor (*_editor->resize_top_cursor);
424 get_window()->set_cursor (*_editor->resize_top_right_cursor);
427 get_window()->set_cursor (*_editor->resize_right_cursor);
430 get_window()->set_cursor (*_editor->resize_bottom_right_cursor);
433 get_window()->set_cursor (*_editor->resize_bottom_cursor);
436 get_window()->set_cursor (*_editor->resize_bottom_left_cursor);
439 get_window()->set_cursor (*_editor->move_cursor);
441 case TO_LEFT_OR_RIGHT:
442 get_window()->set_cursor (*_editor->expand_left_right_cursor);
445 get_window()->set_cursor (*_editor->expand_up_down_cursor);
448 get_window()->set_cursor ();
454 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
456 pair<double, double> xr = _start_editor_x;
457 pair<double, double> yr = _start_editor_y;
458 double y = _start_editor_y.first;
460 if (_move_dragging) {
464 /* don't alter x if we clicked outside and above or below the viewbox */
465 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
466 xr.first += ev->x - _start_mouse_x;
467 xr.second += ev->x - _start_mouse_x;
470 /* don't alter y if we clicked outside and to the left or right of the viewbox */
471 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
472 y += ev->y - _start_mouse_y;
476 xr.second -= xr.first;
485 set_cursor (_start_position);
487 } else if (_zoom_dragging) {
489 double const dx = ev->x - _start_mouse_x;
490 double const dy = ev->y - _start_mouse_y;
492 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
494 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
498 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
500 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
504 set_overlays_dirty ();
505 set_cursor (_zoom_position);
510 set_cursor (get_position (ev->x, ev->y));
518 EditorSummary::on_button_release_event (GdkEventButton*)
520 _move_dragging = false;
521 _zoom_dragging = false;
522 _editor->_dragging_playhead = false;
527 EditorSummary::on_scroll_event (GdkEventScroll* ev)
531 pair<double, double> xr;
532 pair<double, double> yr;
533 get_editor (&xr, &yr);
538 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
540 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
544 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
546 /* primary-wheel == left-right scrolling */
548 if (ev->direction == GDK_SCROLL_UP) {
551 } else if (ev->direction == GDK_SCROLL_DOWN) {
558 if (ev->direction == GDK_SCROLL_DOWN) {
560 } else if (ev->direction == GDK_SCROLL_UP) {
562 } else if (ev->direction == GDK_SCROLL_LEFT) {
565 } else if (ev->direction == GDK_SCROLL_RIGHT) {
575 /** Set the editor to display a given x range and a y range with the top at a given position.
576 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
577 * x and y parameters are specified in summary coordinates.
580 EditorSummary::set_editor (pair<double,double> const & x, double const y)
582 if (_editor->pending_visual_change.idle_handler_id >= 0) {
584 /* As a side-effect, the Editor's visual change idle handler processes
585 pending GTK events. Hence this motion notify handler can be called
586 in the middle of a visual change idle handler, and if this happens,
587 the queue_visual_change calls below modify the variables that the
588 idle handler is working with. This causes problems. Hence this
589 check. It ensures that we won't modify the pending visual change
590 while a visual change idle handler is in progress. It's not perfect,
591 as it also means that we won't change these variables if an idle handler
592 is merely pending but not executing. But c'est la vie.
602 /** Set the editor to display given x and y ranges. x zoom and track heights are
603 * adjusted if necessary.
604 * x and y parameters are specified in summary coordinates.
607 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
609 if (_editor->pending_visual_change.idle_handler_id >= 0) {
610 /* see comment in other set_editor () */
618 /** Set the x range visible in the editor.
619 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
620 * @param x new x range in summary coordinates.
623 EditorSummary::set_editor_x (pair<double, double> const & x)
625 _editor->reset_x_origin (x.first / _x_scale + _start);
628 ((x.second - x.first) / _x_scale) /
629 _editor->frame_to_unit (_editor->current_page_frames())
632 if (nx != _editor->get_current_zoom ()) {
633 _editor->reset_zoom (nx);
637 /** Set the top of the y range visible in the editor.
638 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
639 * @param y new editor top in summary coodinates.
642 EditorSummary::set_editor_y (double const y)
644 double y1 = summary_y_to_editor (y);
645 double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
648 double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
650 if (y2 > full_editor_height) {
651 y1 -= y2 - full_editor_height;
658 _editor->reset_y_origin (y1);
661 /** Set the y range visible in the editor. This is achieved by scaling track heights,
663 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
664 * @param y new editor range in summary coodinates.
667 EditorSummary::set_editor_y (pair<double, double> const & y)
669 /* Compute current height of tracks between y.first and y.second. We add up
670 the total height into `total_height' and the height of complete tracks into
673 pair<double, double> yc = y;
674 double total_height = 0;
675 double scale_height = 0;
677 _editor->_routes->suspend_redisplay ();
679 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
681 if ((*i)->hidden()) {
685 double const h = (*i)->effective_height ();
687 if (yc.first >= 0 && yc.first < _track_height) {
688 total_height += (_track_height - yc.first) * h / _track_height;
689 } else if (yc.first < 0 && yc.second > _track_height) {
692 } else if (yc.second >= 0 && yc.second < _track_height) {
693 total_height += yc.second * h / _track_height;
697 yc.first -= _track_height;
698 yc.second -= _track_height;
701 /* hence required scale factor of the complete tracks to fit the required y range */
702 double const scale = ((_editor->canvas_height() - _editor->get_canvas_timebars_vsize()) - (total_height - scale_height)) / scale_height;
706 /* Scale complete tracks within the range to make it fit */
708 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
710 if ((*i)->hidden()) {
714 if (yc.first < 0 && yc.second > _track_height) {
715 (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
718 yc.first -= _track_height;
719 yc.second -= _track_height;
722 _editor->_routes->resume_redisplay ();
724 set_editor_y (y.first);
728 EditorSummary::playhead_position_changed (framepos_t p)
730 if (_session && int (p * _x_scale) != int (_last_playhead)) {
731 set_overlays_dirty ();
736 EditorSummary::summary_y_to_editor (double y) const
739 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
741 if ((*i)->hidden()) {
745 double const h = (*i)->effective_height ();
746 if (y < _track_height) {
748 return ey + y * h / _track_height;
759 EditorSummary::editor_y_to_summary (double y) const
762 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
764 if ((*i)->hidden()) {
768 double const h = (*i)->effective_height ();
771 return sy + y * _track_height / h;