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 total height of all tracks */
150 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
151 int const t = (*i)->effective_height ();
153 max_height = max (max_height, t);
156 _x_scale = static_cast<double> (_width) / (_end - _start);
157 _y_scale = static_cast<double> (_height) / h;
159 /* tallest a region should ever be in the summary, in pixels */
160 int const tallest_region_pixels = _height / 16;
162 if (max_height * _y_scale > tallest_region_pixels) {
163 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
169 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
170 StreamView* s = (*i)->view ();
173 double const h = (*i)->effective_height () * _y_scale;
174 cairo_set_line_width (cr, h);
176 s->foreach_regionview (sigc::bind (
177 sigc::mem_fun (*this, &EditorSummary::render_region),
185 /* start and end markers */
187 cairo_set_line_width (cr, 1);
188 cairo_set_source_rgb (cr, 1, 1, 0);
190 double const p = (_session->current_start_frame() - _start) * _x_scale;
191 cairo_move_to (cr, p, 0);
192 cairo_line_to (cr, p, _height);
195 double const q = (_session->current_end_frame() - _start) * _x_scale;
196 cairo_move_to (cr, q, 0);
197 cairo_line_to (cr, q, _height);
201 /** Render a region for the summary.
202 * @param r Region view.
203 * @param cr Cairo context.
204 * @param y y coordinate to render at.
207 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
209 uint32_t const c = r->get_fill_color ();
210 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
212 if (r->region()->position() > _start) {
213 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
215 cairo_move_to (cr, 0, y);
218 if ((r->region()->position() + r->region()->length()) > _start) {
219 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
221 cairo_line_to (cr, 0, y);
227 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
229 EditorSummary::set_overlays_dirty ()
231 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
235 /** Handle a size request.
236 * @param req GTK requisition
239 EditorSummary::on_size_request (Gtk::Requisition *req)
241 /* Use a dummy, small width and the actual height that we want */
248 EditorSummary::centre_on_click (GdkEventButton* ev)
250 pair<double, double> xr;
251 pair<double, double> yr;
252 get_editor (&xr, &yr);
254 double const w = xr.second - xr.first;
255 double const h = yr.second - yr.first;
257 xr.first = ev->x - w / 2;
258 xr.second = ev->x + w / 2;
259 yr.first = ev->y - h / 2;
260 yr.second = ev->y + h / 2;
265 } else if (xr.second > _width) {
267 xr.first = _width - w;
273 } else if (yr.second > _height) {
275 yr.first = _height - h;
281 /** Handle a button press.
282 * @param ev GTK event.
285 EditorSummary::on_button_press_event (GdkEventButton* ev)
287 if (ev->button == 1) {
289 pair<double, double> xr;
290 pair<double, double> yr;
291 get_editor (&xr, &yr);
293 _start_editor_x = xr;
294 _start_editor_y = yr;
295 _start_mouse_x = ev->x;
296 _start_mouse_y = ev->y;
299 _start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second &&
300 _start_editor_y.first <= _start_mouse_y && _start_mouse_y <= _start_editor_y.second
303 _start_position = IN_VIEWBOX;
305 } else if (_start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second) {
307 _start_position = BELOW_OR_ABOVE_VIEWBOX;
311 _start_position = TO_LEFT_OR_RIGHT_OF_VIEWBOX;
314 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
316 /* primary-modifier-click: start a zoom drag */
318 double const hx = (xr.first + xr.second) * 0.5;
319 _zoom_left = ev->x < hx;
320 _zoom_dragging = true;
321 _editor->_dragging_playhead = true;
324 /* In theory, we could support vertical dragging, which logically
325 might scale track heights in order to make the editor reflect
326 the dragged viewbox. However, having tried this:
329 c) it doesn't seem particularly useful, especially with the
330 limited height of the summary
332 So at the moment we don't support that...
336 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
338 /* secondary-modifier-click: locate playhead */
340 _session->request_locate (ev->x / _x_scale + _start);
343 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
345 centre_on_click (ev);
349 /* ordinary click: start a move drag */
351 _move_dragging = true;
353 _editor->_dragging_playhead = true;
361 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
363 x->first = (_editor->leftmost_position () - _start) * _x_scale;
364 x->second = x->first + _editor->current_page_frames() * _x_scale;
366 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
367 y->second = y->first + (_editor->canvas_height () - _editor->get_canvas_timebars_vsize()) * _y_scale;
371 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
373 pair<double, double> xr = _start_editor_x;
374 pair<double, double> yr = _start_editor_y;
376 if (_move_dragging) {
380 /* don't alter x if we clicked outside and above or below the viewbox */
381 if (_start_position == IN_VIEWBOX || _start_position == TO_LEFT_OR_RIGHT_OF_VIEWBOX) {
382 xr.first += ev->x - _start_mouse_x;
383 xr.second += ev->x - _start_mouse_x;
386 /* don't alter y if we clicked outside and to the left or right of the viewbox */
387 if (_start_position == IN_VIEWBOX || _start_position == BELOW_OR_ABOVE_VIEWBOX) {
388 yr.first += ev->y - _start_mouse_y;
389 yr.second += ev->y - _start_mouse_y;
393 xr.second -= xr.first;
398 yr.second -= yr.first;
404 } else if (_zoom_dragging) {
406 double const dx = ev->x - _start_mouse_x;
421 EditorSummary::on_button_release_event (GdkEventButton*)
423 _move_dragging = false;
424 _zoom_dragging = false;
425 _editor->_dragging_playhead = false;
430 EditorSummary::on_scroll_event (GdkEventScroll* ev)
434 pair<double, double> xr;
435 pair<double, double> yr;
436 get_editor (&xr, &yr);
440 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
442 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
446 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
448 /* primary-wheel == left-right scrolling */
450 if (ev->direction == GDK_SCROLL_UP) {
453 } else if (ev->direction == GDK_SCROLL_DOWN) {
460 if (ev->direction == GDK_SCROLL_DOWN) {
463 } else if (ev->direction == GDK_SCROLL_UP) {
466 } else if (ev->direction == GDK_SCROLL_LEFT) {
469 } else if (ev->direction == GDK_SCROLL_RIGHT) {
480 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
482 if (_editor->pending_visual_change.idle_handler_id < 0) {
484 /* As a side-effect, the Editor's visual change idle handler processes
485 pending GTK events. Hence this motion notify handler can be called
486 in the middle of a visual change idle handler, and if this happens,
487 the queue_visual_change calls below modify the variables that the
488 idle handler is working with. This causes problems. Hence the
489 check above. It ensures that we won't modify the pending visual change
490 while a visual change idle handler is in progress. It's not perfect,
491 as it also means that we won't change these variables if an idle handler
492 is merely pending but not executing. But c'est la vie.
495 /* proposed bottom of the editor with the requested position */
496 double const pb = y.second / _y_scale;
498 /* bottom of the canvas */
499 double const ch = _editor->full_canvas_height - _editor->canvas_timebars_vsize;
501 /* requested y position */
502 double ly = y.first / _y_scale;
504 /* clamp y position so as not to go off the bottom */
513 _editor->reset_x_origin (x.first / _x_scale + _start);
514 _editor->reset_y_origin (ly);
517 ((x.second - x.first) / _x_scale) /
518 _editor->frame_to_unit (_editor->current_page_frames())
521 if (nx != _editor->get_current_zoom ()) {
522 _editor->reset_zoom (nx);
528 EditorSummary::playhead_position_changed (nframes64_t p)
530 if (_session && int (p * _x_scale) != int (_last_playhead)) {
531 set_overlays_dirty ();