/*
- 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.
-
-*/
+ * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2009-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2009-2019 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2012-2013 Colin Fletcher <colin.m.fletcher@googlemail.com>
+ * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2014-2016 Nick Mainsbridge <mainsbridge@gmail.com>
+ * Copyright (C) 2015-2016 Tim Mayberry <mojofunk@gmail.com>
+ * Copyright (C) 2017-2019 Ben Loftis <ben@harrisonconsoles.com>
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
#include "ardour/session.h"
#include "canvas/debug.h"
+#include <gtkmm/menu.h>
+#include <gtkmm/menuitem.h>
+
+#include "context_menu_helper.h"
#include "time_axis_view.h"
#include "streamview.h"
#include "editor_summary.h"
#include "route_time_axis.h"
#include "ui_config.h"
+#include "pbd/i18n.h"
+
using namespace std;
using namespace ARDOUR;
using Gtkmm2ext::Keyboard;
: EditorComponent (e),
_start (0),
_end (1),
- _overhang_fraction (0.1),
_x_scale (1),
_track_height (16),
_last_playhead (-1),
- _begin_dragging (false),
_move_dragging (false),
- _moved (false),
_view_rectangle_x (0, 0),
_view_rectangle_y (0, 0),
_zoom_trim_dragging (false),
- _zoom_dragging (false),
_old_follow_playhead (false),
_image (0),
_background_dirty (true)
_session->EndTimeChanged.connect (_session_connections, invalidator (*this), boost::bind (&EditorSummary::set_background_dirty, this), gui_context());
_editor->selection->RegionsChanged.connect (sigc::mem_fun(*this, &EditorSummary::set_background_dirty));
}
+
+ UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &EditorSummary::set_colors));
+
+ set_colors();
+
+ _leftmost = max_samplepos;
+ _rightmost = 0;
}
void
/* compute start and end points for the summary */
- framecnt_t const session_length = _session->current_end_frame() - _session->current_start_frame ();
- double const theoretical_start = _session->current_start_frame() - session_length * _overhang_fraction;
+ std::pair<samplepos_t, samplepos_t> ext = _editor->session_gui_extents();
+ double theoretical_start = ext.first;
+ double theoretical_end = ext.second;
+
+ /* the summary should encompass the full extent of everywhere we've visited since the session was opened */
+ if (_leftmost < theoretical_start)
+ theoretical_start = _leftmost;
+ if (_rightmost > theoretical_end)
+ theoretical_end = _rightmost;
+
+ /* range-check */
_start = theoretical_start > 0 ? theoretical_start : 0;
- _end = _session->current_end_frame() + session_length * _overhang_fraction;
+ _end = theoretical_end < max_samplepos ? theoretical_end : max_samplepos;
+
+ /* calculate x scale */
+ if (_end != _start) {
+ _x_scale = static_cast<double> (get_width()) / (_end - _start);
+ } else {
+ _x_scale = 1;
+ }
/* compute track height */
int N = 0;
_track_height = (double) get_height() / N;
}
- /* calculate x scale */
- if (_end != _start) {
- _x_scale = static_cast<double> (get_width()) / (_end - _start);
- } else {
- _x_scale = 1;
- }
-
/* render tracks and regions */
double y = 0;
/* paint a non-bg colored strip to represent the track itself */
- cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
- cairo_set_line_width (cr, _track_height - 1);
- cairo_move_to (cr, 0, y + _track_height / 2);
- cairo_line_to (cr, get_width(), y + _track_height / 2);
- cairo_stroke (cr);
+ if (_track_height > 4) {
+ cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);
+ cairo_set_line_width (cr, _track_height - 1);
+ cairo_move_to (cr, 0, y + _track_height / 2);
+ cairo_line_to (cr, get_width(), y + _track_height / 2);
+ cairo_stroke (cr);
+ }
StreamView* s = (*i)->view ();
cairo_set_line_width (cr, _track_height * 0.8);
s->foreach_regionview (sigc::bind (
- sigc::mem_fun (*this, &EditorSummary::render_region),
- cr,
- y + _track_height / 2
- ));
+ sigc::mem_fun (*this, &EditorSummary::render_region),
+ cr,
+ y + _track_height / 2
+ ));
}
y += _track_height;
cairo_set_line_width (cr, 1);
cairo_set_source_rgb (cr, 1, 1, 0);
- const double p = (_session->current_start_frame() - _start) * _x_scale;
+ const double p = (_session->current_start_sample() - _start) * _x_scale;
cairo_move_to (cr, p, 0);
cairo_line_to (cr, p, get_height());
- double const q = (_session->current_end_frame() - _start) * _x_scale;
+ double const q = (_session->current_end_sample() - _start) * _x_scale;
cairo_move_to (cr, q, 0);
cairo_line_to (cr, q, get_height());
cairo_stroke (cr);
return;
}
+ /* maintain the leftmost and rightmost locations that we've ever reached */
+ samplecnt_t const leftmost = _editor->leftmost_sample ();
+ if (leftmost < _leftmost) {
+ _leftmost = leftmost;
+ _background_dirty = true;
+ }
+ samplecnt_t const rightmost = leftmost + _editor->current_page_samples();
+ if (rightmost > _rightmost) {
+ _rightmost = rightmost;
+ _background_dirty = true;
+ }
+
+ /* draw the background (regions, markers, etc) if they've changed */
if (!_image || _background_dirty) {
render_background_image ();
_background_dirty = false;
int32_t width = _view_rectangle_x.second - _view_rectangle_x.first;
std::min(8, width);
- int32_t height = _view_rectangle_y.second - _view_rectangle_y.first;
cairo_rectangle (cr, _view_rectangle_x.first, 0, width, get_height ());
cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
cairo_fill (cr);
/* Playhead */
cairo_set_line_width (cr, 1);
- /* XXX: colour should be set from configuration file */
- cairo_set_source_rgba (cr, 1, 0, 0, 1);
- const double ph= playhead_frame_to_position (_editor->playhead_cursor->current_frame());
+ double r,g,b,a; Gtkmm2ext::color_to_rgba(_phead_color, r,g,b,a);
+ cairo_set_source_rgb (cr, r,g,b); // playhead color
+
+ const double ph= playhead_sample_to_position (_editor->playhead_cursor->current_sample());
cairo_move_to (cr, ph, 0);
cairo_line_to (cr, ph, get_height());
cairo_stroke (cr);
}
+void
+EditorSummary::set_colors ()
+{
+ _phead_color = UIConfiguration::instance().color ("play head");
+}
+
+
+
/** Render a region for the summary.
* @param r Region view.
* @param cr Cairo context.
void
EditorSummary::render_region (RegionView* r, cairo_t* cr, double y) const
{
+ /*NOTE: you can optimize this operation by coalescing adjacent regions into a single line stroke.
+ * In a session with a single track ~1,000 regions, this reduced render time from 14ms to 11 ms.
+ * However, you lose a lot of visual information. The current method preserves a sense of separation between regions.
+ * The current method shows the current selection (red regions), which needs to be preserved if this is optimized.
+ * I think it's not worth it for now, but we might choose to revisit this someday.
+ */
+
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);
/** Set the summary so that just the overlays (viewbox, playhead etc.) in a given area will be re-rendered */
void
-EditorSummary::set_overlays_dirty (int x, int y, int w, int h)
+EditorSummary::set_overlays_dirty_rect (int x, int y, int w, int h)
{
- ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty);
+ ENSURE_GUI_THREAD (*this, &EditorSummary::set_overlays_dirty_rect);
queue_draw_area (x, y, w, h);
}
{
gint x, y;
GtkAccelKey set_playhead_accel;
+
+ /* XXXX this is really ugly and should be using our own action maps and bindings */
+
if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
if (_session) {
get_pointer (x, y);
- _session->request_locate (_start + (framepos_t) x / _x_scale, _session->transport_rolling());
+ _session->request_locate (_start + (samplepos_t) x / _x_scale, _session->transport_rolling());
return true;
}
}
{
GtkAccelKey set_playhead_accel;
+
+ /* XXXX this is really ugly and should be using our own action maps and bindings */
+
if (gtk_accel_map_lookup_entry ("<Actions>/Editor/set-playhead", &set_playhead_accel)) {
if (key->keyval == set_playhead_accel.accel_key && (int) key->state == set_playhead_accel.accel_mods) {
return true;
return false;
}
+#include "gtkmm2ext/utils.h"
+
/** Handle a button press.
* @param ev GTK event.
*/
{
_old_follow_playhead = _editor->follow_playhead ();
+ if (ev->button == 3) { // right-click: show the reset menu action
+ using namespace Gtk::Menu_Helpers;
+ Gtk::Menu* m = ARDOUR_UI_UTILS::shared_popup_menu ();
+ MenuList& items = m->items ();
+ items.push_back(MenuElem(_("Reset Summary to Extents"),
+ sigc::mem_fun(*this, &EditorSummary::reset_to_extents)));
+ m->popup (ev->button, ev->time);
+ return true;
+ }
+
if (ev->button != 1) {
return true;
}
} else {
- /* start a move or zoom drag */
- /* won't know which one until the mouse moves */
- _begin_dragging = true;
+ /* start a move+zoom drag */
+ get_editor (&_pending_editor_x, &_pending_editor_y);
+ _pending_editor_changed = false;
+ _editor->_dragging_playhead = true;
+ _editor->set_follow_playhead (false);
+
+ _move_dragging = true;
+
+ _last_mx = ev->x;
+ _last_my = ev->y;
+ _last_dx = 0;
+ _last_dy = 0;
+ _last_y_delta = 0;
+
+ get_window()->set_cursor (*_editor->_cursors->expand_left_right);
+
}
return true;
bool
EditorSummary::suspending_editor_updates () const
{
- return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_dragging || _zoom_trim_dragging || _move_dragging));
+ return (!UIConfiguration::instance().get_update_editor_during_summary_drag () && (_zoom_trim_dragging || _move_dragging));
}
/** Fill in x and y with the editor's current viewable area in summary coordinates */
}
}
+void
+EditorSummary::reset_to_extents()
+{
+ /* reset as if the user never went anywhere outside the extents */
+ _leftmost = max_samplepos;
+ _rightmost = 0;
+
+ _editor->temporal_zoom_extents ();
+ set_background_dirty ();
+}
+
+
void
EditorSummary::set_cursor (Position p)
{
get_window()->set_cursor (*_editor->_cursors->move);
break;
case TO_LEFT_OR_RIGHT:
- get_window()->set_cursor (*_editor->_cursors->expand_left_right);
+ get_window()->set_cursor (*_editor->_cursors->move);
break;
default:
assert (0);
}
}
+void
+EditorSummary::summary_zoom_step (int steps /* positive steps to zoom "out" , negative steps to zoom "in" */ )
+{
+ pair<double, double> xn;
+
+ get_editor (&xn);
+
+ xn.first -= steps;
+ xn.second += steps;
+
+ /* for now, disallow really close zooming-in from the scroomer. (Currently it
+ * causes the start-offset to 'walk' because of integer limitations.
+ * To fix this, probably need to maintain float throught the get/set_editor() path.)
+ */
+ if (steps<0) {
+ if ((xn.second - xn.first) < 2)
+ return;
+ }
+
+ set_overlays_dirty ();
+ set_editor_x (xn);
+}
+
+
bool
EditorSummary::on_motion_notify_event (GdkEventMotion* ev)
{
- pair<double, double> xr = _start_editor_x;
- double x = _start_editor_x.first;
-
if (_move_dragging) {
- _moved = true;
+ /* To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
+ * we use screen coordinates for this, not canvas-based grab_x */
+ double mx = ev->x;
+ double dx = mx - _last_mx;
+ double my = ev->y;
+ double dy = my - _last_my;
- assert (_start_position == INSIDE || _start_position == TO_LEFT_OR_RIGHT);
- x += ev->x - _start_mouse_x;
+ /* do zooming in windowed "steps" so it feels more reversible ? */
+ const int stepsize = 2;
+ int y_delta = _start_mouse_y - my;
+ y_delta = y_delta / stepsize;
- if (x < 0) {
- x = 0;
+ /* do the zoom? */
+ const float zscale = 3;
+ if ((dx == 0) && (_last_dx == 0) && (y_delta != _last_y_delta)) {
+
+ summary_zoom_step (dy * zscale);
+
+ /* after the zoom we must re-calculate x-pos grabs */
+ pair<double, double> xr;
+ get_editor (&xr);
+ _start_editor_x = xr;
+ _start_mouse_x = ev->x;
+
+ _last_y_delta = y_delta;
}
- set_editor (x);
+ /* always track horizontal movement, if any */
+ if (dx != 0) {
- } else if (_zoom_dragging) {
+ double x = _start_editor_x.first;
+ x += ev->x - _start_mouse_x;
- //ToDo: refactor into summary_zoom_in/out(
- //ToDo: protect the case where the editor position is small, and results in offsetting the position
+ if (x < 0) {
+ x = 0;
+ }
- double const dy = ev->y - _zoom_last_y;
-
- pair<double, double> xn;
- get_editor (&xn);
+ /* zoom-behavior-tweaks: protect the right edge from expanding beyond the end */
+ pair<double, double> xr;
+ get_editor (&xr);
+ double w = xr.second - xr.first;
+ if (x + w < get_width()) {
+ set_editor (x);
+ }
+ }
+
+ _last_my = my;
+ _last_mx = mx;
+ _last_dx = dx;
+ _last_dy = dy;
- xn.first -= dy;
- xn.second += dy;
-
- set_overlays_dirty ();
- set_editor_x (xn);
-
- _zoom_last_y = ev->y;
-
} else if (_zoom_trim_dragging) {
+ pair<double, double> xr = _start_editor_x;
+
double const dx = ev->x - _start_mouse_x;
if (_zoom_trim_position == LEFT) {
xr.first += dx;
} else if (_zoom_trim_position == RIGHT) {
- xr.second += dx;
+
+ /* zoom-behavior-tweaks: protect the right edge from expanding beyond the edge */
+ if ((xr.second + dx) < get_width()) {
+ xr.second += dx;
+ }
+
} else {
assert (0);
xr.first = -1; /* do not change */
set_cursor (_zoom_trim_position);
set_editor (xr);
- } else if (_begin_dragging) {
-
- double const dx = ev->x - _start_mouse_x;
- double const dy = ev->y - _start_mouse_y;
-
- if ( fabs(dx) > fabs(dy) ) {
-
- /* initiate a move drag */
-
- /* get the editor's state in case we are suspending updates */
- get_editor (&_pending_editor_x, &_pending_editor_y);
- _pending_editor_changed = false;
-
- _move_dragging = true;
- _moved = false;
- _editor->_dragging_playhead = true;
- _editor->set_follow_playhead (false);
-
- get_window()->set_cursor (*_editor->_cursors->expand_left_right);
-
- _begin_dragging = false;
-
- } else if ( fabs(dy) > fabs(dx) ) {
-
- /* initiate a zoom drag */
-
- /* get the editor's state in case we are suspending updates */
- get_editor (&_pending_editor_x, &_pending_editor_y);
- _pending_editor_changed = false;
-
- //_zoom_position = get_position (ev->x, ev->y);
- _zoom_dragging = true;
- _zoom_last_y = ev->y;
- _editor->_dragging_playhead = true;
- _editor->set_follow_playhead (false);
-
- get_window()->set_cursor (*_editor->_cursors->expand_up_down);
-
- _begin_dragging = false;
- }
-
} else {
- set_cursor ( INSIDE );
+ set_cursor (get_position (ev->x, ev->y));
}
return true;
_move_dragging = false;
_zoom_trim_dragging = false;
- _zoom_dragging = false;
_editor->_dragging_playhead = false;
_editor->set_follow_playhead (_old_follow_playhead, false);
switch (ev->direction) {
case GDK_SCROLL_UP: {
- //ToDo: use function summary_zoom_in/out
-
- pair<double, double> xn;
- get_editor (&xn);
-
- xn.first -= 2;
- xn.second += 2;
-
- set_overlays_dirty ();
- set_editor_x (xn);
-
+
+ summary_zoom_step (-4);
+
return true;
} break;
-
+
case GDK_SCROLL_DOWN: {
- pair<double, double> xn;
- get_editor (&xn);
-
- xn.first += 2;
- xn.second -= 2;
-
- set_overlays_dirty ();
- set_editor_x (xn);
-
+
+ summary_zoom_step (4);
+
return true;
} break;
-
+
case GDK_SCROLL_LEFT:
if (Keyboard::modifier_state_equals (ev->state, Keyboard::ScrollZoomHorizontalModifier)) {
_editor->temporal_zoom_step (false);
}
void
-EditorSummary::playhead_position_changed (framepos_t p)
+EditorSummary::playhead_position_changed (samplepos_t p)
{
int const o = int (_last_playhead);
- int const n = int (playhead_frame_to_position (p));
+ int const n = int (playhead_sample_to_position (p));
if (_session && o != n) {
int a = max(2, min (o, n));
int b = max (o, n);
- set_overlays_dirty (a - 2, 0, b + 2, get_height ());
+ set_overlays_dirty_rect (a - 2, 0, b + 2, get_height ());
}
}
}
double
-EditorSummary::playhead_frame_to_position (framepos_t t) const
+EditorSummary::playhead_sample_to_position (samplepos_t t) const
{
return (t - _start) * _x_scale;
}
-framepos_t
-EditorSummary::position_to_playhead_frame_to_position (double pos) const
+samplepos_t
+EditorSummary::position_to_playhead_sample_to_position (double pos) const
{
return _start + (pos * _x_scale);
}