Ripple mode: basic implementation
authorColin Fletcher <colin.m.fletcher@googlemail.com>
Wed, 23 Oct 2013 20:50:01 +0000 (21:50 +0100)
committerColin Fletcher <colin.m.fletcher@googlemail.com>
Wed, 7 May 2014 18:46:24 +0000 (19:46 +0100)
Add a value for Ripple to EditMode enum.

Add Ripple edit mode to edit mode dropdown, by adding it to the
Editor::build_edit_mode_menu() helper function, and remove the old code that
added items to the (now unused) Editor::edit_mode_strings.

Add the regions that should be affected by the drag to RegionDrag::_views so
that the drag carries them along automatically.

Use a copy of the RegionList in Playlist::core_ripple(), since bad things
happen when iterating over regions and they get moved around in the list.

Handle rippling in removal of regions from playlist.

When dragging in ripple mode, exclude all regions that lie before the
original start position of the selected regions being dragged from
rippling: this is what Mixbus does.

Make editor dragging respect snap-to settings, by using the existing
compute_x_delta() function, which did almost the right thing. Move setting
of _last_frame_position out of that function so all ripple-dragged regions
can move.

Ripple when dragging from region list: even though Mixbus doesn't do this, it
seems like a good idea.

Prevent multi-track selection being dragged across tracks, by making
RegionMotionDrag::y_movement_allowed() virtual, and overriding it in
RegionRippleDrag to forbid dragging of selections containing regions on more
than one track to dofferent tracks in ripple mode.

Remember which TimeAxisView a ripple-mode drag that's allowed cross-track
drags started from, so that the effect of rippling regions after any region
that's dragged off that track can be undone.

14 files changed:
gtk2_ardour/editor.cc
gtk2_ardour/editor.h
gtk2_ardour/editor_actions.cc
gtk2_ardour/editor_audio_import.cc
gtk2_ardour/editor_drag.cc
gtk2_ardour/editor_drag.h
gtk2_ardour/editor_mouse.cc
gtk2_ardour/editor_ops.cc
gtk2_ardour/route_time_axis.cc
libs/ardour/ardour/playlist.h
libs/ardour/ardour/types.h
libs/ardour/enums.cc
libs/ardour/playlist.cc
libs/ardour/utils.cc

index f111aa18b1d1dd1bd7b0067e296aa4e10cb5c0b0..87a70102a8ba91af019c713aa9427e86a2a628d6 100644 (file)
@@ -2904,12 +2904,6 @@ Editor::setup_toolbar ()
 
        mouse_mode_box->pack_start (*mouse_mode_align, false, false);
 
-       edit_mode_strings.push_back (edit_mode_to_string (Slide));
-       if (!Profile->get_sae()) {
-               edit_mode_strings.push_back (edit_mode_to_string (Splice));
-       }
-       edit_mode_strings.push_back (edit_mode_to_string (Lock));
-
        edit_mode_selector.set_name ("mouse mode button");
        edit_mode_selector.set_size_request (65, -1);
        edit_mode_selector.add_elements (ArdourButton::Inset);
@@ -3126,6 +3120,7 @@ Editor::build_edit_mode_menu ()
 
        edit_mode_selector.AddMenuElem (MenuElem ( edit_mode_to_string(Slide), sigc::bind (sigc::mem_fun(*this, &Editor::edit_mode_selection_done), (EditMode) Slide)));
        edit_mode_selector.AddMenuElem (MenuElem ( edit_mode_to_string(Splice), sigc::bind (sigc::mem_fun(*this, &Editor::edit_mode_selection_done), (EditMode) Splice)));
+       edit_mode_selector.AddMenuElem (MenuElem ( edit_mode_to_string(Ripple), sigc::bind (sigc::mem_fun(*this, &Editor::edit_mode_selection_done), (EditMode) Ripple)));
        edit_mode_selector.AddMenuElem (MenuElem ( edit_mode_to_string(Lock), sigc::bind (sigc::mem_fun(*this, &Editor::edit_mode_selection_done), (EditMode)  Lock)));
 }
 
