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), gui_context());
51 _editor->playhead_cursor->PositionChanged.connect (position_connection, 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);
65 _session->RegionRemoved.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this), gui_context());
66 _session->StartTimeChanged.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this), gui_context());
67 _session->EndTimeChanged.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this), gui_context());
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 = _height / 16;
158 if (max_height * _y_scale > tallest_region_pixels) {
159 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
165 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
166 StreamView* s = (*i)->view ();
169 double const h = (*i)->effective_height () * _y_scale;
170 cairo_set_line_width (cr, h);
172 s->foreach_regionview (sigc::bind (
173 sigc::mem_fun (*this, &EditorSummary::render_region),
181 /* start and end markers */
183 cairo_set_line_width (cr, 1);
184 cairo_set_source_rgb (cr, 1, 1, 0);
186 double const p = (_session->current_start_frame() - _start) * _x_scale;
187 cairo_move_to (cr, p, 0);
188 cairo_line_to (cr, p, _height);
191 double const q = (_session->current_end_frame() - _start) * _x_scale;
192 cairo_move_to (cr, q, 0);
193 cairo_line_to (cr, q, _height);
197 /** Render a region for the summary.
198 * @param r Region view.
199 * @param cr Cairo context.
200 * @param y y coordinate to render at.
203 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
205 uint32_t const c = r->get_fill_color ();
206 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
208 if (r->region()->position() > _start) {
209 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
211 cairo_move_to (cr, 0, y);
214 if ((r->region()->position() + r->region()->length()) > _start) {
215 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
217 cairo_line_to (cr, 0, y);
223 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
225 EditorSummary::set_overlays_dirty ()
227 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
231 /** Handle a size request.
232 * @param req GTK requisition
235 EditorSummary::on_size_request (Gtk::Requisition *req)
237 /* Use a dummy, small width and the actual height that we want */
244 EditorSummary::centre_on_click (GdkEventButton* ev)
246 pair<double, double> xr;
247 pair<double, double> yr;
248 get_editor (&xr, &yr);
250 double const w = xr.second - xr.first;
251 double const h = yr.second - yr.first;
253 xr.first = ev->x - w / 2;
254 xr.second = ev->x + w / 2;
255 yr.first = ev->y - h / 2;
256 yr.second = ev->y + h / 2;
261 } else if (xr.second > _width) {
263 xr.first = _width - w;
269 } else if (yr.second > _height) {
271 yr.first = _height - h;
277 /** Handle a button press.
278 * @param ev GTK event.
281 EditorSummary::on_button_press_event (GdkEventButton* ev)
283 if (ev->button == 1) {
285 pair<double, double> xr;
286 pair<double, double> yr;
287 get_editor (&xr, &yr);
289 _start_editor_x = xr;
290 _start_editor_y = yr;
291 _start_mouse_x = ev->x;
292 _start_mouse_y = ev->y;
295 _start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second &&
296 _start_editor_y.first <= _start_mouse_y && _start_mouse_y <= _start_editor_y.second
299 _start_position = IN_VIEWBOX;
301 } else if (_start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second) {
303 _start_position = BELOW_OR_ABOVE_VIEWBOX;
307 _start_position = TO_LEFT_OR_RIGHT_OF_VIEWBOX;
310 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
312 /* primary-modifier-click: start a zoom drag */
314 double const hx = (xr.first + xr.second) * 0.5;
315 _zoom_left = ev->x < hx;
316 _zoom_dragging = true;
317 _editor->_dragging_playhead = true;
320 /* In theory, we could support vertical dragging, which logically
321 might scale track heights in order to make the editor reflect
322 the dragged viewbox. However, having tried this:
325 c) it doesn't seem particularly useful, especially with the
326 limited height of the summary
328 So at the moment we don't support that...
332 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
334 /* secondary-modifier-click: locate playhead */
336 _session->request_locate (ev->x / _x_scale + _start);
339 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
341 centre_on_click (ev);
345 /* ordinary click: start a move drag */
347 _move_dragging = true;
349 _editor->_dragging_playhead = true;
357 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
359 x->first = (_editor->leftmost_position () - _start) * _x_scale;
360 x->second = x->first + _editor->current_page_frames() * _x_scale;
362 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
363 y->second = y->first + (_editor->canvas_height () - _editor->get_canvas_timebars_vsize()) * _y_scale;
367 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
369 pair<double, double> xr = _start_editor_x;
370 pair<double, double> yr = _start_editor_y;
372 if (_move_dragging) {
376 /* don't alter x if we clicked outside and above or below the viewbox */
377 if (_start_position == IN_VIEWBOX || _start_position == TO_LEFT_OR_RIGHT_OF_VIEWBOX) {
378 xr.first += ev->x - _start_mouse_x;
379 xr.second += ev->x - _start_mouse_x;
382 /* don't alter y if we clicked outside and to the left or right of the viewbox */
383 if (_start_position == IN_VIEWBOX || _start_position == BELOW_OR_ABOVE_VIEWBOX) {
384 yr.first += ev->y - _start_mouse_y;
385 yr.second += ev->y - _start_mouse_y;
389 xr.second -= xr.first;
394 yr.second -= yr.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);
436 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
438 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
442 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
444 /* primary-wheel == left-right scrolling */
446 if (ev->direction == GDK_SCROLL_UP) {
449 } else if (ev->direction == GDK_SCROLL_DOWN) {
456 if (ev->direction == GDK_SCROLL_DOWN) {
459 } else if (ev->direction == GDK_SCROLL_UP) {
462 } else if (ev->direction == GDK_SCROLL_LEFT) {
465 } else if (ev->direction == GDK_SCROLL_RIGHT) {
476 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
478 if (_editor->pending_visual_change.idle_handler_id < 0) {
480 /* As a side-effect, the Editor's visual change idle handler processes
481 pending GTK events. Hence this motion notify handler can be called
482 in the middle of a visual change idle handler, and if this happens,
483 the queue_visual_change calls below modify the variables that the
484 idle handler is working with. This causes problems. Hence the
485 check above. It ensures that we won't modify the pending visual change
486 while a visual change idle handler is in progress. It's not perfect,
487 as it also means that we won't change these variables if an idle handler
488 is merely pending but not executing. But c'est la vie.
491 /* proposed bottom of the editor with the requested position */
492 double const pb = y.second / _y_scale;
494 /* bottom of the canvas */
495 double const ch = _editor->full_canvas_height - _editor->canvas_timebars_vsize;
497 /* requested y position */
498 double ly = y.first / _y_scale;
500 /* clamp y position so as not to go off the bottom */
509 _editor->reset_x_origin (x.first / _x_scale + _start);
510 _editor->reset_y_origin (ly);
513 ((x.second - x.first) / _x_scale) /
514 _editor->frame_to_unit (_editor->current_page_frames())
517 if (nx != _editor->get_current_zoom ()) {
518 _editor->reset_zoom (nx);
524 EditorSummary::playhead_position_changed (nframes64_t p)
526 if (_session && int (p * _x_scale) != int (_last_playhead)) {
527 set_overlays_dirty ();