first pass (ok, third really) at internal send+return - audio routing inside ardour...
[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           _move_dragging (false),
47           _moved (false),
48           _zoom_dragging (false)
49           
50 {
51         
52 }
53
54 /** Set the session.
55  *  @param s Session.
56  */
57 void
58 EditorSummary::set_session (Session* s)
59 {
60         _session = s;
61
62         Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
63
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));
67
68         set_dirty ();
69 }
70
71 /** Destroy */
72 EditorSummary::~EditorSummary ()
73 {
74         if (_pixmap) {
75                 gdk_pixmap_unref (_pixmap);
76         }
77 }
78
79 /** Handle an expose event.
80  *  @param event Event from GTK.
81  */
82 bool
83 EditorSummary::on_expose_event (GdkEventExpose* event)
84 {
85         /* Render the regions pixmap */
86         
87         Gdk::Rectangle const exposure (
88                 event->area.x, event->area.y, event->area.width, event->area.height
89                 );
90
91         Gdk::Rectangle r = exposure;
92         Gdk::Rectangle content (0, 0, _width, _height);
93         bool intersects;
94         r.intersect (content, intersects);
95         
96         if (intersects) {
97
98                 GdkPixmap* p = get_pixmap (get_window()->gobj ());
99
100                 gdk_draw_drawable (
101                         get_window()->gobj(),
102                         get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
103                         p,
104                         r.get_x(),
105                         r.get_y(),
106                         r.get_x(),
107                         r.get_y(),
108                         r.get_width(),
109                         r.get_height()
110                         );
111         }
112
113         /* Render the view rectangle */
114         
115         pair<double, double> x;
116         pair<double, double> y;
117         get_editor (&x, &y);
118         
119         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
120
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);
130         cairo_stroke (cr);
131
132         cairo_destroy (cr);
133         
134         return true;
135 }
136
137 /** @param drawable GDK drawable.
138  *  @return pixmap for the regions.
139  */
140 GdkPixmap *
141 EditorSummary::get_pixmap (GdkDrawable* drawable)
142 {
143         if (_regions_dirty) {
144
145                 if (_pixmap) {
146                         gdk_pixmap_unref (_pixmap);
147                 }
148                 _pixmap = gdk_pixmap_new (drawable, _width, _height, -1);
149
150                 cairo_t* cr = gdk_cairo_create (_pixmap);
151                 render (cr);
152                 cairo_destroy (cr);
153
154                 _regions_dirty = false;
155         }
156
157         return _pixmap;
158 }
159
160 /** Render the required regions to a cairo context.
161  *  @param cr Context.
162  */
163 void
164 EditorSummary::render (cairo_t* cr)
165 {
166         if (_session == 0) {
167                 return;
168         }
169
170         /* background */
171         
172         cairo_set_source_rgb (cr, 0, 0, 0);
173         cairo_rectangle (cr, 0, 0, _width, _height);
174         cairo_fill (cr);
175
176         /* compute total height of all tracks */
177         
178         int h = 0;
179         int max_height = 0;
180         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
181                 int const t = (*i)->effective_height ();
182                 h += t;
183                 max_height = max (max_height, t);
184         }
185
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;
189
190         /* tallest a region should ever be in the summary, in pixels */
191         int const tallest_region_pixels = 12;
192
193         if (max_height * _y_scale > tallest_region_pixels) {
194                 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
195         }
196
197         /* render regions */
198
199         double y = 0;
200         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
201                 StreamView* s = (*i)->view ();
202
203                 if (s) {
204                         double const h = (*i)->effective_height () * _y_scale;
205                         cairo_set_line_width (cr, h);
206
207                         s->foreach_regionview (bind (
208                                                        mem_fun (*this, &EditorSummary::render_region),
209                                                        cr,
210                                                        start,
211                                                        y + h / 2
212                                                        ));
213                         y += h;
214                 }
215         }
216
217 }
218
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.
224  */
225 void
226 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
227 {
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);
230                         
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);
233         cairo_stroke (cr);
234 }
235
236 /** Set the summary so that the whole thing will be re-rendered next time it is required */
237 void
238 EditorSummary::set_dirty ()
239 {
240         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_dirty));
241
242         _regions_dirty = true;
243         queue_draw ();
244 }
245
246 /** Set the summary so that just the view boundary markers will be re-rendered */
247 void
248 EditorSummary::set_bounds_dirty ()
249 {
250         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_bounds_dirty));
251         queue_draw ();
252 }
253
254 /** Handle a size request.
255  *  @param req GTK requisition
256  */
257 void
258 EditorSummary::on_size_request (Gtk::Requisition *req)
259 {
260         /* Use a dummy, small width and the actual height that we want */
261         req->width = 64;
262         req->height = 64;
263 }
264
265 /** Handle a size allocation.
266  *  @param alloc GTK allocation.
267  */
268 void
269 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
270 {
271         Gtk::EventBox::on_size_allocate (alloc);
272
273         _width = alloc.get_width ();
274         _height = alloc.get_height ();
275
276         set_dirty ();
277 }
278
279 void
280 EditorSummary::centre_on_click (GdkEventButton* ev)
281 {
282         pair<double, double> xr;
283         pair<double, double> yr;
284         get_editor (&xr, &yr);
285
286         double const w = xr.second - xr.first;
287         double const h = yr.second - yr.first;
288         
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;
293
294         if (xr.first < 0) {
295                 xr.first = 0;
296                 xr.second = w;
297         } else if (xr.second > _width) {
298                 xr.second = _width;
299                 xr.first = _width - w;
300         }
301
302         if (yr.first < 0) {
303                 yr.first = 0;
304                 yr.second = h;
305         } else if (yr.second > _height) {
306                 yr.second = _height;
307                 yr.first = _height - h;
308         }
309
310         set_editor (xr, yr);
311 }
312
313 /** Handle a button press.
314  *  @param ev GTK event.
315  */
316 bool
317 EditorSummary::on_button_press_event (GdkEventButton* ev)
318 {
319         if (ev->button == 1) {
320                 
321                 pair<double, double> xr;
322                 pair<double, double> yr;
323                 get_editor (&xr, &yr);
324                 
325                 if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
326                         
327                         _start_editor_x = xr;
328                         _start_editor_y = yr;
329                         _start_mouse_x = ev->x;
330                         _start_mouse_y = ev->y;
331
332                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
333
334                                 /* modifier-click inside the view rectangle: start a zoom drag */
335                                 _zoom_position = NONE;
336
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;
341
342                                 if (ev->x < x1) {
343
344                                         if (ev->y < y1) {
345                                                 _zoom_position = TOP_LEFT;
346                                         } else if (ev->y > y2) {
347                                                 _zoom_position = BOTTOM_LEFT;
348                                         } else {
349                                                 _zoom_position = LEFT;
350                                         }
351                                                         
352                                 } else if (ev->x > x2) {
353
354                                         if (ev->y < y1) {
355                                                 _zoom_position = TOP_RIGHT;
356                                         } else if (ev->y > y2) {
357                                                 _zoom_position = BOTTOM_RIGHT;
358                                         } else {
359                                                 _zoom_position = RIGHT;
360                                         }
361                                         
362                                 } else {
363
364                                         if (ev->y < y1) {
365                                                 _zoom_position = TOP;
366                                         } else if (ev->y > y2) {
367                                                 _zoom_position = BOTTOM;
368                                         }
369                                                 
370                                 }
371                                                 
372                                 if (_zoom_position != NONE) {
373                                         _zoom_dragging = true;
374                                         _editor->_dragging_playhead = true;
375                                 }
376                                         
377                         } else {
378
379                                 /* ordinary click inside the view rectangle: start a move drag */
380                                 
381                                 _move_dragging = true;
382                                 _moved = false;
383                                 _editor->_dragging_playhead = true;
384                         }
385                         
386                 } else {
387                         
388                         /* click outside the view rectangle: centre the view around the mouse click */
389                         centre_on_click (ev);
390                 }
391         }
392
393         return true;
394 }
395
396 void
397 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
398 {
399         x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
400         x->second = x->first + _editor->current_page_frames() * _x_scale;
401
402         y->first = _editor->vertical_adjustment.get_value() * _y_scale;
403         y->second = y->first + _editor->canvas_height () * _y_scale;
404 }
405
406 bool
407 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
408 {
409         pair<double, double> xr = _start_editor_x;
410         pair<double, double> yr = _start_editor_y;
411         
412         if (_move_dragging) {
413
414                 _moved = true;
415
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;
420                 
421                 set_editor (xr, yr);
422
423         } else if (_zoom_dragging) {
424
425                 double const dx = ev->x - _start_mouse_x;
426
427                 if (_zoom_position == TOP_LEFT || _zoom_position == LEFT || _zoom_position == BOTTOM_LEFT) {
428                         xr.first += dx;
429                 }
430
431                 if (_zoom_position == TOP_RIGHT || _zoom_position == RIGHT || _zoom_position == BOTTOM_RIGHT) {
432                         xr.second += dx;
433                 }
434
435                 set_editor (xr, yr);
436         }
437                 
438         return true;
439 }
440
441 bool
442 EditorSummary::on_button_release_event (GdkEventButton* ev)
443 {
444         if (_move_dragging && !_moved) {
445                 centre_on_click (ev);
446         }
447
448         _move_dragging = false;
449         _zoom_dragging = false;
450         _editor->_dragging_playhead = false;
451         return true;
452 }
453
454 bool
455 EditorSummary::on_scroll_event (GdkEventScroll* ev)
456 {
457         /* mouse wheel */
458         
459         pair<double, double> xr;
460         pair<double, double> yr;
461         get_editor (&xr, &yr);
462
463         double const amount = 8;
464                 
465         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
466                 
467                 if (ev->direction == GDK_SCROLL_UP) {
468                         xr.first += amount;
469                         xr.second += amount;
470                 } else {
471                         xr.first -= amount;
472                         xr.second -= amount;
473                 }
474
475         } else {
476                 
477                 if (ev->direction == GDK_SCROLL_DOWN) {
478                         yr.first += amount;
479                         yr.second += amount;
480                 } else {
481                         yr.first -= amount;
482                         yr.second -= amount;
483                 }
484         }
485         
486         set_editor (xr, yr);
487         return true;
488 }
489
490 void
491 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
492 {
493         if (_editor->pending_visual_change.idle_handler_id < 0) {
494
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.
504                 */
505
506                 _editor->reset_x_origin (x.first / _x_scale);
507                 _editor->reset_y_origin (y.first / _y_scale);
508
509                 double const nx = (
510                         ((x.second - x.first) / _x_scale) /
511                         _editor->frame_to_unit (_editor->current_page_frames())
512                         );
513
514                 if (nx != _editor->get_current_zoom ()) {
515                         _editor->reset_zoom (nx);
516                 }
517         }
518 }