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),
47 _move_dragging (false),
49 _zoom_dragging (false)
59 EditorSummary::set_session (Session* s)
63 Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
65 _session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
66 _session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
67 _session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
68 _editor->playhead_cursor->PositionChanged.connect (mem_fun (*this, &EditorSummary::playhead_position_changed));
74 EditorSummary::~EditorSummary ()
77 gdk_pixmap_unref (_pixmap);
81 /** Handle an expose event.
82 * @param event Event from GTK.
85 EditorSummary::on_expose_event (GdkEventExpose* event)
87 /* Render the regions pixmap */
89 Gdk::Rectangle const exposure (
90 event->area.x, event->area.y, event->area.width, event->area.height
93 Gdk::Rectangle r = exposure;
94 Gdk::Rectangle content (0, 0, _width, _height);
96 r.intersect (content, intersects);
100 GdkPixmap* p = get_pixmap (get_window()->gobj ());
103 get_window()->gobj(),
104 get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
115 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
117 /* Render the view rectangle */
119 pair<double, double> x;
120 pair<double, double> y;
123 cairo_move_to (cr, x.first, y.first);
124 cairo_line_to (cr, x.second, y.first);
125 cairo_line_to (cr, x.second, y.second);
126 cairo_line_to (cr, x.first, y.second);
127 cairo_line_to (cr, x.first, y.first);
128 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
129 cairo_fill_preserve (cr);
130 cairo_set_line_width (cr, 1);
131 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
136 cairo_set_line_width (cr, 1);
137 /* XXX: colour should be set from configuration file */
138 cairo_set_source_rgba (cr, 1, 0, 0, 1);
140 double const p = _editor->playhead_cursor->current_frame * _x_scale;
141 cairo_move_to (cr, p, 0);
142 cairo_line_to (cr, p, _height);
151 /** @param drawable GDK drawable.
152 * @return pixmap for the regions.
155 EditorSummary::get_pixmap (GdkDrawable* drawable)
157 if (_regions_dirty) {
160 gdk_pixmap_unref (_pixmap);
162 _pixmap = gdk_pixmap_new (drawable, _width, _height, -1);
164 cairo_t* cr = gdk_cairo_create (_pixmap);
168 _regions_dirty = false;
174 /** Render the required regions to a cairo context.
178 EditorSummary::render (cairo_t* cr)
186 cairo_set_source_rgb (cr, 0, 0, 0);
187 cairo_rectangle (cr, 0, 0, _width, _height);
190 /* compute total height of all tracks */
194 for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
195 int const t = (*i)->effective_height ();
197 max_height = max (max_height, t);
200 nframes_t const start = _session->current_start_frame ();
201 _x_scale = static_cast<double> (_width) / (_session->current_end_frame() - start);
202 _y_scale = static_cast<double> (_height) / h;
204 /* tallest a region should ever be in the summary, in pixels */
205 int const tallest_region_pixels = 4;
207 if (max_height * _y_scale > tallest_region_pixels) {
208 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
214 for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
215 StreamView* s = (*i)->view ();
218 double const h = (*i)->effective_height () * _y_scale;
219 cairo_set_line_width (cr, h);
221 s->foreach_regionview (bind (
222 mem_fun (*this, &EditorSummary::render_region),
233 /** Render a region for the summary.
234 * @param r Region view.
235 * @param cr Cairo context.
236 * @param start Frame offset that the summary starts at.
237 * @param y y coordinate to render at.
240 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
242 uint32_t const c = r->get_fill_color ();
243 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
245 cairo_move_to (cr, (r->region()->position() - start) * _x_scale, y);
246 cairo_line_to (cr, ((r->region()->position() - start + r->region()->length())) * _x_scale, y);
250 /** Set the summary so that the whole thing will be re-rendered next time it is required */
252 EditorSummary::set_dirty ()
254 ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_dirty));
256 _regions_dirty = true;
260 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
262 EditorSummary::set_overlays_dirty ()
264 ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_overlays_dirty));
268 /** Handle a size request.
269 * @param req GTK requisition
272 EditorSummary::on_size_request (Gtk::Requisition *req)
274 /* Use a dummy, small width and the actual height that we want */
276 req->height = _height;
279 /** Handle a size allocation.
280 * @param alloc GTK allocation.
283 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
285 Gtk::EventBox::on_size_allocate (alloc);
287 _width = alloc.get_width ();
288 _height = alloc.get_height ();
294 EditorSummary::centre_on_click (GdkEventButton* ev)
296 pair<double, double> xr;
297 pair<double, double> yr;
298 get_editor (&xr, &yr);
300 double const w = xr.second - xr.first;
301 double const h = yr.second - yr.first;
303 xr.first = ev->x - w / 2;
304 xr.second = ev->x + w / 2;
305 yr.first = ev->y - h / 2;
306 yr.second = ev->y + h / 2;
311 } else if (xr.second > _width) {
313 xr.first = _width - w;
319 } else if (yr.second > _height) {
321 yr.first = _height - h;
327 /** Handle a button press.
328 * @param ev GTK event.
331 EditorSummary::on_button_press_event (GdkEventButton* ev)
333 if (ev->button == 1) {
335 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
337 /* secondary-modifier-click: locate playhead */
339 _session->request_locate (ev->x / _x_scale + _session->current_start_frame());
344 pair<double, double> xr;
345 pair<double, double> yr;
346 get_editor (&xr, &yr);
348 if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
350 _start_editor_x = xr;
351 _start_editor_y = yr;
352 _start_mouse_x = ev->x;
353 _start_mouse_y = ev->y;
355 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
357 /* modifier-click inside the view rectangle: start a zoom drag */
359 double const hx = (xr.first + xr.second) * 0.5;
360 _zoom_left = ev->x < hx;
361 _zoom_dragging = true;
362 _editor->_dragging_playhead = true;
364 /* In theory, we could support vertical dragging, which logically
365 might scale track heights in order to make the editor reflect
366 the dragged viewbox. However, having tried this:
369 c) it doesn't seem particularly useful, especially with the
370 limited height of the summary
372 So at the moment we don't support that...
377 /* ordinary click inside the view rectangle: start a move drag */
379 _move_dragging = true;
381 _editor->_dragging_playhead = true;
386 /* click outside the view rectangle: centre the view around the mouse click */
387 centre_on_click (ev);
396 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
398 x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
399 x->second = x->first + _editor->current_page_frames() * _x_scale;
401 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
402 y->second = y->first + _editor->canvas_height () * _y_scale;
406 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
408 pair<double, double> xr = _start_editor_x;
409 pair<double, double> yr = _start_editor_y;
411 if (_move_dragging) {
415 xr.first += ev->x - _start_mouse_x;
416 xr.second += ev->x - _start_mouse_x;
417 yr.first += ev->y - _start_mouse_y;
418 yr.second += ev->y - _start_mouse_y;
422 } else if (_zoom_dragging) {
424 double const dx = ev->x - _start_mouse_x;
439 EditorSummary::on_button_release_event (GdkEventButton* ev)
441 if (_move_dragging && !_moved) {
442 centre_on_click (ev);
445 _move_dragging = false;
446 _zoom_dragging = false;
447 _editor->_dragging_playhead = false;
452 EditorSummary::on_scroll_event (GdkEventScroll* ev)
456 pair<double, double> xr;
457 pair<double, double> yr;
458 get_editor (&xr, &yr);
460 double const amount = 8;
462 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
464 if (ev->direction == GDK_SCROLL_UP) {
474 if (ev->direction == GDK_SCROLL_DOWN) {
489 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
491 if (_editor->pending_visual_change.idle_handler_id < 0) {
493 /* As a side-effect, the Editor's visual change idle handler processes
494 pending GTK events. Hence this motion notify handler can be called
495 in the middle of a visual change idle handler, and if this happens,
496 the queue_visual_change calls below modify the variables that the
497 idle handler is working with. This causes problems. Hence the
498 check above. It ensures that we won't modify the pending visual change
499 while a visual change idle handler is in progress. It's not perfect,
500 as it also means that we won't change these variables if an idle handler
501 is merely pending but not executing. But c'est la vie.
504 _editor->reset_x_origin (x.first / _x_scale);
505 _editor->reset_y_origin (y.first / _y_scale);
508 ((x.second - x.first) / _x_scale) /
509 _editor->frame_to_unit (_editor->current_page_frames())
512 if (nx != _editor->get_current_zoom ()) {
513 _editor->reset_zoom (nx);
519 EditorSummary::playhead_position_changed (nframes64_t p)
521 if (int (p * _x_scale) != int (_last_playhead)) {
522 set_overlays_dirty ();