summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2014-11-16 17:04:27 -0500
committerDavid Robillard <d@drobilla.net>2014-11-16 22:35:45 -0500
commit2fa6caad95d81f058326d931532f687a157361be (patch)
tree2981806b2bfec9351d62ccfc7e5d0e7dfe3581aa
parent5393982c8022d4117a6fe29340f8ecf2d115648d (diff)
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?
-rw-r--r--gtk2_ardour/automation_region_view.cc28
-rw-r--r--gtk2_ardour/automation_region_view.h5
-rw-r--r--gtk2_ardour/automation_selection.h23
-rw-r--r--gtk2_ardour/automation_streamview.cc19
-rw-r--r--gtk2_ardour/automation_streamview.h6
-rw-r--r--gtk2_ardour/automation_time_axis.cc37
-rw-r--r--gtk2_ardour/automation_time_axis.h6
-rw-r--r--gtk2_ardour/editor.cc14
-rw-r--r--gtk2_ardour/editor_ops.cc140
-rw-r--r--gtk2_ardour/item_counts.h78
-rw-r--r--gtk2_ardour/playlist_selection.h16
-rw-r--r--gtk2_ardour/route_time_axis.cc11
-rw-r--r--gtk2_ardour/route_time_axis.h5
-rw-r--r--gtk2_ardour/selection.cc2
-rw-r--r--gtk2_ardour/time_axis_view.h16
15 files changed, 290 insertions, 116 deletions
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<const ARDOUR::AutomationList> slist)
+{
+ AutomationTimeAxisView* const view = automation_view();
+ boost::shared_ptr<ARDOUR::AutomationList> 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<ARDOUR::AutomationList>(*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<const ARDOUR::AutomationList> slist);
+
inline AutomationTimeAxisView* automation_view() const
{ return dynamic_cast<AutomationTimeAxisView*>(&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 <list>
-namespace ARDOUR {
- class AutomationList;
-}
-
-class AutomationSelection : public std::list<boost::shared_ptr<ARDOUR::AutomationList> > {};
+#include "ardour/automation_list.h"
+#include "evoral/Parameter.hpp"
+
+class AutomationSelection : public std::list<boost::shared_ptr<ARDOUR::AutomationList> > {
+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 <cmath>
#include <cassert>
+#include <cmath>
+#include <list>
#include <utility>
#include <gtkmm.h>
@@ -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<AutomationLine>
-AutomationStreamView::paste_line (framepos_t pos)
+bool
+AutomationStreamView::paste (framepos_t pos,
+ unsigned paste_count,
+ float times,
+ boost::shared_ptr<ARDOUR::AutomationList> 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<AutomationLine> ();
+ return false;
}
region_views.sort (RegionPositionSorter ());
@@ -345,7 +346,5 @@ AutomationStreamView::paste_line (framepos_t pos)
}
AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*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<boost::shared_ptr<AutomationLine> > get_lines () const;
- boost::shared_ptr<AutomationLine> paste_line (ARDOUR::framepos_t);
+
+ bool paste (framepos_t pos,
+ unsigned paste_count,
+ float times,
+ boost::shared_ptr<ARDOUR::AutomationList> 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<AutomationLine> 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<AutomationList> alist(line.the_list());
+ boost::shared_ptr<AutomationList> 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 <cstdlib>
#include <cmath>
#include <string>
+#include <limits>
#include <map>
#include <set>
@@ -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<Evoral::ControlList> 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<AutomationList> al = (*i)->line().the_list();
+ const AutomationLine& line = (*i)->line();
+ const boost::shared_ptr<AutomationList> 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<framepos_t>::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<RouteTimeAxisView*>(*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 <cstddef>
+#include <map>
+#include <utility>
+
+#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<typename Key>
+ size_t
+ get_n(const Key& key, const typename std::map<Key, size_t>& counts) const {
+ typename std::map<Key, size_t>::const_iterator i = counts.find(key);
+ return (i == counts.end()) ? 0 : i->second;
+ }
+
+ template<typename Key>
+ void
+ increase_n(const Key& key, typename std::map<Key, size_t>& counts, size_t delta) {
+ typename std::map<Key, size_t>::iterator i = counts.find(key);
+ if (i != counts.end()) {
+ i->second += delta;
+ } else {
+ counts.insert(std::make_pair(key, delta));
+ }
+ }
+
+ std::map<ARDOUR::DataType, size_t> _playlists;
+ std::map<ARDOUR::DataType, size_t> _regions;
+ std::map<Evoral::Parameter, size_t> _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<boost::shared_ptr<ARDOUR::Playlist> > {};
+struct PlaylistSelection : std::list<boost::shared_ptr<ARDOUR::Playlist> > {
+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<Playlist> pl = playlist ();
- PlaylistSelection::iterator p;
-
- for (p = selection.playlists.begin(); p != selection.playlists.end() && nth; ++p, --nth) {}
+ boost::shared_ptr<Playlist> 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<Evoral::Parameter, boost::shared_ptr<AutomationTimeAxisView> > AutomationTracks;
- AutomationTracks automation_tracks() { return _automation_tracks; }
+ const AutomationTracks& automation_tracks() const { return _automation_tracks; }
boost::shared_ptr<AutomationTimeAxisView> 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&) {}