@@ -3437,10 +3432,11 @@ Editor::cycle_edit_mode ()
                if (Profile->get_sae()) {
                        Config->set_edit_mode (Lock);
                } else {
-                       Config->set_edit_mode (Splice);
+                       Config->set_edit_mode (Ripple);
                }
                break;
        case Splice:
+       case Ripple:
                Config->set_edit_mode (Lock);
                break;
        case Lock:
index 1398936979151d3a31207efad60c46b3e081e805..e0c642b4fce741f40fcf232797e173dfcb9df8bf 100644 (file)
@@ -1613,7 +1613,6 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
        void edit_mode_selection_done ( ARDOUR::EditMode m );
        void build_edit_mode_menu ();
        Gtk::VBox         edit_mode_box;
-       std::vector<std::string> edit_mode_strings;
 
        void set_edit_mode (ARDOUR::EditMode);
        void cycle_edit_mode ();
@@ -2092,6 +2091,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
        friend class RegionDrag;
        friend class RegionMoveDrag;
        friend class RegionSpliceDrag;
+       friend class RegionRippleDrag;
        friend class TrimDrag;
        friend class MeterMarkerDrag;
        friend class TempoMarkerDrag;
index d8889a9c81527e2fe0811200aed5fe29adae7fee..1072b497c2560d9f6c28115fdaaeb2c0f74ecb22 100644 (file)
@@ -476,6 +476,7 @@ Editor::register_actions ()
        ActionManager::register_action (editor_actions, "cycle-edit-point-with-marker", _("Change Edit Point Including Marker"), sigc::bind (sigc::mem_fun (*this, &Editor::cycle_edit_point), true));
        if (!Profile->get_sae()) {
                ActionManager::register_action (editor_actions, "set-edit-splice", _("Splice"), sigc::bind (sigc::mem_fun (*this, &Editor::set_edit_mode), Splice));
+               ActionManager::register_action (editor_actions, "set-edit-ripple", _("Ripple"), bind (mem_fun (*this, &Editor::set_edit_mode), Ripple));
        }
        ActionManager::register_action (editor_actions, "set-edit-slide", _("Slide"), sigc::bind (sigc::mem_fun (*this, &Editor::set_edit_mode), Slide));
        ActionManager::register_action (editor_actions, "set-edit-lock", _("Lock"), sigc::bind (sigc::mem_fun (*this, &Editor::set_edit_mode), Lock));
index da70df5c15a71c2e3ead386b9e43a85fb728f997..7a5f3982bf11bcfd274a5a42ebd175ed638ff100 100644 (file)
@@ -888,6 +888,9 @@ Editor::finish_bringing_in_material (boost::shared_ptr<Region> region, uint32_t
                boost::shared_ptr<Region> copy (RegionFactory::create (region, region->properties()));
                playlist->clear_changes ();
                playlist->add_region (copy, pos);
+               if (Config->get_edit_mode() == Ripple)
+                       playlist->ripple (pos, copy->length(), copy);
+
                _session->add_command (new StatefulDiffCommand (playlist));
                break;
        }
index 191d111b0d1aae03457349e3d9271683b34975b0..420fc52d3197deb1ce9d7a95648ce618e9df1952 100644 (file)
@@ -599,7 +599,6 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r
                        }
                }
 
-               _last_frame_position = *pending_region_position;
        }
 
        return dx;
@@ -667,6 +666,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        /* Work out the change in x */
        framepos_t pending_region_position;
        double const x_delta = compute_x_delta (event, &pending_region_position);
+       _last_frame_position = pending_region_position;
 
        /* Work out the change in y */
 
@@ -1197,7 +1197,7 @@ RegionMoveDrag::remove_region_from_playlist (
                playlist->clear_changes ();
        }
 
-       playlist->remove_region (region);
+       playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
 }
 
 
