Clean up region.h and trim include tree.
[ardour2.git] / gtk2_ardour / editor_summary.cc
blobcd4356eb75bc1cb64585d7e2985625e230b29f19
1 /*
2 Copyright (C) 2009 Paul Davis
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.
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.
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.
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 #include "editor_routes.h"
30 #include "editor_cursors.h"
31 #include "mouse_cursors.h"
33 using namespace std;
34 using namespace ARDOUR;
35 using Gtkmm2ext::Keyboard;
37 /** Construct an EditorSummary.
38 * @param e Editor to represent.
40 EditorSummary::EditorSummary (Editor* e)
41 : EditorComponent (e),
42 _start (0),
43 _end (1),
44 _overhang_fraction (0.1),
45 _x_scale (1),
46 _track_height (16),
47 _last_playhead (-1),
48 _move_dragging (false),
49 _moved (false),
50 _view_rectangle_x (0, 0),
51 _view_rectangle_y (0, 0),
52 _zoom_dragging (false)
54 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
55 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
57 add_events (Gdk::POINTER_MOTION_MASK);
60 /** Connect to a session.
61 * @param s Session.
63 void
64 EditorSummary::set_session (Session* s)
66 SessionHandlePtr::set_session (s);
68 set_dirty ();
70 /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
71 * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
72 * emitted when a cut region is added to the `cutlist' playlist.
75 if (_session) {
76 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
77 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
81 /** Handle an expose event.
82 * @param event Event from GTK.
84 bool
85 EditorSummary::on_expose_event (GdkEventExpose* event)
87 CairoWidget::on_expose_event (event);
89 if (_session == 0) {
90 return false;
93 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
95 /* Render the view rectangle. If there is an editor visual pending, don't update
96 the view rectangle now --- wait until the expose event that we'll get after
97 the visual change. This prevents a flicker.
100 if (_editor->pending_visual_change.idle_handler_id < 0) {
101 get_editor (&_view_rectangle_x, &_view_rectangle_y);
104 cairo_move_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
105 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.first);
106 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.second);
107 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.second);
108 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
109 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
110 cairo_fill_preserve (cr);
111 cairo_set_line_width (cr, 1);
112 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
113 cairo_stroke (cr);
115 /* Playhead */
117 cairo_set_line_width (cr, 1);
118 /* XXX: colour should be set from configuration file */
119 cairo_set_source_rgba (cr, 1, 0, 0, 1);
121 double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
122 cairo_move_to (cr, p, 0);
123 cairo_line_to (cr, p, _height);
124 cairo_stroke (cr);
125 _last_playhead = p;
127 cairo_destroy (cr);
129 return true;
132 /** Render the required regions to a cairo context.
133 * @param cr Context.
135 void
136 EditorSummary::render (cairo_t* cr)
138 /* background */
140 cairo_set_source_rgb (cr, 0, 0, 0);
141 cairo_rectangle (cr, 0, 0, _width, _height);
142 cairo_fill (cr);
144 if (_session == 0) {
145 return;
148 /* compute start and end points for the summary */
150 framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
151 double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
152 _start = theoretical_start > 0 ? theoretical_start : 0;
153 _end = _session->current_end_frame() + session_length * _overhang_fraction;
155 /* compute track height */
156 int N = 0;
157 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
158 if (!(*i)->hidden()) {
159 ++N;
163 if (N == 0) {
164 _track_height = 16;
165 } else {
166 _track_height = (double) _height / N;
169 /* calculate x scale */
170 if (_end != _start) {
171 _x_scale = static_cast<double> (_width) / (_end - _start);
172 } else {
173 _x_scale = 1;
176 /* render tracks and regions */
178 double y = 0;
179 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
181 if ((*i)->hidden()) {
182 continue;
185 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
186 cairo_set_line_width (cr, _track_height - 2);
187 cairo_move_to (cr, 0, y + _track_height / 2);
188 cairo_line_to (cr, _width, y + _track_height / 2);
189 cairo_stroke (cr);
191 StreamView* s = (*i)->view ();
193 if (s) {
194 cairo_set_line_width (cr, _track_height * 0.6);
196 s->foreach_regionview (sigc::bind (
197 sigc::mem_fun (*this, &EditorSummary::render_region),
199 y + _track_height / 2
203 y += _track_height;
206 /* start and end markers */
208 cairo_set_line_width (cr, 1);
209 cairo_set_source_rgb (cr, 1, 1, 0);
211 double const p = (_session->current_start_frame() - _start) * _x_scale;
212 cairo_move_to (cr, p, 0);
213 cairo_line_to (cr, p, _height);
214 cairo_stroke (cr);
216 double const q = (_session->current_end_frame() - _start) * _x_scale;
217 cairo_move_to (cr, q, 0);
218 cairo_line_to (cr, q, _height);
219 cairo_stroke (cr);
222 /** Render a region for the summary.
223 * @param r Region view.
224 * @param cr Cairo context.
225 * @param y y coordinate to render at.
227 void
228 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
230 uint32_t const c = r->get_fill_color ();
231 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
233 if (r->region()->position() > _start) {
234 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
235 } else {
236 cairo_move_to (cr, 0, y);
239 if ((r->region()->position() + r->region()->length()) > _start) {
240 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
241 } else {
242 cairo_line_to (cr, 0, y);
245 cairo_stroke (cr);
248 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
249 void
250 EditorSummary::set_overlays_dirty ()
252 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
253 queue_draw ();
256 /** Handle a size request.
257 * @param req GTK requisition
259 void
260 EditorSummary::on_size_request (Gtk::Requisition *req)
262 /* Use a dummy, small width and the actual height that we want */
263 req->width = 64;
264 req->height = 32;
268 void
269 EditorSummary::centre_on_click (GdkEventButton* ev)
271 pair<double, double> xr;
272 pair<double, double> yr;
273 get_editor (&xr, &yr);
275 double const w = xr.second - xr.first;
277 xr.first = ev->x - w / 2;
278 xr.second = ev->x + w / 2;
280 if (xr.first < 0) {
281 xr.first = 0;
282 xr.second = w;
283 } else if (xr.second > _width) {
284 xr.second = _width;
285 xr.first = _width - w;
288 double ey = summary_y_to_editor (ev->y);
289 ey -= (_editor->canvas_height() - _editor->get_canvas_timebars_vsize ()) / 2;
290 if (ey < 0) {
291 ey = 0;
294 set_editor (xr, editor_y_to_summary (ey));
297 /** Handle a button press.
298 * @param ev GTK event.
300 bool
301 EditorSummary::on_button_press_event (GdkEventButton* ev)
303 if (ev->button == 1) {
305 pair<double, double> xr;
306 pair<double, double> yr;
307 get_editor (&xr, &yr);
309 _start_editor_x = xr;
310 _start_editor_y = yr;
311 _start_mouse_x = ev->x;
312 _start_mouse_y = ev->y;
313 _start_position = get_position (ev->x, ev->y);
315 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
316 _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
319 /* start a zoom drag */
321 _zoom_position = get_position (ev->x, ev->y);
322 _zoom_dragging = true;
323 _editor->_dragging_playhead = true;
325 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
327 /* secondary-modifier-click: locate playhead */
328 if (_session) {
329 _session->request_locate (ev->x / _x_scale + _start);
332 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
334 centre_on_click (ev);
336 } else {
338 /* start a move drag */
340 _move_dragging = true;
341 _moved = false;
342 _editor->_dragging_playhead = true;
346 return true;
349 /** Fill in x and y with the editor's current viewable area in summary coordinates */
350 void
351 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
353 assert (x);
354 assert (y);
356 x->first = (_editor->leftmost_position () - _start) * _x_scale;
357 x->second = x->first + _editor->current_page_frames() * _x_scale;
359 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
360 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
363 /** Get an expression of the position of a point with respect to the view rectangle */
364 EditorSummary::Position
365 EditorSummary::get_position (double x, double y) const
367 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
368 in pixels */
370 int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
371 x_edge_size = min (x_edge_size, 8);
372 x_edge_size = max (x_edge_size, 1);
374 int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
375 y_edge_size = min (y_edge_size, 8);
376 y_edge_size = max (y_edge_size, 1);
378 bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
379 bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
380 bool const near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
381 bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_edge_size);
382 bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
383 bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
385 if (near_left && near_top) {
386 return LEFT_TOP;
387 } else if (near_left && near_bottom) {
388 return LEFT_BOTTOM;
389 } else if (near_right && near_top) {
390 return RIGHT_TOP;
391 } else if (near_right && near_bottom) {
392 return RIGHT_BOTTOM;
393 } else if (near_left && within_y) {
394 return LEFT;
395 } else if (near_right && within_y) {
396 return RIGHT;
397 } else if (near_top && within_x) {
398 return TOP;
399 } else if (near_bottom && within_x) {
400 return BOTTOM;
401 } else if (within_x && within_y) {
402 return INSIDE;
403 } else if (within_x) {
404 return BELOW_OR_ABOVE;
405 } else if (within_y) {
406 return TO_LEFT_OR_RIGHT;
407 } else {
408 return OTHERWISE_OUTSIDE;
412 void
413 EditorSummary::set_cursor (Position p)
415 switch (p) {
416 case LEFT:
417 get_window()->set_cursor (*_editor->_cursors->resize_left);
418 break;
419 case LEFT_TOP:
420 get_window()->set_cursor (*_editor->_cursors->resize_top_left);
421 break;
422 case TOP:
423 get_window()->set_cursor (*_editor->_cursors->resize_top);
424 break;
425 case RIGHT_TOP:
426 get_window()->set_cursor (*_editor->_cursors->resize_top_right);
427 break;
428 case RIGHT:
429 get_window()->set_cursor (*_editor->_cursors->resize_right);
430 break;
431 case RIGHT_BOTTOM:
432 get_window()->set_cursor (*_editor->_cursors->resize_bottom_right);
433 break;
434 case BOTTOM:
435 get_window()->set_cursor (*_editor->_cursors->resize_bottom);
436 break;
437 case LEFT_BOTTOM:
438 get_window()->set_cursor (*_editor->_cursors->resize_bottom_left);
439 break;
440 case INSIDE:
441 get_window()->set_cursor (*_editor->_cursors->move);
442 break;
443 case TO_LEFT_OR_RIGHT:
444 get_window()->set_cursor (*_editor->_cursors->expand_left_right);
445 break;
446 case BELOW_OR_ABOVE:
447 get_window()->set_cursor (*_editor->_cursors->expand_up_down);
448 break;
449 default:
450 get_window()->set_cursor ();
451 break;
455 bool
456 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
458 pair<double, double> xr = _start_editor_x;
459 pair<double, double> yr = _start_editor_y;
460 double y = _start_editor_y.first;
462 if (_move_dragging) {
464 _moved = true;
466 /* don't alter x if we clicked outside and above or below the viewbox */
467 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
468 xr.first += ev->x - _start_mouse_x;
469 xr.second += ev->x - _start_mouse_x;
472 /* don't alter y if we clicked outside and to the left or right of the viewbox */
473 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
474 y += ev->y - _start_mouse_y;
477 if (xr.first < 0) {
478 xr.second -= xr.first;
479 xr.first = 0;
482 if (y < 0) {
483 y = 0;
486 set_editor (xr, y);
487 set_cursor (_start_position);
489 } else if (_zoom_dragging) {
491 double const dx = ev->x - _start_mouse_x;
492 double const dy = ev->y - _start_mouse_y;
494 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
495 xr.first += dx;
496 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
497 xr.second += dx;
500 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
501 yr.first += dy;
502 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
503 yr.second += dy;
506 set_overlays_dirty ();
507 set_cursor (_zoom_position);
508 set_editor (xr, yr);
510 } else {
512 set_cursor (get_position (ev->x, ev->y));
516 return true;
519 bool
520 EditorSummary::on_button_release_event (GdkEventButton*)
522 _move_dragging = false;
523 _zoom_dragging = false;
524 _editor->_dragging_playhead = false;
525 return true;
528 bool
529 EditorSummary::on_scroll_event (GdkEventScroll* ev)
531 /* mouse wheel */
533 pair<double, double> xr;
534 pair<double, double> yr;
535 get_editor (&xr, &yr);
536 double y = yr.first;
538 double amount = 8;
540 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
541 amount = 64;
542 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
543 amount = 1;
546 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
548 /* primary-wheel == left-right scrolling */
550 if (ev->direction == GDK_SCROLL_UP) {
551 xr.first += amount;
552 xr.second += amount;
553 } else if (ev->direction == GDK_SCROLL_DOWN) {
554 xr.first -= amount;
555 xr.second -= amount;
558 } else {
560 if (ev->direction == GDK_SCROLL_DOWN) {
561 y += amount;
562 } else if (ev->direction == GDK_SCROLL_UP) {
563 y -= amount;
564 } else if (ev->direction == GDK_SCROLL_LEFT) {
565 xr.first -= amount;
566 xr.second -= amount;
567 } else if (ev->direction == GDK_SCROLL_RIGHT) {
568 xr.first += amount;
569 xr.second += amount;
573 set_editor (xr, y);
574 return true;
577 /** Set the editor to display a given x range and a y range with the top at a given position.
578 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
579 * x and y parameters are specified in summary coordinates.
581 void
582 EditorSummary::set_editor (pair<double,double> const & x, double const y)
584 if (_editor->pending_visual_change.idle_handler_id >= 0) {
586 /* As a side-effect, the Editor's visual change idle handler processes
587 pending GTK events. Hence this motion notify handler can be called
588 in the middle of a visual change idle handler, and if this happens,
589 the queue_visual_change calls below modify the variables that the
590 idle handler is working with. This causes problems. Hence this
591 check. It ensures that we won't modify the pending visual change
592 while a visual change idle handler is in progress. It's not perfect,
593 as it also means that we won't change these variables if an idle handler
594 is merely pending but not executing. But c'est la vie.
597 return;
600 set_editor_x (x);
601 set_editor_y (y);
604 /** Set the editor to display given x and y ranges. x zoom and track heights are
605 * adjusted if necessary.
606 * x and y parameters are specified in summary coordinates.
608 void
609 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
611 if (_editor->pending_visual_change.idle_handler_id >= 0) {
612 /* see comment in other set_editor () */
613 return;
616 set_editor_x (x);
617 set_editor_y (y);
620 /** Set the x range visible in the editor.
621 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
622 * @param x new x range in summary coordinates.
624 void
625 EditorSummary::set_editor_x (pair<double, double> const & x)
627 _editor->reset_x_origin (x.first / _x_scale + _start);
629 double const nx = (
630 ((x.second - x.first) / _x_scale) /
631 _editor->frame_to_unit (_editor->current_page_frames())
634 if (nx != _editor->get_current_zoom ()) {
635 _editor->reset_zoom (nx);
639 /** Set the top of the y range visible in the editor.
640 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
641 * @param y new editor top in summary coodinates.
643 void
644 EditorSummary::set_editor_y (double const y)
646 double y1 = summary_y_to_editor (y);
647 double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
648 double y2 = y1 + eh;
650 double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
652 if (y2 > full_editor_height) {
653 y1 -= y2 - full_editor_height;
656 if (y1 < 0) {
657 y1 = 0;
660 _editor->reset_y_origin (y1);
663 /** Set the y range visible in the editor. This is achieved by scaling track heights,
664 * if necessary.
665 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
666 * @param y new editor range in summary coodinates.
668 void
669 EditorSummary::set_editor_y (pair<double, double> const & y)
671 /* Compute current height of tracks between y.first and y.second. We add up
672 the total height into `total_height' and the height of complete tracks into
673 `scale height'.
676 /* Copy of target range for use below */
677 pair<double, double> yc = y;
678 /* Total height of all tracks */
679 double total_height = 0;
680 /* Height of any parts of tracks that aren't fully in the desired range */
681 double partial_height = 0;
682 /* Height of any tracks that are fully in the desired range */
683 double scale_height = 0;
685 _editor->_routes->suspend_redisplay ();
687 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
689 if ((*i)->hidden()) {
690 continue;
693 double const h = (*i)->effective_height ();
694 total_height += h;
696 if (yc.first > 0 && yc.first < _track_height) {
697 partial_height += (_track_height - yc.first) * h / _track_height;
698 } else if (yc.first <= 0 && yc.second >= _track_height) {
699 scale_height += h;
700 } else if (yc.second > 0 && yc.second < _track_height) {
701 partial_height += yc.second * h / _track_height;
702 break;
705 yc.first -= _track_height;
706 yc.second -= _track_height;
709 /* Height that we will use for scaling; use the whole editor height unless there are not
710 enough tracks to fill it.
712 double const ch = min (total_height, _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
714 /* hence required scale factor of the complete tracks to fit the required y range;
715 the amount of space they should take up divided by the amount they currently take up.
717 double const scale = (ch - partial_height) / scale_height;
719 yc = y;
721 /* Scale complete tracks within the range to make it fit */
723 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
725 if ((*i)->hidden()) {
726 continue;
729 if (yc.first <= 0 && yc.second >= _track_height) {
730 (*i)->set_height (max (TimeAxisView::preset_height (HeightSmall), (uint32_t) ((*i)->effective_height() * scale)));
733 yc.first -= _track_height;
734 yc.second -= _track_height;
737 _editor->_routes->resume_redisplay ();
739 set_editor_y (y.first);
742 void
743 EditorSummary::playhead_position_changed (framepos_t p)
745 if (_session && int (p * _x_scale) != int (_last_playhead)) {
746 set_overlays_dirty ();
750 double
751 EditorSummary::summary_y_to_editor (double y) const
753 double ey = 0;
754 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
756 if ((*i)->hidden()) {
757 continue;
760 double const h = (*i)->effective_height ();
761 if (y < _track_height) {
762 /* in this track */
763 return ey + y * h / _track_height;
766 ey += h;
767 y -= _track_height;
770 return ey;
773 double
774 EditorSummary::editor_y_to_summary (double y) const
776 double sy = 0;
777 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
779 if ((*i)->hidden()) {
780 continue;
783 double const h = (*i)->effective_height ();
784 if (y < h) {
785 /* in this track */
786 return sy + y * _track_height / h;
789 sy += _track_height;
790 y -= h;
793 return sy;