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"
34 using namespace ARDOUR
;
35 using Gtkmm2ext::Keyboard
;
37 /** Construct an EditorSummary.
38 * @param e Editor to represent.
40 EditorSummary::EditorSummary (Editor
* e
)
41 : EditorComponent (e
),
44 _overhang_fraction (0.1),
48 _move_dragging (false),
50 _view_rectangle_x (0, 0),
51 _view_rectangle_y (0, 0),
52 _zoom_dragging (false)
54 Region::RegionPropertyChanged
.connect (region_property_connection
, invalidator (*this), boost::bind (&CairoWidget::set_dirty
, this), gui_context());
55 _editor
->playhead_cursor
->PositionChanged
.connect (position_connection
, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed
, this, _1
), gui_context());
57 add_events (Gdk::POINTER_MOTION_MASK
);
60 /** Connect to a session.
64 EditorSummary::set_session (Session
* s
)
66 SessionHandlePtr::set_session (s
);
70 /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
71 * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
72 * emitted when a cut region is added to the `cutlist' playlist.
76 _session
->StartTimeChanged
.connect (_session_connections
, invalidator (*this), boost::bind (&EditorSummary::set_dirty
, this), gui_context());
77 _session
->EndTimeChanged
.connect (_session_connections
, invalidator (*this), boost::bind (&EditorSummary::set_dirty
, this), gui_context());
81 /** Handle an expose event.
82 * @param event Event from GTK.
85 EditorSummary::on_expose_event (GdkEventExpose
* event
)
87 CairoWidget::on_expose_event (event
);
93 cairo_t
* cr
= gdk_cairo_create (get_window()->gobj());
95 /* Render the view rectangle. If there is an editor visual pending, don't update
96 the view rectangle now --- wait until the expose event that we'll get after
97 the visual change. This prevents a flicker.
100 if (_editor
->pending_visual_change
.idle_handler_id
< 0) {
101 get_editor (&_view_rectangle_x
, &_view_rectangle_y
);
104 cairo_move_to (cr
, _view_rectangle_x
.first
, _view_rectangle_y
.first
);
105 cairo_line_to (cr
, _view_rectangle_x
.second
, _view_rectangle_y
.first
);
106 cairo_line_to (cr
, _view_rectangle_x
.second
, _view_rectangle_y
.second
);
107 cairo_line_to (cr
, _view_rectangle_x
.first
, _view_rectangle_y
.second
);
108 cairo_line_to (cr
, _view_rectangle_x
.first
, _view_rectangle_y
.first
);
109 cairo_set_source_rgba (cr
, 1, 1, 1, 0.25);
110 cairo_fill_preserve (cr
);
111 cairo_set_line_width (cr
, 1);
112 cairo_set_source_rgba (cr
, 1, 1, 1, 0.5);
117 cairo_set_line_width (cr
, 1);
118 /* XXX: colour should be set from configuration file */
119 cairo_set_source_rgba (cr
, 1, 0, 0, 1);
121 double const p
= (_editor
->playhead_cursor
->current_frame
- _start
) * _x_scale
;
122 cairo_move_to (cr
, p
, 0);
123 cairo_line_to (cr
, p
, _height
);
132 /** Render the required regions to a cairo context.
136 EditorSummary::render (cairo_t
* cr
)
140 cairo_set_source_rgb (cr
, 0, 0, 0);
141 cairo_rectangle (cr
, 0, 0, _width
, _height
);
148 /* compute start and end points for the summary */
150 framecnt_t
const session_length
= _session
->current_end_frame() - _session
->current_start_frame ();
151 double const theoretical_start
= _session
->current_start_frame() - session_length
* _overhang_fraction
;
152 _start
= theoretical_start
> 0 ? theoretical_start
: 0;
153 _end
= _session
->current_end_frame() + session_length
* _overhang_fraction
;
155 /* compute track height */
157 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin(); i
!= _editor
->track_views
.end(); ++i
) {
158 if (!(*i
)->hidden()) {
166 _track_height
= (double) _height
/ N
;
169 /* calculate x scale */
170 if (_end
!= _start
) {
171 _x_scale
= static_cast<double> (_width
) / (_end
- _start
);
176 /* render tracks and regions */
179 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin(); i
!= _editor
->track_views
.end(); ++i
) {
181 if ((*i
)->hidden()) {
185 cairo_set_source_rgb (cr
, 0.2, 0.2, 0.2);
186 cairo_set_line_width (cr
, _track_height
- 2);
187 cairo_move_to (cr
, 0, y
+ _track_height
/ 2);
188 cairo_line_to (cr
, _width
, y
+ _track_height
/ 2);
191 StreamView
* s
= (*i
)->view ();
194 cairo_set_line_width (cr
, _track_height
* 0.6);
196 s
->foreach_regionview (sigc::bind (
197 sigc::mem_fun (*this, &EditorSummary::render_region
),
199 y
+ _track_height
/ 2
206 /* start and end markers */
208 cairo_set_line_width (cr
, 1);
209 cairo_set_source_rgb (cr
, 1, 1, 0);
211 double const p
= (_session
->current_start_frame() - _start
) * _x_scale
;
212 cairo_move_to (cr
, p
, 0);
213 cairo_line_to (cr
, p
, _height
);
216 double const q
= (_session
->current_end_frame() - _start
) * _x_scale
;
217 cairo_move_to (cr
, q
, 0);
218 cairo_line_to (cr
, q
, _height
);
222 /** Render a region for the summary.
223 * @param r Region view.
224 * @param cr Cairo context.
225 * @param y y coordinate to render at.
228 EditorSummary::render_region (RegionView
* r
, cairo_t
* cr
, double y
) const
230 uint32_t const c
= r
->get_fill_color ();
231 cairo_set_source_rgb (cr
, UINT_RGBA_R (c
) / 255.0, UINT_RGBA_G (c
) / 255.0, UINT_RGBA_B (c
) / 255.0);
233 if (r
->region()->position() > _start
) {
234 cairo_move_to (cr
, (r
->region()->position() - _start
) * _x_scale
, y
);
236 cairo_move_to (cr
, 0, y
);
239 if ((r
->region()->position() + r
->region()->length()) > _start
) {
240 cairo_line_to (cr
, ((r
->region()->position() - _start
+ r
->region()->length())) * _x_scale
, y
);
242 cairo_line_to (cr
, 0, y
);
248 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
250 EditorSummary::set_overlays_dirty ()
252 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty
)
256 /** Handle a size request.
257 * @param req GTK requisition
260 EditorSummary::on_size_request (Gtk::Requisition
*req
)
262 /* Use a dummy, small width and the actual height that we want */
269 EditorSummary::centre_on_click (GdkEventButton
* ev
)
271 pair
<double, double> xr
;
272 pair
<double, double> yr
;
273 get_editor (&xr
, &yr
);
275 double const w
= xr
.second
- xr
.first
;
277 xr
.first
= ev
->x
- w
/ 2;
278 xr
.second
= ev
->x
+ w
/ 2;
283 } else if (xr
.second
> _width
) {
285 xr
.first
= _width
- w
;
288 double ey
= summary_y_to_editor (ev
->y
);
289 ey
-= (_editor
->canvas_height() - _editor
->get_canvas_timebars_vsize ()) / 2;
294 set_editor (xr
, editor_y_to_summary (ey
));
297 /** Handle a button press.
298 * @param ev GTK event.
301 EditorSummary::on_button_press_event (GdkEventButton
* ev
)
303 if (ev
->button
== 1) {
305 pair
<double, double> xr
;
306 pair
<double, double> yr
;
307 get_editor (&xr
, &yr
);
309 _start_editor_x
= xr
;
310 _start_editor_y
= yr
;
311 _start_mouse_x
= ev
->x
;
312 _start_mouse_y
= ev
->y
;
313 _start_position
= get_position (ev
->x
, ev
->y
);
315 if (_start_position
!= INSIDE
&& _start_position
!= BELOW_OR_ABOVE
&&
316 _start_position
!= TO_LEFT_OR_RIGHT
&& _start_position
!= OTHERWISE_OUTSIDE
319 /* start a zoom drag */
321 _zoom_position
= get_position (ev
->x
, ev
->y
);
322 _zoom_dragging
= true;
323 _editor
->_dragging_playhead
= true;
325 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
)) {
327 /* secondary-modifier-click: locate playhead */
329 _session
->request_locate (ev
->x
/ _x_scale
+ _start
);
332 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
334 centre_on_click (ev
);
338 /* start a move drag */
340 _move_dragging
= true;
342 _editor
->_dragging_playhead
= true;
349 /** Fill in x and y with the editor's current viewable area in summary coordinates */
351 EditorSummary::get_editor (pair
<double, double>* x
, pair
<double, double>* y
) const
356 x
->first
= (_editor
->leftmost_position () - _start
) * _x_scale
;
357 x
->second
= x
->first
+ _editor
->current_page_frames() * _x_scale
;
359 y
->first
= editor_y_to_summary (_editor
->vertical_adjustment
.get_value ());
360 y
->second
= editor_y_to_summary (_editor
->vertical_adjustment
.get_value () + _editor
->canvas_height() - _editor
->get_canvas_timebars_vsize());
363 /** Get an expression of the position of a point with respect to the view rectangle */
364 EditorSummary::Position
365 EditorSummary::get_position (double x
, double y
) const
367 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
370 int x_edge_size
= (_view_rectangle_x
.second
- _view_rectangle_x
.first
) / 4;
371 x_edge_size
= min (x_edge_size
, 8);
372 x_edge_size
= max (x_edge_size
, 1);
374 int y_edge_size
= (_view_rectangle_y
.second
- _view_rectangle_y
.first
) / 4;
375 y_edge_size
= min (y_edge_size
, 8);
376 y_edge_size
= max (y_edge_size
, 1);
378 bool const near_left
= (std::abs (x
- _view_rectangle_x
.first
) < x_edge_size
);
379 bool const near_right
= (std::abs (x
- _view_rectangle_x
.second
) < x_edge_size
);
380 bool const near_top
= (std::abs (y
- _view_rectangle_y
.first
) < y_edge_size
);
381 bool const near_bottom
= (std::abs (y
- _view_rectangle_y
.second
) < y_edge_size
);
382 bool const within_x
= _view_rectangle_x
.first
< x
&& x
< _view_rectangle_x
.second
;
383 bool const within_y
= _view_rectangle_y
.first
< y
&& y
< _view_rectangle_y
.second
;
385 if (near_left
&& near_top
) {
387 } else if (near_left
&& near_bottom
) {
389 } else if (near_right
&& near_top
) {
391 } else if (near_right
&& near_bottom
) {
393 } else if (near_left
&& within_y
) {
395 } else if (near_right
&& within_y
) {
397 } else if (near_top
&& within_x
) {
399 } else if (near_bottom
&& within_x
) {
401 } else if (within_x
&& within_y
) {
403 } else if (within_x
) {
404 return BELOW_OR_ABOVE
;
405 } else if (within_y
) {
406 return TO_LEFT_OR_RIGHT
;
408 return OTHERWISE_OUTSIDE
;
413 EditorSummary::set_cursor (Position p
)
417 get_window()->set_cursor (*_editor
->_cursors
->resize_left
);
420 get_window()->set_cursor (*_editor
->_cursors
->resize_top_left
);
423 get_window()->set_cursor (*_editor
->_cursors
->resize_top
);
426 get_window()->set_cursor (*_editor
->_cursors
->resize_top_right
);
429 get_window()->set_cursor (*_editor
->_cursors
->resize_right
);
432 get_window()->set_cursor (*_editor
->_cursors
->resize_bottom_right
);
435 get_window()->set_cursor (*_editor
->_cursors
->resize_bottom
);
438 get_window()->set_cursor (*_editor
->_cursors
->resize_bottom_left
);
441 get_window()->set_cursor (*_editor
->_cursors
->move
);
443 case TO_LEFT_OR_RIGHT
:
444 get_window()->set_cursor (*_editor
->_cursors
->expand_left_right
);
447 get_window()->set_cursor (*_editor
->_cursors
->expand_up_down
);
450 get_window()->set_cursor ();
456 EditorSummary::on_motion_notify_event (GdkEventMotion
* ev
)
458 pair
<double, double> xr
= _start_editor_x
;
459 pair
<double, double> yr
= _start_editor_y
;
460 double y
= _start_editor_y
.first
;
462 if (_move_dragging
) {
466 /* don't alter x if we clicked outside and above or below the viewbox */
467 if (_start_position
== INSIDE
|| _start_position
== TO_LEFT_OR_RIGHT
|| _start_position
== OTHERWISE_OUTSIDE
) {
468 xr
.first
+= ev
->x
- _start_mouse_x
;
469 xr
.second
+= ev
->x
- _start_mouse_x
;
472 /* don't alter y if we clicked outside and to the left or right of the viewbox */
473 if (_start_position
== INSIDE
|| _start_position
== BELOW_OR_ABOVE
) {
474 y
+= ev
->y
- _start_mouse_y
;
478 xr
.second
-= xr
.first
;
487 set_cursor (_start_position
);
489 } else if (_zoom_dragging
) {
491 double const dx
= ev
->x
- _start_mouse_x
;
492 double const dy
= ev
->y
- _start_mouse_y
;
494 if (_zoom_position
== LEFT
|| _zoom_position
== LEFT_TOP
|| _zoom_position
== LEFT_BOTTOM
) {
496 } else if (_zoom_position
== RIGHT
|| _zoom_position
== RIGHT_TOP
|| _zoom_position
== RIGHT_BOTTOM
) {
500 if (_zoom_position
== TOP
|| _zoom_position
== LEFT_TOP
|| _zoom_position
== RIGHT_TOP
) {
502 } else if (_zoom_position
== BOTTOM
|| _zoom_position
== LEFT_BOTTOM
|| _zoom_position
== RIGHT_BOTTOM
) {
506 set_overlays_dirty ();
507 set_cursor (_zoom_position
);
512 set_cursor (get_position (ev
->x
, ev
->y
));
520 EditorSummary::on_button_release_event (GdkEventButton
*)
522 _move_dragging
= false;
523 _zoom_dragging
= false;
524 _editor
->_dragging_playhead
= false;
529 EditorSummary::on_scroll_event (GdkEventScroll
* ev
)
533 pair
<double, double> xr
;
534 pair
<double, double> yr
;
535 get_editor (&xr
, &yr
);
540 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
)) {
542 } else if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
546 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
548 /* primary-wheel == left-right scrolling */
550 if (ev
->direction
== GDK_SCROLL_UP
) {
553 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
560 if (ev
->direction
== GDK_SCROLL_DOWN
) {
562 } else if (ev
->direction
== GDK_SCROLL_UP
) {
564 } else if (ev
->direction
== GDK_SCROLL_LEFT
) {
567 } else if (ev
->direction
== GDK_SCROLL_RIGHT
) {
577 /** Set the editor to display a given x range and a y range with the top at a given position.
578 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
579 * x and y parameters are specified in summary coordinates.
582 EditorSummary::set_editor (pair
<double,double> const & x
, double const y
)
584 if (_editor
->pending_visual_change
.idle_handler_id
>= 0) {
586 /* As a side-effect, the Editor's visual change idle handler processes
587 pending GTK events. Hence this motion notify handler can be called
588 in the middle of a visual change idle handler, and if this happens,
589 the queue_visual_change calls below modify the variables that the
590 idle handler is working with. This causes problems. Hence this
591 check. It ensures that we won't modify the pending visual change
592 while a visual change idle handler is in progress. It's not perfect,
593 as it also means that we won't change these variables if an idle handler
594 is merely pending but not executing. But c'est la vie.
604 /** Set the editor to display given x and y ranges. x zoom and track heights are
605 * adjusted if necessary.
606 * x and y parameters are specified in summary coordinates.
609 EditorSummary::set_editor (pair
<double,double> const & x
, pair
<double, double> const & y
)
611 if (_editor
->pending_visual_change
.idle_handler_id
>= 0) {
612 /* see comment in other set_editor () */
620 /** Set the x range visible in the editor.
621 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
622 * @param x new x range in summary coordinates.
625 EditorSummary::set_editor_x (pair
<double, double> const & x
)
627 _editor
->reset_x_origin (x
.first
/ _x_scale
+ _start
);
630 ((x
.second
- x
.first
) / _x_scale
) /
631 _editor
->frame_to_unit (_editor
->current_page_frames())
634 if (nx
!= _editor
->get_current_zoom ()) {
635 _editor
->reset_zoom (nx
);
639 /** Set the top of the y range visible in the editor.
640 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
641 * @param y new editor top in summary coodinates.
644 EditorSummary::set_editor_y (double const y
)
646 double y1
= summary_y_to_editor (y
);
647 double const eh
= _editor
->canvas_height() - _editor
->get_canvas_timebars_vsize ();
650 double const full_editor_height
= _editor
->full_canvas_height
- _editor
->get_canvas_timebars_vsize();
652 if (y2
> full_editor_height
) {
653 y1
-= y2
- full_editor_height
;
660 _editor
->reset_y_origin (y1
);
663 /** Set the y range visible in the editor. This is achieved by scaling track heights,
665 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
666 * @param y new editor range in summary coodinates.
669 EditorSummary::set_editor_y (pair
<double, double> const & y
)
671 /* Compute current height of tracks between y.first and y.second. We add up
672 the total height into `total_height' and the height of complete tracks into
676 /* Copy of target range for use below */
677 pair
<double, double> yc
= y
;
678 /* Total height of all tracks */
679 double total_height
= 0;
680 /* Height of any parts of tracks that aren't fully in the desired range */
681 double partial_height
= 0;
682 /* Height of any tracks that are fully in the desired range */
683 double scale_height
= 0;
685 _editor
->_routes
->suspend_redisplay ();
687 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin(); i
!= _editor
->track_views
.end(); ++i
) {
689 if ((*i
)->hidden()) {
693 double const h
= (*i
)->effective_height ();
696 if (yc
.first
> 0 && yc
.first
< _track_height
) {
697 partial_height
+= (_track_height
- yc
.first
) * h
/ _track_height
;
698 } else if (yc
.first
<= 0 && yc
.second
>= _track_height
) {
700 } else if (yc
.second
> 0 && yc
.second
< _track_height
) {
701 partial_height
+= yc
.second
* h
/ _track_height
;
705 yc
.first
-= _track_height
;
706 yc
.second
-= _track_height
;
709 /* Height that we will use for scaling; use the whole editor height unless there are not
710 enough tracks to fill it.
712 double const ch
= min (total_height
, _editor
->canvas_height() - _editor
->get_canvas_timebars_vsize());
714 /* hence required scale factor of the complete tracks to fit the required y range;
715 the amount of space they should take up divided by the amount they currently take up.
717 double const scale
= (ch
- partial_height
) / scale_height
;
721 /* Scale complete tracks within the range to make it fit */
723 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin(); i
!= _editor
->track_views
.end(); ++i
) {
725 if ((*i
)->hidden()) {
729 if (yc
.first
<= 0 && yc
.second
>= _track_height
) {
730 (*i
)->set_height (max (TimeAxisView::preset_height (HeightSmall
), (uint32_t) ((*i
)->effective_height() * scale
)));
733 yc
.first
-= _track_height
;
734 yc
.second
-= _track_height
;
737 _editor
->_routes
->resume_redisplay ();
739 set_editor_y (y
.first
);
743 EditorSummary::playhead_position_changed (framepos_t p
)
745 if (_session
&& int (p
* _x_scale
) != int (_last_playhead
)) {
746 set_overlays_dirty ();
751 EditorSummary::summary_y_to_editor (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 ();
761 if (y
< _track_height
) {
763 return ey
+ y
* h
/ _track_height
;
774 EditorSummary::editor_y_to_summary (double y
) const
777 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin (); i
!= _editor
->track_views
.end(); ++i
) {
779 if ((*i
)->hidden()) {
783 double const h
= (*i
)->effective_height ();
786 return sy
+ y
* _track_height
/ h
;