@@ -1257,11 +1257,15 @@ RegionMoveDrag::collect_new_region_view (RegionView* rv)
 void
 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
 {
+       std::cerr << "RegionMoveDrag::add_stateful_diff_commands_for_playlists ()" << std::endl;
        for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
+               std::cerr << "playlist: " << (*i)->name() << std::endl;
                StatefulDiffCommand* c = new StatefulDiffCommand (*i);
                if (!c->empty()) {
+                       std::cerr << "added StatefulDiffCommand!" << std::endl;
                        _editor->session()->add_command (c);
                } else {
+                       std::cerr << "no StatefulDiffcommand to add..." << std::endl;
                        delete c;
                }
        }
@@ -1368,6 +1372,12 @@ RegionInsertDrag::finished (GdkEvent *, bool)
        _editor->begin_reversible_command (Operations::insert_region);
        playlist->clear_changes ();
        playlist->add_region (_primary->region (), _last_frame_position);
+
+       // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
+       if (Config->get_edit_mode() == Ripple) {
+               playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
+       }
+
        _editor->session()->add_command (new StatefulDiffCommand (playlist));
        _editor->commit_reversible_command ();
 
@@ -1424,10 +1434,11 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
                dir = -1;
        }
 
-       RegionSelection copy (_editor->selection->regions);
-
-       RegionSelectionByPosition cmp;
-       copy.sort (cmp);
+       // RegionSelection copy (_editor->selection->regions);
+       // RegionSelectionByPosition cmp;
+       // copy.sort (cmp);
+       RegionSelection copy;
+       _editor->selection->regions.by_position(copy);
 
        framepos_t const pf = adjusted_current_frame (event);
 
@@ -1476,6 +1487,252 @@ RegionSpliceDrag::aborted (bool)
        /* XXX: TODO */
 }
 
