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 _zoom_dragging (false)
50 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
51 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
54 /** Connect to a session.
58 EditorSummary::set_session (Session* s)
60 EditorComponent::set_session (s);
64 /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
65 * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
66 * emitted when a cut region is added to the `cutlist' playlist.
70 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
71 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
75 /** Handle an expose event.
76 * @param event Event from GTK.
79 EditorSummary::on_expose_event (GdkEventExpose* event)
81 CairoWidget::on_expose_event (event);
87 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
89 /* Render the view rectangle */
91 pair<double, double> x;
92 pair<double, double> y;
95 cairo_move_to (cr, x.first, y.first);
96 cairo_line_to (cr, x.second, y.first);
97 cairo_line_to (cr, x.second, y.second);
98 cairo_line_to (cr, x.first, y.second);
99 cairo_line_to (cr, x.first, y.first);
100 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
101 cairo_fill_preserve (cr);
102 cairo_set_line_width (cr, 1);
103 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
108 cairo_set_line_width (cr, 1);
109 /* XXX: colour should be set from configuration file */
110 cairo_set_source_rgba (cr, 1, 0, 0, 1);
112 double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
113 cairo_move_to (cr, p, 0);
114 cairo_line_to (cr, p, _height);
123 /** Render the required regions to a cairo context.
127 EditorSummary::render (cairo_t* cr)
131 cairo_set_source_rgb (cr, 0, 0, 0);
132 cairo_rectangle (cr, 0, 0, _width, _height);
139 /* compute start and end points for the summary */
141 nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
142 double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
143 _start = theoretical_start > 0 ? theoretical_start : 0;
144 _end = _session->current_end_frame() + session_length * _overhang_fraction;
146 /* compute track height */
147 size_t const N = _editor->track_views.size ();
151 _track_height = (double) _height / N;
154 /* calculate x scale */
155 if (_end != _start) {
156 _x_scale = static_cast<double> (_width) / (_end - _start);
161 /* render tracks and regions */
164 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
166 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
167 cairo_set_line_width (cr, _track_height - 2);
168 cairo_move_to (cr, 0, y + _track_height / 2);
169 cairo_line_to (cr, _width, y + _track_height / 2);
172 StreamView* s = (*i)->view ();
175 cairo_set_line_width (cr, _track_height * 0.6);
177 s->foreach_regionview (sigc::bind (
178 sigc::mem_fun (*this, &EditorSummary::render_region),
180 y + _track_height / 2
187 /* start and end markers */
189 cairo_set_line_width (cr, 1);
190 cairo_set_source_rgb (cr, 1, 1, 0);
192 double const p = (_session->current_start_frame() - _start) * _x_scale;
193 cairo_move_to (cr, p, 0);
194 cairo_line_to (cr, p, _height);
197 double const q = (_session->current_end_frame() - _start) * _x_scale;
198 cairo_move_to (cr, q, 0);
199 cairo_line_to (cr, q, _height);
203 /** Render a region for the summary.
204 * @param r Region view.
205 * @param cr Cairo context.
206 * @param y y coordinate to render at.
209 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
211 uint32_t const c = r->get_fill_color ();
212 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
214 if (r->region()->position() > _start) {
215 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
217 cairo_move_to (cr, 0, y);
220 if ((r->region()->position() + r->region()->length()) > _start) {
221 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
223 cairo_line_to (cr, 0, y);
229 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
231 EditorSummary::set_overlays_dirty ()
233 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
237 /** Handle a size request.
238 * @param req GTK requisition
241 EditorSummary::on_size_request (Gtk::Requisition *req)
243 /* Use a dummy, small width and the actual height that we want */
250 EditorSummary::centre_on_click (GdkEventButton* ev)
252 pair<double, double> xr;
253 pair<double, double> yr;
254 get_editor (&xr, &yr);
256 double const w = xr.second - xr.first;
258 xr.first = ev->x - w / 2;
259 xr.second = ev->x + w / 2;
264 } else if (xr.second > _width) {
266 xr.first = _width - w;
269 double ey = summary_y_to_editor (ev->y);
270 ey -= (_editor->canvas_height() - _editor->get_canvas_timebars_vsize ()) / 2;
275 set_editor (xr, editor_y_to_summary (ey));
278 /** Handle a button press.
279 * @param ev GTK event.
282 EditorSummary::on_button_press_event (GdkEventButton* ev)
284 if (ev->button == 1) {
286 pair<double, double> xr;
287 pair<double, double> yr;
288 get_editor (&xr, &yr);
290 _start_editor_x = xr;
291 _start_editor_y = yr;
292 _start_mouse_x = ev->x;
293 _start_mouse_y = ev->y;
296 _start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second &&
297 _start_editor_y.first <= _start_mouse_y && _start_mouse_y <= _start_editor_y.second
300 _start_position = IN_VIEWBOX;
302 } else if (_start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second) {
304 _start_position = BELOW_OR_ABOVE_VIEWBOX;
308 _start_position = TO_LEFT_OR_RIGHT_OF_VIEWBOX;
311 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
313 /* primary-modifier-click: start a zoom drag */
315 double const hx = (xr.first + xr.second) * 0.5;
316 _zoom_left = ev->x < hx;
317 _zoom_dragging = true;
318 _editor->_dragging_playhead = true;
321 /* In theory, we could support vertical dragging, which logically
322 might scale track heights in order to make the editor reflect
323 the dragged viewbox. However, having tried this:
326 c) it doesn't seem particularly useful, especially with the
327 limited height of the summary
329 So at the moment we don't support that...
333 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
335 /* secondary-modifier-click: locate playhead */
337 _session->request_locate (ev->x / _x_scale + _start);
340 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
342 centre_on_click (ev);
346 /* ordinary click: start a move drag */
348 _move_dragging = true;
350 _editor->_dragging_playhead = true;
358 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
360 x->first = (_editor->leftmost_position () - _start) * _x_scale;
361 x->second = x->first + _editor->current_page_frames() * _x_scale;
363 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
364 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
368 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
370 pair<double, double> xr = _start_editor_x;
371 pair<double, double> yr = _start_editor_y;
372 double y = _start_editor_y.first;
374 if (_move_dragging) {
378 /* don't alter x if we clicked outside and above or below the viewbox */
379 if (_start_position == IN_VIEWBOX || _start_position == TO_LEFT_OR_RIGHT_OF_VIEWBOX) {
380 xr.first += ev->x - _start_mouse_x;
381 xr.second += ev->x - _start_mouse_x;
384 /* don't alter y if we clicked outside and to the left or right of the viewbox */
385 if (_start_position == IN_VIEWBOX || _start_position == BELOW_OR_ABOVE_VIEWBOX) {
386 y += ev->y - _start_mouse_y;
390 xr.second -= xr.first;
400 } else if (_zoom_dragging) {
402 double const dx = ev->x - _start_mouse_x;
417 EditorSummary::on_button_release_event (GdkEventButton*)
419 _move_dragging = false;
420 _zoom_dragging = false;
421 _editor->_dragging_playhead = false;
426 EditorSummary::on_scroll_event (GdkEventScroll* ev)
430 pair<double, double> xr;
431 pair<double, double> yr;
432 get_editor (&xr, &yr);
437 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
439 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
443 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
445 /* primary-wheel == left-right scrolling */
447 if (ev->direction == GDK_SCROLL_UP) {
450 } else if (ev->direction == GDK_SCROLL_DOWN) {
457 if (ev->direction == GDK_SCROLL_DOWN) {
459 } else if (ev->direction == GDK_SCROLL_UP) {
461 } else if (ev->direction == GDK_SCROLL_LEFT) {
464 } else if (ev->direction == GDK_SCROLL_RIGHT) {
474 /** Set the editor to display a given x range and a y range with the top at a given position.
475 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
476 * x and y parameters are specified in summary coordinates.
479 EditorSummary::set_editor (pair<double,double> const & x, double const y)
481 if (_editor->pending_visual_change.idle_handler_id >= 0) {
483 /* As a side-effect, the Editor's visual change idle handler processes
484 pending GTK events. Hence this motion notify handler can be called
485 in the middle of a visual change idle handler, and if this happens,
486 the queue_visual_change calls below modify the variables that the
487 idle handler is working with. This causes problems. Hence this
488 check. It ensures that we won't modify the pending visual change
489 while a visual change idle handler is in progress. It's not perfect,
490 as it also means that we won't change these variables if an idle handler
491 is merely pending but not executing. But c'est la vie.
497 double y1 = summary_y_to_editor (y);
498 double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
501 double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
503 if (y2 > full_editor_height) {
504 y1 -= y2 - full_editor_height;
511 _editor->reset_x_origin (x.first / _x_scale + _start);
512 _editor->reset_y_origin (y1);
515 ((x.second - x.first) / _x_scale) /
516 _editor->frame_to_unit (_editor->current_page_frames())
519 if (nx != _editor->get_current_zoom ()) {
520 _editor->reset_zoom (nx);
525 EditorSummary::playhead_position_changed (nframes64_t p)
527 if (_session && int (p * _x_scale) != int (_last_playhead)) {
528 set_overlays_dirty ();
533 EditorSummary::summary_y_to_editor (double y) const
536 TrackViewList::const_iterator i = _editor->track_views.begin ();
537 while (i != _editor->track_views.end()) {
538 double const h = (*i)->effective_height ();
539 if (y < _track_height) {
541 return ey + y * h / _track_height;
553 EditorSummary::editor_y_to_summary (double y) const
556 TrackViewList::const_iterator i = _editor->track_views.begin ();
557 while (i != _editor->track_views.end()) {
558 double const h = (*i)->effective_height ();
561 return sy + y * _track_height / h;