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 if (_end != _start) {
157 _x_scale = static_cast<double> (_width) / (_end - _start);
161 _y_scale = static_cast<double> (_height) / h;
163 /* tallest a region should ever be in the summary, in pixels */
164 int const tallest_region_pixels = _height / 16;
166 if (max_height * _y_scale > tallest_region_pixels) {
167 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
173 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
174 StreamView* s = (*i)->view ();
177 double const h = (*i)->effective_height () * _y_scale;
178 cairo_set_line_width (cr, h);
180 s->foreach_regionview (sigc::bind (
181 sigc::mem_fun (*this, &EditorSummary::render_region),
189 /* start and end markers */
191 cairo_set_line_width (cr, 1);
192 cairo_set_source_rgb (cr, 1, 1, 0);
194 double const p = (_session->current_start_frame() - _start) * _x_scale;
195 cairo_move_to (cr, p, 0);
196 cairo_line_to (cr, p, _height);
199 double const q = (_session->current_end_frame() - _start) * _x_scale;
200 cairo_move_to (cr, q, 0);
201 cairo_line_to (cr, q, _height);
205 /** Render a region for the summary.
206 * @param r Region view.
207 * @param cr Cairo context.
208 * @param y y coordinate to render at.
211 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
213 uint32_t const c = r->get_fill_color ();
214 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
216 if (r->region()->position() > _start) {
217 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
219 cairo_move_to (cr, 0, y);
222 if ((r->region()->position() + r->region()->length()) > _start) {
223 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
225 cairo_line_to (cr, 0, y);
231 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
233 EditorSummary::set_overlays_dirty ()
235 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
239 /** Handle a size request.
240 * @param req GTK requisition
243 EditorSummary::on_size_request (Gtk::Requisition *req)
245 /* Use a dummy, small width and the actual height that we want */
252 EditorSummary::centre_on_click (GdkEventButton* ev)
254 pair<double, double> xr;
255 pair<double, double> yr;
256 get_editor (&xr, &yr);
258 double const w = xr.second - xr.first;
259 double const h = yr.second - yr.first;
261 xr.first = ev->x - w / 2;
262 xr.second = ev->x + w / 2;
263 yr.first = ev->y - h / 2;
264 yr.second = ev->y + h / 2;
269 } else if (xr.second > _width) {
271 xr.first = _width - w;
277 } else if (yr.second > _height) {
279 yr.first = _height - h;
285 /** Handle a button press.
286 * @param ev GTK event.
289 EditorSummary::on_button_press_event (GdkEventButton* ev)
291 if (ev->button == 1) {
293 pair<double, double> xr;
294 pair<double, double> yr;
295 get_editor (&xr, &yr);
297 _start_editor_x = xr;
298 _start_editor_y = yr;
299 _start_mouse_x = ev->x;
300 _start_mouse_y = ev->y;
303 _start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second &&
304 _start_editor_y.first <= _start_mouse_y && _start_mouse_y <= _start_editor_y.second
307 _start_position = IN_VIEWBOX;
309 } else if (_start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second) {
311 _start_position = BELOW_OR_ABOVE_VIEWBOX;
315 _start_position = TO_LEFT_OR_RIGHT_OF_VIEWBOX;
318 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
320 /* primary-modifier-click: start a zoom drag */
322 double const hx = (xr.first + xr.second) * 0.5;
323 _zoom_left = ev->x < hx;
324 _zoom_dragging = true;
325 _editor->_dragging_playhead = true;
328 /* In theory, we could support vertical dragging, which logically
329 might scale track heights in order to make the editor reflect
330 the dragged viewbox. However, having tried this:
333 c) it doesn't seem particularly useful, especially with the
334 limited height of the summary
336 So at the moment we don't support that...
340 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
342 /* secondary-modifier-click: locate playhead */
344 _session->request_locate (ev->x / _x_scale + _start);
347 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
349 centre_on_click (ev);
353 /* ordinary click: start a move drag */
355 _move_dragging = true;
357 _editor->_dragging_playhead = true;
365 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
367 x->first = (_editor->leftmost_position () - _start) * _x_scale;
368 x->second = x->first + _editor->current_page_frames() * _x_scale;
370 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
371 y->second = y->first + (_editor->canvas_height () - _editor->get_canvas_timebars_vsize()) * _y_scale;
375 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
377 pair<double, double> xr = _start_editor_x;
378 pair<double, double> yr = _start_editor_y;
380 if (_move_dragging) {
384 /* don't alter x if we clicked outside and above or below the viewbox */
385 if (_start_position == IN_VIEWBOX || _start_position == TO_LEFT_OR_RIGHT_OF_VIEWBOX) {
386 xr.first += ev->x - _start_mouse_x;
387 xr.second += ev->x - _start_mouse_x;
390 /* don't alter y if we clicked outside and to the left or right of the viewbox */
391 if (_start_position == IN_VIEWBOX || _start_position == BELOW_OR_ABOVE_VIEWBOX) {
392 yr.first += ev->y - _start_mouse_y;
393 yr.second += ev->y - _start_mouse_y;
397 xr.second -= xr.first;
402 yr.second -= yr.first;
408 } else if (_zoom_dragging) {
410 double const dx = ev->x - _start_mouse_x;
425 EditorSummary::on_button_release_event (GdkEventButton*)
427 _move_dragging = false;
428 _zoom_dragging = false;
429 _editor->_dragging_playhead = false;
434 EditorSummary::on_scroll_event (GdkEventScroll* ev)
438 pair<double, double> xr;
439 pair<double, double> yr;
440 get_editor (&xr, &yr);
444 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
446 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
450 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
452 /* primary-wheel == left-right scrolling */
454 if (ev->direction == GDK_SCROLL_UP) {
457 } else if (ev->direction == GDK_SCROLL_DOWN) {
464 if (ev->direction == GDK_SCROLL_DOWN) {
467 } else if (ev->direction == GDK_SCROLL_UP) {
470 } else if (ev->direction == GDK_SCROLL_LEFT) {
473 } else if (ev->direction == GDK_SCROLL_RIGHT) {
484 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
486 if (_editor->pending_visual_change.idle_handler_id < 0) {
488 /* As a side-effect, the Editor's visual change idle handler processes
489 pending GTK events. Hence this motion notify handler can be called
490 in the middle of a visual change idle handler, and if this happens,
491 the queue_visual_change calls below modify the variables that the
492 idle handler is working with. This causes problems. Hence the
493 check above. It ensures that we won't modify the pending visual change
494 while a visual change idle handler is in progress. It's not perfect,
495 as it also means that we won't change these variables if an idle handler
496 is merely pending but not executing. But c'est la vie.
499 /* proposed bottom of the editor with the requested position */
500 double const pb = y.second / _y_scale;
502 /* bottom of the canvas */
503 double const ch = _editor->full_canvas_height - _editor->canvas_timebars_vsize;
505 /* requested y position */
506 double ly = y.first / _y_scale;
508 /* clamp y position so as not to go off the bottom */
517 _editor->reset_x_origin (x.first / _x_scale + _start);
518 _editor->reset_y_origin (ly);
521 ((x.second - x.first) / _x_scale) /
522 _editor->frame_to_unit (_editor->current_page_frames())
525 if (nx != _editor->get_current_zoom ()) {
526 _editor->reset_zoom (nx);
532 EditorSummary::playhead_position_changed (nframes64_t p)
534 if (_session && int (p * _x_scale) != int (_last_playhead)) {
535 set_overlays_dirty ();