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 = 12;
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 */
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 pair<double, double> xr;
336 pair<double, double> yr;
337 get_editor (&xr, &yr);
339 if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
341 _start_editor_x = xr;
342 _start_editor_y = yr;
343 _start_mouse_x = ev->x;
344 _start_mouse_y = ev->y;
346 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
348 /* modifier-click inside the view rectangle: start a zoom drag */
350 double const hx = (xr.first + xr.second) * 0.5;
351 _zoom_left = ev->x < hx;
352 _zoom_dragging = true;
353 _editor->_dragging_playhead = true;
355 /* In theory, we could support vertical dragging, which logically
356 might scale track heights in order to make the editor reflect
357 the dragged viewbox. However, having tried this:
360 c) it doesn't seem particularly useful, especially with the
361 limited height of the summary
363 So at the moment we don't support that...
368 /* ordinary click inside the view rectangle: start a move drag */
370 _move_dragging = true;
372 _editor->_dragging_playhead = true;
377 /* click outside the view rectangle: centre the view around the mouse click */
378 centre_on_click (ev);
386 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
388 x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
389 x->second = x->first + _editor->current_page_frames() * _x_scale;
391 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
392 y->second = y->first + _editor->canvas_height () * _y_scale;
396 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
398 pair<double, double> xr = _start_editor_x;
399 pair<double, double> yr = _start_editor_y;
401 if (_move_dragging) {
405 xr.first += ev->x - _start_mouse_x;
406 xr.second += ev->x - _start_mouse_x;
407 yr.first += ev->y - _start_mouse_y;
408 yr.second += ev->y - _start_mouse_y;
412 } else if (_zoom_dragging) {
414 double const dx = ev->x - _start_mouse_x;
429 EditorSummary::on_button_release_event (GdkEventButton* ev)
431 if (_move_dragging && !_moved) {
432 centre_on_click (ev);
435 _move_dragging = false;
436 _zoom_dragging = false;
437 _editor->_dragging_playhead = false;
442 EditorSummary::on_scroll_event (GdkEventScroll* ev)
446 pair<double, double> xr;
447 pair<double, double> yr;
448 get_editor (&xr, &yr);
450 double const amount = 8;
452 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
454 if (ev->direction == GDK_SCROLL_UP) {
464 if (ev->direction == GDK_SCROLL_DOWN) {
479 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
481 if (_editor->pending_visual_change.idle_handler_id < 0) {
483 /* As a side-effect, the Editor's visual change idle handler processes
484 pending GTK events. Hence this motion notify handler can be called
485 in the middle of a visual change idle handler, and if this happens,
486 the queue_visual_change calls below modify the variables that the
487 idle handler is working with. This causes problems. Hence the
488 check above. It ensures that we won't modify the pending visual change
489 while a visual change idle handler is in progress. It's not perfect,
490 as it also means that we won't change these variables if an idle handler
491 is merely pending but not executing. But c'est la vie.
494 _editor->reset_x_origin (x.first / _x_scale);
495 _editor->reset_y_origin (y.first / _y_scale);
498 ((x.second - x.first) / _x_scale) /
499 _editor->frame_to_unit (_editor->current_page_frames())
502 if (nx != _editor->get_current_zoom ()) {
503 _editor->reset_zoom (nx);
509 EditorSummary::playhead_position_changed (nframes64_t p)
511 if (int (p * _x_scale) != int (_last_playhead)) {
512 set_overlays_dirty ();