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"
32 using namespace ARDOUR;
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)
53 /** Connect to a session.
57 EditorSummary::connect_to_session (Session* s)
59 EditorComponent::connect_to_session (s);
61 Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
63 _session_connections.push_back (_session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty))));
64 _session_connections.push_back (_session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty)));
65 _session_connections.push_back (_session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty)));
66 _editor->playhead_cursor->PositionChanged.connect (mem_fun (*this, &EditorSummary::playhead_position_changed));
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 (PublicEditor::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 (PublicEditor::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 (bind (
174 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 (mem_fun (*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);
412 double const amount = 8;
414 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
416 if (ev->direction == GDK_SCROLL_UP) {
426 if (ev->direction == GDK_SCROLL_DOWN) {
441 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
443 if (_editor->pending_visual_change.idle_handler_id < 0) {
445 /* As a side-effect, the Editor's visual change idle handler processes
446 pending GTK events. Hence this motion notify handler can be called
447 in the middle of a visual change idle handler, and if this happens,
448 the queue_visual_change calls below modify the variables that the
449 idle handler is working with. This causes problems. Hence the
450 check above. It ensures that we won't modify the pending visual change
451 while a visual change idle handler is in progress. It's not perfect,
452 as it also means that we won't change these variables if an idle handler
453 is merely pending but not executing. But c'est la vie.
456 /* proposed bottom of the editor with the requested position */
457 double const pb = y.second / _y_scale;
459 /* bottom of the canvas */
460 double const ch = _editor->full_canvas_height - _editor->canvas_timebars_vsize;
462 /* requested y position */
463 double ly = y.first / _y_scale;
465 /* clamp y position so as not to go off the bottom */
474 _editor->reset_x_origin (x.first / _x_scale + _start);
475 _editor->reset_y_origin (ly);
478 ((x.second - x.first) / _x_scale) /
479 _editor->frame_to_unit (_editor->current_page_frames())
482 if (nx != _editor->get_current_zoom ()) {
483 _editor->reset_zoom (nx);
489 EditorSummary::playhead_position_changed (nframes64_t p)
491 if (int (p * _x_scale) != int (_last_playhead)) {
492 set_overlays_dirty ();