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 */
335 _zoom_position = NONE;
337 double const x1 = xr.first + (xr.second - xr.first) * 0.33;
338 double const x2 = xr.first + (xr.second - xr.first) * 0.67;
339 double const y1 = yr.first + (yr.second - yr.first) * 0.33;
340 double const y2 = yr.first + (yr.second - yr.first) * 0.67;
345 _zoom_position = TOP_LEFT;
346 } else if (ev->y > y2) {
347 _zoom_position = BOTTOM_LEFT;
349 _zoom_position = LEFT;
352 } else if (ev->x > x2) {
355 _zoom_position = TOP_RIGHT;
356 } else if (ev->y > y2) {
357 _zoom_position = BOTTOM_RIGHT;
359 _zoom_position = RIGHT;
365 _zoom_position = TOP;
366 } else if (ev->y > y2) {
367 _zoom_position = BOTTOM;
372 if (_zoom_position != NONE) {
373 _zoom_dragging = true;
374 _editor->_dragging_playhead = true;
379 /* ordinary click inside the view rectangle: start a move drag */
381 _move_dragging = true;
383 _editor->_dragging_playhead = true;
388 /* click outside the view rectangle: centre the view around the mouse click */
389 centre_on_click (ev);
397 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
399 x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
400 x->second = x->first + _editor->current_page_frames() * _x_scale;
402 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
403 y->second = y->first + _editor->canvas_height () * _y_scale;
407 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
409 pair<double, double> xr = _start_editor_x;
410 pair<double, double> yr = _start_editor_y;
412 if (_move_dragging) {
416 xr.first += ev->x - _start_mouse_x;
417 xr.second += ev->x - _start_mouse_x;
418 yr.first += ev->y - _start_mouse_y;
419 yr.second += ev->y - _start_mouse_y;
423 } else if (_zoom_dragging) {
425 double const dx = ev->x - _start_mouse_x;
427 if (_zoom_position == TOP_LEFT || _zoom_position == LEFT || _zoom_position == BOTTOM_LEFT) {
431 if (_zoom_position == TOP_RIGHT || _zoom_position == RIGHT || _zoom_position == BOTTOM_RIGHT) {
442 EditorSummary::on_button_release_event (GdkEventButton* ev)
444 if (_move_dragging && !_moved) {
445 centre_on_click (ev);
448 _move_dragging = false;
449 _zoom_dragging = false;
450 _editor->_dragging_playhead = false;
455 EditorSummary::on_scroll_event (GdkEventScroll* ev)
459 pair<double, double> xr;
460 pair<double, double> yr;
461 get_editor (&xr, &yr);
463 double const amount = 8;
465 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
467 if (ev->direction == GDK_SCROLL_UP) {
477 if (ev->direction == GDK_SCROLL_DOWN) {
491 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
493 if (_editor->pending_visual_change.idle_handler_id < 0) {
495 /* As a side-effect, the Editor's visual change idle handler processes
496 pending GTK events. Hence this motion notify handler can be called
497 in the middle of a visual change idle handler, and if this happens,
498 the queue_visual_change calls below modify the variables that the
499 idle handler is working with. This causes problems. Hence the
500 check above. It ensures that we won't modify the pending visual change
501 while a visual change idle handler is in progress. It's not perfect,
502 as it also means that we won't change these variables if an idle handler
503 is merely pending but not executing. But c'est la vie.
506 _editor->reset_x_origin (x.first / _x_scale);
507 _editor->reset_y_origin (y.first / _y_scale);
510 ((x.second - x.first) / _x_scale) /
511 _editor->frame_to_unit (_editor->current_page_frames())
514 if (nx != _editor->get_current_zoom ()) {
515 _editor->reset_zoom (nx);