more hacking on the processor list and processor box - note that ctrl-x/c/v now work...
[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         : 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)
45           
46 {
47         
48 }
49
50 /** Connect to a session.
51  *  @param s Session.
52  */
53 void
54 EditorSummary::connect_to_session (Session* s)
55 {
56         EditorComponent::connect_to_session (s);
57
58         Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
59
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));
64
65         set_dirty ();
66 }
67
68 /** Handle an expose event.
69  *  @param event Event from GTK.
70  */
71 bool
72 EditorSummary::on_expose_event (GdkEventExpose* event)
73 {
74         CairoWidget::on_expose_event (event);
75
76         if (_session == 0) {
77                 return false;
78         }
79
80         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
81
82         /* Render the view rectangle */
83         
84         pair<double, double> x;
85         pair<double, double> y;
86         get_editor (&x, &y);
87         
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);
98
99         /* Playhead */
100
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);
104
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;
110
111         cairo_destroy (cr);
112         
113         return true;
114 }
115
116 /** Render the required regions to a cairo context.
117  *  @param cr Context.
118  */
119 void
120 EditorSummary::render (cairo_t* cr)
121 {
122         /* background */
123         
124         cairo_set_source_rgb (cr, 0, 0, 0);
125         cairo_rectangle (cr, 0, 0, _width, _height);
126         cairo_fill (cr);
127
128         if (_session == 0) {
129                 return;
130         }
131
132         /* compute total height of all tracks */
133         
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);
140         }
141
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;
145
146         /* tallest a region should ever be in the summary, in pixels */
147         int const tallest_region_pixels = 4;
148
149         if (max_height * _y_scale > tallest_region_pixels) {
150                 _y_scale = static_cast<double> (tallest_region_pixels) / max_height;
151
152         }
153
154         /* render regions */
155
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 ();
159
160                 if (s) {
161                         double const h = (*i)->effective_height () * _y_scale;
162                         cairo_set_line_width (cr, h);
163
164                         s->foreach_regionview (bind (
165                                                        mem_fun (*this, &EditorSummary::render_region),
166                                                        cr,
167                                                        start,
168                                                        y + h / 2
169                                                        ));
170                         y += h;
171                 }
172         }
173
174 }
175
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.
181  */
182 void
183 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
184 {
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);
187                         
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);
191 }
192
193 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
194 void
195 EditorSummary::set_overlays_dirty ()
196 {
197         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_overlays_dirty));
198         queue_draw ();
199 }
200
201 /** Handle a size request.
202  *  @param req GTK requisition
203  */
204 void
205 EditorSummary::on_size_request (Gtk::Requisition *req)
206 {
207         /* Use a dummy, small width and the actual height that we want */
208         req->width = 64;
209         req->height = 32;
210 }
211
212
213 void
214 EditorSummary::centre_on_click (GdkEventButton* ev)
215 {
216         pair<double, double> xr;
217         pair<double, double> yr;
218         get_editor (&xr, &yr);
219
220         double const w = xr.second - xr.first;
221         double const h = yr.second - yr.first;
222         
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;
227
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;
234         }
235
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;
242         }
243
244         set_editor (xr, yr);
245 }
246
247 /** Handle a button press.
248  *  @param ev GTK event.
249  */
250 bool
251 EditorSummary::on_button_press_event (GdkEventButton* ev)
252 {
253         if (ev->button == 1) {
254
255                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
256
257                         /* secondary-modifier-click: locate playhead */
258                         if (_session) {
259                                 _session->request_locate (ev->x / _x_scale + _session->current_start_frame());
260                         }
261
262                 } else {
263                 
264                         pair<double, double> xr;
265                         pair<double, double> yr;
266                         get_editor (&xr, &yr);
267                         
268                         if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
269                                 
270                                 _start_editor_x = xr;
271                                 _start_editor_y = yr;
272                                 _start_mouse_x = ev->x;
273                                 _start_mouse_y = ev->y;
274                                 
275                                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
276                                         
277                                         /* modifier-click inside the view rectangle: start a zoom drag */
278                                         
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;
283                                         
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:
287                                            a) it's hard to do
288                                            b) it's quite slow
289                                            c) it doesn't seem particularly useful, especially with the
290                                            limited height of the summary
291                                            
292                                            So at the moment we don't support that...
293                                         */
294                                         
295                                 } else {
296                                         
297                                         /* ordinary click inside the view rectangle: start a move drag */
298                                         
299                                         _move_dragging = true;
300                                         _moved = false;
301                                         _editor->_dragging_playhead = true;
302                                 }
303                                 
304                         } else {
305                         
306                                 /* click outside the view rectangle: centre the view around the mouse click */
307                                 centre_on_click (ev);
308                         }
309                 }
310         }
311         
312         return true;
313 }
314
315 void
316 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
317 {
318         x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _x_scale;
319         x->second = x->first + _editor->current_page_frames() * _x_scale;
320
321         y->first = _editor->vertical_adjustment.get_value() * _y_scale;
322         y->second = y->first + _editor->canvas_height () * _y_scale;
323 }
324
325 bool
326 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
327 {
328         pair<double, double> xr = _start_editor_x;
329         pair<double, double> yr = _start_editor_y;
330         
331         if (_move_dragging) {
332
333                 _moved = true;
334
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;
339                 
340                 set_editor (xr, yr);
341
342         } else if (_zoom_dragging) {
343
344                 double const dx = ev->x - _start_mouse_x;
345
346                 if (_zoom_left) {
347                         xr.first += dx;
348                 } else {
349                         xr.second += dx;
350                 }
351
352                 set_editor (xr, yr);
353         }
354                 
355         return true;
356 }
357
358 bool
359 EditorSummary::on_button_release_event (GdkEventButton* ev)
360 {
361         if (_move_dragging && !_moved) {
362                 centre_on_click (ev);
363         }
364
365         _move_dragging = false;
366         _zoom_dragging = false;
367         _editor->_dragging_playhead = false;
368         return true;
369 }
370
371 bool
372 EditorSummary::on_scroll_event (GdkEventScroll* ev)
373 {
374         /* mouse wheel */
375         
376         pair<double, double> xr;
377         pair<double, double> yr;
378         get_editor (&xr, &yr);
379
380         double const amount = 8;
381                 
382         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
383                 
384                 if (ev->direction == GDK_SCROLL_UP) {
385                         xr.first += amount;
386                         xr.second += amount;
387                 } else {
388                         xr.first -= amount;
389                         xr.second -= amount;
390                 }
391
392         } else {
393                 
394                 if (ev->direction == GDK_SCROLL_DOWN) {
395                         yr.first += amount;
396                         yr.second += amount;
397                 } else {
398                         yr.first -= amount;
399                         yr.second -= amount;
400                 }
401                 
402         }
403         
404         set_editor (xr, yr);
405         return true;
406 }
407
408 void
409 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
410 {
411         if (_editor->pending_visual_change.idle_handler_id < 0) {
412
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.
422                 */
423
424                 _editor->reset_x_origin (x.first / _x_scale);
425                 _editor->reset_y_origin (y.first / _y_scale);
426
427                 double const nx = (
428                         ((x.second - x.first) / _x_scale) /
429                         _editor->frame_to_unit (_editor->current_page_frames())
430                         );
431
432                 if (nx != _editor->get_current_zoom ()) {
433                         _editor->reset_zoom (nx);
434                 }
435         }
436 }
437
438 void
439 EditorSummary::playhead_position_changed (nframes64_t p)
440 {
441         if (int (p * _x_scale) != int (_last_playhead)) {
442                 set_overlays_dirty ();
443         }
444 }
445
446