X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Feditor_summary.cc;h=5c2135ac43ac45d8dbedae624f3f29ba8956720c;hb=a4664d68c031c77c5e436fe97a92005b7d2019d8;hp=c35e0a113398e8d9a114e4944ece9c5793f00511;hpb=ea5827b51cb12a33ca910f6082675a078ae0d486;p=ardour.git diff --git a/gtk2_ardour/editor_summary.cc b/gtk2_ardour/editor_summary.cc index c35e0a1133..5c2135ac43 100644 --- a/gtk2_ardour/editor_summary.cc +++ b/gtk2_ardour/editor_summary.cc @@ -1,3 +1,22 @@ +/* + Copyright (C) 2009 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + #include "ardour/session.h" #include "time_axis_view.h" #include "streamview.h" @@ -9,51 +28,47 @@ #include "keyboard.h" using namespace std; -using namespace sigc; using namespace ARDOUR; +using Gtkmm2ext::Keyboard; /** Construct an EditorSummary. * @param e Editor to represent. */ EditorSummary::EditorSummary (Editor* e) - : _editor (e), - _session (0), - _pixmap (0), - _regions_dirty (true), - _width (512), - _height (64), - _pixels_per_frame (1), - _vertical_scale (1), + : EditorComponent (e), + _start (0), + _end (1), + _overhang_fraction (0.1), + _x_scale (1), + _y_scale (1), + _last_playhead (-1), _move_dragging (false), _moved (false), _zoom_dragging (false) - + { - + Region::RegionPropertyChanged.connect (region_property_connection, invalidator (*this), boost::bind (&CairoWidget::set_dirty, this), gui_context()); + _editor->playhead_cursor->PositionChanged.connect (position_connection, invalidator (*this), ui_bind (&EditorSummary::playhead_position_changed, this, _1), gui_context()); } -/** Set the session. +/** Connect to a session. * @param s Session. */ void EditorSummary::set_session (Session* s) { - _session = s; - - Region::RegionPropertyChanged.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty))); - - _session->RegionRemoved.connect (sigc::hide (mem_fun (*this, &EditorSummary::set_dirty))); - _session->EndTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty)); - _session->StartTimeChanged.connect (mem_fun (*this, &EditorSummary::set_dirty)); + EditorComponent::set_session (s); set_dirty (); -} -/** Destroy */ -EditorSummary::~EditorSummary () -{ - if (_pixmap) { - gdk_pixmap_unref (_pixmap); + /* Note: the EditorSummary already finds out about new regions from Editor::region_view_added + * (which attaches to StreamView::RegionViewAdded), and cut regions by the RegionPropertyChanged + * emitted when a cut region is added to the `cutlist' playlist. + */ + + if (_session) { + _session->StartTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context()); + _session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_dirty, this), gui_context()); } } @@ -63,41 +78,19 @@ EditorSummary::~EditorSummary () bool EditorSummary::on_expose_event (GdkEventExpose* event) { - /* Render the regions pixmap */ - - Gdk::Rectangle const exposure ( - event->area.x, event->area.y, event->area.width, event->area.height - ); - - Gdk::Rectangle r = exposure; - Gdk::Rectangle content (0, 0, _width, _height); - bool intersects; - r.intersect (content, intersects); - - if (intersects) { - - GdkPixmap* p = get_pixmap (get_window()->gobj ()); - - gdk_draw_drawable ( - get_window()->gobj(), - get_style()->get_fg_gc (Gtk::STATE_NORMAL)->gobj(), - p, - r.get_x(), - r.get_y(), - r.get_x(), - r.get_y(), - r.get_width(), - r.get_height() - ); + CairoWidget::on_expose_event (event); + + if (_session == 0) { + return false; } + cairo_t* cr = gdk_cairo_create (get_window()->gobj()); + /* Render the view rectangle */ - + pair x; pair y; - editor_view (&x, &y); - - cairo_t* cr = gdk_cairo_create (get_window()->gobj()); + get_editor (&x, &y); cairo_move_to (cr, x.first, y.first); cairo_line_to (cr, x.second, y.first); @@ -110,32 +103,21 @@ EditorSummary::on_expose_event (GdkEventExpose* event) cairo_set_source_rgba (cr, 1, 1, 1, 0.5); cairo_stroke (cr); - cairo_destroy (cr); - - return true; -} + /* Playhead */ -/** @param drawable GDK drawable. - * @return pixmap for the regions. - */ -GdkPixmap * -EditorSummary::get_pixmap (GdkDrawable* drawable) -{ - if (_regions_dirty) { - - if (_pixmap) { - gdk_pixmap_unref (_pixmap); - } - _pixmap = gdk_pixmap_new (drawable, _width, _height, -1); + cairo_set_line_width (cr, 1); + /* XXX: colour should be set from configuration file */ + cairo_set_source_rgba (cr, 1, 0, 0, 1); - cairo_t* cr = gdk_cairo_create (_pixmap); - render (cr); - cairo_destroy (cr); + double const p = (_editor->playhead_cursor->current_frame - _start) * _x_scale; + cairo_move_to (cr, p, 0); + cairo_line_to (cr, p, _height); + cairo_stroke (cr); + _last_playhead = p; - _regions_dirty = false; - } + cairo_destroy (cr); - return _pixmap; + return true; } /** Render the required regions to a cairo context. @@ -144,81 +126,113 @@ EditorSummary::get_pixmap (GdkDrawable* drawable) void EditorSummary::render (cairo_t* cr) { + /* background */ + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_rectangle (cr, 0, 0, _width, _height); + cairo_fill (cr); + if (_session == 0) { return; } - /* background */ + /* compute start and end points for the summary */ - cairo_set_source_rgb (cr, 0, 0, 0); - cairo_rectangle (cr, 0, 0, _width, _height); - cairo_fill (cr); + nframes_t const session_length = _session->current_end_frame() - _session->current_start_frame (); + double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction; + _start = theoretical_start > 0 ? theoretical_start : 0; + _end = _session->current_end_frame() + session_length * _overhang_fraction; /* compute total height of all tracks */ - + int h = 0; - for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) { - h += (*i)->effective_height (); + int max_height = 0; + for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) { + int const t = (*i)->effective_height (); + h += t; + max_height = max (max_height, t); } - nframes_t const start = _session->current_start_frame (); - _pixels_per_frame = static_cast (_width) / (_session->current_end_frame() - start); - _vertical_scale = static_cast (_height) / h; + if (_end != _start) { + _x_scale = static_cast (_width) / (_end - _start); + } else { + _x_scale = 1; + } + _y_scale = static_cast (_height) / h; + + /* tallest a region should ever be in the summary, in pixels */ + int const tallest_region_pixels = _height / 16; + + if (max_height * _y_scale > tallest_region_pixels) { + _y_scale = static_cast (tallest_region_pixels) / max_height; + } /* render regions */ double y = 0; - for (PublicEditor::TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) { + for (TrackViewList::const_iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) { StreamView* s = (*i)->view (); if (s) { - double const h = (*i)->effective_height () * _vertical_scale; + double const h = (*i)->effective_height () * _y_scale; cairo_set_line_width (cr, h); - s->foreach_regionview (bind ( - mem_fun (*this, &EditorSummary::render_region), + s->foreach_regionview (sigc::bind ( + sigc::mem_fun (*this, &EditorSummary::render_region), cr, - start, y + h / 2 )); y += h; } } + /* start and end markers */ + + cairo_set_line_width (cr, 1); + cairo_set_source_rgb (cr, 1, 1, 0); + + double const p = (_session->current_start_frame() - _start) * _x_scale; + cairo_move_to (cr, p, 0); + cairo_line_to (cr, p, _height); + cairo_stroke (cr); + + double const q = (_session->current_end_frame() - _start) * _x_scale; + cairo_move_to (cr, q, 0); + cairo_line_to (cr, q, _height); + cairo_stroke (cr); } /** Render a region for the summary. * @param r Region view. * @param cr Cairo context. - * @param start Frame offset that the summary starts at. * @param y y coordinate to render at. */ void -EditorSummary::render_region (RegionView* r, cairo_t* cr, nframes_t start, double y) const +EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const { uint32_t const c = r->get_fill_color (); cairo_set_source_rgb (cr, UINT_RGBA_R (c) / 255.0, UINT_RGBA_G (c) / 255.0, UINT_RGBA_B (c) / 255.0); - - cairo_move_to (cr, (r->region()->position() - start) * _pixels_per_frame, y); - cairo_line_to (cr, ((r->region()->position() - start + r->region()->length())) * _pixels_per_frame, y); - cairo_stroke (cr); -} -/** Set the summary so that the whole thing will be re-rendered next time it is required */ -void -EditorSummary::set_dirty () -{ - ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_dirty)); + if (r->region()->position() > _start) { + cairo_move_to (cr, (r->region()->position() - _start) * _x_scale, y); + } else { + cairo_move_to (cr, 0, y); + } - _regions_dirty = true; - queue_draw (); + if ((r->region()->position() + r->region()->length()) > _start) { + cairo_line_to (cr, ((r->region()->position() - _start + r->region()->length())) * _x_scale, y); + } else { + cairo_line_to (cr, 0, y); + } + + cairo_stroke (cr); } -/** Set the summary so that just the view boundary markers will be re-rendered */ +/** Set the summary so that just the overlays (viewbox, playhead etc.) will be re-rendered */ void -EditorSummary::set_bounds_dirty () +EditorSummary::set_overlays_dirty () { - ENSURE_GUI_THREAD (mem_fun (*this, &EditorSummary::set_bounds_dirty)); + ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty) queue_draw (); } @@ -230,45 +244,42 @@ EditorSummary::on_size_request (Gtk::Requisition *req) { /* Use a dummy, small width and the actual height that we want */ req->width = 64; - req->height = 64; + req->height = 32; } -/** Handle a size allocation. - * @param alloc GTK allocation. - */ -void -EditorSummary::on_size_allocate (Gtk::Allocation& alloc) -{ - Gtk::EventBox::on_size_allocate (alloc); - - _width = alloc.get_width (); - _height = alloc.get_height (); - - set_dirty (); -} void EditorSummary::centre_on_click (GdkEventButton* ev) { - nframes_t x = (ev->x / _pixels_per_frame) + _session->current_start_frame(); - nframes_t const xh = _editor->current_page_frames () / 2; - if (x > xh) { - x -= xh; - } else { - x = 0; + pair xr; + pair yr; + get_editor (&xr, &yr); + + double const w = xr.second - xr.first; + double const h = yr.second - yr.first; + + xr.first = ev->x - w / 2; + xr.second = ev->x + w / 2; + yr.first = ev->y - h / 2; + yr.second = ev->y + h / 2; + + if (xr.first < 0) { + xr.first = 0; + xr.second = w; + } else if (xr.second > _width) { + xr.second = _width; + xr.first = _width - w; } - - _editor->reset_x_origin (x); - - double y = ev->y / _vertical_scale; - double const yh = _editor->canvas_height () / 2; - if (y > yh) { - y -= yh; - } else { - y = 0; + + if (yr.first < 0) { + yr.first = 0; + yr.second = h; + } else if (yr.second > _height) { + yr.second = _height; + yr.first = _height - h; } - - _editor->reset_y_origin (y); + + set_editor (xr, yr); } /** Handle a button press. @@ -281,50 +292,69 @@ EditorSummary::on_button_press_event (GdkEventButton* ev) pair xr; pair yr; - editor_view (&xr, &yr); - - if (xr.first <= ev->x && ev->x <= xr.second && yr.first <= ev->y && ev->y <= yr.second) { - - if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { - - /* modifier-click inside the view rectangle: start a zoom drag */ - _zoom_position = NONE; - - double const x1 = xr.first + (xr.second - xr.first) * 0.33; - double const x2 = xr.first + (xr.second - xr.first) * 0.67; - - if (ev->x < x1) { - _zoom_position = LEFT; - } else if (ev->x > x2) { - _zoom_position = RIGHT; - } else { - _zoom_position = NONE; - } - - if (_zoom_position != NONE) { - _zoom_dragging = true; - _mouse_x_start = ev->x; - _width_start = xr.second - xr.first; - _zoom_start = _editor->get_current_zoom (); - _frames_start = _editor->leftmost_position (); - _editor->_dragging_playhead = true; - } - - } else { - - /* ordinary click inside the view rectangle: start a move drag */ - - _move_dragging = true; - _moved = false; - _x_offset = ev->x - xr.first; - _y_offset = ev->y - yr.first; - _editor->_dragging_playhead = true; - } - + get_editor (&xr, &yr); + + _start_editor_x = xr; + _start_editor_y = yr; + _start_mouse_x = ev->x; + _start_mouse_y = ev->y; + + if ( + _start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second && + _start_editor_y.first <= _start_mouse_y && _start_mouse_y <= _start_editor_y.second + ) { + + _start_position = IN_VIEWBOX; + + } else if (_start_editor_x.first <= _start_mouse_x && _start_mouse_x <= _start_editor_x.second) { + + _start_position = BELOW_OR_ABOVE_VIEWBOX; + } else { - - /* click outside the view rectangle: centre the view around the mouse click */ + + _start_position = TO_LEFT_OR_RIGHT_OF_VIEWBOX; + } + + if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + + /* primary-modifier-click: start a zoom drag */ + + double const hx = (xr.first + xr.second) * 0.5; + _zoom_left = ev->x < hx; + _zoom_dragging = true; + _editor->_dragging_playhead = true; + + + /* In theory, we could support vertical dragging, which logically + might scale track heights in order to make the editor reflect + the dragged viewbox. However, having tried this: + a) it's hard to do + b) it's quite slow + c) it doesn't seem particularly useful, especially with the + limited height of the summary + + So at the moment we don't support that... + */ + + + } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::SecondaryModifier)) { + + /* secondary-modifier-click: locate playhead */ + if (_session) { + _session->request_locate (ev->x / _x_scale + _start); + } + + } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { + centre_on_click (ev); + + } else { + + /* ordinary click: start a move drag */ + + _move_dragging = true; + _moved = false; + _editor->_dragging_playhead = true; } } @@ -332,74 +362,178 @@ EditorSummary::on_button_press_event (GdkEventButton* ev) } void -EditorSummary::editor_view (pair* x, pair* y) const +EditorSummary::get_editor (pair* x, pair* y) const { - x->first = (_editor->leftmost_position () - _session->current_start_frame ()) * _pixels_per_frame; - x->second = x->first + _editor->current_page_frames() * _pixels_per_frame; + x->first = (_editor->leftmost_position () - _start) * _x_scale; + x->second = x->first + _editor->current_page_frames() * _x_scale; - y->first = _editor->get_trackview_group_vertical_offset () * _vertical_scale; - y->second = y->first + _editor->canvas_height () * _vertical_scale; + y->first = _editor->vertical_adjustment.get_value() * _y_scale; + y->second = y->first + (_editor->canvas_height () - _editor->get_canvas_timebars_vsize()) * _y_scale; } bool EditorSummary::on_motion_notify_event (GdkEventMotion* ev) { + pair xr = _start_editor_x; + pair yr = _start_editor_y; + if (_move_dragging) { _moved = true; - _editor->reset_x_origin (((ev->x - _x_offset) / _pixels_per_frame) + _session->current_start_frame ()); - _editor->reset_y_origin ((ev->y - _y_offset) / _vertical_scale); - return true; - } else if (_zoom_dragging) { + /* don't alter x if we clicked outside and above or below the viewbox */ + if (_start_position == IN_VIEWBOX || _start_position == TO_LEFT_OR_RIGHT_OF_VIEWBOX) { + xr.first += ev->x - _start_mouse_x; + xr.second += ev->x - _start_mouse_x; + } - double const dx = ev->x - _mouse_x_start; - - nframes64_t rx = _frames_start; - double f = 1; - - switch (_zoom_position) { - case LEFT: - f = 1 - (dx / _width_start); - rx += (dx / _pixels_per_frame); - break; - case RIGHT: - f = 1 + (dx / _width_start); - break; - case NONE: - break; + /* don't alter y if we clicked outside and to the left or right of the viewbox */ + if (_start_position == IN_VIEWBOX || _start_position == BELOW_OR_ABOVE_VIEWBOX) { + yr.first += ev->y - _start_mouse_y; + yr.second += ev->y - _start_mouse_y; } - if (_editor->pending_visual_change.idle_handler_id < 0) { - - /* As a side-effect, the Editor's visual change idle handler processes - pending GTK events. Hence this motion notify handler can be called - in the middle of a visual change idle handler, and if this happens, - the queue_visual_change calls below modify the variables that the - idle handler is working with. This causes problems. Hence the - check above. It ensures that we won't modify the pending visual change - while a visual change idle handler is in progress. It's not perfect, - as it also means that we won't change these variables if an idle handler - is merely pending but not executing. But c'est la vie. - */ - - _editor->queue_visual_change (rx); - _editor->queue_visual_change (_zoom_start * f); + if (xr.first < 0) { + xr.second -= xr.first; + xr.first = 0; } + + if (yr.first < 0) { + yr.second -= yr.first; + yr.first = 0; + } + + set_editor (xr, yr); + + } else if (_zoom_dragging) { + + double const dx = ev->x - _start_mouse_x; + + if (_zoom_left) { + xr.first += dx; + } else { + xr.second += dx; + } + + set_editor (xr, yr); } - + return true; } bool -EditorSummary::on_button_release_event (GdkEventButton* ev) +EditorSummary::on_button_release_event (GdkEventButton*) { - if (_move_dragging && !_moved) { - centre_on_click (ev); - } - _move_dragging = false; _zoom_dragging = false; _editor->_dragging_playhead = false; return true; } + +bool +EditorSummary::on_scroll_event (GdkEventScroll* ev) +{ + /* mouse wheel */ + + pair xr; + pair yr; + get_editor (&xr, &yr); + + double amount = 8; + + if (Keyboard::modifier_state_contains (ev->state, Keyboard::SecondaryModifier)) { + amount = 64; + } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) { + amount = 1; + } + + if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { + + /* primary-wheel == left-right scrolling */ + + if (ev->direction == GDK_SCROLL_UP) { + xr.first += amount; + xr.second += amount; + } else if (ev->direction == GDK_SCROLL_DOWN) { + xr.first -= amount; + xr.second -= amount; + } + + } else { + + if (ev->direction == GDK_SCROLL_DOWN) { + yr.first += amount; + yr.second += amount; + } else if (ev->direction == GDK_SCROLL_UP) { + yr.first -= amount; + yr.second -= amount; + } else if (ev->direction == GDK_SCROLL_LEFT) { + xr.first -= amount; + xr.second -= amount; + } else if (ev->direction == GDK_SCROLL_RIGHT) { + xr.first += amount; + xr.second += amount; + } + } + + set_editor (xr, yr); + return true; +} + +void +EditorSummary::set_editor (pair const & x, pair const & y) +{ + if (_editor->pending_visual_change.idle_handler_id < 0) { + + /* As a side-effect, the Editor's visual change idle handler processes + pending GTK events. Hence this motion notify handler can be called + in the middle of a visual change idle handler, and if this happens, + the queue_visual_change calls below modify the variables that the + idle handler is working with. This causes problems. Hence the + check above. It ensures that we won't modify the pending visual change + while a visual change idle handler is in progress. It's not perfect, + as it also means that we won't change these variables if an idle handler + is merely pending but not executing. But c'est la vie. + */ + + /* proposed bottom of the editor with the requested position */ + double const pb = y.second / _y_scale; + + /* bottom of the canvas */ + double const ch = _editor->full_canvas_height - _editor->canvas_timebars_vsize; + + /* requested y position */ + double ly = y.first / _y_scale; + + /* clamp y position so as not to go off the bottom */ + if (pb > ch) { + ly -= (pb - ch); + } + + if (ly < 0) { + ly = 0; + } + + _editor->reset_x_origin (x.first / _x_scale + _start); + _editor->reset_y_origin (ly); + + double const nx = ( + ((x.second - x.first) / _x_scale) / + _editor->frame_to_unit (_editor->current_page_frames()) + ); + + if (nx != _editor->get_current_zoom ()) { + _editor->reset_zoom (nx); + } + } +} + +void +EditorSummary::playhead_position_changed (nframes64_t p) +{ + if (_session && int (p * _x_scale) != int (_last_playhead)) { + set_overlays_dirty (); + } +} + +