handle multiple imports of the same file better (via better source naming); make...
[ardour2.git] / gtk2_ardour / editor_summary.cc
blobb75f228c52125f02e588200cf409ce6b8e09ad55
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"
30 using namespace std;
31 using namespace ARDOUR;
32 using Gtkmm2ext::Keyboard;
34 /** Construct an EditorSummary.
35 * @param e Editor to represent.
37 EditorSummary::EditorSummary (Editor* e)
38 : EditorComponent (e),
39 _start (0),
40 _end (1),
41 _overhang_fraction (0.1),
42 _x_scale (1),
43 _track_height (16),
44 _last_playhead (-1),
45 _move_dragging (false),
46 _moved (false),
47 _view_rectangle_x (0, 0),
48 _view_rectangle_y (0, 0),
49 _zoom_dragging (false)
51 Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context());
52 _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context());
54 add_events (Gdk::POINTER_MOTION_MASK);
57 /** Connect to a session.
58 * @param s Session.
60 void
61 EditorSummary::set_session (Session* s)
63 SessionHandlePtr::set_session (s);
65 set_dirty ();
67 /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added
68 * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged
69 * emitted when a cut region is added to the `cutlist' playlist.
72 if (_session) {
73 _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
74 _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context());
78 /** Handle an expose event.
79 * @param event Event from GTK.
81 bool
82 EditorSummary::on_expose_event (GdkEventExpose* event)
84 CairoWidget::on_expose_event (event);
86 if (_session == 0) {
87 return false;
90 cairo_t* cr = gdk_cairo_create (get_window()->gobj());
92 /* Render the view rectangle. If there is an editor visual pending, don't update
93 the view rectangle now --- wait until the expose event that we'll get after
94 the visual change. This prevents a flicker.
97 if (_editor->pending_visual_change.idle_handler_id < 0) {
98 get_editor (&_view_rectangle_x, &_view_rectangle_y);
101 cairo_move_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
102 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.first);
103 cairo_line_to (cr, _view_rectangle_x.second, _view_rectangle_y.second);
104 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.second);
105 cairo_line_to (cr, _view_rectangle_x.first, _view_rectangle_y.first);
106 cairo_set_source_rgba (cr, 1, 1, 1, 0.25);
107 cairo_fill_preserve (cr);
108 cairo_set_line_width (cr, 1);
109 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
110 cairo_stroke (cr);
112 /* Playhead */
114 cairo_set_line_width (cr, 1);
115 /* XXX: colour should be set from configuration file */
116 cairo_set_source_rgba (cr, 1, 0, 0, 1);
118 double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale;
119 cairo_move_to (cr, p, 0);
120 cairo_line_to (cr, p, _height);
121 cairo_stroke (cr);
122 _last_playhead = p;
124 cairo_destroy (cr);
126 return true;
129 /** Render the required regions to a cairo context.
130 * @param cr Context.
132 void
133 EditorSummary::render (cairo_t* cr)
135 /* background */
137 cairo_set_source_rgb (cr, 0, 0, 0);
138 cairo_rectangle (cr, 0, 0, _width, _height);
139 cairo_fill (cr);
141 if (_session == 0) {
142 return;
145 /* compute start and end points for the summary */
147 nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
148 double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
149 _start = theoretical_start > 0 ? theoretical_start : 0;
150 _end = _session->current_end_frame() + session_length * _overhang_fraction;
152 /* compute track height */
153 int N = 0;
154 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
155 if (!(*i)->hidden()) {
156 ++N;
160 if (N == 0) {
161 _track_height = 16;
162 } else {
163 _track_height = (double) _height / N;
166 /* calculate x scale */
167 if (_end != _start) {
168 _x_scale = static_cast<double> (_width) / (_end - _start);
169 } else {
170 _x_scale = 1;
173 /* render tracks and regions */
175 double y = 0;
176 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
178 if ((*i)->hidden()) {
179 continue;
182 cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
183 cairo_set_line_width (cr, _track_height - 2);
184 cairo_move_to (cr, 0, y + _track_height / 2);
185 cairo_line_to (cr, _width, y + _track_height / 2);
186 cairo_stroke (cr);
188 StreamView* s = (*i)->view ();
190 if (s) {
191 cairo_set_line_width (cr, _track_height * 0.6);
193 s->foreach_regionview (sigc::bind (
194 sigc::mem_fun (*this, &EditorSummary::render_region),
196 y + _track_height / 2
200 y += _track_height;
203 /* start and end markers */
205 cairo_set_line_width (cr, 1);
206 cairo_set_source_rgb (cr, 1, 1, 0);
208 double const p = (_session->current_start_frame() - _start) * _x_scale;
209 cairo_move_to (cr, p, 0);
210 cairo_line_to (cr, p, _height);
211 cairo_stroke (cr);
213 double const q = (_session->current_end_frame() - _start) * _x_scale;
214 cairo_move_to (cr, q, 0);
215 cairo_line_to (cr, q, _height);
216 cairo_stroke (cr);
219 /** Render a region for the summary.
220 * @param r Region view.
221 * @param cr Cairo context.
222 * @param y y coordinate to render at.
224 void
225 EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
227 uint32_t const c = r->get_fill_color ();
228 cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0);
230 if (r->region()->position() > _start) {
231 cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y);
232 } else {
233 cairo_move_to (cr, 0, y);
236 if ((r->region()->position() + r->region()->length()) > _start) {
237 cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y);
238 } else {
239 cairo_line_to (cr, 0, y);
242 cairo_stroke (cr);
245 /** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */
246 void
247 EditorSummary::set_overlays_dirty ()
249 ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty)
250 queue_draw ();
253 /** Handle a size request.
254 * @param req GTK requisition
256 void
257 EditorSummary::on_size_request (Gtk::Requisition *req)
259 /* Use a dummy, small width and the actual height that we want */
260 req->width = 64;
261 req->height = 32;
265 void
266 EditorSummary::centre_on_click (GdkEventButton* ev)
268 pair<double, double> xr;
269 pair<double, double> yr;
270 get_editor (&xr, &yr);
272 double const w = xr.second - xr.first;
274 xr.first = ev->x - w / 2;
275 xr.second = ev->x + w / 2;
277 if (xr.first < 0) {
278 xr.first = 0;
279 xr.second = w;
280 } else if (xr.second > _width) {
281 xr.second = _width;
282 xr.first = _width - w;
285 double ey = summary_y_to_editor (ev->y);
286 ey -= (_editor->canvas_height() - _editor->get_canvas_timebars_vsize ()) / 2;
287 if (ey < 0) {
288 ey = 0;
291 set_editor (xr, editor_y_to_summary (ey));
294 /** Handle a button press.
295 * @param ev GTK event.
297 bool
298 EditorSummary::on_button_press_event (GdkEventButton* ev)
300 if (ev->button == 1) {
302 pair<double, double> xr;
303 pair<double, double> yr;
304 get_editor (&xr, &yr);
306 _start_editor_x = xr;
307 _start_editor_y = yr;
308 _start_mouse_x = ev->x;
309 _start_mouse_y = ev->y;
310 _start_position = get_position (ev->x, ev->y);
312 if (_start_position != INSIDE && _start_position != BELOW_OR_ABOVE &&
313 _start_position != TO_LEFT_OR_RIGHT && _start_position != OTHERWISE_OUTSIDE
316 /* start a zoom drag */
318 _zoom_position = get_position (ev->x, ev->y);
319 _zoom_dragging = true;
320 _editor->_dragging_playhead = true;
322 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) {
324 /* secondary-modifier-click: locate playhead */
325 if (_session) {
326 _session->request_locate (ev->x / _x_scale + _start);
329 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
331 centre_on_click (ev);
333 } else {
335 /* start a move drag */
337 _move_dragging = true;
338 _moved = false;
339 _editor->_dragging_playhead = true;
343 return true;
346 /** Fill in x and y with the editor's current viewable area in summary coordinates */
347 void
348 EditorSummary::get_editor (pair<double, double>* x, pair<double, double>* y) const
350 assert (x);
351 assert (y);
353 x->first = (_editor->leftmost_position () - _start) * _x_scale;
354 x->second = x->first + _editor->current_page_frames() * _x_scale;
356 y->first = editor_y_to_summary (_editor->vertical_adjustment.get_value ());
357 y->second = editor_y_to_summary (_editor->vertical_adjustment.get_value () + _editor->canvas_height() - _editor->get_canvas_timebars_vsize());
360 /** Get an expression of the position of a point with respect to the view rectangle */
361 EditorSummary::Position
362 EditorSummary::get_position (double x, double y) const
364 /* how close the mouse has to be to the edge of the view rectangle to be considered `on it',
365 in pixels */
367 int x_edge_size = (_view_rectangle_x.second - _view_rectangle_x.first) / 4;
368 x_edge_size = min (x_edge_size, 8);
369 x_edge_size = max (x_edge_size, 1);
371 int y_edge_size = (_view_rectangle_y.second - _view_rectangle_y.first) / 4;
372 y_edge_size = min (y_edge_size, 8);
373 y_edge_size = max (y_edge_size, 1);
375 bool const near_left = (std::abs (x - _view_rectangle_x.first) < x_edge_size);
376 bool const near_right = (std::abs (x - _view_rectangle_x.second) < x_edge_size);
377 bool const near_top = (std::abs (y - _view_rectangle_y.first) < y_edge_size);
378 bool const near_bottom = (std::abs (y - _view_rectangle_y.second) < y_edge_size);
379 bool const within_x = _view_rectangle_x.first < x && x < _view_rectangle_x.second;
380 bool const within_y = _view_rectangle_y.first < y && y < _view_rectangle_y.second;
382 if (near_left && near_top) {
383 return LEFT_TOP;
384 } else if (near_left && near_bottom) {
385 return LEFT_BOTTOM;
386 } else if (near_right && near_top) {
387 return RIGHT_TOP;
388 } else if (near_right && near_bottom) {
389 return RIGHT_BOTTOM;
390 } else if (near_left && within_y) {
391 return LEFT;
392 } else if (near_right && within_y) {
393 return RIGHT;
394 } else if (near_top && within_x) {
395 return TOP;
396 } else if (near_bottom && within_x) {
397 return BOTTOM;
398 } else if (within_x && within_y) {
399 return INSIDE;
400 } else if (within_x) {
401 return BELOW_OR_ABOVE;
402 } else if (within_y) {
403 return TO_LEFT_OR_RIGHT;
404 } else {
405 return OTHERWISE_OUTSIDE;
409 void
410 EditorSummary::set_cursor (Position p)
412 switch (p) {
413 case LEFT:
414 get_window()->set_cursor (*_editor->left_side_trim_cursor);
415 break;
416 case LEFT_TOP:
417 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_LEFT_CORNER));
418 break;
419 case TOP:
420 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_SIDE));
421 break;
422 case RIGHT_TOP:
423 get_window()->set_cursor (Gdk::Cursor (Gdk::TOP_RIGHT_CORNER));
424 break;
425 case RIGHT:
426 get_window()->set_cursor (*_editor->right_side_trim_cursor);
427 break;
428 case RIGHT_BOTTOM:
429 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_RIGHT_CORNER));
430 break;
431 case BOTTOM:
432 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_SIDE));
433 break;
434 case LEFT_BOTTOM:
435 get_window()->set_cursor (Gdk::Cursor (Gdk::BOTTOM_LEFT_CORNER));
436 break;
437 case INSIDE:
438 get_window()->set_cursor (Gdk::Cursor (Gdk::FLEUR));
439 break;
440 default:
441 get_window()->set_cursor ();
442 break;
446 bool
447 EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
449 pair<double, double> xr = _start_editor_x;
450 pair<double, double> yr = _start_editor_y;
451 double y = _start_editor_y.first;
453 if (_move_dragging) {
455 _moved = true;
457 /* don't alter x if we clicked outside and above or below the viewbox */
458 if (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT || _start_position == OTHERWISE_OUTSIDE) {
459 xr.first += ev->x - _start_mouse_x;
460 xr.second += ev->x - _start_mouse_x;
463 /* don't alter y if we clicked outside and to the left or right of the viewbox */
464 if (_start_position == INSIDE || _start_position == BELOW_OR_ABOVE) {
465 y += ev->y - _start_mouse_y;
468 if (xr.first < 0) {
469 xr.second -= xr.first;
470 xr.first = 0;
473 if (y < 0) {
474 y = 0;
477 set_editor (xr, y);
478 set_cursor (INSIDE);
480 } else if (_zoom_dragging) {
482 double const dx = ev->x - _start_mouse_x;
483 double const dy = ev->y - _start_mouse_y;
485 if (_zoom_position == LEFT || _zoom_position == LEFT_TOP || _zoom_position == LEFT_BOTTOM) {
486 xr.first += dx;
487 } else if (_zoom_position == RIGHT || _zoom_position == RIGHT_TOP || _zoom_position == RIGHT_BOTTOM) {
488 xr.second += dx;
491 if (_zoom_position == TOP || _zoom_position == LEFT_TOP || _zoom_position == RIGHT_TOP) {
492 yr.first += dy;
493 } else if (_zoom_position == BOTTOM || _zoom_position == LEFT_BOTTOM || _zoom_position == RIGHT_BOTTOM) {
494 yr.second += dy;
497 set_overlays_dirty ();
498 set_cursor (_zoom_position);
499 set_editor (xr, yr);
501 } else {
503 set_cursor (get_position (ev->x, ev->y));
507 return true;
510 bool
511 EditorSummary::on_button_release_event (GdkEventButton*)
513 _move_dragging = false;
514 _zoom_dragging = false;
515 _editor->_dragging_playhead = false;
516 return true;
519 bool
520 EditorSummary::on_scroll_event (GdkEventScroll* ev)
522 /* mouse wheel */
524 pair<double, double> xr;
525 pair<double, double> yr;
526 get_editor (&xr, &yr);
527 double y = yr.first;
529 double amount = 8;
531 if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) {
532 amount = 64;
533 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
534 amount = 1;
537 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
539 /* primary-wheel == left-right scrolling */
541 if (ev->direction == GDK_SCROLL_UP) {
542 xr.first += amount;
543 xr.second += amount;
544 } else if (ev->direction == GDK_SCROLL_DOWN) {
545 xr.first -= amount;
546 xr.second -= amount;
549 } else {
551 if (ev->direction == GDK_SCROLL_DOWN) {
552 y += amount;
553 } else if (ev->direction == GDK_SCROLL_UP) {
554 y -= amount;
555 } else if (ev->direction == GDK_SCROLL_LEFT) {
556 xr.first -= amount;
557 xr.second -= amount;
558 } else if (ev->direction == GDK_SCROLL_RIGHT) {
559 xr.first += amount;
560 xr.second += amount;
564 set_editor (xr, y);
565 return true;
568 /** Set the editor to display a given x range and a y range with the top at a given position.
569 * The editor's x zoom is adjusted if necessary, but the y zoom is not changed.
570 * x and y parameters are specified in summary coordinates.
572 void
573 EditorSummary::set_editor (pair<double,double> const & x, double const y)
575 if (_editor->pending_visual_change.idle_handler_id >= 0) {
577 /* As a side-effect, the Editor's visual change idle handler processes
578 pending GTK events. Hence this motion notify handler can be called
579 in the middle of a visual change idle handler, and if this happens,
580 the queue_visual_change calls below modify the variables that the
581 idle handler is working with. This causes problems. Hence this
582 check. It ensures that we won't modify the pending visual change
583 while a visual change idle handler is in progress. It's not perfect,
584 as it also means that we won't change these variables if an idle handler
585 is merely pending but not executing. But c'est la vie.
588 return;
591 set_editor_x (x);
592 set_editor_y (y);
595 /** Set the editor to display given x and y ranges. x zoom and track heights are
596 * adjusted if necessary.
597 * x and y parameters are specified in summary coordinates.
599 void
600 EditorSummary::set_editor (pair<double,double> const & x, pair<double, double> const & y)
602 if (_editor->pending_visual_change.idle_handler_id >= 0) {
603 /* see comment in other set_editor () */
604 return;
607 set_editor_x (x);
608 set_editor_y (y);
611 /** Set the x range visible in the editor.
612 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
613 * @param x new x range in summary coordinates.
615 void
616 EditorSummary::set_editor_x (pair<double, double> const & x)
618 _editor->reset_x_origin (x.first / _x_scale + _start);
620 double const nx = (
621 ((x.second - x.first) / _x_scale) /
622 _editor->frame_to_unit (_editor->current_page_frames())
625 if (nx != _editor->get_current_zoom ()) {
626 _editor->reset_zoom (nx);
630 /** Set the top of the y range visible in the editor.
631 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
632 * @param y new editor top in summary coodinates.
634 void
635 EditorSummary::set_editor_y (double const y)
637 double y1 = summary_y_to_editor (y);
638 double const eh = _editor->canvas_height() - _editor->get_canvas_timebars_vsize ();
639 double y2 = y1 + eh;
641 double const full_editor_height = _editor->full_canvas_height - _editor->get_canvas_timebars_vsize();
643 if (y2 > full_editor_height) {
644 y1 -= y2 - full_editor_height;
647 if (y1 < 0) {
648 y1 = 0;
651 _editor->reset_y_origin (y1);
654 /** Set the y range visible in the editor. This is achieved by scaling track heights,
655 * if necessary.
656 * Caller should have checked that Editor::pending_visual_change.idle_handler_id is < 0
657 * @param y new editor range in summary coodinates.
659 void
660 EditorSummary::set_editor_y (pair<double, double> const & y)
662 /* Compute current height of tracks between y.first and y.second. We add up
663 the total height into `total_height' and the height of complete tracks into
664 `scale height'.
666 pair<double, double> yc = y;
667 double total_height = 0;
668 double scale_height = 0;
669 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
671 if ((*i)->hidden()) {
672 continue;
675 double const h = (*i)->effective_height ();
677 if (yc.first >= 0 && yc.first < _track_height) {
678 total_height += (_track_height - yc.first) * h / _track_height;
679 } else if (yc.first < 0 && yc.second > _track_height) {
680 total_height += h;
681 scale_height += h;
682 } else if (yc.second >= 0 && yc.second < _track_height) {
683 total_height += yc.second * h / _track_height;
684 break;
687 yc.first -= _track_height;
688 yc.second -= _track_height;
691 /* hence required scale factor of the complete tracks to fit the required y range */
692 double const scale = ((_editor->canvas_height() - _editor->get_canvas_timebars_vsize()) - (total_height - scale_height)) / scale_height;
694 yc = y;
696 /* Scale complete tracks within the range to make it fit */
698 for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
700 if ((*i)->hidden()) {
701 continue;
704 if (yc.first < 0 && yc.second > _track_height) {
705 (*i)->set_height ((*i)->effective_height() * scale);
708 yc.first -= _track_height;
709 yc.second -= _track_height;
712 set_editor_y (y.first);
715 void
716 EditorSummary::playhead_position_changed (framepos_t p)
718 if (_session && int (p * _x_scale) != int (_last_playhead)) {
719 set_overlays_dirty ();
723 double
724 EditorSummary::summary_y_to_editor (double y) const
726 double ey = 0;
727 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
729 if ((*i)->hidden()) {
730 continue;
733 double const h = (*i)->effective_height ();
734 if (y < _track_height) {
735 /* in this track */
736 return ey + y * h / _track_height;
739 ey += h;
740 y -= _track_height;
743 return ey;
746 double
747 EditorSummary::editor_y_to_summary (double y) const
749 double sy = 0;
750 for (TrackViewList::const_iterator i = _editor->track_views.begin (); i != _editor->track_views.end(); ++i) {
752 if ((*i)->hidden()) {
753 continue;
756 double const h = (*i)->effective_height ();
757 if (y < h) {
758 /* in this track */
759 return sy + y * _track_height / h;
762 sy += _track_height;
763 y -= h;
766 return sy;