+/***
+ * ripple mode...
+ */
+
+void
+RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
+{
+       RegionSelection to_ripple;
+       TrackViewList tracks;
+       tracks.push_back (tav); // rv.get_time_axis_view ());
+
+       _editor->get_regions_after (to_ripple, where, tracks);
+
+       for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
+               if (!exclude.contains (*i)) {
+                       // the selection has already been added to _views
+
+                       if (drag_in_progress) {
+                               (*i)->drag_start();
+                               ArdourCanvas::Group* rvg = (*i)->get_canvas_group();
+                               Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
+                               rvg->reparent (_editor->_region_motion_group);
+                               (*i)->fake_set_opaque (true);
+                               rvg->set_position (rv_canvas_offset);
+                       }
+                       _views.push_back (DraggingView (*i, this));
+               }
+       }
+}
+
+void
+RegionRippleDrag::remove_unselected_from_views(framecnt_t amount)
+{
+
+       std::cerr << "_views contains " << _views.size() << " views, including those on " << prev_tav->name() << std::endl;
+
+       for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
+               // we added all the regions after the selection 
+               std::cerr << "iterating _views..." << std::endl;
+               std::cerr << "found " << i->view->region()->name() << " in _views..." << std::endl;
+
+               std::list<DraggingView>::iterator to_erase = i++;
+               if (!_editor->selection->regions.contains (to_erase->view)) {
+                       std::cerr << "removing " << to_erase->view->region()->name() << " from _views..." << std::endl;
+                       // restore the non-selected regions to their original playlist & positions,
+                       // and then ripple them back by the length of the regions that were dragged away
+                       // do the same things as RegionMotionDrag::aborted
+
+                       if (_item) {
+                               _item->ungrab ();
+                       }
+
+                       RegionView *rv = to_erase->view;
+
+#if 0
+                       // this is how RegionMotionDrag::aborted() does it...
+                       TimeAxisView* tv = &(rv->get_time_axis_view ());
+                       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
+                       assert (rtv);
+                       assert (rtv == prev_tav);
+                       rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
+#else  
+                       // this should be equivalent...
+                       rv->get_canvas_group()->reparent(prev_tav->view()->canvas_item());
+#endif
+                       rv->get_canvas_group()->set_y_position (0);
+                       rv->drag_end ();
+                       rv->fake_set_opaque (false);
+                       rv->move(-amount, 0);   // XXX second parameter is y delta - do we need to do something?
+                       // rv->set_height (rtv->view()->child_height ());
+
+                       _views.erase (to_erase);
+                       if (i == _views.end()) {
+                               std::cerr << "reached end of _views iterator in loop!" << std::endl;
+                               // break;
+                       }
+               }
+       }
+}
+
+RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
+       : RegionMoveDrag (e, i, p, v, false, false)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
+       // compute length of selection
+       RegionSelection selected_regions = _editor->selection->regions;
+       selection_length = selected_regions.end_frame() - selected_regions.start();
+
+       // we'll only allow dragging to another track in ripple mode if all the regions
+       // being dragged start off on the same track
+       allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
+       prev_tav = NULL;
+       prev_amount = 0;
+       exclude = new RegionList;
+       for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
+               exclude->push_back((*i)->region());
+       }
+
+       // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
+       RegionSelection copy;
+       selected_regions.by_position(copy); // get selected regions sorted by position into copy
+
+       std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
+       std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
+
+       for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
+               // find ripple start point on each applicable playlist
+               RegionView *first_selected_on_this_track = NULL;
+               for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
+                       if ((*i)->region()->playlist() == (*pi)) {
+                               // region is on this playlist - it's the first, because they're sorted
+                               first_selected_on_this_track = *i;
+                               break;
+                       }
+               }
+               assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
+               add_all_after_to_views (
+                               &first_selected_on_this_track->get_time_axis_view(),
+                               first_selected_on_this_track->region()->position() + first_selected_on_this_track->region()->length(),
+                               selected_regions, false);
+       }
+
+       if (allow_moves_across_tracks) {
+               orig_tav = &(*selected_regions.begin())->get_time_axis_view();
+       } else {
+               orig_tav = NULL;
+       }
+
+}
+
+void
+RegionRippleDrag::motion (GdkEvent* event, bool first_move)
+{
+       /* Which trackview is this ? */
+
+       pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
+
+       /* The region motion is only processed if the pointer is over
+          an audio track.
+        */
+
+       if (!tv || !tv->is_track()) {
+               /* To make sure we hide the verbose canvas cursor when the mouse is
+                  not held over an audiotrack.
+                */
+               _editor->verbose_cursor()->hide ();
+               return;
+       }
+
+       framepos_t where = adjusted_current_frame (event);
+       assert (where >= 0);
+       framepos_t after;
+       double delta = compute_x_delta (event, &after);
+
+       framecnt_t amount = _editor->pixel_to_sample (delta);
+
+       if (allow_moves_across_tracks) {
+               // all the originally selected regions were on the same track
+
+               framecnt_t adjust = 0;
+               if (prev_tav && tv != prev_tav) {
+                       // dragged onto a different track 
+                       // remove the unselected regions from _views, restore them to their original positions
+                       // and add the regions after the drop point on the new playlist to _views instead.
+                       // undo the effect of rippling the previous playlist, and include the effect of removing
+                       // the dragged region(s) from this track
+
+                       remove_unselected_from_views (prev_amount);
+                       // ripple previous playlist according to the regions that have been removed onto the new playlist
+                       prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
+                       prev_amount = 0;
+
+                       // move just the selected regions
+                       std::cerr << "calling RegionMoveDrag::motion() for single-track selection dragged across tracks, _views.size() now " << _views.size() << std::endl;
+                       RegionMoveDrag::motion(event, first_move); 
+                       std::cerr << "RegionRippleDrag::motion() done!" << std::endl;
+
+                       // ensure that the ripple operation on the new playlist inserts selection_length time 
+                       adjust = selection_length;
+                       // ripple the new current playlist
+                       tv->playlist()->ripple (where, amount+adjust, exclude);
+
+                       // add regions after point where drag entered this track to subsequent ripples
+                       add_all_after_to_views (tv, where, _editor->selection->regions, true);
+                       std::cerr << "added regions on new track " << tv->name() << ", _views now contains " << _views.size() << " views" << std::endl;
+
+               } else {
+                       // motion on same track
+                       // std::cerr << "calling RegionMoveDrag::motion() for single-track selection dragged within track..." << std::endl;
+                       RegionMoveDrag::motion(event, first_move); 
+                       // std::cerr << "RegionRippleDrag::motion() done!" << std::endl;
+
+               }
+               prev_tav = tv;
+
+               // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
+               prev_position = where;
+               if (!_x_constrained) {
+                       prev_amount += amount;
+               }
+       } else {
+               // selection encompasses multiple tracks - just drag
+               // cross-track drags are forbidden
+               std::cerr << "calling RegionMoveDrag::motion() for multiple-track selection..." << std::endl;
+               RegionMoveDrag::motion(event, first_move); 
+               std::cerr << "RegionRippleDrag::motion() done!" << std::endl;
+
+       }
+
+       _last_frame_position = after;
+}
+
+void
+RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
+{
+       if (!movement_occurred) {
+               return;
+       }
+
+       _editor->begin_reversible_command(_("Ripple drag"));
+       
+       // if regions were dragged across tracks, we've rippled any later
+       // regions on the track the regions were dragged off, so we need
+       // to add the original track to the undo record
+       if (orig_tav) {
+               vector<Command*> cmds;
+               orig_tav->playlist()->rdiff (cmds);
+               _editor->session()->add_commands (cmds);
+       }
+
+       // other modified playlists are added to undo by RegionMoveDrag::finished()
+       RegionMoveDrag::finished (event, movement_occurred);
+       _editor->commit_reversible_command();
+
+}
+
+void
+RegionRippleDrag::aborted (bool movement_occurred)
+{
+       /* XXX: TODO */
+       RegionMoveDrag::aborted (movement_occurred);
+       _views.clear ();
+}
+
+
 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
        : Drag (e, i),
          _view (dynamic_cast<MidiTimeAxisView*> (v))
