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) {
98 get_editor (&_view_rectangle_x
, &_view_rectangle_y
);
101 cairo_move_to (cr
, _view_rectangle_x
.first
, _view_rectangle_y
.first
);
102 cairo_line_to (cr
, _view_rectangle_x
.second
, _view_rectangle_y
.first
);
103 cairo_line_to (cr
, _view_rectangle_x
.second
, _view_rectangle_y
.second
);
104 cairo_line_to (cr
, _view_rectangle_x
.first
, _view_rectangle_y
.second
);
105 cairo_line_to (cr
, _view_rectangle_x
.first
, _view_rectangle_y
.first
);
106 cairo_set_source_rgba (cr
, 1, 1, 1, 0.25);
107 cairo_fill_preserve (cr
);
108 cairo_set_line_width (cr
, 1);
109 cairo_set_source_rgba (cr
, 1, 1, 1, 0.5);
114 cairo_set_line_width (cr
, 1);
115 /* XXX: colour should be set from configuration file */
116 cairo_set_source_rgba (cr
, 1, 0, 0, 1);
118 double const p
= (_editor
->playhead_cursor
->current_frame
- _start
) * _x_scale
;
119 cairo_move_to (cr
, p
, 0);
120 cairo_line_to (cr
, p
, _height
);
129 /** Render the required regions to a cairo context.
133 EditorSummary::render (cairo_t
* cr
)
137 cairo_set_source_rgb (cr
, 0, 0, 0);
138 cairo_rectangle (cr
, 0, 0, _width
, _height
);
145 /* compute start and end points for the summary */
147 nframes_t
const session_length
= _session
->current_end_frame() - _session
->current_start_frame ();
148 double const theoretical_start
= _session
->current_start_frame() - session_length
* _overhang_fraction
;
149 _start
= theoretical_start
> 0 ? theoretical_start
: 0;
150 _end
= _session
->current_end_frame() + session_length
* _overhang_fraction
;
152 /* compute track height */
154 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin(); i
!= _editor
->track_views
.end(); ++i
) {
155 if (!(*i
)->hidden()) {
163 _track_height
= (double) _height
/ N
;
166 /* calculate x scale */
167 if (_end
!= _start
) {
168 _x_scale
= static_cast<double> (_width
) / (_end
- _start
);
173 /* render tracks and regions */
176 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin(); i
!= _editor
->track_views
.end(); ++i
) {
178 if ((*i
)->hidden()) {
182 cairo_set_source_rgb (cr
, 0.2, 0.2, 0.2);
183 cairo_set_line_width (cr
, _track_height
- 2);
184 cairo_move_to (cr
, 0, y
+ _track_height
/ 2);
185 cairo_line_to (cr
, _width
, y
+ _track_height
/ 2);
188 StreamView
* s
= (*i
)->view ();
191 cairo_set_line_width (cr
, _track_height
* 0.6);
193 s
->foreach_regionview (sigc::bind (
194 sigc::mem_fun (*this, &EditorSummary::render_region
),
196 y
+ _track_height
/ 2
203 /* start and end markers */
205 cairo_set_line_width (cr
, 1);
206 cairo_set_source_rgb (cr
, 1, 1, 0);
208 double const p
= (_session
->current_start_frame() - _start
) * _x_scale
;
209 cairo_move_to (cr
, p
, 0);
210 cairo_line_to (cr
, p
, _height
);
213 double const q
= (_session
->current_end_frame() - _start
) * _x_scale
;
214 cairo_move_to (cr
, q
, 0);
215 cairo_line_to (cr
, q
, _height
);
219 /** Render a region for the summary.
220 * @param r Region view.
221 * @param cr Cairo context.
222 * @param y y coordinate to render at.
225 EditorSummary::render_region (RegionView
* r
, cairo_t
* cr
, double y
) const
227 uint32_t const c
= r
->get_fill_color ();
228 cairo_set_source_rgb (cr
, UINT_RGBA_R (c
) / 255.0, UINT_RGBA_G (c
) / 255.0, UINT_RGBA_B (c
) / 255.0);
230 if (r
->region()->position() > _start
) {
231 cairo_move_to (cr
, (r
->region()->position() - _start
) * _x_scale
, y
);
233 cairo_move_to (cr
, 0, y
);
236 if ((r
->region()->position() + r
->region()->length()) > _start
) {
237 cairo_line_to (cr
, ((r
->region()->position() - _start
+ r
->region()->length())) * _x_scale
, y
);
239 cairo_line_to (cr
, 0, y
);
245 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
247 EditorSummary::set_overlays_dirty ()
249 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty
)
253 /** Handle a size request.
254 * @param req GTK requisition
257 EditorSummary::on_size_request (Gtk::Requisition
*req
)
259 /* Use a dummy, small width and the actual height that we want */
266 EditorSummary::centre_on_click (GdkEventButton
* ev
)
268 pair
<double, double> xr
;
269 pair
<double, double> yr
;
270 get_editor (&xr
, &yr
);
272 double const w
= xr
.second
- xr
.first
;
274 xr
.first
= ev
->x
- w
/ 2;
275 xr
.second
= ev
->x
+ w
/ 2;
280 } else if (xr
.second
> _width
) {
282 xr
.first
= _width
- w
;
285 double ey
= summary_y_to_editor (ev
->y
);
286 ey
-= (_editor
->canvas_height() - _editor
->get_canvas_timebars_vsize ()) / 2;
291 set_editor (xr
, editor_y_to_summary (ey
));
294 /** Handle a button press.
295 * @param ev GTK event.
298 EditorSummary::on_button_press_event (GdkEventButton
* ev
)
300 if (ev
->button
== 1) {
302 pair
<double, double> xr
;
303 pair
<double, double> yr
;
304 get_editor (&xr
, &yr
);
306 _start_editor_x
= xr
;
307 _start_editor_y
= yr
;
308 _start_mouse_x
= ev
->x
;
309 _start_mouse_y
= ev
->y
;
310 _start_position
= get_position (ev
->x
, ev
->y
);
312 if (_start_position
!= INSIDE
&& _start_position
!= BELOW_OR_ABOVE
&&
313 _start_position
!= TO_LEFT_OR_RIGHT
&& _start_position
!= OTHERWISE_OUTSIDE
316 /* start a zoom drag */
318 _zoom_position
= get_position (ev
->x
, ev
->y
);
319 _zoom_dragging
= true;
320 _editor
->_dragging_playhead
= true;
322 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::SecondaryModifier
)) {
324 /* secondary-modifier-click: locate playhead */
326 _session
->request_locate (ev
->x
/ _x_scale
+ _start
);
329 } else if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
331 centre_on_click (ev
);
335 /* start a move drag */
337 _move_dragging
= true;
339 _editor
->_dragging_playhead
= true;
346 /** Fill in x and y with the editor's current viewable area in summary coordinates */
348 EditorSummary::get_editor (pair
<double, double>* x
, pair
<double, double>* y
) const
353 x
->first
= (_editor
->leftmost_position () - _start
) * _x_scale
;
354 x
->second
= x
->first
+ _editor
->current_page_frames() * _x_scale
;
356 y
->first
= editor_y_to_summary (_editor
->vertical_adjustment
.get_value ());
357 y
->second
= editor_y_to_summary (_editor
->vertical_adjustment
.get_value () + _editor
->canvas_height() - _editor
->get_canvas_timebars_vsize());
360 /** Get an expression of the position of a point with respect to the view rectangle */
361 EditorSummary::Position
362 EditorSummary::get_position (double x
, double y
) const
364 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
366 int const edge_size
= 8;
368 bool const near_left
= (std::abs (x
- _view_rectangle_x
.first
) < edge_size
);
369 bool const near_right
= (std::abs (x
- _view_rectangle_x
.second
) < edge_size
);
370 bool const near_top
= (std::abs (y
- _view_rectangle_y
.first
) < edge_size
);
371 bool const near_bottom
= (std::abs (y
- _view_rectangle_y
.second
) < edge_size
);
372 bool const within_x
= _view_rectangle_x
.first
< x
&& x
< _view_rectangle_x
.second
;
373 bool const within_y
= _view_rectangle_y
.first
< y
&& y
< _view_rectangle_y
.second
;
375 if (near_left
&& near_top
) {
377 } else if (near_left
&& near_bottom
) {
379 } else if (near_right
&& near_top
) {
381 } else if (near_right
&& near_bottom
) {
383 } else if (near_left
&& within_y
) {
385 } else if (near_right
&& within_y
) {
387 } else if (near_top
&& within_x
) {
389 } else if (near_bottom
&& within_x
) {
391 } else if (within_x
&& within_y
) {
393 } else if (within_x
) {
394 return BELOW_OR_ABOVE
;
395 } else if (within_y
) {
396 return TO_LEFT_OR_RIGHT
;
398 return OTHERWISE_OUTSIDE
;
403 EditorSummary::set_cursor (Position p
)
407 get_window()->set_cursor (*_editor
->left_side_trim_cursor
);
410 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_LEFT_CORNER
));
413 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_SIDE
));
416 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_RIGHT_CORNER
));
419 get_window()->set_cursor (*_editor
->right_side_trim_cursor
);
422 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_RIGHT_CORNER
));
425 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_SIDE
));
428 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_LEFT_CORNER
));
431 get_window()->set_cursor (Gdk::Cursor (Gdk::FLEUR
));
434 get_window()->set_cursor ();
440 EditorSummary::on_motion_notify_event (GdkEventMotion
* ev
)
442 pair
<double, double> xr
= _start_editor_x
;
443 pair
<double, double> yr
= _start_editor_y
;
444 double y
= _start_editor_y
.first
;
446 if (_move_dragging
) {
450 /* don't alter x if we clicked outside and above or below the viewbox */
451 if (_start_position
== INSIDE
|| _start_position
== TO_LEFT_OR_RIGHT
) {
452 xr
.first
+= ev
->x
- _start_mouse_x
;
453 xr
.second
+= ev
->x
- _start_mouse_x
;
456 /* don't alter y if we clicked outside and to the left or right of the viewbox */
457 if (_start_position
== INSIDE
|| _start_position
== BELOW_OR_ABOVE
) {
458 y
+= ev
->y
- _start_mouse_y
;
462 xr
.second
-= xr
.first
;
473 } else if (_zoom_dragging
) {
475 double const dx
= ev
->x
- _start_mouse_x
;
476 double const dy
= ev
->y
- _start_mouse_y
;
478 if (_zoom_position
== LEFT
|| _zoom_position
== LEFT_TOP
|| _zoom_position
== LEFT_BOTTOM
) {
480 } else if (_zoom_position
== RIGHT
|| _zoom_position
== RIGHT_TOP
|| _zoom_position
== RIGHT_BOTTOM
) {
484 if (_zoom_position
== TOP
|| _zoom_position
== LEFT_TOP
|| _zoom_position
== RIGHT_TOP
) {
486 } else if (_zoom_position
== BOTTOM
|| _zoom_position
== LEFT_BOTTOM
|| _zoom_position
== RIGHT_BOTTOM
) {
490 set_overlays_dirty ();
491 set_cursor (_zoom_position
);
496 set_cursor (get_position (ev
->x
, ev
->y
));
504 EditorSummary::on_button_release_event (GdkEventButton
*)
506 _move_dragging
= false;
507 _zoom_dragging
= false;
508 _editor
->_dragging_playhead
= false;
513 EditorSummary::on_scroll_event (GdkEventScroll
* ev
)
517 pair
<double, double> xr
;
518 pair
<double, double> yr
;
519 get_editor (&xr
, &yr
);
524 if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::SecondaryModifier
)) {
526 } else if (Keyboard::modifier_state_contains (ev
->state
, Keyboard::TertiaryModifier
)) {
530 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
532 /* primary-wheel == left-right scrolling */
534 if (ev
->direction
== GDK_SCROLL_UP
) {
537 } else if (ev
->direction
== GDK_SCROLL_DOWN
) {
544 if (ev
->direction
== GDK_SCROLL_DOWN
) {
546 } else if (ev
->direction
== GDK_SCROLL_UP
) {
548 } else if (ev
->direction
== GDK_SCROLL_LEFT
) {
551 } else if (ev
->direction
== GDK_SCROLL_RIGHT
) {
561 /** Set the editor to display a given x range and a y range with the top at a given position.
562 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
563 * x and y parameters are specified in summary coordinates.
566 EditorSummary::set_editor (pair
<double,double> const & x
, double const y
)
568 if (_editor
->pending_visual_change
.idle_handler_id
>= 0) {
570 /* As a side-effect, the Editor's visual change idle handler processes
571 pending GTK events. Hence this motion notify handler can be called
572 in the middle of a visual change idle handler, and if this happens,
573 the queue_visual_change calls below modify the variables that the
574 idle handler is working with. This causes problems. Hence this
575 check. It ensures that we won't modify the pending visual change
576 while a visual change idle handler is in progress. It's not perfect,
577 as it also means that we won't change these variables if an idle handler
578 is merely pending but not executing. But c'est la vie.
588 /** Set the editor to display given x and y ranges. x zoom and track heights are
589 * adjusted if necessary.
590 * x and y parameters are specified in summary coordinates.
593 EditorSummary::set_editor (pair
<double,double> const & x
, pair
<double, double> const & y
)
595 if (_editor
->pending_visual_change
.idle_handler_id
>= 0) {
596 /* see comment in other set_editor () */
604 /** Set the x range visible in the editor.
605 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
606 * @param x new x range in summary coordinates.
609 EditorSummary::set_editor_x (pair
<double, double> const & x
)
611 _editor
->reset_x_origin (x
.first
/ _x_scale
+ _start
);
614 ((x
.second
- x
.first
) / _x_scale
) /
615 _editor
->frame_to_unit (_editor
->current_page_frames())
618 if (nx
!= _editor
->get_current_zoom ()) {
619 _editor
->reset_zoom (nx
);
623 /** Set the top of the y range visible in the editor.
624 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
625 * @param y new editor top in summary coodinates.
628 EditorSummary::set_editor_y (double const y
)
630 double y1
= summary_y_to_editor (y
);
631 double const eh
= _editor
->canvas_height() - _editor
->get_canvas_timebars_vsize ();
634 double const full_editor_height
= _editor
->full_canvas_height
- _editor
->get_canvas_timebars_vsize();
636 if (y2
> full_editor_height
) {
637 y1
-= y2
- full_editor_height
;
644 _editor
->reset_y_origin (y1
);
647 /** Set the y range visible in the editor. This is achieved by scaling track heights,
649 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
650 * @param y new editor range in summary coodinates.
653 EditorSummary::set_editor_y (pair
<double, double> const & y
)
655 /* Compute current height of tracks between y.first and y.second. We add up
656 the total height into `total_height' and the height of complete tracks into
659 pair
<double, double> yc
= y
;
660 double total_height
= 0;
661 double scale_height
= 0;
662 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin(); i
!= _editor
->track_views
.end(); ++i
) {
664 if ((*i
)->hidden()) {
668 double const h
= (*i
)->effective_height ();
670 if (yc
.first
>= 0 && yc
.first
< _track_height
) {
671 total_height
+= (_track_height
- yc
.first
) * h
/ _track_height
;
672 } else if (yc
.first
< 0 && yc
.second
> _track_height
) {
675 } else if (yc
.second
>= 0 && yc
.second
< _track_height
) {
676 total_height
+= yc
.second
* h
/ _track_height
;
680 yc
.first
-= _track_height
;
681 yc
.second
-= _track_height
;
684 /* hence required scale factor of the complete tracks to fit the required y range */
685 double const scale
= ((_editor
->canvas_height() - _editor
->get_canvas_timebars_vsize()) - (total_height
- scale_height
)) / scale_height
;
689 /* Scale complete tracks within the range to make it fit */
691 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin(); i
!= _editor
->track_views
.end(); ++i
) {
693 if ((*i
)->hidden()) {
697 if (yc
.first
< 0 && yc
.second
> _track_height
) {
698 (*i
)->set_height ((*i
)->effective_height() * scale
);
701 yc
.first
-= _track_height
;
702 yc
.second
-= _track_height
;
705 set_editor_y (y
.first
);
709 EditorSummary::playhead_position_changed (nframes64_t p
)
711 if (_session
&& int (p
* _x_scale
) != int (_last_playhead
)) {
712 set_overlays_dirty ();
717 EditorSummary::summary_y_to_editor (double y
) const
720 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin (); i
!= _editor
->track_views
.end(); ++i
) {
722 if ((*i
)->hidden()) {
726 double const h
= (*i
)->effective_height ();
727 if (y
< _track_height
) {
729 return ey
+ y
* h
/ _track_height
;
740 EditorSummary::editor_y_to_summary (double y
) const
743 for (TrackViewList::const_iterator i
= _editor
->track_views
.begin (); i
!= _editor
->track_views
.end(); ++i
) {
745 if ((*i
)->hidden()) {
749 double const h
= (*i
)->effective_height ();
752 return sy
+ y
* _track_height
/ h
;