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?
This commit is contained in:
David Robillard 2014-11-16 17:04:27 -05:00
parent 5393982c80
commit 2fa6caad95
15 changed files with 289 additions and 115 deletions

View file

@ -192,6 +192,34 @@ AutomationRegionView::add_automation_event (GdkEvent *, framepos_t when, double
view->session()->set_dirty (); 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 void
AutomationRegionView::set_height (double h) AutomationRegionView::set_height (double h)
{ {

View file

@ -49,6 +49,11 @@ public:
void init (bool wfd); 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 inline AutomationTimeAxisView* automation_view() const
{ return dynamic_cast<AutomationTimeAxisView*>(&trackview); } { return dynamic_cast<AutomationTimeAxisView*>(&trackview); }

View file

@ -22,10 +22,23 @@
#include <list> #include <list>
namespace ARDOUR { #include "ardour/automation_list.h"
class AutomationList; #include "evoral/Parameter.hpp"
}
class AutomationSelection : public std::list<boost::shared_ptr<ARDOUR::AutomationList> > {}; 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__ */ #endif /* __ardour_gtk_automation_selection_h__ */

View file

@ -16,8 +16,9 @@
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/ */
#include <cmath>
#include <cassert> #include <cassert>
#include <cmath>
#include <list>
#include <utility> #include <utility>
#include <gtkmm.h> #include <gtkmm.h>
@ -314,16 +315,16 @@ struct RegionPositionSorter {
}; };
/** @param pos Position, in session frames. bool
* @return AutomationLine to paste to for that position, or 0 if there is none appropriate. AutomationStreamView::paste (framepos_t pos,
*/ unsigned paste_count,
boost::shared_ptr<AutomationLine> float times,
AutomationStreamView::paste_line (framepos_t pos) 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 */ /* XXX: not sure how best to pick this; for now, just use the last region which starts before pos */
if (region_views.empty()) { if (region_views.empty()) {
return boost::shared_ptr<AutomationLine> (); return false;
} }
region_views.sort (RegionPositionSorter ()); region_views.sort (RegionPositionSorter ());
@ -345,7 +346,5 @@ AutomationStreamView::paste_line (framepos_t pos)
} }
AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*prev); AutomationRegionView* arv = dynamic_cast<AutomationRegionView*> (*prev);
assert (arv); return arv ? arv->paste(pos, paste_count, times, alist) : false;
return arv->line ();
} }

View file

@ -64,7 +64,11 @@ class AutomationStreamView : public StreamView
void set_selected_points (PointSelection &); void set_selected_points (PointSelection &);
std::list<boost::shared_ptr<AutomationLine> > get_lines () const; 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: private:
void setup_rec_box (); void setup_rec_box ();

View file