index 0bcfed99799a11d33f8d8517a1da7a71bc8554ed..4b7114c67ca027b1d2f15b6fcb52f08767558628 100644 (file)
@@ -313,7 +313,7 @@ public:
 protected:
 
        double compute_x_delta (GdkEvent const *, ARDOUR::framepos_t *);
-       bool y_movement_allowed (int, double) const;
+       virtual bool y_movement_allowed (int, double) const;
 
        bool _brushing;
        ARDOUR::framepos_t _last_frame_position; ///< last position of the thing being dragged
@@ -346,9 +346,11 @@ public:
 
        void setup_pointer_frame_offset ();
 
-private:
+protected:
        typedef std::set<boost::shared_ptr<ARDOUR::Playlist> > PlaylistSet;
+       void add_stateful_diff_commands_for_playlists (PlaylistSet const &);
 
+private:
        void finished_no_copy (
                bool const,
                bool const,
@@ -375,7 +377,6 @@ private:
                PlaylistSet& modified_playlists
                );
 
-       void add_stateful_diff_commands_for_playlists (PlaylistSet const &);
 
        void collect_new_region_view (RegionView *);
 
@@ -408,6 +409,42 @@ public:
        void aborted (bool);
 };
 
+/** Region drag in ripple mode */
+
+class RegionRippleDrag : public RegionMoveDrag
+{
+public:
+       RegionRippleDrag (Editor *, ArdourCanvas::Item *, RegionView *, std::list<RegionView*> const &);
+       ~RegionRippleDrag () { delete exclude; }
+
+       void motion (GdkEvent *, bool);
+       void finished (GdkEvent *, bool);
+       void aborted (bool);
+protected:
+       bool y_movement_allowed (int delta_track, double delta_layer) const {
+               std::cerr << "RegionRippleDrag::y_movement_allowed (" << delta_track << ", " << delta_layer << ")..." << std::endl;
+               if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer)) {
+                       if (delta_track) {
+                               return allow_moves_across_tracks;
+                       } else {
+                               return true;
+                       }
+               }
+               return false;
+       }
+private:
+       TimeAxisView *prev_tav;         // where regions were most recently dragged from
+       TimeAxisView *orig_tav;         // where drag started
+       framecnt_t prev_amount;
+       framepos_t prev_position;
+       framecnt_t selection_length;
+       bool allow_moves_across_tracks; // only if all selected regions are on one track
+       ARDOUR::RegionList *exclude;
+       void add_all_after_to_views (TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress);
+       void remove_unselected_from_views (framecnt_t amount);
+
+};
+
 /** Drags to create regions */
 class RegionCreateDrag : public Drag
 {
index 928801d0b213cda746af011ff2c70dca01ac73e4..8e5a357ac4fa831211ec405702c6b958d4e14b2e 100644 (file)
@@ -2687,10 +2687,16 @@ Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView* region
 
        _region_motion_group->raise_to_top ();
 
-       if (Config->get_edit_mode() == Splice) {
-               _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
-       } else {
-               _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
+       switch (Config->get_edit_mode()) {
+               case Splice:
+                       _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
+                       break;
+               case Ripple:
+                       _drags->add (new RegionRippleDrag (this, item, region_view, selection->regions.by_layer()));
+                       break;
+               default:
+                       _drags->add (new RegionMoveDrag (this, item, region_view, selection->regions.by_layer(), false, false));
+                       break;
        }
 }
 
@@ -2717,7 +2723,7 @@ Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent*, RegionView*
                return;
        }
 
