From 2fa6caad95d81f058326d931532f687a157361be Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 16 Nov 2014 17:04:27 -0500 Subject: Support cut/copy/paste of several regions and lines at once. The idea here is to do the reasonable thing, and copy objects of some type (e.g. MIDI region, gain line) to tracks with a matching type. The user can override this with a track selection, which will be used straight-up. Lost: ability to copy/paste lines across types, e.g. gain to pan. This is often questionable, but sometimes useful, so we will need to implement some sort of "greedy mode" to make it possible. Implementation simple, but not sure what to do. Perhaps this should only be possible if one automation track is explicitly (i.e. via track selection) involved, and the types are at least compatible-ish? --- gtk2_ardour/automation_region_view.cc | 28 +++++++ gtk2_ardour/automation_region_view.h | 5 ++ gtk2_ardour/automation_selection.h | 23 ++++-- gtk2_ardour/automation_streamview.cc | 19 +++-- gtk2_ardour/automation_streamview.h | 6 +- gtk2_ardour/automation_time_axis.cc | 37 ++++----- gtk2_ardour/automation_time_axis.h | 6 +- gtk2_ardour/editor.cc | 14 +--- gtk2_ardour/editor_ops.cc | 140 +++++++++++++++++++++------------- gtk2_ardour/item_counts.h | 78 +++++++++++++++++++ gtk2_ardour/playlist_selection.h | 16 +++- gtk2_ardour/route_time_axis.cc | 11 +-- gtk2_ardour/route_time_axis.h | 5 +- gtk2_ardour/selection.cc | 2 +- gtk2_ardour/time_axis_view.h | 16 +++- 15 files changed, 290 insertions(+), 116 deletions(-) create mode 100644 gtk2_ardour/item_counts.h diff --git a/gtk2_ardour/automation_region_view.cc b/gtk2_ardour/automation_region_view.cc index 59bc1f6250..76591fa3d8 100644 --- a/gtk2_ardour/automation_region_view.cc +++ b/gtk2_ardour/automation_region_view.cc @@ -192,6 +192,34 @@ AutomationRegionView::add_automation_event (GdkEvent *, framepos_t when, double view->session()->set_dirty (); } +bool +AutomationRegionView::paste (framepos_t pos, + unsigned paste_count, + float times, + boost::shared_ptr slist) +{ + AutomationTimeAxisView* const view = automation_view(); + boost::shared_ptr my_list = _line->the_list(); + + if (view->session()->transport_rolling() && my_list->automation_write()) { + /* do not paste if this control is in write mode and we're rolling */ + return false; + } + + /* add multi-paste offset if applicable */ + pos += view->editor().get_paste_offset( + pos, paste_count, _line->time_converter().to(slist->length())); + + const double model_pos = _line->time_converter().from(pos - _line->time_converter().origin_b()); + + XMLNode& before = my_list->get_state(); + my_list->paste(*slist, model_pos, times); + view->session()->add_command( + new MementoCommand(*my_list.get(), &before, &my_list->get_state())); + + return true; +} + void AutomationRegionView::set_height (double h) { diff --git a/gtk2_ardour/automation_region_view.h b/gtk2_ardour/automation_region_view.h index 0bebf12a32..4e97e2f367 100644 --- a/gtk2_ardour/automation_region_view.h +++ b/gtk2_ardour/automation_region_view.h @@ -49,6 +49,11 @@ public: void init (bool wfd); + bool paste (framepos_t pos, + unsigned paste_count, + float times, + boost::shared_ptr slist); + inline AutomationTimeAxisView* automation_view() const { return dynamic_cast(&trackview); } diff --git a/gtk2_ardour/automation_selection.h b/gtk2_ardour/automation_selection.h index 6f30c588e2..204f4f19be 100644 --- a/gtk2_ardour/automation_selection.h +++ b/gtk2_ardour/automation_selection.h @@ -22,10 +22,23 @@ #include -namespace ARDOUR { - class AutomationList; -} - -class AutomationSelection : public std::list > {}; +#include "ardour/automation_list.h" +#include "evoral/Parameter.hpp" + +class AutomationSelection : public std::list > { +public: + const_iterator + get_nth(const Evoral::Parameter& param, size_t nth) const { + size_t count = 0; + for (const_iterator l = begin(); l != end(); ++l) { + if ((*l)->parameter() == param) { + if (count++ == nth) { + return l; + } + } + } + return end(); + } +}; #endif /* __ardour_gtk_automation_selection_h__ */ diff --git a/gtk2_ardour/automation_streamview.cc b/gtk2_ardour/automation_streamview.cc index 6dc766bdc5..5616cdebdb 100644 --- a/gtk2_ardour/automation_streamview.cc +++ b/gtk2_ardour/automation_streamview.cc @@ -16,8 +16,9 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include #include +#include +#include #include #include @@ -314,16 +315,16 @@ struct RegionPositionSorter { }; -/** @param pos Position, in session frames. - * @return AutomationLine to paste to for that position, or 0 if there is none appropriate. - */ -boost::shared_ptr -AutomationStreamView::paste_line (framepos_t pos) +bool +AutomationStreamView::paste (framepos_t pos, + unsigned paste_count, + float times, + boost::shared_ptr alist) { /* XXX: not sure how best to pick this; for now, just use the last region which starts before pos */ if (region_views.empty()) { - return boost::shared_ptr (); + return false; } region_views.sort (RegionPositionSorter ()); @@ -345,7 +346,5 @@ AutomationStreamView::paste_line (framepos_t pos) } AutomationRegionView* arv = dynamic_cast (*prev); - assert (arv); - - return arv->line (); + return arv ? arv->paste(pos, paste_count, times, alist) : false; } diff --git a/gtk2_ardour/automation_streamview.h b/gtk2_ardour/automation_streamview.h index d058f02434..082e3cc379 100644 --- a/gtk2_ardour/automation_streamview.h +++ b/gtk2_ardour/automation_streamview.h @@ -64,7 +64,11 @@ class AutomationStreamView : public StreamView void set_selected_points (PointSelection &); std::list > get_lines () const; - boost::shared_ptr paste_line (ARDOUR::framepos_t); + + bool paste (framepos_t pos, + unsigned paste_count, + float times, + boost::shared_ptr list); private: void setup_rec_box (); diff --git a/gtk2_ardour/automation_time_axis.cc b/gtk2_ardour/automation_time_axis.cc index e0e9b9428f..61c5d28e19 100644 --- a/gtk2_ardour/automation_time_axis.cc +++ b/gtk2_ardour/automation_time_axis.cc @@ -48,6 +48,7 @@ #include "point_selection.h" #include "control_point.h" #include "utils.h" +#include "item_counts.h" #include "i18n.h" @@ -630,51 +631,43 @@ AutomationTimeAxisView::add_automation_event (GdkEvent* event, framepos_t when, _session->set_dirty (); } -/** Paste a selection. - * @param pos Position to paste to (session frames). - * @param times Number of times to paste. - * @param selection Selection to paste. - * @param nth Index of the AutomationList within the selection to paste from. - */ bool -AutomationTimeAxisView::paste (framepos_t pos, unsigned paste_count, float times, Selection& selection, size_t nth) +AutomationTimeAxisView::paste (framepos_t pos, unsigned paste_count, float times, const Selection& selection, ItemCounts& counts) { - boost::shared_ptr line; - if (_line) { - line = _line; + return paste_one (pos, paste_count, times, selection, counts); } else if (_view) { - line = _view->paste_line (pos); - } - - if (!line) { - return false; + AutomationSelection::const_iterator l = selection.lines.get_nth(_parameter, counts.n_lines(_parameter)); + if (l != selection.lines.end() && _view->paste (pos, paste_count, times, *l)) { + counts.increase_n_lines(_parameter); + return true; + } } - return paste_one (*line, pos, paste_count, times, selection, nth); + return false; } bool -AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, unsigned paste_count, float times, Selection& selection, size_t nth) +AutomationTimeAxisView::paste_one (framepos_t pos, unsigned paste_count, float times, const Selection& selection, ItemCounts& counts) { - AutomationSelection::iterator p; - boost::shared_ptr alist(line.the_list()); + boost::shared_ptr alist(_line->the_list()); if (_session->transport_rolling() && alist->automation_write()) { /* do not paste if this control is in write mode and we're rolling */ return false; } - for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {} - + /* Get appropriate list from selection. */ + AutomationSelection::const_iterator p = selection.lines.get_nth(_parameter, counts.n_lines(_parameter)); if (p == selection.lines.end()) { return false; } + counts.increase_n_lines(_parameter); /* add multi-paste offset if applicable */ pos += _editor.get_paste_offset(pos, paste_count, (*p)->length()); - double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ()); + double const model_pos = _line->time_converter().from (pos - _line->time_converter().origin_b ()); XMLNode &before = alist->get_state(); alist->paste (**p, model_pos, times); diff --git a/gtk2_ardour/automation_time_axis.h b/gtk2_ardour/automation_time_axis.h index 39a211a456..6db4bd4e64 100644 --- a/gtk2_ardour/automation_time_axis.h +++ b/gtk2_ardour/automation_time_axis.h @@ -51,7 +51,7 @@ class Selection; class Selectable; class AutomationStreamView; class AutomationController; - +class ItemCounts; class AutomationTimeAxisView : public TimeAxisView { public: @@ -93,7 +93,7 @@ class AutomationTimeAxisView : public TimeAxisView { /* editing operations */ void cut_copy_clear (Selection&, Editing::CutCopyOp); - bool paste (ARDOUR::framepos_t, unsigned paste_count, float times, Selection&, size_t nth); + bool paste (ARDOUR::framepos_t, unsigned paste_count, float times, const Selection&, ItemCounts&); int set_state (const XMLNode&, int version); @@ -171,7 +171,7 @@ class AutomationTimeAxisView : public TimeAxisView { void build_display_menu (); void cut_copy_clear_one (AutomationLine&, Selection&, Editing::CutCopyOp); - bool paste_one (AutomationLine&, ARDOUR::framepos_t, unsigned, float times, Selection&, size_t nth); + bool paste_one (ARDOUR::framepos_t, unsigned, float times, const Selection&, ItemCounts& counts); void route_going_away (); void set_automation_state (ARDOUR::AutoState); diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index e50ef98290..d6ab14dafc 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -3887,16 +3887,10 @@ Editor::get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t durat /* calculate basic unsnapped multi-paste offset */ framecnt_t offset = paste_count * duration; - bool success = true; - double snap_beats = get_grid_type_as_beats(success, pos); - if (success) { - /* we're snapped to something musical, round duration up */ - BeatsFramesConverter conv(_session->tempo_map(), pos); - const Evoral::MusicalTime dur_beats = conv.from(duration); - const framecnt_t snap_dur_beats = ceil(dur_beats / snap_beats) * snap_beats; - - offset = paste_count * conv.to(snap_dur_beats); - } + /* snap offset so pos + offset is aligned to the grid */ + framepos_t offset_pos = pos + offset; + snap_to(offset_pos, RoundUpMaybe); + offset = offset_pos - pos; return offset; } diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index 5e0716d0dd..ed1cc7b256 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,7 @@ #include "audio_region_view.h" #include "audio_streamview.h" #include "audio_time_axis.h" +#include "automation_region_view.h" #include "automation_time_axis.h" #include "control_point.h" #include "debug.h" @@ -75,6 +77,7 @@ #include "gui_thread.h" #include "insert_time_dialog.h" #include "interthread_progress_window.h" +#include "item_counts.h" #include "keyboard.h" #include "midi_region_view.h" #include "mixer_strip.h" @@ -3843,26 +3846,8 @@ Editor::cut_copy (CutCopyOp op) bool did_edit = false; - if (!selection->points.empty()) { - begin_reversible_command (opname + _(" points")); - did_edit = true; - cut_copy_points (op); - if (op == Cut || op == Delete) { - selection->clear_points (); - } - } else if (!selection->regions.empty() || !selection->points.empty()) { - - string thing_name; - - if (selection->regions.empty()) { - thing_name = _("points"); - } else if (selection->points.empty()) { - thing_name = _("regions"); - } else { - thing_name = _("objects"); - } - - begin_reversible_command (opname + ' ' + thing_name); + if (!selection->regions.empty() || !selection->points.empty()) { + begin_reversible_command (opname + ' ' + _("objects")); did_edit = true; if (!selection->regions.empty()) { @@ -3889,7 +3874,7 @@ Editor::cut_copy (CutCopyOp op) selection->set (start, end); } } else if (!selection->time.empty()) { - begin_reversible_command (opname + _(" range")); + begin_reversible_command (opname + ' ' + _("range")); did_edit = true; cut_copy_ranges (op); @@ -3912,10 +3897,11 @@ Editor::cut_copy (CutCopyOp op) } struct AutomationRecord { - AutomationRecord () : state (0) {} - AutomationRecord (XMLNode* s) : state (s) {} + AutomationRecord () : state (0) , line(NULL) {} + AutomationRecord (XMLNode* s, const AutomationLine* l) : state (s) , line (l) {} XMLNode* state; ///< state before any operation + const AutomationLine* line; ///< line this came from boost::shared_ptr copy; ///< copied events for the cut buffer }; @@ -3938,12 +3924,13 @@ Editor::cut_copy_points (CutCopyOp op) /* Go through all selected points, making an AutomationRecord for each distinct AutomationList */ for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) { - boost::shared_ptr al = (*i)->line().the_list(); + const AutomationLine& line = (*i)->line(); + const boost::shared_ptr al = line.the_list(); if (lists.find (al) == lists.end ()) { /* We haven't seen this list yet, so make a record for it. This includes taking a copy of its current state, in case this is needed for undo later. */ - lists[al] = AutomationRecord (&al->get_state ()); + lists[al] = AutomationRecord (&al->get_state (), &line); } } @@ -3951,8 +3938,12 @@ Editor::cut_copy_points (CutCopyOp op) /* This operation will involve putting things in the cut buffer, so create an empty ControlList for each of our source lists to put the cut buffer data in. */ + framepos_t start = std::numeric_limits::max(); for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) { i->second.copy = i->first->create (i->first->parameter ()); + + /* Calculate earliest start position of any point in selection. */ + start = std::min(start, i->second.line->session_position(i->first->begin())); } /* Add all selected points to the relevant copy ControlLists */ @@ -3962,11 +3953,18 @@ Editor::cut_copy_points (CutCopyOp op) lists[al].copy->fast_simple_add ((*j)->when, (*j)->value); } + /* Snap start time backwards, so copy/paste is snap aligned. */ + snap_to(start, RoundDownMaybe); + for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) { - /* Correct this copy list so that it starts at time 0 */ - double const start = i->second.copy->front()->when; + /* Correct this copy list so that it is relative to the earliest + start time, so relative ordering between points is preserved + when copying from several lists. */ + const AutomationLine* line = i->second.line; + const double line_offset = line->time_converter().from(start); + for (AutomationList::iterator j = i->second.copy->begin(); j != i->second.copy->end(); ++j) { - (*j)->when -= start; + (*j)->when -= line_offset; } /* And add it to the cut buffer */ @@ -4352,14 +4350,8 @@ Editor::paste_internal (framepos_t position, float times) { DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("apparent paste position is %1\n", position)); - if (internal_editing()) { - if (cut_buffer->midi_notes.empty()) { - return; - } - } else { - if (cut_buffer->empty()) { - return; - } + if (cut_buffer->empty(internal_editing())) { + return; } if (position == max_framepos) { @@ -4376,27 +4368,64 @@ Editor::paste_internal (framepos_t position, float times) last_paste_pos = position; } - TrackViewList ts; - TrackViewList::iterator i; - size_t nth; - /* get everything in the correct order */ - if (_edit_point == Editing::EditAtMouse && entered_track) { - /* With the mouse edit point, paste onto the track under the mouse */ - ts.push_back (entered_track); - } else if (_edit_point == Editing::EditAtMouse && entered_regionview) { - /* With the mouse edit point, paste onto the track of the region under the mouse */ - ts.push_back (&entered_regionview->get_time_axis_view()); - } else if (!selection->tracks.empty()) { - /* Otherwise, if there are some selected tracks, paste to them */ + TrackViewList ts; + if (!selection->tracks.empty()) { + /* If there is a track selection, paste into exactly those tracks and + only those tracks. This allows the user to be explicit and override + the below "do the reasonable thing" logic. */ ts = selection->tracks.filter_to_unique_playlists (); sort_track_selection (ts); - } else if (_last_cut_copy_source_track) { - /* Otherwise paste to the track that the cut/copy came from; - see discussion in mantis #3333. - */ - ts.push_back (_last_cut_copy_source_track); + } else { + /* Figure out which track to base the paste at. */ + TimeAxisView* base_track; + if (_edit_point == Editing::EditAtMouse && entered_track) { + /* With the mouse edit point, paste onto the track under the mouse. */ + base_track = entered_track; + } else if (_edit_point == Editing::EditAtMouse && entered_regionview) { + /* With the mouse edit point, paste onto the track of the region under the mouse. */ + base_track = &entered_regionview->get_time_axis_view(); + } else if (_last_cut_copy_source_track) { + /* Paste to the track that the cut/copy came from (see mantis #333). */ + base_track = _last_cut_copy_source_track; + } + + /* Walk up to parent if necessary, so base track is a route. */ + while (base_track->get_parent()) { + base_track = base_track->get_parent(); + } + + /* Add base track and all tracks below it. The paste logic will select + the appropriate object types from the cut buffer in relative order. */ + for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { + if ((*i)->order() >= base_track->order()) { + ts.push_back(*i); + } + } + + /* Sort tracks so the nth track of type T will pick the nth object of type T. */ + sort_track_selection (ts); + + /* Add automation children of each track in order, for pasting several lines. */ + for (TrackViewList::iterator i = ts.begin(); i != ts.end();) { + /* Add any automation children for pasting several lines */ + RouteTimeAxisView* rtv = dynamic_cast(*i++); + if (!rtv) { + continue; + } + + typedef RouteTimeAxisView::AutomationTracks ATracks; + const ATracks& atracks = rtv->automation_tracks(); + for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) { + i = ts.insert(i, a->second.get()); + ++i; + } + } + + /* We now have a list of trackviews starting at base_track, including + automation children, in the order shown in the editor, e.g. R1, + R1.A1, R1.A2, R2, R2.A1, ... */ } if (internal_editing ()) { @@ -4424,8 +4453,9 @@ Editor::paste_internal (framepos_t position, float times) begin_reversible_command (Operations::paste); - for (nth = 0, i = ts.begin(); i != ts.end(); ++i, ++nth) { - (*i)->paste (position, paste_count, times, *cut_buffer, nth); + ItemCounts counts; + for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) { + (*i)->paste (position, paste_count, times, *cut_buffer, counts); } commit_reversible_command (); diff --git a/gtk2_ardour/item_counts.h b/gtk2_ardour/item_counts.h new file mode 100644 index 0000000000..b7c6dbd9c6 --- /dev/null +++ b/gtk2_ardour/item_counts.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2014 Paul Davis + Author: David Robillard + + 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. +*/ + +#ifndef __ardour_item_counts_h__ +#define __ardour_item_counts_h__ + +#include +#include +#include + +#include "ardour/data_type.h" +#include "evoral/Parameter.hpp" + +/** A count of various GUI items. + * + * This is used to keep track of 'consumption' of a selection when pasting, but + * may be useful elsewhere. + */ +class ItemCounts +{ +public: + size_t n_playlists(ARDOUR::DataType t) const { return get_n(t, _playlists); } + size_t n_regions(ARDOUR::DataType t) const { return get_n(t, _regions); } + size_t n_lines(Evoral::Parameter t) const { return get_n(t, _lines); } + + void increase_n_playlists(ARDOUR::DataType t, size_t delta=1) { + increase_n(t, _playlists, delta); + } + + void increase_n_regions(ARDOUR::DataType t, size_t delta=1) { + increase_n(t, _regions, delta); + } + + void increase_n_lines(Evoral::Parameter t, size_t delta=1) { + increase_n(t, _lines, delta); + } + +private: + template + size_t + get_n(const Key& key, const typename std::map& counts) const { + typename std::map::const_iterator i = counts.find(key); + return (i == counts.end()) ? 0 : i->second; + } + + template + void + increase_n(const Key& key, typename std::map& counts, size_t delta) { + typename std::map::iterator i = counts.find(key); + if (i != counts.end()) { + i->second += delta; + } else { + counts.insert(std::make_pair(key, delta)); + } + } + + std::map _playlists; + std::map _regions; + std::map _lines; +}; + +#endif /* __ardour_item_counts_h__ */ diff --git a/gtk2_ardour/playlist_selection.h b/gtk2_ardour/playlist_selection.h index 4fcf1c64c8..93aea79093 100644 --- a/gtk2_ardour/playlist_selection.h +++ b/gtk2_ardour/playlist_selection.h @@ -27,6 +27,20 @@ namespace ARDOUR { class Playlist; } -struct PlaylistSelection : std::list > {}; +struct PlaylistSelection : std::list > { +public: + const_iterator + get_nth(ARDOUR::DataType type, size_t nth) const { + size_t count = 0; + for (const_iterator l = begin(); l != end(); ++l) { + if ((*l)->data_type() == type) { + if (count++ == nth) { + return l; + } + } + } + return end(); + } +}; #endif /* __ardour_gtk_playlist_selection_h__ */ diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc index b447288566..1d89833b79 100644 --- a/gtk2_ardour/route_time_axis.cc +++ b/gtk2_ardour/route_time_axis.cc @@ -63,6 +63,7 @@ #include "automation_time_axis.h" #include "enums.h" #include "gui_thread.h" +#include "item_counts.h" #include "keyboard.h" #include "playlist_selector.h" #include "point_selection.h" @@ -1534,20 +1535,20 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op) } bool -RouteTimeAxisView::paste (framepos_t pos, unsigned paste_count, float times, Selection& selection, size_t nth) +RouteTimeAxisView::paste (framepos_t pos, unsigned paste_count, float times, const Selection& selection, ItemCounts& counts) { if (!is_track()) { return false; } - boost::shared_ptr pl = playlist (); - PlaylistSelection::iterator p; - - for (p = selection.playlists.begin(); p != selection.playlists.end() && nth; ++p, --nth) {} + boost::shared_ptr pl = playlist (); + const ARDOUR::DataType type = pl->data_type(); + PlaylistSelection::const_iterator p = selection.playlists.get_nth(type, counts.n_playlists(type)); if (p == selection.playlists.end()) { return false; } + counts.increase_n_playlists(type); DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("paste to %1\n", pos)); diff --git a/gtk2_ardour/route_time_axis.h b/gtk2_ardour/route_time_axis.h index 45b8afd82d..03060c702f 100644 --- a/gtk2_ardour/route_time_axis.h +++ b/gtk2_ardour/route_time_axis.h @@ -70,6 +70,7 @@ class AutomationLine; class ProcessorAutomationLine; class TimeSelection; class RouteGroupMenu; +class ItemCounts; class RouteTimeAxisView : public RouteUI, public TimeAxisView { @@ -99,7 +100,7 @@ public: /* Editing operations */ void cut_copy_clear (Selection&, Editing::CutCopyOp); - bool paste (ARDOUR::framepos_t, unsigned paste_count, float times, Selection&, size_t nth); + bool paste (ARDOUR::framepos_t, unsigned paste_count, float times, const Selection&, ItemCounts&); RegionView* combine_regions (); void uncombine_regions (); void uncombine_region (RegionView*); @@ -125,7 +126,7 @@ public: virtual void create_automation_child (const Evoral::Parameter& param, bool show) = 0; typedef std::map > AutomationTracks; - AutomationTracks automation_tracks() { return _automation_tracks; } + const AutomationTracks& automation_tracks() const { return _automation_tracks; } boost::shared_ptr automation_child(Evoral::Parameter param); virtual Gtk::CheckMenuItem* automation_child_menu_item (Evoral::Parameter); diff --git a/gtk2_ardour/selection.cc b/gtk2_ardour/selection.cc index 1bdc0fe8b0..abb49b7daf 100644 --- a/gtk2_ardour/selection.cc +++ b/gtk2_ardour/selection.cc @@ -951,7 +951,7 @@ Selection::empty (bool internal_selection) as a cut buffer. */ - return object_level_empty && midi_notes.empty(); + return object_level_empty && midi_notes.empty() && points.empty(); } void diff --git a/gtk2_ardour/time_axis_view.h b/gtk2_ardour/time_axis_view.h index 3dc440b54c..c46d23ae58 100644 --- a/gtk2_ardour/time_axis_view.h +++ b/gtk2_ardour/time_axis_view.h @@ -78,6 +78,7 @@ class RegionView; class GhostRegion; class StreamView; class ArdourDialog; +class ItemCounts; /** Abstract base class for time-axis views (horizontal editor 'strips') * @@ -165,7 +166,20 @@ class TimeAxisView : public virtual AxisView /* editing operations */ virtual void cut_copy_clear (Selection&, Editing::CutCopyOp) {} - virtual bool paste (ARDOUR::framepos_t, unsigned /*paste_count*/, float /*times*/, Selection&, size_t /*nth*/) { return false; } + + /** Paste a selection. + * @param pos Position to paste to (session frames). + * @param paste_count Number of pastes to the same location previously (multi-paste). + * @param times Number of times to paste. + * @param selection Selection to paste. + * @param counts Count of consumed selection items (used to find the + * correct item to paste here, then updated for the next pastee). + */ + virtual bool paste (ARDOUR::framepos_t pos, + unsigned paste_count, + float times, + const Selection& selection, + ItemCounts& counts) { return false; } virtual void set_selected_regionviews (RegionSelection&) {} virtual void set_selected_points (PointSelection&) {} -- cgit v1.2.3