Remove idiocy.
[ardour2.git] / gtk2_ardour / editor_summary.cc
blob6740ef92eb7385d21a8c138c496784dbacec8706
1 /*
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"
25 #include "editor.h"
26 #include "region_view.h"
27 #include "rgb_macros.h"
28 #include "keyboard.h"
30 using namespace std;
31 using namespace sigc;
32 using namespace ARDOUR;
34 /** Construct an EditorSummary.
35 * @param e Editor to represent.
37 EditorSummary::EditorSummary (Editor* e)
38 : EditorComponent (e),
39 _x_scale (1),
40 _y_scale (1),
41 _last_playhead (-1),
42 _move_dragging (false),
43 _moved (false),
44 _zoom_dragging (false)
50 /** Connect to a session.
51 * @param s Session.
53 void
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));
65 set_dirty ();
68 /** Handle an expose event.
69 * @param event Event from GTK.
71 bool
72 EditorSummary::on_expose_event (GdkEventExpose* event)
74 CairoWidget::on_expose_event (event);
76 if (_session == 0) {
77 return false;
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;
86 get_editor (&x, &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);
97 cairo_stroke (cr);
99 /* Playhead */
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);
108 cairo_stroke (cr);
109 _last_playhead = p;
111 cairo_destroy (cr);
113 return true;
116 /** Render the required regions to a cairo context.
117 * @param cr Context.
119 void
120 EditorSummary::render (cairo_t* cr)
122 /* background */
124 cairo_set_source_rgb (cr, 0, 0, 0);
125 cairo_rectangle (cr, 0, 0, _width, _height);
126 cairo_fill (cr);
128 if (_session == 0) {
129 return;
132 /* compute total height of all tracks */
134 int h = 0;
135 int max_height = 0;
136 for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
137 int const t = (*i)->effective_height ();
138 h += t;
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;
154 /* render regions */
156 double y = 0;
157 for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
158 StreamView* s = (*i)->view ();
160 if (s) {
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),
167 start,
168 y + h / 2
170 y += h;
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.
182 void
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);
190 cairo_stroke (cr);
193 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
194 void
195 EditorSummary::set_overlays_dirty ()
197 ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_overlays_dirty));
198 queue_draw ();
201 /** Handle a size request.
202 * @param req GTK requisition
204 void
205 EditorSummary::on_size_request (Gtk::Requisition *req)
207 /* Use a dummy, small width and the actual height that we want */
208 req->width = 64;
209 req->height = 32;
213 void
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;
228 if (xr.first < 0) {
229 xr.first = 0;
230 xr.second = w;
231 } else if (xr.second > _width) {
232 xr.second = _width;
233 xr.first = _width - w;
236 if (yr.first < 0) {
237 yr.first = 0;
238 yr.second = h;
239 } else if (yr.second > _height) {
240 yr.second = _height;
241 yr.first = _height - h;
244 set_editor (xr, yr);
247 /** Handle a button press.
248 * @param ev GTK event.
250 bool
251 EditorSummary::on_button_press_event (GdkEventButton* ev)
253 if (ev->button == 1) {
255 pair<double, double> xr;
256 pair<double, double> yr;
257 get_editor (&xr, &yr);
259 _start_editor_x = xr;
260 _start_editor_y = yr;
261 _start_mouse_x = ev->x;
262 _start_mouse_y = ev->y;
264 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
266 /* primary-modifier-click: start a zoom drag */
268 double const hx = (xr.first + xr.second) * 0.5;
269 _zoom_left = ev->x < hx;
270 _zoom_dragging = true;
271 _editor->_dragging_playhead = true;
274 /* In theory, we could support vertical dragging, which logically
275 might scale track heights in order to make the editor reflect
276 the dragged viewbox. However, having tried this:
277 a) it's hard to do
278 b) it's quite slow
279 c) it doesn't seem particularly useful, especially with the
280 limited height of the summary
282 So at the moment we don't support that...
286 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
288 /* secondary-modifier-click: locate playhead */
289 if (_session) {
290 _session->request_locate (ev->x / _x_scale + _session->current_start_frame());
293 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
295 centre_on_click (ev);
297 } else {
299 /* ordinary click: start a move drag */
301 _move_dragging = true;
302 _moved = false;
303 _editor->_dragging_playhead = true;
307 return true;
310 void
311 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
313 x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
314 x->second = x->first + _editor->current_page_frames() * _x_scale;
316 y->first = _editor->vertical_adjustment.get_value() * _y_scale;
317 y->second = y->first + _editor->canvas_height () * _y_scale;
320 bool
321 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
323 pair<double, double> xr = _start_editor_x;
324 pair<double, double> yr = _start_editor_y;
326 if (_move_dragging) {
328 _moved = true;
330 xr.first += ev->x - _start_mouse_x;
331 xr.second += ev->x - _start_mouse_x;
332 yr.first += ev->y - _start_mouse_y;
333 yr.second += ev->y - _start_mouse_y;
335 set_editor (xr, yr);
337 } else if (_zoom_dragging) {
339 double const dx = ev->x - _start_mouse_x;
341 if (_zoom_left) {
342 xr.first += dx;
343 } else {
344 xr.second += dx;
347 set_editor (xr, yr);
350 return true;
353 bool
354 EditorSummary::on_button_release_event (GdkEventButton*)
356 _move_dragging = false;
357 _zoom_dragging = false;
358 _editor->_dragging_playhead = false;
359 return true;
362 bool
363 EditorSummary::on_scroll_event (GdkEventScroll* ev)
365 /* mouse wheel */
367 pair<double, double> xr;
368 pair<double, double> yr;
369 get_editor (&xr, &yr);
371 double const amount = 8;
373 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
375 if (ev->direction == GDK_SCROLL_UP) {
376 xr.first += amount;
377 xr.second += amount;
378 } else {
379 xr.first -= amount;
380 xr.second -= amount;
383 } else {
385 if (ev->direction == GDK_SCROLL_DOWN) {
386 yr.first += amount;
387 yr.second += amount;
388 } else {
389 yr.first -= amount;
390 yr.second -= amount;
395 set_editor (xr, yr);
396 return true;
399 void
400 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
402 if (_editor->pending_visual_change.idle_handler_id < 0) {
404 /* As a side-effect, the Editor's visual change idle handler processes
405 pending GTK events. Hence this motion notify handler can be called
406 in the middle of a visual change idle handler, and if this happens,
407 the queue_visual_change calls below modify the variables that the
408 idle handler is working with. This causes problems. Hence the
409 check above. It ensures that we won't modify the pending visual change
410 while a visual change idle handler is in progress. It's not perfect,
411 as it also means that we won't change these variables if an idle handler
412 is merely pending but not executing. But c'est la vie.
415 _editor->reset_x_origin (x.first / _x_scale);
416 _editor->reset_y_origin (y.first / _y_scale);
418 double const nx = (
419 ((x.second - x.first) / _x_scale) /
420 _editor->frame_to_unit (_editor->current_page_frames())
423 if (nx != _editor->get_current_zoom ()) {
424 _editor->reset_zoom (nx);
429 void
430 EditorSummary::playhead_position_changed (nframes64_t p)
432 if (int (p * _x_scale) != int (_last_playhead)) {
433 set_overlays_dirty ();