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 x and y scale */
148 if (_end != _start) {
149 _x_scale = static_cast<double> (_width) / (_end - _start);
155 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
156 h += (*i)->effective_height ();
159 double vh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize();
161 _y_scale = _height / vh;
163 _y_scale = _height / h;
166 /* render tracks and regions */
169 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
171 double const h = (*i)->effective_height () * _y_scale;
172 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
173 cairo_set_line_width (cr, h - 2);
174 cairo_move_to (cr, 0, y + h / 2);
175 cairo_line_to (cr, _width, y + h / 2);
178 StreamView* s = (*i)->view ();
181 cairo_set_line_width (cr, h * 0.6);
183 s->foreach_regionview (sigc::bind (
184 sigc::mem_fun (*this, &EditorSummary::render_region),
192 /* start and end markers */
194 cairo_set_line_width (cr, 1);
195 cairo_set_source_rgb (cr, 1, 1, 0);
197 double const p = (_session->current_start_frame() - _start) * _x_scale;
198 cairo_move_to (cr, p, 0);
199 cairo_line_to (cr, p, _height);
202 double const q = (_session->current_end_frame() - _start) * _x_scale;
203 cairo_move_to (cr, q, 0);
204 cairo_line_to (cr, q, _height);
208 /** Render a region for the summary.
209 * @param r Region view.
210 * @param cr Cairo context.
211 * @param y y coordinate to render at.
214 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
216 uint32_t const c = r->get_fill_color ();
217 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
219 if (r->region()->position() > _start) {
220 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
222 cairo_move_to (cr, 0, y);
225 if ((r->region()->position() + r->region()->length()) > _start) {
226 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
228 cairo_line_to (cr, 0, y);
234 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
236 EditorSummary::set_overlays_dirty ()
238 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
242 /** Handle a size request.
243 * @param req GTK requisition
246 EditorSummary::on_size_request (Gtk::Requisition *req)
248 /* Use a dummy, small width and the actual height that we want */
255 EditorSummary::centre_on_click (GdkEventButton* ev)
257 pair<double, double> xr;
258 pair<double, double> yr;
259 get_editor (&xr, &yr);
261 double const w = xr.second - xr.first;
262 double const h = yr.second - yr.first;
264 xr.first = ev->x - w / 2;
265 xr.second = ev->x + w / 2;
266 yr.first = ev->y - h / 2;
267 yr.second = ev->y + h / 2;
272 } else if (xr.second > _width) {
274 xr.first = _width - w;
280 } else if (yr.second > _height) {
282 yr.first = _height - h;
288 /** Handle a button press.
289 * @param ev GTK event.
292 EditorSummary::on_button_press_event (GdkEventButton* ev)
294 if (ev->button == 1) {
296 pair<double, double> xr;
297 pair<double, double> yr;
298 get_editor (&xr, &yr);
300 _start_editor_x = xr;
301 _start_editor_y = yr;
302 _start_mouse_x = ev->x;
303 _start_mouse_y = ev->y;
306 _start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second &&
307 _start_editor_y.first <= _start_mouse_y && _start_mouse_y <= _start_editor_y.second
310 _start_position = IN_VIEWBOX;
312 } else if (_start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second) {
314 _start_position = BELOW_OR_ABOVE_VIEWBOX;
318 _start_position = TO_LEFT_OR_RIGHT_OF_VIEWBOX;
321 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
323 /* primary-modifier-click: start a zoom drag */
325 double const hx = (xr.first + xr.second) * 0.5;
326 _zoom_left = ev->x < hx;
327 _zoom_dragging = true;
328 _editor->_dragging_playhead = true;
331 /* In theory, we could support vertical dragging, which logically
332 might scale track heights in order to make the editor reflect
333 the dragged viewbox. However, having tried this:
336 c) it doesn't seem particularly useful, especially with the
337 limited height of the summary
339 So at the moment we don't support that...
343 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
345 /* secondary-modifier-click: locate playhead */
347 _session->request_locate (ev->x / _x_scale + _start);
350 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
352 centre_on_click (ev);
356 /* ordinary click: start a move drag */
358 _move_dragging = true;
360 _editor->_dragging_playhead = true;
368 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
370 x->first = (_editor->leftmost_position () - _start) * _x_scale;
371 x->second = x->first + _editor->current_page_frames() * _x_scale;
373 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
374 y->second = y->first + (_editor->canvas_height () - _editor->get_canvas_timebars_vsize()) * _y_scale;
378 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
380 pair<double, double> xr = _start_editor_x;
381 pair<double, double> yr = _start_editor_y;
383 if (_move_dragging) {
387 /* don't alter x if we clicked outside and above or below the viewbox */
388 if (_start_position == IN_VIEWBOX || _start_position == TO_LEFT_OR_RIGHT_OF_VIEWBOX) {
389 xr.first += ev->x - _start_mouse_x;
390 xr.second += ev->x - _start_mouse_x;
393 /* don't alter y if we clicked outside and to the left or right of the viewbox */
394 if (_start_position == IN_VIEWBOX || _start_position == BELOW_OR_ABOVE_VIEWBOX) {
395 yr.first += ev->y - _start_mouse_y;
396 yr.second += ev->y - _start_mouse_y;
400 xr.second -= xr.first;
405 yr.second -= yr.first;
411 } else if (_zoom_dragging) {
413 double const dx = ev->x - _start_mouse_x;
428 EditorSummary::on_button_release_event (GdkEventButton*)
430 _move_dragging = false;
431 _zoom_dragging = false;
432 _editor->_dragging_playhead = false;
437 EditorSummary::on_scroll_event (GdkEventScroll* ev)
441 pair<double, double> xr;
442 pair<double, double> yr;
443 get_editor (&xr, &yr);
447 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
449 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
453 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
455 /* primary-wheel == left-right scrolling */
457 if (ev->direction == GDK_SCROLL_UP) {
460 } else if (ev->direction == GDK_SCROLL_DOWN) {
467 if (ev->direction == GDK_SCROLL_DOWN) {
470 } else if (ev->direction == GDK_SCROLL_UP) {
473 } else if (ev->direction == GDK_SCROLL_LEFT) {
476 } else if (ev->direction == GDK_SCROLL_RIGHT) {
487 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
489 if (_editor->pending_visual_change.idle_handler_id < 0) {
491 /* As a side-effect, the Editor's visual change idle handler processes
492 pending GTK events. Hence this motion notify handler can be called
493 in the middle of a visual change idle handler, and if this happens,
494 the queue_visual_change calls below modify the variables that the
495 idle handler is working with. This causes problems. Hence the
496 check above. It ensures that we won't modify the pending visual change
497 while a visual change idle handler is in progress. It's not perfect,
498 as it also means that we won't change these variables if an idle handler
499 is merely pending but not executing. But c'est la vie.
502 /* proposed bottom of the editor with the requested position */
503 double const pb = y.second / _y_scale;
505 /* bottom of the canvas */
506 double const ch = _editor->full_canvas_height - _editor->canvas_timebars_vsize;
508 /* requested y position */
509 double ly = y.first / _y_scale;
511 /* clamp y position so as not to go off the bottom */
520 _editor->reset_x_origin (x.first / _x_scale + _start);
521 _editor->reset_y_origin (ly);
524 ((x.second - x.first) / _x_scale) /
525 _editor->frame_to_unit (_editor->current_page_frames())
528 if (nx != _editor->get_current_zoom ()) {
529 _editor->reset_zoom (nx);
535 EditorSummary::playhead_position_changed (nframes64_t p)
537 if (_session && int (p * _x_scale) != int (_last_playhead)) {
538 set_overlays_dirty ();