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, boost::bind (&CairoWidget::set_dirty, this));
51 _editor->playhead_cursor->PositionChanged.connect (position_connection, boost::bind (&EditorSummary::playhead_position_changed, this, _1));
54 /** Connect to a session.
58 EditorSummary::set_session (Session* s)
60 EditorComponent::set_session (s);
65 _session->RegionRemoved.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this));
66 _session->StartTimeChanged.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this));
67 _session->EndTimeChanged.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this));
71 /** Handle an expose event.
72 * @param event Event from GTK.
75 EditorSummary::on_expose_event (GdkEventExpose* event)
77 CairoWidget::on_expose_event (event);
83 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
85 /* Render the view rectangle */
87 pair<double, double> x;
88 pair<double, double> y;
91 cairo_move_to (cr, x.first, y.first);
92 cairo_line_to (cr, x.second, y.first);
93 cairo_line_to (cr, x.second, y.second);
94 cairo_line_to (cr, x.first, y.second);
95 cairo_line_to (cr, x.first, y.first);
96 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
97 cairo_fill_preserve (cr);
98 cairo_set_line_width (cr, 1);
99 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
104 cairo_set_line_width (cr, 1);
105 /* XXX: colour should be set from configuration file */
106 cairo_set_source_rgba (cr, 1, 0, 0, 1);
108 double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
109 cairo_move_to (cr, p, 0);
110 cairo_line_to (cr, p, _height);
119 /** Render the required regions to a cairo context.
123 EditorSummary::render (cairo_t* cr)
127 cairo_set_source_rgb (cr, 0, 0, 0);
128 cairo_rectangle (cr, 0, 0, _width, _height);
135 /* compute start and end points for the summary */
137 nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
138 double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
139 _start = theoretical_start > 0 ? theoretical_start : 0;
140 _end = _session->current_end_frame() + session_length * _overhang_fraction;
142 /* compute total height of all tracks */
146 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
147 int const t = (*i)->effective_height ();
149 max_height = max (max_height, t);
152 _x_scale = static_cast<double> (_width) / (_end - _start);
153 _y_scale = static_cast<double> (_height) / h;
155 /* tallest a region should ever be in the summary, in pixels */
156 int const tallest_region_pixels = 4;
158 if (max_height * _y_scale > tallest_region_pixels) {
159 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
166 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
167 StreamView* s = (*i)->view ();
170 double const h = (*i)->effective_height () * _y_scale;
171 cairo_set_line_width (cr, h);
173 s->foreach_regionview (sigc::bind (
174 sigc::mem_fun (*this, &EditorSummary::render_region),
182 /* start and end markers */
184 cairo_set_line_width (cr, 1);
185 cairo_set_source_rgb (cr, 1, 1, 0);
187 double const p = (_session->current_start_frame() - _start) * _x_scale;
188 cairo_move_to (cr, p, 0);
189 cairo_line_to (cr, p, _height);
192 double const q = (_session->current_end_frame() - _start) * _x_scale;
193 cairo_move_to (cr, q, 0);
194 cairo_line_to (cr, q, _height);
198 /** Render a region for the summary.
199 * @param r Region view.
200 * @param cr Cairo context.
201 * @param y y coordinate to render at.
204 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
206 uint32_t const c = r->get_fill_color ();
207 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
209 if (r->region()->position() > _start) {
210 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
212 cairo_move_to (cr, 0, y);
215 if ((r->region()->position() + r->region()->length()) > _start) {
216 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
218 cairo_line_to (cr, 0, y);
224 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
226 EditorSummary::set_overlays_dirty ()
228 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
232 /** Handle a size request.
233 * @param req GTK requisition
236 EditorSummary::on_size_request (Gtk::Requisition *req)
238 /* Use a dummy, small width and the actual height that we want */
245 EditorSummary::centre_on_click (GdkEventButton* ev)
247 pair<double, double> xr;
248 pair<double, double> yr;
249 get_editor (&xr, &yr);
251 double const w = xr.second - xr.first;
252 double const h = yr.second - yr.first;
254 xr.first = ev->x - w / 2;
255 xr.second = ev->x + w / 2;
256 yr.first = ev->y - h / 2;
257 yr.second = ev->y + h / 2;
262 } else if (xr.second > _width) {
264 xr.first = _width - w;
270 } else if (yr.second > _height) {
272 yr.first = _height - h;
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;
295 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
297 /* primary-modifier-click: start a zoom drag */
299 double const hx = (xr.first + xr.second) * 0.5;
300 _zoom_left = ev->x < hx;
301 _zoom_dragging = true;
302 _editor->_dragging_playhead = true;
305 /* In theory, we could support vertical dragging, which logically
306 might scale track heights in order to make the editor reflect
307 the dragged viewbox. However, having tried this:
310 c) it doesn't seem particularly useful, especially with the
311 limited height of the summary
313 So at the moment we don't support that...
317 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
319 /* secondary-modifier-click: locate playhead */
321 _session->request_locate (ev->x / _x_scale + _start);
324 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
326 centre_on_click (ev);
330 /* ordinary click: start a move drag */
332 _move_dragging = true;
334 _editor->_dragging_playhead = true;
342 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
344 x->first = (_editor->leftmost_position () - _start) * _x_scale;
345 x->second = x->first + _editor->current_page_frames() * _x_scale;
347 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
348 y->second = y->first + (_editor->canvas_height () - _editor->get_canvas_timebars_vsize()) * _y_scale;
352 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
354 pair<double, double> xr = _start_editor_x;
355 pair<double, double> yr = _start_editor_y;
357 if (_move_dragging) {
361 xr.first += ev->x - _start_mouse_x;
362 xr.second += ev->x - _start_mouse_x;
363 yr.first += ev->y - _start_mouse_y;
364 yr.second += ev->y - _start_mouse_y;
367 xr.second -= xr.first;
372 yr.second -= yr.first;
378 } else if (_zoom_dragging) {
380 double const dx = ev->x - _start_mouse_x;
395 EditorSummary::on_button_release_event (GdkEventButton*)
397 _move_dragging = false;
398 _zoom_dragging = false;
399 _editor->_dragging_playhead = false;
404 EditorSummary::on_scroll_event (GdkEventScroll* ev)
408 pair<double, double> xr;
409 pair<double, double> yr;
410 get_editor (&xr, &yr);
414 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
416 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
420 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
422 /* primary-wheel == left-right scrolling */
424 if (ev->direction == GDK_SCROLL_UP) {
427 } else if (ev->direction == GDK_SCROLL_DOWN) {
434 if (ev->direction == GDK_SCROLL_DOWN) {
437 } else if (ev->direction == GDK_SCROLL_UP) {
440 } else if (ev->direction == GDK_SCROLL_LEFT) {
443 } else if (ev->direction == GDK_SCROLL_RIGHT) {
454 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
456 if (_editor->pending_visual_change.idle_handler_id < 0) {
458 /* As a side-effect, the Editor's visual change idle handler processes
459 pending GTK events. Hence this motion notify handler can be called
460 in the middle of a visual change idle handler, and if this happens,
461 the queue_visual_change calls below modify the variables that the
462 idle handler is working with. This causes problems. Hence the
463 check above. It ensures that we won't modify the pending visual change
464 while a visual change idle handler is in progress. It's not perfect,
465 as it also means that we won't change these variables if an idle handler
466 is merely pending but not executing. But c'est la vie.
469 /* proposed bottom of the editor with the requested position */
470 double const pb = y.second / _y_scale;
472 /* bottom of the canvas */
473 double const ch = _editor->full_canvas_height - _editor->canvas_timebars_vsize;
475 /* requested y position */
476 double ly = y.first / _y_scale;
478 /* clamp y position so as not to go off the bottom */
487 _editor->reset_x_origin (x.first / _x_scale + _start);
488 _editor->reset_y_origin (ly);
491 ((x.second - x.first) / _x_scale) /
492 _editor->frame_to_unit (_editor->current_page_frames())
495 if (nx != _editor->get_current_zoom ()) {
496 _editor->reset_zoom (nx);
502 EditorSummary::playhead_position_changed (nframes64_t p)
504 if (_session && int (p * _x_scale) != int (_last_playhead)) {
505 set_overlays_dirty ();