changes to help strp silence
[ardour.git] / gtk2_ardour / editor_summary.cc
1 /*
2     Copyright (C) 2009 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
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"
29
30 using namespace std;
31 using namespace ARDOUR;
32 using Gtkmm2ext::Keyboard;
33
34 /** Construct an EditorSummary.
35  *  @param e Editor to represent.
36  */
37 EditorSummary::EditorSummary (Editor* e)
38         : EditorComponent (e),
39           _start (0),
40           _end (1),
41           _overhang_fraction (0.1),
42           _x_scale (1),
43           _y_scale (1),
44           _last_playhead (-1),
45           _move_dragging (false),
46           _moved (false),
47           _zoom_dragging (false)
48
49 {
50         Region::RegionPropertyChanged.connect (region_property_connection, boost::bind (&CairoWidget::set_dirty, this), gui_context());
51         _editor->playhead_cursor->PositionChanged.connect (position_connection, ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
52 }
53
54 /** Connect to a session.
55  *  @param s Session.
56  */
57 void
58 EditorSummary::set_session (Session* s)
59 {
60         EditorComponent::set_session (s);
61
62         set_dirty ();
63
64         if (_session) {
65                 _session->RegionRemoved.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this), gui_context());
66                 _session->StartTimeChanged.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this), gui_context());
67                 _session->EndTimeChanged.connect (_session_connections, boost::bind (&EditorSummary::set_dirty, this), gui_context());
68         }
69 }
70
71 /** Handle an expose event.
72  *  @param event Event from GTK.
73  */
74 bool
75 EditorSummary::on_expose_event (GdkEventExpose* event)
76 {
77         CairoWidget::on_expose_event (event);
78
79         if (_session == 0) {
80                 return false;
81         }
82
83         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
84
85         /* Render the view rectangle */
86
87         pair<double, double> x;
88         pair<double, double> y;
89         get_editor (&x, &y);
90
91         cairo_move_to (cr, x.first, y.first);
92         cairo_line_to (cr, x.second, y.first);
93         cairo_line_to (cr, x.second, y.second);
94         cairo_line_to (cr, x.first, y.second);
95         cairo_line_to (cr, x.first, y.first);
96         cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
97         cairo_fill_preserve (cr);
98         cairo_set_line_width (cr, 1);
99         cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
100         cairo_stroke (cr);
101
102         /* Playhead */
103
104         cairo_set_line_width (cr, 1);
105         /* XXX: colour should be set from configuration file */
106         cairo_set_source_rgba (cr, 1, 0, 0, 1);
107
108         double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
109         cairo_move_to (cr, p, 0);
110         cairo_line_to (cr, p, _height);
111         cairo_stroke (cr);
112         _last_playhead = p;
113
114         cairo_destroy (cr);
115
116         return true;
117 }
118
119 /** Render the required regions to a cairo context.
120  *  @param cr Context.
121  */
122 void
123 EditorSummary::render (cairo_t* cr)
124 {
125         /* background */
126
127         cairo_set_source_rgb (cr, 0, 0, 0);
128         cairo_rectangle (cr, 0, 0, _width, _height);
129         cairo_fill (cr);
130
131         if (_session == 0) {
132                 return;
133         }
134
135         /* compute start and end points for the summary */
136         
137         nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
138         double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
139         _start = theoretical_start > 0 ? theoretical_start : 0;
140         _end = _session->current_end_frame() + session_length * _overhang_fraction;
141
142         /* compute total height of all tracks */
143
144         int h = 0;
145         int max_height = 0;
146         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
147                 int const t = (*i)->effective_height ();
148                 h += t;
149                 max_height = max (max_height, t);
150         }
151
152         _x_scale = static_cast<double> (_width) / (_end - _start);
153         _y_scale = static_cast<double> (_height) / h;
154
155         /* tallest a region should ever be in the summary, in pixels */
156         int const tallest_region_pixels = _height / 16;
157
158         if (max_height * _y_scale > tallest_region_pixels) {
159                 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
160         }
161
162         /* render regions */
163
164         double y = 0;
165         for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
166                 StreamView* s = (*i)->view ();
167
168                 if (s) {
169                         double const h = (*i)->effective_height () * _y_scale;
170                         cairo_set_line_width (cr, h);
171
172                         s->foreach_regionview (sigc::bind (
173                                                        sigc::mem_fun (*this, &EditorSummary::render_region),
174                                                        cr,
175                                                        y + h / 2
176                                                        ));
177                         y += h;
178                 }
179         }
180
181         /* start and end markers */
182
183         cairo_set_line_width (cr, 1);
184         cairo_set_source_rgb (cr, 1, 1, 0);
185
186         double const p = (_session->current_start_frame() - _start) * _x_scale;
187         cairo_move_to (cr, p, 0);
188         cairo_line_to (cr, p, _height);
189         cairo_stroke (cr);
190
191         double const q = (_session->current_end_frame() - _start) * _x_scale;
192         cairo_move_to (cr, q, 0);
193         cairo_line_to (cr, q, _height);
194         cairo_stroke (cr);
195 }
196
197 /** Render a region for the summary.
198  *  @param r Region view.
199  *  @param cr Cairo context.
200  *  @param y y coordinate to render at.
201  */
202 void
203 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
204 {
205         uint32_t const c = r->get_fill_color ();
206         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
207
208         if (r->region()->position() > _start) {
209                 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
210         } else {
211                 cairo_move_to (cr, 0, y);
212         }
213
214         if ((r->region()->position() + r->region()->length()) > _start) {
215                 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
216         } else {
217                 cairo_line_to (cr, 0, y);
218         }
219
220         cairo_stroke (cr);
221 }
222
223 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
224 void
225 EditorSummary::set_overlays_dirty ()
226 {
227         ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
228         queue_draw ();
229 }
230
231 /** Handle a size request.
232  *  @param req GTK requisition
233  */
234 void
235 EditorSummary::on_size_request (Gtk::Requisition *req)
236 {
237         /* Use a dummy, small width and the actual height that we want */
238         req->width = 64;
239         req->height = 32;
240 }
241
242
243 void
244 EditorSummary::centre_on_click (GdkEventButton* ev)
245 {
246         pair<double, double> xr;
247         pair<double, double> yr;
248         get_editor (&xr, &yr);
249
250         double const w = xr.second - xr.first;
251         double const h = yr.second - yr.first;
252
253         xr.first = ev->x - w / 2;
254         xr.second = ev->x + w / 2;
255         yr.first = ev->y - h / 2;
256         yr.second = ev->y + h / 2;
257
258         if (xr.first < 0) {
259                 xr.first = 0;
260                 xr.second = w;
261         } else if (xr.second > _width) {
262                 xr.second = _width;
263                 xr.first = _width - w;
264         }
265
266         if (yr.first < 0) {
267                 yr.first = 0;
268                 yr.second = h;
269         } else if (yr.second > _height) {
270                 yr.second = _height;
271                 yr.first = _height - h;
272         }
273
274         set_editor (xr, yr);
275 }
276
277 /** Handle a button press.
278  *  @param ev GTK event.
279  */
280 bool
281 EditorSummary::on_button_press_event (GdkEventButton* ev)
282 {
283         if (ev->button == 1) {
284
285                 pair<double, double> xr;
286                 pair<double, double> yr;
287                 get_editor (&xr, &yr);
288
289                 _start_editor_x = xr;
290                 _start_editor_y = yr;
291                 _start_mouse_x = ev->x;
292                 _start_mouse_y = ev->y;
293
294                 if (
295                         _start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second &&
296                         _start_editor_y.first <= _start_mouse_y && _start_mouse_y <= _start_editor_y.second
297                         ) {
298
299                         _start_position = IN_VIEWBOX;
300
301                 } else if (_start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second) {
302
303                         _start_position = BELOW_OR_ABOVE_VIEWBOX;
304
305                 } else {
306
307                         _start_position = TO_LEFT_OR_RIGHT_OF_VIEWBOX;
308                 }
309
310                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
311
312                         /* primary-modifier-click: start a zoom drag */
313
314                         double const hx = (xr.first + xr.second) * 0.5;
315                         _zoom_left = ev->x < hx;
316                         _zoom_dragging = true;
317                         _editor->_dragging_playhead = true;
318
319
320                         /* In theory, we could support vertical dragging, which logically
321                            might scale track heights in order to make the editor reflect
322                            the dragged viewbox.  However, having tried this:
323                            a) it's hard to do
324                            b) it's quite slow
325                            c) it doesn't seem particularly useful, especially with the
326                            limited height of the summary
327
328                            So at the moment we don't support that...
329                         */
330
331
332                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
333
334                         /* secondary-modifier-click: locate playhead */
335                         if (_session) {
336                                 _session->request_locate (ev->x / _x_scale + _start);
337                         }
338
339                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
340
341                         centre_on_click (ev);
342
343                 } else {
344
345                         /* ordinary click: start a move drag */
346
347                         _move_dragging = true;
348                         _moved = false;
349                         _editor->_dragging_playhead = true;
350                 }
351         }
352
353         return true;
354 }
355
356 void
357 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
358 {
359         x->first = (_editor->leftmost_position () - _start) * _x_scale;
360         x->second = x->first + _editor->current_page_frames() * _x_scale;
361
362         y->first = _editor->vertical_adjustment.get_value() * _y_scale;
363         y->second = y->first + (_editor->canvas_height () - _editor->get_canvas_timebars_vsize()) * _y_scale;
364 }
365
366 bool
367 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
368 {
369         pair<double, double> xr = _start_editor_x;
370         pair<double, double> yr = _start_editor_y;
371
372         if (_move_dragging) {
373
374                 _moved = true;
375
376                 /* don't alter x if we clicked outside and above or below the viewbox */
377                 if (_start_position == IN_VIEWBOX || _start_position == TO_LEFT_OR_RIGHT_OF_VIEWBOX) {
378                         xr.first += ev->x - _start_mouse_x;
379                         xr.second += ev->x - _start_mouse_x;
380                 }
381
382                 /* don't alter y if we clicked outside and to the left or right of the viewbox */
383                 if (_start_position == IN_VIEWBOX || _start_position == BELOW_OR_ABOVE_VIEWBOX) {
384                         yr.first += ev->y - _start_mouse_y;
385                         yr.second += ev->y - _start_mouse_y;
386                 }
387
388                 if (xr.first < 0) {
389                         xr.second -= xr.first;
390                         xr.first = 0;
391                 }
392
393                 if (yr.first < 0) {
394                         yr.second -= yr.first;
395                         yr.first = 0;
396                 }
397
398                 set_editor (xr, yr);
399
400         } else if (_zoom_dragging) {
401
402                 double const dx = ev->x - _start_mouse_x;
403
404                 if (_zoom_left) {
405                         xr.first += dx;
406                 } else {
407                         xr.second += dx;
408                 }
409
410                 set_editor (xr, yr);
411         }
412
413         return true;
414 }
415
416 bool
417 EditorSummary::on_button_release_event (GdkEventButton*)
418 {
419         _move_dragging = false;
420         _zoom_dragging = false;
421         _editor->_dragging_playhead = false;
422         return true;
423 }
424
425 bool
426 EditorSummary::on_scroll_event (GdkEventScroll* ev)
427 {
428         /* mouse wheel */
429
430         pair<double, double> xr;
431         pair<double, double> yr;
432         get_editor (&xr, &yr);
433
434         double amount = 8;
435
436         if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
437                 amount = 64;
438         } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
439                 amount = 1;
440         }
441
442         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
443
444                 /* primary-wheel == left-right scrolling */
445
446                 if (ev->direction == GDK_SCROLL_UP) {
447                         xr.first += amount;
448                         xr.second += amount;
449                 } else if (ev->direction == GDK_SCROLL_DOWN) {
450                         xr.first -= amount;
451                         xr.second -= amount;
452                 }
453
454         } else {
455
456                 if (ev->direction == GDK_SCROLL_DOWN) {
457                         yr.first += amount;
458                         yr.second += amount;
459                 } else if (ev->direction == GDK_SCROLL_UP) {
460                         yr.first -= amount;
461                         yr.second -= amount;
462                 } else if (ev->direction == GDK_SCROLL_LEFT) {
463                         xr.first -= amount;
464                         xr.second -= amount;
465                 } else if (ev->direction == GDK_SCROLL_RIGHT) {
466                         xr.first += amount;
467                         xr.second += amount;
468                 }
469         }
470
471         set_editor (xr, yr);
472         return true;
473 }
474
475 void
476 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
477 {
478         if (_editor->pending_visual_change.idle_handler_id < 0) {
479
480                 /* As a side-effect, the Editor's visual change idle handler processes
481                    pending GTK events.  Hence this motion notify handler can be called
482                    in the middle of a visual change idle handler, and if this happens,
483                    the queue_visual_change calls below modify the variables that the
484                    idle handler is working with.  This causes problems.  Hence the
485                    check above.  It ensures that we won't modify the pending visual change
486                    while a visual change idle handler is in progress.  It's not perfect,
487                    as it also means that we won't change these variables if an idle handler
488                    is merely pending but not executing.  But c'est la vie.
489                 */
490
491                 /* proposed bottom of the editor with the requested position */
492                 double const pb = y.second / _y_scale;
493
494                 /* bottom of the canvas */
495                 double const ch = _editor->full_canvas_height - _editor->canvas_timebars_vsize;
496
497                 /* requested y position */
498                 double ly = y.first / _y_scale;
499
500                 /* clamp y position so as not to go off the bottom */
501                 if (pb > ch) {
502                         ly -= (pb - ch);
503                 }
504
505                 if (ly < 0) {
506                         ly = 0;
507                 }
508
509                 _editor->reset_x_origin (x.first / _x_scale + _start);
510                 _editor->reset_y_origin (ly);
511
512                 double const nx = (
513                         ((x.second - x.first) / _x_scale) /
514                         _editor->frame_to_unit (_editor->current_page_frames())
515                         );
516
517                 if (nx != _editor->get_current_zoom ()) {
518                         _editor->reset_zoom (nx);
519                 }
520         }
521 }
522
523 void
524 EditorSummary::playhead_position_changed (nframes64_t p)
525 {
526         if (_session && int (p * _x_scale) != int (_last_playhead)) {
527                 set_overlays_dirty ();
528         }
529 }
530
531