@ -48,6 +48,7 @@
#include "point_selection.h" #include "point_selection.h"
#include "control_point.h" #include "control_point.h"
#include "utils.h" #include "utils.h"
#include "item_counts.h"
#include "i18n.h" #include "i18n.h"
@ -630,51 +631,43 @@ AutomationTimeAxisView::add_automation_event (GdkEvent* event, framepos_t when,
_session->set_dirty (); _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 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) { if (_line) {
line = _line; return paste_one (pos, paste_count, times, selection, counts);
} else if (_view) { } else if (_view) {
line = _view->paste_line (pos); 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;
}
} }
if (!line) {
return false; return false;
} }
return paste_one (*line, pos, paste_count, times, selection, nth);
}
bool 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()) { if (_session->transport_rolling() && alist->automation_write()) {
/* do not paste if this control is in write mode and we're rolling */ /* do not paste if this control is in write mode and we're rolling */
return false; 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()) { if (p == selection.lines.end()) {
return false; return false;
} }
counts.increase_n_lines(_parameter);
/* add multi-paste offset if applicable */ /* add multi-paste offset if applicable */
pos += _editor.get_paste_offset(pos, paste_count, (*p)->length()); 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(); XMLNode &before = alist->get_state();
alist->paste (**p, model_pos, times); alist->paste (**p, model_pos, times);

View file

@ -51,7 +51,7 @@ class Selection;
class Selectable; class Selectable;
class AutomationStreamView; class AutomationStreamView;
class AutomationController; class AutomationController;
class ItemCounts;
class AutomationTimeAxisView : public TimeAxisView { class AutomationTimeAxisView : public TimeAxisView {
public: public:
@ -93,7 +93,7 @@ class AutomationTimeAxisView : public TimeAxisView {
/* editing operations */ /* editing operations */
void cut_copy_clear (Selection&, Editing::CutCopyOp); 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); int set_state (const XMLNode&, int version);
@ -171,7 +171,7 @@ class AutomationTimeAxisView : public TimeAxisView {
void build_display_menu (); void build_display_menu ();
void cut_copy_clear_one (AutomationLine&, Selection&, Editing::CutCopyOp); 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 route_going_away ();
void set_automation_state (ARDOUR::AutoState); void set_automation_state (ARDOUR::AutoState);

View file

@ -3887,16 +3887,10 @@ Editor::get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t durat
/* calculate basic unsnapped multi-paste offset */ /* calculate basic unsnapped multi-paste offset */
framecnt_t offset = paste_count * duration; framecnt_t offset = paste_count * duration;
bool success = true; /* snap offset so pos + offset is aligned to the grid */
double snap_beats = get_grid_type_as_beats(success, pos); framepos_t offset_pos = pos + offset;
if (success) { snap_to(offset_pos, RoundUpMaybe);
/* we're snapped to something musical, round duration up */ offset = offset_pos - pos;
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);
}
return offset; return offset;
} }

View file

@ -24,6 +24,7 @@
#include <cstdlib> #include <cstdlib>
#include <cmath> #include <cmath>
#include <string> #include <string>
#include <limits>
#include <map> #include <map>
#include <set> #include <set>
@ -63,6 +64,7 @@
#include "audio_region_view.h" #include "audio_region_view.h"
#include "audio_streamview.h" #include "audio_streamview.h"
#include "audio_time_axis.h" #include "audio_time_axis.h"
#include "automation_region_view.h"
#include "automation_time_axis.h" #include "automation_time_axis.h"
#include "control_point.h" #include "control_point.h"
#include "debug.h" #include "debug.h"
@ -75,6 +77,7 @@
#include "gui_thread.h" #include "gui_thread.h"
#include "insert_time_dialog.h" #include "insert_time_dialog.h"
#include "interthread_progress_window.h" #include "interthread_progress_window.h"
#include "item_counts.h"
#include "keyboard.h" #include "keyboard.h"
#include "midi_region_view.h" #include "midi_region_view.h"
#include "mixer_strip.h" #include "mixer_strip.h"
@ -3843,26 +3846,8 @@ Editor::cut_copy (CutCopyOp op)
bool did_edit = false; bool did_edit = false;
if (!selection->points.empty()) { if (!selection->regions.empty() || !selection->points.empty()) {
begin_reversible_command (opname + _(" points")); begin_reversible_command (opname + ' ' + _("objects"));
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);
did_edit = true; did_edit = true;
if (!selection->regions.empty()) { if (!selection->regions.empty()) {
@ -3889,7 +3874,7 @@ Editor::cut_copy (CutCopyOp op)
selection->set (start, end); selection->set (start, end);
} }
} else if (!selection->time.empty()) { } else if (!selection->time.empty()) {
begin_reversible_command (opname + _(" range")); begin_reversible_command (opname + ' ' + _("range"));
did_edit = true; did_edit = true;
cut_copy_ranges (op); cut_copy_ranges (op);
@ -3912,10 +3897,11 @@ Editor::cut_copy (CutCopyOp op)
} }
struct AutomationRecord { struct AutomationRecord {
AutomationRecord () : state (0) {} AutomationRecord () : state (0) , line(NULL) {}
AutomationRecord (XMLNode* s) : state (s) {} AutomationRecord (XMLNode* s, const AutomationLine* l) : state (s) , line (l) {}
XMLNode* state; ///< state before any operation 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 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 */ /* Go through all selected points, making an AutomationRecord for each distinct AutomationList */
for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) { 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 ()) { if (lists.find (al) == lists.end ()) {
/* We haven't seen this list yet, so make a record for it. This includes /* 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. 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 /* 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. 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) { for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) {
i->second.copy = i->first->create (i->first->parameter ()); 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 */ /* 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); 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) { for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) {
/* Correct this copy list so that it starts at time 0 */ /* Correct this copy list so that it is relative to the earliest
double const start = i->second.copy->front()->when; 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) { 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 */ /* And add it to the cut buffer */
@ -4352,15 +4350,9 @@ Editor::paste_internal (framepos_t position, float times)
{ {
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("apparent paste position is %1\n", position)); DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("apparent paste position is %1\n", position));
if (internal_editing()) { if (cut_buffer->empty(internal_editing())) {
if (cut_buffer->midi_notes.empty()) {
return; return;
} }
} else {
if (cut_buffer->empty()) {
return;
}
}
if (position == max_framepos) { if (position == max_framepos) {
position = get_preferred_edit_position(); position = get_preferred_edit_position();
@ -4376,27 +4368,64 @@ Editor::paste_internal (framepos_t position, float times)
last_paste_pos = position; last_paste_pos = position;
} }
TrackViewList ts;
TrackViewList::iterator i;
size_t nth;
/* get everything in the correct order */ /* get everything in the correct order */
if (_edit_point == Editing::EditAtMouse && entered_track) { TrackViewList ts;
/* With the mouse edit point, paste onto the track under the mouse */ if (!selection->tracks.empty()) {
ts.push_back (entered_track); /* If there is a track selection, paste into exactly those tracks and
} else if (_edit_point == Editing::EditAtMouse && entered_regionview) { only those tracks. This allows the user to be explicit and override
/* With the mouse edit point, paste onto the track of the region under the mouse */ the below "do the reasonable thing" logic. */
ts.push_back (&entered_regionview->get_time_axis_view());
} else if (!selection->tracks.empty()) {
/* Otherwise, if there are some selected tracks, paste to them */
ts = selection->tracks.filter_to_unique_playlists (); ts = selection->tracks.filter_to_unique_playlists ();
sort_track_selection (ts); sort_track_selection (ts);
} 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) { } else if (_last_cut_copy_source_track) {
/* Otherwise paste to the track that the cut/copy came from; /* Paste to the track that the cut/copy came from (see mantis #333). */
see discussion in mantis #3333. base_track = _last_cut_copy_source_track;
*/ }
ts.push_back (_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 ()) { if (internal_editing ()) {
@ -4424,8 +4453,9 @@ Editor::paste_internal (framepos_t position, float times)
begin_reversible_command (Operations::paste); begin_reversible_command (Operations::paste);
for (nth = 0, i = ts.begin(); i != ts.end(); ++i, ++nth) { ItemCounts counts;
(*i)->paste (position, paste_count, times, *cut_buffer, nth); for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
(*i)->paste (position, paste_count, times, *cut_buffer, counts);
} }
commit_reversible_command (); commit_reversible_command ();

78
gtk2_ardour/item_counts.h Normal file
View file

@ -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__ */

View file

@ -27,6 +27,20 @@ namespace ARDOUR {
class Playlist; 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__ */ #endif /* __ardour_gtk_playlist_selection_h__ */

View file

@ -63,6 +63,7 @@
#include "automation_time_axis.h" #include "automation_time_axis.h"
#include "enums.h" #include "enums.h"
#include "gui_thread.h" #include "gui_thread.h"
#include "item_counts.h"
#include "keyboard.h" #include "keyboard.h"
#include "playlist_selector.h" #include "playlist_selector.h"
#include "point_selection.h" #include "point_selection.h"
@ -1534,20 +1535,20 @@ RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
} }
bool 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()) { if (!is_track()) {
return false; return false;
} }
boost::shared_ptr<Playlist> pl = playlist (); boost::shared_ptr<Playlist> pl = playlist ();
PlaylistSelection::iterator p; const ARDOUR::DataType type = pl->data_type();
PlaylistSelection::const_iterator p = selection.playlists.get_nth(type, counts.n_playlists(type));
for (p = selection.playlists.begin(); p != selection.playlists.end() && nth; ++p, --nth) {}
if (p == selection.playlists.end()) { if (p == selection.playlists.end()) {
return false; return false;
} }
counts.increase_n_playlists(type);
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("paste to %1\n", pos)); DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("paste to %1\n", pos));