-       if (Config->get_edit_mode() == Splice) {
+       if (Config->get_edit_mode() == Splice || Config->get_edit_mode() == Ripple) {
                return;
        }
 
index 08d6297faae3162c388f7a76b9cf411307d224b1..30ca540535e30486538e7b4fab72ba2bed958c73 100644 (file)
@@ -2119,6 +2119,9 @@ Editor::insert_region_list_drag (boost::shared_ptr<Region> region, int x, int y)
        begin_reversible_command (_("insert dragged region"));
        playlist->clear_changes ();
        playlist->add_region (RegionFactory::create (region, true), where, 1.0);
+       if (Config->get_edit_mode() == Ripple)
+               playlist->ripple (where, region->length(), boost::shared_ptr<Region>());
+
        _session->add_command(new StatefulDiffCommand (playlist));
        commit_reversible_command ();
 }
@@ -2192,6 +2195,9 @@ Editor::insert_region_list_selection (float times)
        begin_reversible_command (_("insert region"));
        playlist->clear_changes ();
        playlist->add_region ((RegionFactory::create (region, true)), get_preferred_edit_position(), times);
+       if (Config->get_edit_mode() == Ripple)
+               playlist->ripple (get_preferred_edit_position(), region->length() * times, boost::shared_ptr<Region>());
+
        _session->add_command(new StatefulDiffCommand (playlist));
        commit_reversible_command ();
 }
@@ -4054,10 +4060,11 @@ Editor::remove_clicked_region ()
 
        boost::shared_ptr<Playlist> playlist = clicked_routeview->playlist();
 
-       begin_reversible_command (_("remove region"));
        playlist->clear_changes ();
        playlist->clear_owned_changes ();
        playlist->remove_region (clicked_regionview->region());
