Support cut/copy/paste of several regions and lines at once.
authorDavid Robillard <d@drobilla.net>
Sun, 16 Nov 2014 22:04:27 +0000 (17:04 -0500)
committerDavid Robillard <d@drobilla.net>
Mon, 17 Nov 2014 03:35:45 +0000 (22:35 -0500)
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?

15 files changed:
gtk2_ardour/automation_region_view.cc
gtk2_ardour/automation_region_view.h
gtk2_ardour/automation_selection.h
gtk2_ardour/automation_streamview.cc
gtk2_ardour/automation_streamview.h
gtk2_ardour/automation_time_axis.cc
gtk2_ardour/automation_time_axis.h
gtk2_ardour/editor.cc
gtk2_ardour/editor_ops.cc
gtk2_ardour/item_counts.h [new file with mode: 0644]
gtk2_ardour/playlist_selection.h
gtk2_ardour/route_time_axis.cc
gtk2_ardour/route_time_axis.h
gtk2_ardour/selection.cc
gtk2_ardour/time_axis_view.h

index 59bc1f62502209c3ad046f8d26916da3994e89b5..76591fa3d8ade048c50748325b8ddc3dc0854ffb 100644 (file)
@@ -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)
 {
index 0bebf12a32e4774bdf5050a5420987c6973a9a7d..4e97e2f36781fc7b5bc9b200fd40cadc151cbb95 100644 (file)
@@ -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); }
 
index 6f30c588e24827b932a966569bd334485e1e9245..204f4f19becd9188ed902289c8a9ca8babebd498 100644 (file)
 
 #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__ */
index 6dc766bdc53412b423da82a42ad104ea2ef13348..5616cdebdbee67679590857d3c76689c52c427e6 100644 (file)
@@ -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;
 }
index d058f0243451638314559facf7450c2d81a88c67..082e3cc379bafb250db2206b5d5b37ad1df82397 100644 (file)
@@ -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 ();
index e0e9b9428f73e30f2eb3f95f90b8076dba380de2..61c5d28e19b183d66e21be03caf09ba0dbc5ac3c 100644 (file)
@@ -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);
index 39a211a456af0062bb84c38d86fb6e94ffe7077e..6db4bd4e64b32914491bdc9a865e08f82b991e35 100644 (file)
@@ -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);
index e50ef982901634ce0879ec3af18ec935775d7370..d6ab14dafc5f1d3ff6c390ddaf071be59f0f2ac0 100644 (file)
@@ -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;
 }
index 5e0716d0dda7f42995ff13f29ddcf77a09509c1c..ed1cc7b25699a0a682fc45022338cc2924583b3c 100644 (file)
@@ -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 (file)
index 0000000..b7c6dbd
--- /dev/null
@@ -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__ */
index 4fcf1c64c8bd35358eb97dbf61cfc233d101fb5c..93aea79093af941472fb61e4b9b2131abf5facc9 100644 (file)
@@ -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__ */
index b44728856632a96a8898751ac62de8cfe9f8a570..1d89833b79ba254b1fffd52cb51c100b89ecbb6a 100644 (file)
@@ -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));
 
index 45b8afd82dc4930313fc5f78f48bb71fcf777dfe..03060c702f1ac77eb9b774693843ac7a25df5e81 100644 (file)
@@ -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);
index 1bdc0fe8b0ae24521a2897437d484d7eb6450109..abb49b7daf0c895bf38885068b83bf8f61fc094b 100644 (file)
@@ -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
index 3dc440b54c5211c9b1a7913a2a21172585ba94f2..c46d23ae58b97bb314a4f4b8b88f7ae487cb9600 100644 (file)
@@ -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&) {}