solo models work again (amazing how hard this was); remove crufty debug output; remov...
[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 sigc;
32 using namespace ARDOUR;
33
34 /** Construct an EditorSummary.
35  *  @param e Editor to represent.
36  */
37 EditorSummary::EditorSummary (Editor* e)
38         : _editor (e),
39           _session (0),
40           _pixmap (0),
41           _regions_dirty (true),
42           _width (512),
43           _height (64),
44           _x_scale (1),
45           _y_scale (1),
46           _last_playhead (-1),
47           _move_dragging (false),
48           _moved (false),
49           _zoom_dragging (false)
50           
51 {
52         
53 }
54
55 /** Set the session.
56  *  @param s Session.
57  */
58 void
59 EditorSummary::set_session (Session* s)
60 {
61         _session = s;
62
63         Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
64
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));
69
70         set_dirty ();
71 }
72
73 /** Destroy */
74 EditorSummary::~EditorSummary ()
75 {
76         if (_pixmap) {
77                 gdk_pixmap_unref (_pixmap);
78         }
79 }
80
81 /** Handle an expose event.
82  *  @param event Event from GTK.
83  */
84 bool
85 EditorSummary::on_expose_event (GdkEventExpose* event)
86 {
87         /* Render the regions pixmap */
88         
89         Gdk::Rectangle const exposure (
90                 event->area.x, event->area.y, event->area.width, event->area.height
91                 );
92
93         Gdk::Rectangle r = exposure;
94         Gdk::Rectangle content (0, 0, _width, _height);
95         bool intersects;
96         r.intersect (content, intersects);
97         
98         if (intersects) {
99
100                 GdkPixmap* p = get_pixmap (get_window()->gobj ());
101
102                 gdk_draw_drawable (
103                         get_window()->gobj(),
104                         get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
105                         p,
106                         r.get_x(),
107                         r.get_y(),
108                         r.get_x(),
109                         r.get_y(),
110                         r.get_width(),
111                         r.get_height()
112                         );
113         }
114
115         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
116
117         /* Render the view rectangle */
118         
119         pair<double, double> x;
120         pair<double, double> y;
121         get_editor (&x, &y);
122         
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);
132         cairo_stroke (cr);
133
134         /* Playhead */
135
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);
139
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);
143         cairo_stroke (cr);
144         _last_playhead = p;
145
146         cairo_destroy (cr);
147         
148         return true;
149 }
150
151 /** @param drawable GDK drawable.
152  *  @return pixmap for the regions.
153  */
154 GdkPixmap *
155 EditorSummary::get_pixmap (GdkDrawable* drawable)
156 {
157         if (_regions_dirty) {
158
159                 if (_pixmap) {
160                         gdk_pixmap_unref (_pixmap);
161                 }
162                 _pixmap = gdk_pixmap_new (drawable, _width, _height, -1);
163
164                 cairo_t* cr = gdk_cairo_create (_pixmap);
165                 render (cr);
166                 cairo_destroy (cr);
167
168                 _regions_dirty = false;
169         }
170
171         return _pixmap;
172 }
173
174 /** Render the required regions to a cairo context.
175  *  @param cr Context.
176  */
177 void
178 EditorSummary::render (cairo_t* cr)
179 {
180         if (_session == 0) {
181                 return;
182         }
183
184         /* background */
185         
186         cairo_set_source_rgb (cr, 0, 0, 0);
187         cairo_rectangle (cr, 0, 0, _width, _height);
188         cairo_fill (cr);
189
190         /* compute total height of all tracks */
191         
192         int h = 0;
193         int max_height = 0;
194         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
195                 int const t = (*i)->effective_height ();
196                 h += t;
197                 max_height = max (max_height, t);
198         }
199
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;
203
204         /* tallest a region should ever be in the summary, in pixels */
205         int const tallest_region_pixels = 12;
206
207         if (max_height * _y_scale > tallest_region_pixels) {
208                 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
209         }
210
211         /* render regions */
212
213         double y = 0;
214         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
215                 StreamView* s = (*i)->view ();
216
217                 if (s) {
218                         double const h = (*i)->effective_height () * _y_scale;
219                         cairo_set_line_width (cr, h);
220
221                         s->foreach_regionview (bind (
222                                                        mem_fun (*this, &EditorSummary::render_region),
223                                                        cr,
224                                                        start,
225                                                        y + h / 2
226                                                        ));
227                         y += h;
228                 }
229         }
230
231 }
232
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.
238  */
239 void
240 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
241 {
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);
244                         
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);
247         cairo_stroke (cr);
248 }
249
250 /** Set the summary so that the whole thing will be re-rendered next time it is required */
251 void
252 EditorSummary::set_dirty ()
253 {
254         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_dirty));
255
256         _regions_dirty = true;
257         queue_draw ();
258 }
259
260 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
261 void
262 EditorSummary::set_overlays_dirty ()
263 {
264         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_overlays_dirty));
265         queue_draw ();
266 }
267
268 /** Handle a size request.
269  *  @param req GTK requisition
270  */
271 void
272 EditorSummary::on_size_request (Gtk::Requisition *req)
273 {
274         /* Use a dummy, small width and the actual height that we want */
275         req->width = 64;
276         req->height = 64;
277 }
278
279 /** Handle a size allocation.
280  *  @param alloc GTK allocation.
281  */
282 void
283 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
284 {
285         Gtk::EventBox::on_size_allocate (alloc);
286
287         _width = alloc.get_width ();
288         _height = alloc.get_height ();
289
290         set_dirty ();
291 }
292
293 void
294 EditorSummary::centre_on_click (GdkEventButton* ev)
295 {
296         pair<double, double> xr;
297         pair<double, double> yr;
298         get_editor (&xr, &yr);
299
300         double const w = xr.second - xr.first;
301         double const h = yr.second - yr.first;
302         
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;
307
308         if (xr.first < 0) {
309                 xr.first = 0;
310                 xr.second = w;
311         } else if (xr.second > _width) {
312                 xr.second = _width;
313                 xr.first = _width - w;
314         }
315
316         if (yr.first < 0) {
317                 yr.first = 0;
318                 yr.second = h;
319         } else if (yr.second > _height) {
320                 yr.second = _height;
321                 yr.first = _height - h;
322         }
323
324         set_editor (xr, yr);
325 }
326
327 /** Handle a button press.
328  *  @param ev GTK event.
329  */
330 bool
331 EditorSummary::on_button_press_event (GdkEventButton* ev)
332 {
333         if (ev->button == 1) {
334                 
335                 pair<double, double> xr;
336                 pair<double, double> yr;
337                 get_editor (&xr, &yr);
338                 
339                 if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
340                         
341                         _start_editor_x = xr;
342                         _start_editor_y = yr;
343                         _start_mouse_x = ev->x;
344                         _start_mouse_y = ev->y;
345
346                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
347
348                                 /* modifier-click inside the view rectangle: start a zoom drag */
349                                 
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;
354
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:
358                                    a) it's hard to do
359                                    b) it's quite slow
360                                    c) it doesn't seem particularly useful, especially with the
361                                    limited height of the summary
362
363                                    So at the moment we don't support that...
364                                 */
365                                         
366                         } else {
367
368                                 /* ordinary click inside the view rectangle: start a move drag */
369                                 
370                                 _move_dragging = true;
371                                 _moved = false;
372                                 _editor->_dragging_playhead = true;
373                         }
374                         
375                 } else {
376                         
377                         /* click outside the view rectangle: centre the view around the mouse click */
378                         centre_on_click (ev);
379                 }
380         }
381
382         return true;
383 }
384
385 void
386 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
387 {
388         x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
389         x->second = x->first + _editor->current_page_frames() * _x_scale;
390
391         y->first = _editor->vertical_adjustment.get_value() * _y_scale;
392         y->second = y->first + _editor->canvas_height () * _y_scale;
393 }
394
395 bool
396 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
397 {
398         pair<double, double> xr = _start_editor_x;
399         pair<double, double> yr = _start_editor_y;
400         
401         if (_move_dragging) {
402
403                 _moved = true;
404
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;
409                 
410                 set_editor (xr, yr);
411
412         } else if (_zoom_dragging) {
413
414                 double const dx = ev->x - _start_mouse_x;
415
416                 if (_zoom_left) {
417                         xr.first += dx;
418                 } else {
419                         xr.second += dx;
420                 }
421
422                 set_editor (xr, yr);
423         }
424                 
425         return true;
426 }
427
428 bool
429 EditorSummary::on_button_release_event (GdkEventButton* ev)
430 {
431         if (_move_dragging && !_moved) {
432                 centre_on_click (ev);
433         }
434
435         _move_dragging = false;
436         _zoom_dragging = false;
437         _editor->_dragging_playhead = false;
438         return true;
439 }
440
441 bool
442 EditorSummary::on_scroll_event (GdkEventScroll* ev)
443 {
444         /* mouse wheel */
445         
446         pair<double, double> xr;
447         pair<double, double> yr;
448         get_editor (&xr, &yr);
449
450         double const amount = 8;
451                 
452         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
453                 
454                 if (ev->direction == GDK_SCROLL_UP) {
455                         xr.first += amount;
456                         xr.second += amount;
457                 } else {
458                         xr.first -= amount;
459                         xr.second -= amount;
460                 }
461
462         } else {
463                 
464                 if (ev->direction == GDK_SCROLL_DOWN) {
465                         yr.first += amount;
466                         yr.second += amount;
467                 } else {
468                         yr.first -= amount;
469                         yr.second -= amount;
470                 }
471                 
472         }
473         
474         set_editor (xr, yr);
475         return true;
476 }
477
478 void
479 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
480 {
481         if (_editor->pending_visual_change.idle_handler_id < 0) {
482
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.
492                 */
493
494                 _editor->reset_x_origin (x.first / _x_scale);
495                 _editor->reset_y_origin (y.first / _y_scale);
496
497                 double const nx = (
498                         ((x.second - x.first) / _x_scale) /
499                         _editor->frame_to_unit (_editor->current_page_frames())
500                         );
501
502                 if (nx != _editor->get_current_zoom ()) {
503                         _editor->reset_zoom (nx);
504                 }
505         }
506 }
507
508 void
509 EditorSummary::playhead_position_changed (nframes64_t p)
510 {
511         if (int (p * _x_scale) != int (_last_playhead)) {
512                 set_overlays_dirty ();
513         }
514 }
515
516