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)
41 _regions_dirty (true),
46 _move_dragging (false),
48 _zoom_dragging (false)
58 EditorSummary::set_session (Session* s)
62 Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
64 _session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
65 _session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
66 _session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
72 EditorSummary::~EditorSummary ()
75 gdk_pixmap_unref (_pixmap);
79 /** Handle an expose event.
80 * @param event Event from GTK.
83 EditorSummary::on_expose_event (GdkEventExpose* event)
85 /* Render the regions pixmap */
87 Gdk::Rectangle const exposure (
88 event->area.x, event->area.y, event->area.width, event->area.height
91 Gdk::Rectangle r = exposure;
92 Gdk::Rectangle content (0, 0, _width, _height);
94 r.intersect (content, intersects);
98 GdkPixmap* p = get_pixmap (get_window()->gobj ());
101 get_window()->gobj(),
102 get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
113 /* Render the view rectangle */
115 pair<double, double> x;
116 pair<double, double> y;
119 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
121 cairo_move_to (cr, x.first, y.first);
122 cairo_line_to (cr, x.second, y.first);
123 cairo_line_to (cr, x.second, y.second);
124 cairo_line_to (cr, x.first, y.second);
125 cairo_line_to (cr, x.first, y.first);
126 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
127 cairo_fill_preserve (cr);
128 cairo_set_line_width (cr, 1);
129 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
137 /** @param drawable GDK drawable.
138 * @return pixmap for the regions.
141 EditorSummary::get_pixmap (GdkDrawable* drawable)
143 if (_regions_dirty) {
146 gdk_pixmap_unref (_pixmap);
148 _pixmap = gdk_pixmap_new (drawable, _width, _height, -1);
150 cairo_t* cr = gdk_cairo_create (_pixmap);
154 _regions_dirty = false;
160 /** Render the required regions to a cairo context.
164 EditorSummary::render (cairo_t* cr)
172 cairo_set_source_rgb (cr, 0, 0, 0);
173 cairo_rectangle (cr, 0, 0, _width, _height);
176 /* compute total height of all tracks */
180 for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
181 int const t = (*i)->effective_height ();
183 max_height = max (max_height, t);
186 nframes_t const start = _session->current_start_frame ();
187 _x_scale = static_cast<double> (_width) / (_session->current_end_frame() - start);
188 _y_scale = static_cast<double> (_height) / h;
190 /* tallest a region should ever be in the summary, in pixels */
191 int const tallest_region_pixels = 12;
193 if (max_height * _y_scale > tallest_region_pixels) {
194 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
200 for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
201 StreamView* s = (*i)->view ();
204 double const h = (*i)->effective_height () * _y_scale;
205 cairo_set_line_width (cr, h);
207 s->foreach_regionview (bind (
208 mem_fun (*this, &EditorSummary::render_region),
219 /** Render a region for the summary.
220 * @param r Region view.
221 * @param cr Cairo context.
222 * @param start Frame offset that the summary starts at.
223 * @param y y coordinate to render at.
226 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
228 uint32_t const c = r->get_fill_color ();
229 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
231 cairo_move_to (cr, (r->region()->position() - start) * _x_scale, y);
232 cairo_line_to (cr, ((r->region()->position() - start + r->region()->length())) * _x_scale, y);
236 /** Set the summary so that the whole thing will be re-rendered next time it is required */
238 EditorSummary::set_dirty ()
240 ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_dirty));
242 _regions_dirty = true;
246 /** Set the summary so that just the view boundary markers will be re-rendered */
248 EditorSummary::set_bounds_dirty ()
250 ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_bounds_dirty));
254 /** Handle a size request.
255 * @param req GTK requisition
258 EditorSummary::on_size_request (Gtk::Requisition *req)
260 /* Use a dummy, small width and the actual height that we want */
265 /** Handle a size allocation.
266 * @param alloc GTK allocation.
269 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
271 Gtk::EventBox::on_size_allocate (alloc);
273 _width = alloc.get_width ();
274 _height = alloc.get_height ();
280 EditorSummary::centre_on_click (GdkEventButton* ev)
282 pair<double, double> xr;
283 pair<double, double> yr;
284 get_editor (&xr, &yr);
286 double const w = xr.second - xr.first;
287 double const h = yr.second - yr.first;
289 xr.first = ev->x - w / 2;
290 xr.second = ev->x + w / 2;
291 yr.first = ev->y - h / 2;
292 yr.second = ev->y + h / 2;
297 } else if (xr.second > _width) {
299 xr.first = _width - w;
305 } else if (yr.second > _height) {
307 yr.first = _height - h;
313 /** Handle a button press.
314 * @param ev GTK event.
317 EditorSummary::on_button_press_event (GdkEventButton* ev)
319 if (ev->button == 1) {
321 pair<double, double> xr;
322 pair<double, double> yr;
323 get_editor (&xr, &yr);
325 if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
327 _start_editor_x = xr;
328 _start_editor_y = yr;
329 _start_mouse_x = ev->x;
330 _start_mouse_y = ev->y;
332 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
334 /* modifier-click inside the view rectangle: start a zoom drag */
336 double const hx = (xr.first + xr.second) * 0.5;
337 _zoom_left = ev->x < hx;
338 _zoom_dragging = true;
339 _editor->_dragging_playhead = true;
341 /* In theory, we could support vertical dragging, which logically
342 might scale track heights in order to make the editor reflect
343 the dragged viewbox. However, having tried this:
346 c) it doesn't seem particularly useful, especially with the
347 limited height of the summary
349 So at the moment we don't support that...
354 /* ordinary click inside the view rectangle: start a move drag */
356 _move_dragging = true;
358 _editor->_dragging_playhead = true;
363 /* click outside the view rectangle: centre the view around the mouse click */
364 centre_on_click (ev);
372 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
374 x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
375 x->second = x->first + _editor->current_page_frames() * _x_scale;
377 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
378 y->second = y->first + _editor->canvas_height () * _y_scale;
382 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
384 pair<double, double> xr = _start_editor_x;
385 pair<double, double> yr = _start_editor_y;
387 if (_move_dragging) {
391 xr.first += ev->x - _start_mouse_x;
392 xr.second += ev->x - _start_mouse_x;
393 yr.first += ev->y - _start_mouse_y;
394 yr.second += ev->y - _start_mouse_y;
398 } else if (_zoom_dragging) {
400 double const dx = ev->x - _start_mouse_x;
415 EditorSummary::on_button_release_event (GdkEventButton* ev)
417 if (_move_dragging && !_moved) {
418 centre_on_click (ev);
421 _move_dragging = false;
422 _zoom_dragging = false;
423 _editor->_dragging_playhead = false;
428 EditorSummary::on_scroll_event (GdkEventScroll* ev)
432 pair<double, double> xr;
433 pair<double, double> yr;
434 get_editor (&xr, &yr);
436 double const amount = 8;
438 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
440 if (ev->direction == GDK_SCROLL_UP) {
450 if (ev->direction == GDK_SCROLL_DOWN) {
464 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
466 if (_editor->pending_visual_change.idle_handler_id < 0) {
468 /* As a side-effect, the Editor's visual change idle handler processes
469 pending GTK events. Hence this motion notify handler can be called
470 in the middle of a visual change idle handler, and if this happens,
471 the queue_visual_change calls below modify the variables that the
472 idle handler is working with. This causes problems. Hence the
473 check above. It ensures that we won't modify the pending visual change
474 while a visual change idle handler is in progress. It's not perfect,
475 as it also means that we won't change these variables if an idle handler
476 is merely pending but not executing. But c'est la vie.
479 _editor->reset_x_origin (x.first / _x_scale);
480 _editor->reset_y_origin (y.first / _y_scale);
483 ((x.second - x.first) / _x_scale) /
484 _editor->frame_to_unit (_editor->current_page_frames())
487 if (nx != _editor->get_current_zoom ()) {
488 _editor->reset_zoom (nx);