basics of autoscroll for pianoroll (mostly shared with Editor)

More work to do moving/testing pianoroll autoscroll variant back into EditingContext
and sharing it with Editor.
This commit is contained in:
Paul Davis 2024-02-23 11:25:07 -07:00
parent d6dcb1c575
commit d191330235
10 changed files with 526 additions and 171 deletions

View File

@ -141,16 +141,6 @@ CueEditor::get_y_origin () const
return 0.;
}
void
CueEditor::reset_x_origin (samplepos_t)
{
}
void
CueEditor::reset_y_origin (double)
{
}
void
CueEditor::set_zoom_focus (Editing::ZoomFocus)
{
@ -163,7 +153,7 @@ CueEditor::get_zoom_focus () const
}
void
CueEditor::reset_zoom (samplecnt_t n)
CueEditor::set_samples_per_pixel (samplecnt_t n)
{
samples_per_pixel = n;
ZoomChanged(); /* EMIT SIGNAL */

View File

@ -65,13 +65,11 @@ class CueEditor : public EditingContext
void redo_selection_op ();
double get_y_origin () const;
void reset_x_origin (samplepos_t);
void reset_y_origin (double);
void set_zoom_focus (Editing::ZoomFocus);
Editing::ZoomFocus get_zoom_focus () const;
samplecnt_t get_current_zoom () const;
void reset_zoom (samplecnt_t);
void set_samples_per_pixel (samplecnt_t);
void reposition_and_zoom (samplepos_t, double);
void set_mouse_mode (Editing::MouseMode, bool force = false);

View File

@ -124,6 +124,10 @@ EditingContext::EditingContext (std::string const & name)
, vertical_adjustment (0.0, 0.0, 10.0, 400.0)
, horizontal_adjustment (0.0, 0.0, 1e16)
, mouse_mode (MouseObject)
, visual_change_queued (false)
, autoscroll_horizontal_allowed (false)
, autoscroll_vertical_allowed (false)
, autoscroll_cnt (0)
{
if (!button_bindings) {
button_bindings = new Bindings ("editor-mouse");
@ -2362,3 +2366,89 @@ EditingContext::register_grid_actions ()
ActionManager::register_radio_action (snap_actions, grid_choice_group, X_("grid-type-none"), grid_type_strings[(int)GridTypeNone].c_str(), (sigc::bind (sigc::mem_fun(*this, &EditingContext::grid_type_chosen), Editing::GridTypeNone)));
}
void
EditingContext::ensure_visual_change_idle_handler ()
{
if (pending_visual_change.idle_handler_id < 0) {
/* see comment in add_to_idle_resize above. */
pending_visual_change.idle_handler_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, _idle_visual_changer, this, NULL);
pending_visual_change.being_handled = false;
}
}
int
EditingContext::_idle_visual_changer (void* arg)
{
return static_cast<EditingContext*>(arg)->idle_visual_changer ();
}
int
EditingContext::idle_visual_changer ()
{
pending_visual_change.idle_handler_id = -1;
if (pending_visual_change.pending == 0) {
return 0;
}
/* set_horizontal_position() below (and maybe other calls) call
gtk_main_iteration(), so it's possible that a signal will be handled
half-way through this method. If this signal wants an
idle_visual_changer we must schedule another one after this one, so
mark the idle_handler_id as -1 here to allow that. Also make a note
that we are doing the visual change, so that changes in response to
super-rapid-screen-update can be dropped if we are still processing
the last one.
*/
if (visual_change_queued) {
return 0;
}
pending_visual_change.being_handled = true;
VisualChange vc = pending_visual_change;
pending_visual_change.pending = (VisualChange::Type) 0;
visual_changer (vc);
pending_visual_change.being_handled = false;
visual_change_queued = true;
return 0; /* this is always a one-shot call */
}
/** Queue up a change to the viewport x origin.
* @param sample New x origin.
*/
void
EditingContext::reset_x_origin (samplepos_t sample)
{
pending_visual_change.add (VisualChange::TimeOrigin);
pending_visual_change.time_origin = sample;
ensure_visual_change_idle_handler ();
}
void
EditingContext::reset_y_origin (double y)
{
pending_visual_change.add (VisualChange::YOrigin);
pending_visual_change.y_origin = y;
ensure_visual_change_idle_handler ();
}
void
EditingContext::reset_zoom (samplecnt_t spp)
{
if (spp == samples_per_pixel) {
return;
}
pending_visual_change.add (VisualChange::ZoomLevel);
pending_visual_change.samples_per_pixel = spp;
ensure_visual_change_idle_handler ();
}

View File

@ -269,12 +269,15 @@ public:
ARDOUR::SnapPref gpref) const;
virtual double get_y_origin () const = 0;
virtual void reset_x_origin (samplepos_t) = 0;
virtual void reset_y_origin (double) = 0;
void reset_x_origin (samplepos_t);
void reset_y_origin (double);
void reset_zoom (samplecnt_t);
void set_samples_per_pixel (samplecnt_t);
virtual void on_samples_per_pixel_changed () {}
virtual void set_zoom_focus (Editing::ZoomFocus) = 0;
virtual Editing::ZoomFocus get_zoom_focus () const = 0;
virtual void reset_zoom (samplecnt_t) = 0;
virtual void reposition_and_zoom (samplepos_t, double) = 0;
sigc::signal<void> ZoomChanged;
@ -343,7 +346,7 @@ public:
virtual samplecnt_t current_page_samples() const = 0;
virtual ArdourCanvas::GtkCanvasViewport* get_canvas_viewport() const = 0;
virtual ArdourCanvas::Canvas* get_canvas() const = 0;
virtual ArdourCanvas::GtkCanvas* get_canvas() const = 0;
virtual size_t push_canvas_cursor (Gdk::Cursor*);
virtual void pop_canvas_cursor ();
@ -563,6 +566,43 @@ public:
void set_common_editing_state (XMLNode const & node);
void get_common_editing_state (XMLNode& node) const;
struct VisualChange {
enum Type {
TimeOrigin = 0x1,
ZoomLevel = 0x2,
YOrigin = 0x4,
VideoTimeline = 0x8
};
Type pending;
samplepos_t time_origin;
samplecnt_t samples_per_pixel;
double y_origin;
int idle_handler_id;
/** true if we are currently in the idle handler */
bool being_handled;
VisualChange() : pending ((VisualChange::Type) 0), time_origin (0), samples_per_pixel (0), idle_handler_id (-1), being_handled (false) {}
void add (Type t) {
pending = Type (pending | t);
}
};
VisualChange pending_visual_change;
bool visual_change_queued;
static int _idle_visual_changer (void* arg);
int idle_visual_changer ();
void ensure_visual_change_idle_handler ();
virtual void visual_changer (const VisualChange&) = 0;
sigc::connection autoscroll_connection;
bool autoscroll_horizontal_allowed;
bool autoscroll_vertical_allowed;
uint32_t autoscroll_cnt;
ArdourCanvas::Rect autoscroll_boundary;
private:
static std::queue<EditingContext*> ec_stack;