+       if (Config->get_edit_mode() == Ripple)
+               playlist->ripple (clicked_regionview->region()->position(), -clicked_regionview->region()->length(), boost::shared_ptr<Region>());
 
        /* We might have removed regions, which alters other regions' layering_index,
           so we need to do a recursive diff here.
@@ -4120,6 +4127,9 @@ Editor::remove_selected_regions ()
                playlist->clear_owned_changes ();
                playlist->freeze ();
                playlist->remove_region (*rl);
+               if (Config->get_edit_mode() == Ripple)
+                       playlist->ripple ((*rl)->position(), -(*rl)->length(), boost::shared_ptr<Region>());
+
        }
 
        vector<boost::shared_ptr<Playlist> >::iterator pl;
@@ -4249,12 +4259,16 @@ Editor::cut_copy_regions (CutCopyOp op, RegionSelection& rs)
                switch (op) {
                case Delete:
                        pl->remove_region (r);
+                       if (Config->get_edit_mode() == Ripple)
+                               pl->ripple (r->position(), -r->length(), boost::shared_ptr<Region>());
                        break;
                        
                case Cut:
                        _xx = RegionFactory::create (r);
                        npl->add_region (_xx, r->position() - first_position);
                        pl->remove_region (r);
+                       if (Config->get_edit_mode() == Ripple)
+                               pl->ripple (r->position(), -r->length(), boost::shared_ptr<Region>());
                        break;
 
                case Copy:
@@ -4263,7 +4277,9 @@ Editor::cut_copy_regions (CutCopyOp op, RegionSelection& rs)
                        break;
 
                case Clear:
-                       pl->remove_region (r);  
+                       pl->remove_region (r);
+                       if (Config->get_edit_mode() == Ripple)
+                               pl->ripple (r->position(), -r->length(), boost::shared_ptr<Region>());
                        break;
                }
 
index 8eb4f585326fd735d118e0f220af52ee27c06018..fd8f7f5995f5b61428ee226d3775e9f31eac1687 100644 (file)
@@ -1424,6 +1424,11 @@ RouteTimeAxisView::paste (framepos_t pos, float times, Selection& selection, siz
        }
 
         pl->clear_changes ();
+       if (Config->get_edit_mode() == Ripple) {
+               std::pair<framepos_t, framepos_t> extent = (*p)->get_extent();
+               framecnt_t amount = extent.second - extent.first;
+               pl->ripple(pos, amount * times, boost::shared_ptr<Region>());
+       }
        pl->paste (*p, pos, times);
        _session->add_command (new StatefulDiffCommand (pl));
 
index 5629a0462968cd913697b4e0b10e8bb1ae43c28c..ababa600631d19f14e324b1e892d5dd8acb4ffa1 100644 (file)
@@ -144,6 +144,14 @@ public:
        void uncombine (boost::shared_ptr<Region>);
 
        void shuffle (boost::shared_ptr<Region>, int dir);
+       void ripple (framepos_t at, framecnt_t distance, RegionList *exclude);
+       void ripple (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude) {
+                RegionList el;
+                if (exclude)
+                        el.push_back (exclude);
+                ripple (at, distance, &el);
+       }
+
        void update_after_tempo_map_change ();
 
        boost::shared_ptr<Playlist> cut  (std::list<AudioRange>&, bool result_is_hidden = true);
@@ -283,6 +291,7 @@ public:
        bool             first_set_state;
        bool            _hidden;
        bool            _splicing;
+       bool            _rippling;
        bool            _shuffling;
        bool            _nudging;
        uint32_t        _refcnt;
@@ -337,6 +346,11 @@ public:
        void splice_locked (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude);
        void splice_unlocked (framepos_t at, framecnt_t distance, boost::shared_ptr<Region> exclude);
 
+       void core_ripple (framepos_t at, framecnt_t distance, RegionList *exclude);
+       void ripple_locked (framepos_t at, framecnt_t distance, RegionList *exclude);
+       void ripple_unlocked (framepos_t at, framecnt_t distance, RegionList *exclude);
+
+
        virtual void remove_dependents (boost::shared_ptr<Region> /*region*/) {}
 
        virtual XMLNode& state (bool);