View file

@ -70,6 +70,7 @@ class AutomationLine;
class ProcessorAutomationLine; class ProcessorAutomationLine;
class TimeSelection; class TimeSelection;
class RouteGroupMenu; class RouteGroupMenu;
class ItemCounts;
class RouteTimeAxisView : public RouteUI, public TimeAxisView class RouteTimeAxisView : public RouteUI, public TimeAxisView
{ {
@ -99,7 +100,7 @@ public:
/* Editing operations */ /* Editing operations */
void cut_copy_clear (Selection&, Editing::CutCopyOp); 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 (); RegionView* combine_regions ();
void uncombine_regions (); void uncombine_regions ();
void uncombine_region (RegionView*); void uncombine_region (RegionView*);
@ -125,7 +126,7 @@ public:
virtual void create_automation_child (const Evoral::Parameter& param, bool show) = 0; virtual void create_automation_child (const Evoral::Parameter& param, bool show) = 0;
typedef std::map<Evoral::Parameter, boost::shared_ptr<AutomationTimeAxisView> > AutomationTracks; 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); boost::shared_ptr<AutomationTimeAxisView> automation_child(Evoral::Parameter param);
virtual Gtk::CheckMenuItem* automation_child_menu_item (Evoral::Parameter); virtual Gtk::CheckMenuItem* automation_child_menu_item (Evoral::Parameter);

View file

@ -951,7 +951,7 @@ Selection::empty (bool internal_selection)
as a cut buffer. as a cut buffer.
*/ */
return object_level_empty && midi_notes.empty(); return object_level_empty && midi_notes.empty() && points.empty();
} }
void void

View file

@ -78,6 +78,7 @@ class RegionView;
class GhostRegion; class GhostRegion;
class StreamView; class StreamView;
class ArdourDialog; class ArdourDialog;
class ItemCounts;
/** Abstract base class for time-axis views (horizontal editor 'strips') /** Abstract base class for time-axis views (horizontal editor 'strips')
* *
@ -165,7 +166,20 @@ class TimeAxisView : public virtual AxisView
/* editing operations */ /* editing operations */
virtual void cut_copy_clear (Selection&, Editing::CutCopyOp) {} 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_regionviews (RegionSelection&) {}
virtual void set_selected_points (PointSelection&) {} virtual void set_selected_points (PointSelection&) {}