View File

@ -317,7 +317,6 @@ Editor::Editor ()
, _full_canvas_height (0)
, edit_controls_left_menu (0)
, edit_controls_right_menu (0)
, visual_change_queued(false)
, _tvl_no_redisplay(false)
, _tvl_redisplay_on_resume(false)
, _last_update_time (0)
@ -378,10 +377,6 @@ Editor::Editor ()
, _sections (0)
, _snapshots (0)
, _locations (0)
, autoscroll_horizontal_allowed (false)
, autoscroll_vertical_allowed (false)
, autoscroll_cnt (0)
, autoscroll_widget (0)
, show_gain_after_trim (false)
, _no_not_select_reimported_tracks (false)
, selection_op_cmd_depth (0)
@ -4018,36 +4013,6 @@ Editor::get_y_origin () const
return vertical_adjustment.get_value ();
}
/** Queue up a change to the viewport x origin.
* @param sample New x origin.
*/
void
Editor::reset_x_origin (samplepos_t sample)
{
pending_visual_change.add (VisualChange::TimeOrigin);
pending_visual_change.time_origin = sample;
ensure_visual_change_idle_handler ();
}
void
Editor::reset_y_origin (double y)
{
pending_visual_change.add (VisualChange::YOrigin);
pending_visual_change.y_origin = y;
ensure_visual_change_idle_handler ();
}
void
Editor::reset_zoom (samplecnt_t spp)
{
if (spp == samples_per_pixel) {
return;
}
pending_visual_change.add (VisualChange::ZoomLevel);
pending_visual_change.samples_per_pixel = spp;
ensure_visual_change_idle_handler ();
}
void
Editor::reposition_and_zoom (samplepos_t sample, double fpu)
@ -4218,77 +4183,6 @@ Editor::playhead_cursor_sample () const
return _playhead_cursor->current_sample();
}
void
Editor::queue_visual_videotimeline_update ()
{
pending_visual_change.add (VisualChange::VideoTimeline);
ensure_visual_change_idle_handler ();
}
void
Editor::ensure_visual_change_idle_handler ()
{
if (pending_visual_change.idle_handler_id < 0) {
/* see comment in add_to_idle_resize above. */
pending_visual_change.idle_handler_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, _idle_visual_changer, this, NULL);
pending_visual_change.being_handled = false;
}
}
int
Editor::_idle_visual_changer (void* arg)
{
return static_cast<Editor*>(arg)->idle_visual_changer ();
}
void
Editor::pre_render ()
{
visual_change_queued = false;
if (pending_visual_change.pending != 0) {
ensure_visual_change_idle_handler();
}
}
int
Editor::idle_visual_changer ()
{
pending_visual_change.idle_handler_id = -1;
if (pending_visual_change.pending == 0) {
return 0;
}
/* set_horizontal_position() below (and maybe other calls) call
gtk_main_iteration(), so it's possible that a signal will be handled
half-way through this method. If this signal wants an
idle_visual_changer we must schedule another one after this one, so
mark the idle_handler_id as -1 here to allow that. Also make a note
that we are doing the visual change, so that changes in response to
super-rapid-screen-update can be dropped if we are still processing
the last one.
*/
if (visual_change_queued) {
return 0;
}
pending_visual_change.being_handled = true;
VisualChange vc = pending_visual_change;
pending_visual_change.pending = (VisualChange::Type) 0;
visual_changer (vc);
pending_visual_change.being_handled = false;
visual_change_queued = true;
return 0; /* this is always a one-shot call */
}
void
Editor::visual_changer (const VisualChange& vc)
{
@ -4346,6 +4240,23 @@ Editor::visual_changer (const VisualChange& vc)
_summary->set_overlays_dirty ();
}
void
Editor::queue_visual_videotimeline_update ()
{
pending_visual_change.add (VisualChange::VideoTimeline);
ensure_visual_change_idle_handler ();
}
void
Editor::pre_render ()
{
visual_change_queued = false;
if (pending_visual_change.pending != 0) {
ensure_visual_change_idle_handler();
}
}
struct EditorOrderTimeAxisSorter {
bool operator() (const TimeAxisView* a, const TimeAxisView* b) const {
return a->order () < b->order ();

View File

@ -384,9 +384,6 @@ public:
void restore_editing_space();
double get_y_origin () const;
void reset_x_origin (samplepos_t);
void reset_y_origin (double);
void reset_zoom (samplecnt_t);
void reposition_and_zoom (samplepos_t, double);
void reset_x_origin_to_follow_playhead ();
@ -480,7 +477,7 @@ public:
ArdourCanvas::Container* get_drag_motion_group () const { return _drag_motion_group; }
ArdourCanvas::GtkCanvasViewport* get_canvas_viewport () const;
ArdourCanvas::Canvas* get_canvas () const;
ArdourCanvas::GtkCanvas* get_canvas () const;
void override_visible_track_count ();
@ -1062,38 +1059,9 @@ private:
void tie_vertical_scrolling ();
struct VisualChange {
enum Type {
TimeOrigin = 0x1,
ZoomLevel = 0x2,
YOrigin = 0x4,
VideoTimeline = 0x8
};
Type pending;
samplepos_t time_origin;
samplecnt_t samples_per_pixel;
double y_origin;
int idle_handler_id;
/** true if we are currently in the idle handler */
bool being_handled;
VisualChange() : pending ((VisualChange::Type) 0), time_origin (0), samples_per_pixel (0), idle_handler_id (-1), being_handled (false) {}
void add (Type t) {
pending = Type (pending | t);
}
};
VisualChange pending_visual_change;
bool visual_change_queued;
void pre_render ();
static int _idle_visual_changer (void* arg);
int idle_visual_changer ();
void visual_changer (const VisualChange&);
void ensure_visual_change_idle_handler ();
/* track views */
TrackViewList track_views;
@ -1918,13 +1886,6 @@ private:
/* autoscrolling */
sigc::connection autoscroll_connection;
bool autoscroll_horizontal_allowed;
bool autoscroll_vertical_allowed;
uint32_t autoscroll_cnt;
Gtk::Widget* autoscroll_widget;
ArdourCanvas::Rect autoscroll_boundary;
bool autoscroll_canvas ();
void start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary);
void stop_canvas_autoscroll ();

View File

@ -1120,7 +1120,7 @@ Editor::get_canvas_viewport() const
return _track_canvas_viewport;
}
ArdourCanvas::Canvas*
ArdourCanvas::GtkCanvas*
Editor::get_canvas() const
{
return _track_canvas_viewport->canvas();

View File

@ -61,6 +61,7 @@ MidiCueEditor::MidiCueEditor()
, bbt_metric (*this)
{
mouse_mode = Editing::MouseContent;
autoscroll_vertical_allowed = false;
bindings = Bindings::get_bindings (editor_name());
@ -103,7 +104,7 @@ MidiCueEditor::get_canvas_viewport() const
return _canvas_viewport;
}
ArdourCanvas::Canvas*
ArdourCanvas::GtkCanvas*
MidiCueEditor::get_canvas() const
{
return _canvas;
@ -360,9 +361,9 @@ MidiCueEditor::snap_to_internal (timepos_t& start, Temporal::RoundMode direction
}
void
MidiCueEditor::reset_zoom (samplecnt_t spp)
MidiCueEditor::set_samples_per_pixel (samplecnt_t spp)
{
CueEditor::reset_zoom (spp);
CueEditor::set_samples_per_pixel (spp);
if (view) {
view->set_samples_per_pixel (spp);
@ -1037,3 +1038,355 @@ MidiCueEditor::get_state () const
get_common_editing_state (*node);
return *node;
}
/** @param allow_horiz true to allow horizontal autoscroll, otherwise false.
*
* @param allow_vert true to allow vertical autoscroll, otherwise false.
*
*/
void
MidiCueEditor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers)
{
if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) {
return;
}
/* define a rectangular boundary for scrolling. If the mouse moves
* outside of this area and/or continue to be outside of this area,
* then we will continuously auto-scroll the canvas in the appropriate
* direction(s)
*
* the boundary is defined in coordinates relative to canvas' own
* window since that is what we're going to call ::get_pointer() on
* during autoscrolling to determine if we're still outside the
* boundary or not.
*/
ArdourCanvas::Rect scrolling_boundary;
Gtk::Allocation alloc;
alloc = get_canvas()->get_allocation ();
if (allow_vert) {
/* reduce height by the height of the timebars, which happens
to correspond to the position of the hv_scroll_group.
*/
alloc.set_height (alloc.get_height() - hv_scroll_group->position().y);
alloc.set_y (alloc.get_y() + hv_scroll_group->position().y);
/* now reduce it again so that we start autoscrolling before we
* move off the top or bottom of the canvas
*/
alloc.set_height (alloc.get_height() - 20);
alloc.set_y (alloc.get_y() + 10);
}
/* the effective width of the autoscroll boundary so
that we start scrolling before we hit the edge.
this helps when the window is slammed up against the
right edge of the screen, making it hard to scroll
effectively.
*/
if (allow_horiz && (alloc.get_width() > 20)) {
alloc.set_width (alloc.get_width() - 20);
alloc.set_x (alloc.get_x() + 10);
}
scrolling_boundary = ArdourCanvas::Rect (0., 0., alloc.get_width(), alloc.get_height());
int x, y;
Gdk::ModifierType mask;
get_canvas()->get_window()->get_pointer (x, y, mask);
if ((allow_horiz && ((x < scrolling_boundary.x0 && _leftmost_sample > 0) || x >= scrolling_boundary.x1)) ||
(allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) {
start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary);
}
}
bool
MidiCueEditor::autoscroll_active () const
{
return autoscroll_connection.connected ();
}
bool
MidiCueEditor::autoscroll_canvas ()
{
using std::max;
using std::min;
int x, y;
Gdk::ModifierType mask;
sampleoffset_t dx = 0;
bool no_stop = false;
Gtk::Window* toplevel = dynamic_cast<Gtk::Window*>(_canvas_viewport->get_toplevel());
if (!toplevel) {
return false;
}
get_canvas()->get_window()->get_pointer (x, y, mask);
VisualChange vc;
bool vertical_motion = false;
if (autoscroll_horizontal_allowed) {
samplepos_t new_sample = _leftmost_sample;
/* horizontal */
if (x > autoscroll_boundary.x1) {
/* bring it back into view */
dx = x - autoscroll_boundary.x1;
dx += 10 + (2 * (autoscroll_cnt/2));
dx = pixel_to_sample (dx);
dx *= UIConfiguration::instance().get_draggable_playhead_speed();
if (_leftmost_sample < max_samplepos - dx) {
new_sample = _leftmost_sample + dx;
} else {
new_sample = max_samplepos;
}
no_stop = true;
} else if (x < autoscroll_boundary.x0) {
dx = autoscroll_boundary.x0 - x;
dx += 10 + (2 * (autoscroll_cnt/2));
dx = pixel_to_sample (dx);
dx *= UIConfiguration::instance().get_draggable_playhead_speed();
if (_leftmost_sample >= dx) {
new_sample = _leftmost_sample - dx;
} else {
new_sample = 0;
}
no_stop = true;
}
if (new_sample != _leftmost_sample) {
vc.time_origin = new_sample;
vc.add (VisualChange::TimeOrigin);
}
}
if (autoscroll_vertical_allowed) {
// const double vertical_pos = vertical_adjustment.get_value();
const int speed_factor = 10;
/* vertical */
if (y < autoscroll_boundary.y0) {
/* scroll to make higher tracks visible */
if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
// XXX SCROLL UP
vertical_motion = true;
}
no_stop = true;
} else if (y > autoscroll_boundary.y1) {
if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) {
// XXX SCROLL DOWN
vertical_motion = true;
}
no_stop = true;
}
}
if (vc.pending || vertical_motion) {
/* change horizontal first */
if (vc.pending) {
visual_changer (vc);
}
/* now send a motion event to notify anyone who cares
that we have moved to a new location (because we scrolled)
*/
GdkEventMotion ev;
ev.type = GDK_MOTION_NOTIFY;
ev.state = Gdk::BUTTON1_MASK;
/* the motion handler expects events in canvas coordinate space */
/* we asked for the mouse position above (::get_pointer()) via
* our own top level window (we being the Editor). Convert into
* coordinates within the canvas window.
*/
int cx;
int cy;
//toplevel->translate_coordinates (*get_canvas(), x, y, cx,
//cy);
cx = x;
cy = y;
/* clamp x and y to remain within the autoscroll boundary,
* which is defined in window coordinates
*/
x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1);
y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1);
/* now convert from Editor window coordinates to canvas
* window coordinates
*/
ArdourCanvas::Duple d = get_canvas()->window_to_canvas (ArdourCanvas::Duple (cx, cy));
ev.x = d.x;
ev.y = d.y;
ev.state = mask;
motion_handler (0, (GdkEvent*) &ev, true);
} else if (no_stop) {
/* not changing visual state but pointer is outside the scrolling boundary
* so we still need to deliver a fake motion event
*/
GdkEventMotion ev;
ev.type = GDK_MOTION_NOTIFY;
ev.state = Gdk::BUTTON1_MASK;
/* the motion handler expects events in canvas coordinate space */
/* first convert from Editor window coordinates to canvas
* window coordinates
*/
int cx;
int cy;
/* clamp x and y to remain within the visible area. except
* .. if horizontal scrolling is allowed, always allow us to
* move back to zero
*/
if (autoscroll_horizontal_allowed) {
x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1);
} else {
x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1);
}
y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1);
// toplevel->translate_coordinates (*get_canvas_viewport(), x,
// y, cx, cy);
cx = x;
cy = y;
ArdourCanvas::Duple d = get_canvas()->window_to_canvas (ArdourCanvas::Duple (cx, cy));
ev.x = d.x;
ev.y = d.y;
ev.state = mask;
motion_handler (0, (GdkEvent*) &ev, true);
} else {
stop_canvas_autoscroll ();
return false;
}
autoscroll_cnt++;
return true; /* call me again */
}
void
MidiCueEditor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary)
{
if (!_session) {
return;
}
stop_canvas_autoscroll ();
autoscroll_horizontal_allowed = allow_horiz;
autoscroll_vertical_allowed = allow_vert;
autoscroll_boundary = boundary;
/* do the first scroll right now
*/
autoscroll_canvas ();
/* scroll again at very very roughly 30FPS */
autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &MidiCueEditor::autoscroll_canvas), 30);
}
void
MidiCueEditor::stop_canvas_autoscroll ()
{
autoscroll_connection.disconnect ();
autoscroll_cnt = 0;
}
void
MidiCueEditor::visual_changer (const VisualChange& vc)
{
/**
* Changed first so the correct horizontal canvas position is calculated in
* EditingContext::set_horizontal_position
*/
if (vc.pending & VisualChange::ZoomLevel) {
set_samples_per_pixel (vc.samples_per_pixel);
}
if (vc.pending & VisualChange::TimeOrigin) {
double new_time_origin = sample_to_pixel_unrounded (vc.time_origin);
set_horizontal_position (new_time_origin);
}
if (vc.pending & VisualChange::YOrigin) {
vertical_adjustment.set_value (vc.y_origin);
}
/**
* Now the canvas is in the final state before render the canvas items that
* support the Item::prepare_for_render interface can calculate the correct
* item to visible canvas intersection.
*/
if (vc.pending & VisualChange::ZoomLevel) {
on_samples_per_pixel_changed ();
// update_tempo_based_rulers ();
}
if (!(vc.pending & VisualChange::ZoomLevel)) {
/* If the canvas is not being zoomed then the canvas items will not change
* and cause Item::prepare_for_render to be called so do it here manually.
* Not ideal, but I can't think of a better solution atm.
*/
get_canvas()->prepare_for_render();
}
/* If we are only scrolling vertically there is no need to update these */
if (vc.pending != VisualChange::YOrigin) {
// XXX update_fixed_rulers ();
// XXX redisplay_grid (true);
}
}