index 1b9c3326c00f64b724ddc8e810e1135672adeb5d..92a8c0da5be97d4e2c35d060551b2ec92c95b052 100644 (file)
@@ -341,6 +341,7 @@ namespace ARDOUR {
        enum EditMode {
                Slide,
                Splice,
+               Ripple,
                Lock
        };
 
index 948025cc2b7e80d64a7045faccd5973013e4c23f..3f5ce75eb3a5ea06f32ca2e0cc9deb0fa6bd1bd3 100644 (file)
@@ -237,6 +237,7 @@ setup_enum_writer ()
 
        REGISTER_ENUM (Slide);
        REGISTER_ENUM (Splice);
+       REGISTER_ENUM (Ripple); // XXX do the old enum values have to stay in order?
        REGISTER_ENUM (Lock);
        REGISTER (_EditMode);
 
index d939ba61b055ad1a72a40c5eb6b7250f0f522aaa..11ca20972e7fe7c2ef889efb2f2fd72ac8f8a83d 100644 (file)
@@ -172,6 +172,7 @@ Playlist::Playlist (boost::shared_ptr<const Playlist> other, string namestr, boo
        in_set_state--;
 
        _splicing  = other->_splicing;
+       _rippling  = other->_rippling;
        _nudging   = other->_nudging;
        _edit_mode = other->_edit_mode;
 
@@ -302,6 +303,7 @@ Playlist::init (bool hide)
        _refcnt = 0;
        _hidden = hide;
        _splicing = false;
+       _rippling = false;
        _shuffling = false;
        _nudging = false;
        in_set_state = 0;
@@ -1399,7 +1401,7 @@ Playlist::flush_notifications (bool from_undo)
 
         if (_edit_mode == Splice) {
                 splice_locked (at, distance, exclude);
-        }
+        } 
  }
 
  void
@@ -1456,12 +1458,63 @@ Playlist::flush_notifications (bool from_undo)
         _splicing = false;
 
         notify_contents_changed ();
- }
+}
 
- void
- Playlist::region_bounds_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
- {
-        if (in_set_state || _splicing || _nudging || _shuffling) {
+void
+Playlist::ripple_locked (framepos_t at, framecnt_t distance, RegionList *exclude)
+{
+       {
+               RegionWriteLock rl (this);
+               core_ripple (at, distance, exclude);
+       }
+}
+
+void
+Playlist::ripple_unlocked (framepos_t at, framecnt_t distance, RegionList *exclude)
+{
+       core_ripple (at, distance, exclude);
+}
+
+void
+Playlist::core_ripple (framepos_t at, framecnt_t distance, RegionList *exclude)
+{
+       if (distance == 0) {
+               return;
+       }
+
+       _rippling = true;
+       RegionListProperty copy = regions;
+       for (RegionList::iterator i = copy.begin(); i != copy.end(); ++i) {
+               assert (i != copy.end());
+
+               if (exclude) {
+                       if (std::find(exclude->begin(), exclude->end(), (*i)) != exclude->end()) {
+                               continue;
+                       }
+               }
+
+               if ((*i)->position() >= at) {
+                       framepos_t new_pos = (*i)->position() + distance;
+                       framepos_t limit = max_framepos - (*i)->length();
+                       if (new_pos < 0) {
+                               new_pos = 0;
+                       } else if (new_pos >= limit ) {
+                               new_pos = limit;
+                       } 
+                               
+                       (*i)->set_position (new_pos);
+               }
+       }
+
+       _rippling = false;
+       notify_contents_changed ();
+}
+
+
+void
+Playlist::region_bounds_changed (const PropertyChange& what_changed, boost::shared_ptr<Region> region)
+{
+        if (in_set_state || _splicing || _rippling || _nudging || _shuffling) {
                 return;
         }
 
@@ -2694,6 +2747,12 @@ Playlist::region_is_shuffle_constrained (boost::shared_ptr<Region>)
        return false;
 }
 
+void
+Playlist::ripple (framepos_t at, framecnt_t distance, RegionList *exclude)
+{
+       ripple_locked (at, distance, exclude);
+}
+
 void
 Playlist::update_after_tempo_map_change ()
 {
index d1d2372977c831cc4168240692347bdabc38bd79..715c0d67dc26f7dfde72420602f2bc0504558c24 100644 (file)
@@ -396,6 +396,8 @@ string_to_edit_mode (string str)
                return Splice;
        } else if (str == _("Slide")) {
                return Slide;
+       } else if (str == _("Ripple")) {
+               return Ripple;
        } else if (str == _("Lock")) {
                return Lock;
        }
@@ -414,6 +416,9 @@ edit_mode_to_string (EditMode mode)
        case Lock:
                return _("Lock");
 
+       case Ripple:
+               return _("Ripple");
+
        default:
        case Splice:
                return _("Splice");