Put a limit on the tallest that a region can be in the summary.
[ardour.git] / gtk2_ardour / editor_summary.cc
1 #include "ardour/session.h"
2 #include "time_axis_view.h"
3 #include "streamview.h"
4 #include "editor_summary.h"
5 #include "gui_thread.h"
6 #include "editor.h"
7 #include "region_view.h"
8 #include "rgb_macros.h"
9 #include "keyboard.h"
10
11 using namespace std;
12 using namespace sigc;
13 using namespace ARDOUR;
14
15 /** Construct an EditorSummary.
16  *  @param e Editor to represent.
17  */
18 EditorSummary::EditorSummary (Editor* e)
19         : _editor (e),
20           _session (0),
21           _pixmap (0),
22           _regions_dirty (true),
23           _width (512),
24           _height (64),
25           _pixels_per_frame (1),
26           _vertical_scale (1),
27           _move_dragging (false),
28           _moved (false),
29           _zoom_dragging (false)
30           
31 {
32         
33 }
34
35 /** Set the session.
36  *  @param s Session.
37  */
38 void
39 EditorSummary::set_session (Session* s)
40 {
41         _session = s;
42
43         Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
44
45         _session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty)));
46         _session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
47         _session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty));
48
49         set_dirty ();
50 }
51
52 /** Destroy */
53 EditorSummary::~EditorSummary ()
54 {
55         if (_pixmap) {
56                 gdk_pixmap_unref (_pixmap);
57         }
58 }
59
60 /** Handle an expose event.
61  *  @param event Event from GTK.
62  */
63 bool
64 EditorSummary::on_expose_event (GdkEventExpose* event)
65 {
66         /* Render the regions pixmap */
67         
68         Gdk::Rectangle const exposure (
69                 event->area.x, event->area.y, event->area.width, event->area.height
70                 );
71
72         Gdk::Rectangle r = exposure;
73         Gdk::Rectangle content (0, 0, _width, _height);
74         bool intersects;
75         r.intersect (content, intersects);
76         
77         if (intersects) {
78
79                 GdkPixmap* p = get_pixmap (get_window()->gobj ());
80
81                 gdk_draw_drawable (
82                         get_window()->gobj(),
83                         get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(),
84                         p,
85                         r.get_x(),
86                         r.get_y(),
87                         r.get_x(),
88                         r.get_y(),
89                         r.get_width(),
90                         r.get_height()
91                         );
92         }
93
94         /* Render the view rectangle */
95         
96         pair<double, double> x;
97         pair<double, double> y;
98         editor_view (&x, &y);
99         
100         cairo_t* cr = gdk_cairo_create (get_window()->gobj());
101
102         cairo_move_to (cr, x.first, y.first);
103         cairo_line_to (cr, x.second, y.first);
104         cairo_line_to (cr, x.second, y.second);
105         cairo_line_to (cr, x.first, y.second);
106         cairo_line_to (cr, x.first, y.first);
107         cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
108         cairo_fill_preserve (cr);
109         cairo_set_line_width (cr, 1);
110         cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
111         cairo_stroke (cr);
112
113         cairo_destroy (cr);
114         
115         return true;
116 }
117
118 /** @param drawable GDK drawable.
119  *  @return pixmap for the regions.
120  */
121 GdkPixmap *
122 EditorSummary::get_pixmap (GdkDrawable* drawable)
123 {
124         if (_regions_dirty) {
125
126                 if (_pixmap) {
127                         gdk_pixmap_unref (_pixmap);
128                 }
129                 _pixmap = gdk_pixmap_new (drawable, _width, _height, -1);
130
131                 cairo_t* cr = gdk_cairo_create (_pixmap);
132                 render (cr);
133                 cairo_destroy (cr);
134
135                 _regions_dirty = false;
136         }
137
138         return _pixmap;
139 }
140
141 /** Render the required regions to a cairo context.
142  *  @param cr Context.
143  */
144 void
145 EditorSummary::render (cairo_t* cr)
146 {
147         if (_session == 0) {
148                 return;
149         }
150
151         /* background */
152         
153         cairo_set_source_rgb (cr, 0, 0, 0);
154         cairo_rectangle (cr, 0, 0, _width, _height);
155         cairo_fill (cr);
156
157         /* compute total height of all tracks */
158         
159         int h = 0;
160         int max_height = 0;
161         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
162                 int const t = (*i)->effective_height ();
163                 h += t;
164                 max_height = max (max_height, t);
165         }
166
167         nframes_t const start = _session->current_start_frame ();
168         _pixels_per_frame = static_cast<double> (_width) / (_session->current_end_frame() - start);
169         _vertical_scale = static_cast<double> (_height) / h;
170
171         /* tallest a region should ever be in the summary, in pixels */
172         int const tallest_region_pixels = 12;
173
174         if (max_height * _vertical_scale > tallest_region_pixels) {
175                 _vertical_scale = static_cast<double> (tallest_region_pixels) / max_height;
176         }
177
178         /* render regions */
179
180         double y = 0;
181         for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
182                 StreamView* s = (*i)->view ();
183
184                 if (s) {
185                         double const h = (*i)->effective_height () * _vertical_scale;
186                         cairo_set_line_width (cr, h);
187
188                         s->foreach_regionview (bind (
189                                                        mem_fun (*this, &EditorSummary::render_region),
190                                                        cr,
191                                                        start,
192                                                        y + h / 2
193                                                        ));
194                         y += h;
195                 }
196         }
197
198 }
199
200 /** Render a region for the summary.
201  *  @param r Region view.
202  *  @param cr Cairo context.
203  *  @param start Frame offset that the summary starts at.
204  *  @param y y coordinate to render at.
205  */
206 void
207 EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const
208 {
209         uint32_t const c = r->get_fill_color ();
210         cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
211                         
212         cairo_move_to (cr, (r->region()->position() - start) * _pixels_per_frame, y);
213         cairo_line_to (cr, ((r->region()->position() - start + r->region()->length())) * _pixels_per_frame, y);
214         cairo_stroke (cr);
215 }
216
217 /** Set the summary so that the whole thing will be re-rendered next time it is required */
218 void
219 EditorSummary::set_dirty ()
220 {
221         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_dirty));
222
223         _regions_dirty = true;
224         queue_draw ();
225 }
226
227 /** Set the summary so that just the view boundary markers will be re-rendered */
228 void
229 EditorSummary::set_bounds_dirty ()
230 {
231         ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_bounds_dirty));
232         queue_draw ();
233 }
234
235 /** Handle a size request.
236  *  @param req GTK requisition
237  */
238 void
239 EditorSummary::on_size_request (Gtk::Requisition *req)
240 {
241         /* Use a dummy, small width and the actual height that we want */
242         req->width = 64;
243         req->height = 64;
244 }
245
246 /** Handle a size allocation.
247  *  @param alloc GTK allocation.
248  */
249 void
250 EditorSummary::on_size_allocate (Gtk::Allocation& alloc)
251 {
252         Gtk::EventBox::on_size_allocate (alloc);
253
254         _width = alloc.get_width ();
255         _height = alloc.get_height ();
256
257         set_dirty ();
258 }
259
260 void
261 EditorSummary::centre_on_click (GdkEventButton* ev)
262 {
263         nframes_t x = (ev->x / _pixels_per_frame) + _session->current_start_frame();
264         nframes_t const xh = _editor->current_page_frames () / 2;
265         if (x > xh) {
266                 x -= xh;
267         } else {
268                 x = 0;
269         }
270         
271         _editor->reset_x_origin (x);
272         
273         double y = ev->y / _vertical_scale;
274         double const yh = _editor->canvas_height () / 2;
275         if (y > yh) {
276                 y -= yh;
277         } else {
278                 y = 0;
279         }
280         
281         _editor->reset_y_origin (y);
282 }
283
284 /** Handle a button press.
285  *  @param ev GTK event.
286  */
287 bool
288 EditorSummary::on_button_press_event (GdkEventButton* ev)
289 {
290         if (ev->button == 1) {
291
292                 pair<double, double> xr;
293                 pair<double, double> yr;
294                 editor_view (&xr, &yr);
295
296                 if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) {
297
298                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
299
300                                 /* modifier-click inside the view rectangle: start a zoom drag */
301                                 _zoom_position = NONE;
302
303                                 double const x1 = xr.first + (xr.second - xr.first) * 0.33;
304                                 double const x2 = xr.first + (xr.second - xr.first) * 0.67;
305
306                                 if (ev->x < x1) {
307                                         _zoom_position = LEFT;
308                                 } else if (ev->x > x2) {
309                                         _zoom_position = RIGHT;
310                                 } else {
311                                         _zoom_position = NONE;
312                                 }
313                                                 
314                                 if (_zoom_position != NONE) {
315                                         _zoom_dragging = true;
316                                         _mouse_x_start = ev->x;
317                                         _width_start = xr.second - xr.first;
318                                         _zoom_start = _editor->get_current_zoom ();
319                                         _frames_start = _editor->leftmost_position ();
320                                         _editor->_dragging_playhead = true;
321                                 }
322                                         
323                         } else {
324
325                                 /* ordinary click inside the view rectangle: start a move drag */
326                                 
327                                 _move_dragging = true;
328                                 _moved = false;
329                                 _x_offset = ev->x - xr.first;
330                                 _y_offset = ev->y - yr.first;
331                                 _editor->_dragging_playhead = true;
332                         }
333                         
334                 } else {
335                         
336                         /* click outside the view rectangle: centre the view around the mouse click */
337                         centre_on_click (ev);
338                 }
339         }
340
341         return true;
342 }
343
344 void
345 EditorSummary::editor_view (pair<double, double>* x, pair<double, double>* y) const
346 {
347         x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _pixels_per_frame;
348         x->second = x->first + _editor->current_page_frames() * _pixels_per_frame;
349
350         y->first = _editor->get_trackview_group_vertical_offset () * _vertical_scale;
351         y->second = y->first + _editor->canvas_height () * _vertical_scale;
352 }
353
354 bool
355 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
356 {
357         if (_move_dragging) {
358
359                 _moved = true;
360                 _editor->reset_x_origin (((ev->x - _x_offset) / _pixels_per_frame) + _session->current_start_frame ());
361                 _editor->reset_y_origin ((ev->y - _y_offset) / _vertical_scale);
362                 return true;
363
364         } else if (_zoom_dragging) {
365
366                 double const dx = ev->x - _mouse_x_start;
367
368                 nframes64_t rx = _frames_start;
369                 double f = 1;
370                 
371                 switch (_zoom_position) {
372                 case LEFT:
373                         f = 1 - (dx / _width_start);
374                         rx += (dx / _pixels_per_frame);
375                         break;
376                 case RIGHT:
377                         f = 1 + (dx / _width_start);
378                         break;
379                 case NONE:
380                         break;
381                 }
382
383                 if (_editor->pending_visual_change.idle_handler_id < 0) {
384                         
385                         /* As a side-effect, the Editor's visual change idle handler processes
386                            pending GTK events.  Hence this motion notify handler can be called
387                            in the middle of a visual change idle handler, and if this happens,
388                            the queue_visual_change calls below modify the variables that the
389                            idle handler is working with.  This causes problems.  Hence the
390                            check above.  It ensures that we won't modify the pending visual change
391                            while a visual change idle handler is in progress.  It's not perfect,
392                            as it also means that we won't change these variables if an idle handler
393                            is merely pending but not executing.  But c'est la vie.
394                         */
395                            
396                         _editor->queue_visual_change (rx);
397                         _editor->queue_visual_change (_zoom_start * f);
398                 }
399         }
400                 
401         return true;
402 }
403
404 bool
405 EditorSummary::on_button_release_event (GdkEventButton* ev)
406 {
407         if (_move_dragging && !_moved) {
408                 centre_on_click (ev);
409         }
410
411         _move_dragging = false;
412         _zoom_dragging = false;
413         _editor->_dragging_playhead = false;
414         return true;
415 }