View File

@ -75,7 +75,7 @@ class MidiCueEditor : public CueEditor
ArdourCanvas::ScrollGroup* get_hscroll_group () const { return h_scroll_group; }
ArdourCanvas::ScrollGroup* get_cursor_scroll_group () const { return cursor_scroll_group; }
void reset_zoom (samplecnt_t);
void set_samples_per_pixel (samplecnt_t);
void set_mouse_mode (Editing::MouseMode, bool force = false);
void step_mouse_mode (bool next);
@ -86,11 +86,14 @@ class MidiCueEditor : public CueEditor
size_t n_timebars;
ArdourCanvas::GtkCanvasViewport* get_canvas_viewport() const;
ArdourCanvas::Canvas* get_canvas() const;
ArdourCanvas::GtkCanvas* get_canvas() const;
int set_state (const XMLNode&, int version);
XMLNode& get_state () const;
void maybe_autoscroll (bool, bool, bool);
bool autoscroll_active() const;
protected:
void register_actions ();
@ -177,6 +180,14 @@ class MidiCueEditor : public CueEditor
bool canvas_pre_event (GdkEvent*);
void setup_toolbar ();
/* autoscrolling */
bool autoscroll_canvas ();
void start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary);
void stop_canvas_autoscroll ();
void visual_changer (const VisualChange&);
};

View File

@ -141,7 +141,8 @@ MidiView::MidiView (std::shared_ptr<MidiTrack> mt,
MidiView::MidiView (MidiView const & other)
: _midi_track (other._midi_track)
: sigc::trackable ()
, _midi_track (other._midi_track)
, _editing_context (other.editing_context())
, _midi_context (other.midi_context())
, _midi_region (other.midi_region())