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),
42 _move_dragging (false),
44 _zoom_dragging (false)
50 /** Connect to a session.
54 EditorSummary::connect_to_session (Session* s)
56 EditorComponent::connect_to_session (s);
58 Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
60 _session_connections.push_back (_session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty))));
61 _session_connections.push_back (_session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty)));
62 _session_connections.push_back (_session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty)));
63 _editor->playhead_cursor->PositionChanged.connect (mem_fun (*this, &EditorSummary::playhead_position_changed));
68 /** Handle an expose event.
69 * @param event Event from GTK.
72 EditorSummary::on_expose_event (GdkEventExpose* event)
74 CairoWidget::on_expose_event (event);
80 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
82 /* Render the view rectangle */
84 pair<double, double> x;
85 pair<double, double> y;
88 cairo_move_to (cr, x.first, y.first);
89 cairo_line_to (cr, x.second, y.first);
90 cairo_line_to (cr, x.second, y.second);
91 cairo_line_to (cr, x.first, y.second);
92 cairo_line_to (cr, x.first, y.first);
93 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
94 cairo_fill_preserve (cr);
95 cairo_set_line_width (cr, 1);
96 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
101 cairo_set_line_width (cr, 1);
102 /* XXX: colour should be set from configuration file */
103 cairo_set_source_rgba (cr, 1, 0, 0, 1);
105 double const p = _editor->playhead_cursor->current_frame * _x_scale;
106 cairo_move_to (cr, p, 0);
107 cairo_line_to (cr, p, _height);
116 /** Render the required regions to a cairo context.
120 EditorSummary::render (cairo_t* cr)
124 cairo_set_source_rgb (cr, 0, 0, 0);
125 cairo_rectangle (cr, 0, 0, _width, _height);
132 /* compute total height of all tracks */
136 for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
137 int const t = (*i)->effective_height ();
139 max_height = max (max_height, t);
142 nframes_t const start = _session->current_start_frame ();
143 _x_scale = static_cast<double> (_width) / (_session->current_end_frame() - start);
144 _y_scale = static_cast<double> (_height) / h;
146 /* tallest a region should ever be in the summary, in pixels */
147 int const tallest_region_pixels = 4;
149 if (max_height * _y_scale > tallest_region_pixels) {
150 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
157 for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
158 StreamView* s = (*i)->view ();
161 double const h = (*i)->effective_height () * _y_scale;
162 cairo_set_line_width (cr, h);
164 s->foreach_regionview (bind (
165 mem_fun (*this, &EditorSummary::render_region),
176 /** Render a region for the summary.
177 * @param r Region view.
178 * @param cr Cairo context.
179 * @param start Frame offset that the summary starts at.
180 * @param y y coordinate to render at.
183 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
185 uint32_t const c = r->get_fill_color ();
186 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
188 cairo_move_to (cr, (r->region()->position() - start) * _x_scale, y);
189 cairo_line_to (cr, ((r->region()->position() - start + r->region()->length())) * _x_scale, y);
193 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
195 EditorSummary::set_overlays_dirty ()
197 ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_overlays_dirty));
201 /** Handle a size request.
202 * @param req GTK requisition
205 EditorSummary::on_size_request (Gtk::Requisition *req)
207 /* Use a dummy, small width and the actual height that we want */
214 EditorSummary::centre_on_click (GdkEventButton* ev)
216 pair<double, double> xr;
217 pair<double, double> yr;
218 get_editor (&xr, &yr);
220 double const w = xr.second - xr.first;
221 double const h = yr.second - yr.first;
223 xr.first = ev->x - w / 2;
224 xr.second = ev->x + w / 2;
225 yr.first = ev->y - h / 2;
226 yr.second = ev->y + h / 2;
231 } else if (xr.second > _width) {
233 xr.first = _width - w;
239 } else if (yr.second > _height) {
241 yr.first = _height - h;
247 /** Handle a button press.
248 * @param ev GTK event.
251 EditorSummary::on_button_press_event (GdkEventButton* ev)
253 if (ev->button == 1) {
255 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
257 /* secondary-modifier-click: locate playhead */
259 _session->request_locate (ev->x / _x_scale + _session->current_start_frame());
264 pair<double, double> xr;
265 pair<double, double> yr;
266 get_editor (&xr, &yr);
268 if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
270 _start_editor_x = xr;
271 _start_editor_y = yr;
272 _start_mouse_x = ev->x;
273 _start_mouse_y = ev->y;
275 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
277 /* modifier-click inside the view rectangle: start a zoom drag */
279 double const hx = (xr.first + xr.second) * 0.5;
280 _zoom_left = ev->x < hx;
281 _zoom_dragging = true;
282 _editor->_dragging_playhead = true;
284 /* In theory, we could support vertical dragging, which logically
285 might scale track heights in order to make the editor reflect
286 the dragged viewbox. However, having tried this:
289 c) it doesn't seem particularly useful, especially with the
290 limited height of the summary
292 So at the moment we don't support that...
297 /* ordinary click inside the view rectangle: start a move drag */
299 _move_dragging = true;
301 _editor->_dragging_playhead = true;
306 /* click outside the view rectangle: centre the view around the mouse click */
307 centre_on_click (ev);
316 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
318 x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
319 x->second = x->first + _editor->current_page_frames() * _x_scale;
321 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
322 y->second = y->first + _editor->canvas_height () * _y_scale;
326 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
328 pair<double, double> xr = _start_editor_x;
329 pair<double, double> yr = _start_editor_y;
331 if (_move_dragging) {
335 xr.first += ev->x - _start_mouse_x;
336 xr.second += ev->x - _start_mouse_x;
337 yr.first += ev->y - _start_mouse_y;
338 yr.second += ev->y - _start_mouse_y;
342 } else if (_zoom_dragging) {
344 double const dx = ev->x - _start_mouse_x;
359 EditorSummary::on_button_release_event (GdkEventButton* ev)
361 if (_move_dragging && !_moved) {
362 centre_on_click (ev);
365 _move_dragging = false;
366 _zoom_dragging = false;
367 _editor->_dragging_playhead = false;
372 EditorSummary::on_scroll_event (GdkEventScroll* ev)
376 pair<double, double> xr;
377 pair<double, double> yr;
378 get_editor (&xr, &yr);
380 double const amount = 8;
382 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
384 if (ev->direction == GDK_SCROLL_UP) {
394 if (ev->direction == GDK_SCROLL_DOWN) {
409 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
411 if (_editor->pending_visual_change.idle_handler_id < 0) {
413 /* As a side-effect, the Editor's visual change idle handler processes
414 pending GTK events. Hence this motion notify handler can be called
415 in the middle of a visual change idle handler, and if this happens,
416 the queue_visual_change calls below modify the variables that the
417 idle handler is working with. This causes problems. Hence the
418 check above. It ensures that we won't modify the pending visual change
419 while a visual change idle handler is in progress. It's not perfect,
420 as it also means that we won't change these variables if an idle handler
421 is merely pending but not executing. But c'est la vie.
424 _editor->reset_x_origin (x.first / _x_scale);
425 _editor->reset_y_origin (y.first / _y_scale);
428 ((x.second - x.first) / _x_scale) /
429 _editor->frame_to_unit (_editor->current_page_frames())
432 if (nx != _editor->get_current_zoom ()) {
433 _editor->reset_zoom (nx);
439 EditorSummary::playhead_position_changed (nframes64_t p)
441 if (int (p * _x_scale) != int (_last_playhead)) {
442 set_overlays_dirty ();