use new method in MidiPatchManager to use MIDNAM data when setting a MidiTimeAxisView
[ardour.git] / gtk2_ardour / editor_ops.cc
index 7173e267e65425d2c89c42df53689ccb7a5845a1..195d4d16436b6bbb41aa9e926eafbe0dcbd9abfc 100644 (file)
@@ -1,21 +1,32 @@
 /*
-    Copyright (C) 2000-2004 Paul Davis
-
-    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.
-
-*/
+ * Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
+ * Copyright (C) 2005-2009 Sampo Savolainen <v2@iki.fi>
+ * Copyright (C) 2005-2018 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2005 Karsten Wiese <fzuuzf@googlemail.com>
+ * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2007-2017 Tim Mayberry <mojofunk@gmail.com>
+ * Copyright (C) 2013-2016 Colin Fletcher <colin.m.fletcher@googlemail.com>
+ * Copyright (C) 2013-2017 John Emmas <john@creativepost.co.uk>
+ * Copyright (C) 2013-2017 Nick Mainsbridge <mainsbridge@gmail.com>
+ * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2014-2019 Ben Loftis <ben@harrisonconsoles.com>
+ * Copyright (C) 2015 AndrĂ© Nusser <andre.nusser@googlemail.com>
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
 
 /* Note: public Editor methods are documented in public_editor.h */
 
 #include "ardour/legatize.h"
 #include "ardour/region_factory.h"
 #include "ardour/reverse.h"
+#include "ardour/selection.h"
 #include "ardour/session.h"
 #include "ardour/session_playlists.h"
+#include "ardour/source.h"
 #include "ardour/strip_silence.h"
 #include "ardour/transient_detector.h"
+#include "ardour/transport_master_manager.h"
 #include "ardour/transpose.h"
 #include "ardour/vca_manager.h"
 
 #include "canvas/canvas.h"
 
 #include "actions.h"
+#include "ardour_message.h"
+#include "ardour_ui.h"
 #include "audio_region_view.h"
 #include "audio_streamview.h"
 #include "audio_time_axis.h"
@@ -80,6 +96,7 @@
 #include "editor_cursors.h"
 #include "editor_drag.h"
 #include "editor_regions.h"
+#include "editor_sources.h"
 #include "editor_routes.h"
 #include "gui_thread.h"
 #include "insert_remove_time_dialog.h"
 #include "transpose_dialog.h"
 #include "transform_dialog.h"
 #include "ui_config.h"
+#include "utils.h"
 #include "vca_time_axis.h"
 
 #include "pbd/i18n.h"
@@ -137,6 +155,7 @@ Editor::undo (uint32_t n)
        if (_drags->active ()) {
                _drags->abort ();
        }
+       paste_count = 0;
 
        if (_session) {
                _session->undo (n);
@@ -161,6 +180,7 @@ Editor::redo (uint32_t n)
        if (_drags->active ()) {
                _drags->abort ();
        }
+       paste_count = 0;
 
        if (_session) {
        _session->redo (n);
@@ -173,13 +193,10 @@ Editor::redo (uint32_t n)
 }
 
 void
-Editor::split_regions_at (MusicSample where, RegionSelection& regions, bool snap_sample)
+Editor::split_regions_at (MusicSample where, RegionSelection& regions)
 {
        bool frozen = false;
 
-       RegionSelection pre_selected_regions = selection->regions;
-       bool working_on_selection = !pre_selected_regions.empty();
-
        list<boost::shared_ptr<Playlist> > used_playlists;
        list<RouteTimeAxisView*> used_trackviews;
 
@@ -189,25 +206,11 @@ Editor::split_regions_at (MusicSample where, RegionSelection& regions, bool snap
 
        begin_reversible_command (_("split"));
 
-       // if splitting a single region, and snap-to is using
-       // region boundaries, don't pay attention to them
 
        if (regions.size() == 1) {
-//             switch (_snap_type) {   //ToDo !!!
-//             case SnapToRegionStart:
-//             case SnapToRegionSync:
-//             case SnapToRegionEnd:
-//                     break;
-//             default:
-//                     if (snap_sample) {
-                               snap_to (where);
-//                     }
-//             }
+               /* TODO:  if splitting a single region, and snap-to is using
+                region boundaries, mabye we shouldn't pay attention to them? */
        } else {
-               if (snap_sample) {
-                       snap_to (where);
-               }
-
                frozen = true;
                EditorFreeze(); /* Emit Signal */
        }
@@ -281,22 +284,23 @@ Editor::split_regions_at (MusicSample where, RegionSelection& regions, bool snap
                EditorThaw(); /* Emit Signal */
        }
 
-       if (working_on_selection) {
-               // IFF we were working on selected regions, try to reinstate the other region selections that existed before the freeze/thaw.
-
-               RegionSelectionAfterSplit rsas = Config->get_region_selection_after_split();
-               /* There are three classes of regions that we might want selected after
-                  splitting selected regions:
-                   - regions selected before the split operation, and unaffected by it
-                   - newly-created regions before the split
-                   - newly-created regions after the split
-                */
-
-               if (rsas & Existing) {
-                       // region selections that existed before the split.
-                       selection->add (pre_selected_regions);
-               }
+       RegionSelectionAfterSplit rsas = Config->get_region_selection_after_split();
 
+       //if the user has "Clear Selection" as their post-split behavior, then clear the selection
+       if (!latest_regionviews.empty() && (rsas == None)) {
+               selection->clear_objects();
+               selection->clear_time();
+               //but leave track selection intact
+       }
+       
+       //if the user doesn't want to preserve the "Existing" selection, then clear the selection
+       if (!(rsas & Existing)) {
+               selection->clear_objects();
+               selection->clear_time();
+       }
+       
+       //if the user wants newly-created regions to be selected, then select them:
+       if (mouse_mode == MouseObject) {
                for (RegionSelection::iterator ri = latest_regionviews.begin(); ri != latest_regionviews.end(); ri++) {
                        if ((*ri)->region()->position() < where.sample) {
                                // new regions created before the split
@@ -453,7 +457,7 @@ Editor::nudge_forward (bool next, bool force_playhead)
                                                loc->set_end (max_samplepos, false, true, divisions);
                                        }
                                        if (loc->is_session_range()) {
-                                               _session->set_end_is_free (false);
+                                               _session->set_session_range_is_free (false);
                                        }
                                }
                                if (!in_command) {
@@ -547,7 +551,7 @@ Editor::nudge_backward (bool next, bool force_playhead)
                                                loc->set_end (loc->length(), false, true, get_grid_music_divisions(0));
                                        }
                                        if (loc->is_session_range()) {
-                                               _session->set_end_is_free (false);
+                                               _session->set_session_range_is_free (false);
                                        }
                                }
                                if (!in_command) {
@@ -708,12 +712,12 @@ void
 Editor::build_region_boundary_cache ()
 {
 
-       //ToDo:  maybe set a timer so we don't recalutate when lots of changes are coming in 
-       //ToDo:  maybe somehow defer this until session is fully loaded.
-       
-       if ( !_region_boundary_cache_dirty )
+       /* TODO:  maybe set a timer so we don't recalutate when lots of changes are coming in */
+       /* TODO:  maybe somehow defer this until session is fully loaded.  */
+
+       if (!_region_boundary_cache_dirty)
                return;
-       
+
        samplepos_t pos = 0;
        vector<RegionPoint> interesting_points;
        boost::shared_ptr<Region> r;
@@ -727,30 +731,30 @@ Editor::build_region_boundary_cache ()
        }
 
        bool maybe_first_sample = false;
-               
-       if ( UIConfiguration::instance().get_snap_to_region_start() ) {
+
+       if (UIConfiguration::instance().get_snap_to_region_start()) {
                interesting_points.push_back (Start);
                maybe_first_sample = true;
        }
-       
-       if ( UIConfiguration::instance().get_snap_to_region_end() ) {
+
+       if (UIConfiguration::instance().get_snap_to_region_end()) {
                interesting_points.push_back (End);
        }
-       
-       if ( UIConfiguration::instance().get_snap_to_region_sync() ) {
+
+       if (UIConfiguration::instance().get_snap_to_region_sync()) {
                interesting_points.push_back (SyncPoint);
        }
-       
+
+       /* if no snap selections are set, boundary cache should be left empty */
+       if ( interesting_points.empty() ) {
+               _region_boundary_cache_dirty = false;
+               return;
+       }
+
        TimeAxisView *ontrack = 0;
        TrackViewList tlist;
 
-       //in the past, we used the track selection to limit snap.  I think this is not desired.
-       //or if it is,  it needs to be updated every time the track selection changes (so the snapped-cursor can show it)
-//     if (!selection->tracks.empty()) {
-//             tlist = selection->tracks.filter_to_unique_playlists ();
-//     } else {
-               tlist = track_views.filter_to_unique_playlists ();
-//     }
+       tlist = track_views.filter_to_unique_playlists ();
 
        if (maybe_first_sample) {
                TrackViewList::const_iterator i;
@@ -763,10 +767,18 @@ Editor::build_region_boundary_cache ()
                }
        }
 
-       while (pos < _session->current_end_sample() && !at_end) {
+       //allow regions to snap to the video start (if any) as if it were a "region"
+       if (ARDOUR_UI::instance()->video_timeline) {
+               region_boundary_cache.push_back (ARDOUR_UI::instance()->video_timeline->get_video_start_offset());
+       }
+
+       std::pair<samplepos_t, samplepos_t> ext = session_gui_extents (false);
+       samplepos_t session_end = ext.second;
+
+       while (pos < session_end && !at_end) {
 
                samplepos_t rpos;
-               samplepos_t lpos = max_samplepos;
+               samplepos_t lpos = session_end;
 
                for (vector<RegionPoint>::iterator p = interesting_points.begin(); p != interesting_points.end(); ++p) {
 
@@ -822,7 +834,7 @@ Editor::build_region_boundary_cache ()
        /* finally sort to be sure that the order is correct */
 
        sort (region_boundary_cache.begin(), region_boundary_cache.end());
-       
+
        _region_boundary_cache_dirty = false;
 }
 
@@ -1791,7 +1803,13 @@ Editor::temporal_zoom (samplecnt_t fpp)
        new_page_size = (samplepos_t) floor (_visible_canvas_width * nfpp);
        half_page_size = new_page_size / 2;
 
-       switch (zoom_focus) {
+       Editing::ZoomFocus zf = zoom_focus;
+
+       if (zf == ZoomFocusEdit && _edit_point == EditAtMouse) {
+               zf = ZoomFocusMouse;
+       }
+
+       switch (zf) {
        case ZoomFocusLeft:
                leftmost_after_zoom = current_leftmost;
                break;
@@ -1861,9 +1879,7 @@ Editor::temporal_zoom (samplecnt_t fpp)
        case ZoomFocusEdit:
                /* try to keep the edit point in the same place */
                where = get_preferred_edit_position ();
-
-               if (where > 0) {
-
+               {
                        double l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where);
 
                        if (l < 0) {
@@ -1873,10 +1889,6 @@ Editor::temporal_zoom (samplecnt_t fpp)
                        } else {
                                leftmost_after_zoom = (samplepos_t) l;
                        }
-
-               } else {
-                       /* edit point not defined */
-                       return;
                }
                break;
 
@@ -1962,13 +1974,13 @@ void
 Editor::temporal_zoom_selection (Editing::ZoomAxis axes)
 {
        if (!selection) return;
-       
-       if ( selection->regions.empty() && selection->time.empty() ) {
+
+       if (selection->regions.empty() && selection->time.empty()) {
                if (axes == Horizontal || axes == Both) {
                        temporal_zoom_step(true);
                }
                if (axes == Vertical || axes == Both) {
-                       if ( !track_views.empty() ) {
+                       if (!track_views.empty()) {
 
                                TrackViewList tvl;
 
@@ -1977,7 +1989,7 @@ Editor::temporal_zoom_selection (Editing::ZoomAxis axes)
                                const double btm = top + _visible_canvas_height + 10;
 
                                for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) {
-                                       if ( (*iter)->covered_by_y_range (top, btm) ) {
+                                       if ((*iter)->covered_by_y_range (top, btm)) {
                                                tvl.push_back(*iter);
                                        }
                                }
@@ -2004,10 +2016,10 @@ Editor::temporal_zoom_selection (Editing::ZoomAxis axes)
        if (axes == Vertical || axes == Both) {
                fit_selection ();
        }
-       
+
        //normally, we don't do anything "automatic" to the user's selection.
        //but in this case, we will clear the selection after a zoom-to-selection.
-       selection->clear();  
+       selection->clear();
 }
 
 void
@@ -2155,7 +2167,7 @@ Editor::temporal_zoom_to_sample (bool coarser, samplepos_t sample)
 
 
 bool
-Editor::choose_new_marker_name(string &name) {
+Editor::choose_new_marker_name(string &name, bool is_range) {
 
        if (!UIConfiguration::instance().get_name_new_markers()) {
                /* don't prompt user for a new name */
@@ -2166,7 +2178,11 @@ Editor::choose_new_marker_name(string &name) {
 
        dialog.set_prompt (_("New Name:"));
 
-       dialog.set_title (_("New Location Marker"));
+       if (is_range) {
+               dialog.set_title(_("New Range"));
+       } else {
+               dialog.set_title (_("New Location Marker"));
+       }
 
        dialog.set_name ("MarkNameWindow");
        dialog.set_size_request (250, -1);
@@ -2207,6 +2223,9 @@ Editor::add_location_from_selection ()
        samplepos_t end = selection->time[clicked_selection].end;
 
        _session->locations()->next_available_name(rangename,"selection");
+       if (!choose_new_marker_name(rangename, true)) {
+               return;
+       }
        Location *location = new Location (*_session, start, end, rangename, Location::IsRangeMarker, get_grid_music_divisions(0));
 
        begin_reversible_command (_("add marker"));
@@ -2263,6 +2282,8 @@ Editor::set_session_start_from_playhead ()
 
                commit_reversible_command ();
        }
+
+       _session->set_session_range_is_free (false);
 }
 
 void
@@ -2288,7 +2309,7 @@ Editor::set_session_end_from_playhead ()
                commit_reversible_command ();
        }
 
-       _session->set_end_is_free (false);
+       _session->set_session_range_is_free (false);
 }
 
 
@@ -2536,7 +2557,7 @@ Editor::unhide_ranges ()
 /* INSERT/REPLACE */
 
 void
-Editor::insert_region_list_selection (float times)
+Editor::insert_source_list_selection (float times)
 {
        RouteTimeAxisView *tv = 0;
        boost::shared_ptr<Playlist> playlist;
@@ -2559,7 +2580,7 @@ Editor::insert_region_list_selection (float times)
                return;
        }
 
-       boost::shared_ptr<Region> region = _regions->get_single_selection ();
+       boost::shared_ptr<Region> region = _sources->get_single_selection ();
        if (region == 0) {
                return;
        }
@@ -2599,7 +2620,7 @@ Editor::transition_to_rolling (bool fwd)
        }
 
        if (_session->config.get_external_sync()) {
-               switch (Config->get_sync_source()) {
+               switch (TransportMasterManager::instance().current()->type()) {
                case Engine:
                        break;
                default:
@@ -3204,8 +3225,18 @@ Editor::separate_regions_between (const TimeSelection& ts)
                }
        }
 
-       if (in_command) {
-//             selection->set (new_selection);
+       if (in_command) {
+
+               RangeSelectionAfterSplit rsas = Config->get_range_selection_after_split();
+
+               //if our config preference says to clear the selection, clear the Range selection
+               if (rsas == ClearSel) {
+                       selection->clear_time();
+                       //but leave track selection intact
+               } else if (rsas == ForceSel) {
+                       //note: forcing the regions to be selected *might* force a tool-change to Object here
+                       selection->set(new_selection);  
+               }
 
                commit_reversible_command ();
        }
@@ -3961,7 +3992,7 @@ Editor::freeze_route ()
        }
 
        if (!clicked_routeview->track()->bounceable (clicked_routeview->track()->main_outs(), true)) {
-               MessageDialog d (
+               ArdourMessageDialog d (
                        _("This track/bus cannot be frozen because the signal adds or loses channels before reaching the outputs.\n"
                          "This is typically caused by plugins that generate stereo output from mono input or vice versa.")
                        );
@@ -3971,9 +4002,9 @@ Editor::freeze_route ()
        }
 
        if (clicked_routeview->track()->has_external_redirects()) {
-               MessageDialog d (string_compose (_("<b>%1</b>\n\nThis track has at least one send/insert/return as part of its signal flow.\n\n"
-                                                  "Freezing will only process the signal as far as the first send/insert/return."),
-                                                clicked_routeview->track()->name()), true, MESSAGE_INFO, BUTTONS_NONE, true);
+               ArdourMessageDialog d (string_compose (_("<b>%1</b>\n\nThis track has at least one send/insert/return as part of its signal flow.\n\n"
+                                                        "Freezing will only process the signal as far as the first send/insert/return."),
+                                                      clicked_routeview->track()->name()), true, MESSAGE_INFO, BUTTONS_NONE, true);
 
                d.add_button (_("Freeze anyway"), Gtk::RESPONSE_OK);
                d.add_button (_("Don't freeze"), Gtk::RESPONSE_CANCEL);
@@ -4022,7 +4053,7 @@ Editor::bounce_range_selection (bool replace, bool enable_processing)
                        RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
 
                        if (rtv && rtv->track() && replace && enable_processing && !rtv->track()->bounceable (rtv->track()->main_outs(), false)) {
-                               MessageDialog d (
+                               ArdourMessageDialog d (
                                        _("You can't perform this operation because the processing of the signal "
                                          "will cause one or more of the tracks to end up with a region with more channels than this track has inputs.\n\n"
                                          "You can do this without processing, which is a different operation.")
@@ -4441,12 +4472,15 @@ Editor::remove_clicked_region ()
        begin_reversible_command (_("remove region"));
 
        boost::shared_ptr<Playlist> playlist = clicked_routeview->playlist();
+       boost::shared_ptr<Region> region = clicked_regionview->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>());
+       playlist->remove_region (region);
+
+       if (Config->get_edit_mode() == Ripple) {
+               playlist->ripple (region->position(), - 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.
@@ -4460,6 +4494,32 @@ Editor::remove_clicked_region ()
 }
 
 
+void
+Editor::recover_regions (ARDOUR::RegionList regions)
+{
+#ifdef RECOVER_REGIONS_IS_WORKING
+       begin_reversible_command (_("recover regions"));
+
+       for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+               boost::shared_ptr<ARDOUR::Source> source = (*i)->source();
+
+               RouteList routes = _session->get_routelist();
+               for (RouteList::iterator it = routes.begin(); it != routes.end(); ++it) {
+                       boost::shared_ptr<ARDOUR::Track> track = boost::dynamic_pointer_cast<Track>(*it);
+                       if (track) {
+                               //ToDo
+                               if (source->captured_for() == track->) {
+                                       //_session->add_command(new StatefulDiffCommand (playlist));    
+                               }
+                       }
+               }
+       }
+
+       commit_reversible_command ();
+#endif
+}
+
+
 /** Remove the selected regions */
 void
 Editor::remove_selected_regions ()
@@ -4772,10 +4832,7 @@ Editor::paste_internal (samplepos_t position, float times, const int32_t sub_num
                DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("preferred edit position is %1\n", position));
        }
 
-       if (position == last_paste_pos) {
-               /* repeated paste in the same position */
-               ++paste_count;
-       } else {
+       if (position != last_paste_pos) {
                /* paste in new location, reset repeated paste state */
                paste_count = 0;
                last_paste_pos = position;
@@ -4864,6 +4921,8 @@ Editor::paste_internal (samplepos_t position, float times, const int32_t sub_num
                }
        }
 
+       ++paste_count;
+
        commit_reversible_command ();
 }
 
@@ -4882,17 +4941,40 @@ Editor::duplicate_some_regions (RegionSelection& regions, float times)
        }
 
        boost::shared_ptr<Playlist> playlist;
+       std::set<boost::shared_ptr<Playlist> > playlists; // list of unique playlists affected by duplication
        RegionSelection sel = regions; // clear (below) may  clear the argument list if its the current region selection
        RegionSelection foo;
 
        samplepos_t const start_sample = regions.start ();
        samplepos_t const end_sample = regions.end_sample ();
-       samplecnt_t const gap = end_sample - start_sample + 1;
+       samplecnt_t const span = end_sample - start_sample + 1;
 
        begin_reversible_command (Operations::duplicate_region);
 
        selection->clear_regions ();
 
+       /* ripple first so that we don't move the duplicates that will be added */
+
+       if (Config->get_edit_mode() == Ripple) {
+
+               /* convert RegionSelection into RegionList so that we can pass it to ripple and exclude the regions we will duplicate */
+
+               RegionList exclude;
+
+               for (RegionSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
+                       exclude.push_back ((*i)->region());
+                       playlist = (*i)->region()->playlist();
+                       if (playlists.insert (playlist).second) {
+                               /* successfully inserted into set, so it's the first time we've seen this playlist */
+                               playlist->clear_changes ();
+                       }
+               }
+
+               for (set<boost::shared_ptr<Playlist> >::iterator p = playlists.begin(); p != playlists.end(); ++p) {
+                       (*p)->ripple (start_sample, span * times, &exclude);
+               }
+       }
+
        for (RegionSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
 
                boost::shared_ptr<Region> r ((*i)->region());
@@ -4904,15 +4986,27 @@ Editor::duplicate_some_regions (RegionSelection& regions, float times)
 
                samplepos_t const position = end_sample + (r->first_sample() - start_sample + 1);
                playlist = (*i)->region()->playlist();
-               playlist->clear_changes ();
-               playlist->duplicate (r, position, gap, times);
-               _session->add_command(new StatefulDiffCommand (playlist));
+
+               if (Config->get_edit_mode() != Ripple) {
+                       if (playlists.insert (playlist).second) {
+                               playlist->clear_changes ();
+                       }
+               }
+
+               playlist->duplicate (r, position, span, times);
 
                c.disconnect ();
 
                foo.insert (foo.end(), latest_regionviews.begin(), latest_regionviews.end());
        }
 
+       for (set<boost::shared_ptr<Playlist> >::iterator p = playlists.begin(); p != playlists.end(); ++p) {
+               _session->add_command (new StatefulDiffCommand (*p));
+               vector<Command*> cmds;
+               (*p)->rdiff (cmds);
+               _session->add_commands (cmds);
+       }
+
        if (!foo.empty()) {
                selection->set (foo);
        }
@@ -5087,6 +5181,95 @@ Editor::remove_last_capture ()
        }
 }
 
+void
+Editor::tag_regions (RegionList regions)
+{
+       ArdourDialog d (_("Tag Last Capture"), true, false);
+       Entry entry;
+       Label label (_("Tag:"));
+       HBox hbox;
+
+       hbox.set_spacing (6);
+       hbox.pack_start (label, false, false);
+       hbox.pack_start (entry, true, true);
+
+       d.get_vbox()->set_border_width (12);
+       d.get_vbox()->pack_start (hbox, false, false);
+
+       d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+       d.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+       d.set_size_request (300, -1);
+
+       entry.set_text (_("Good"));
+       entry.select_region (0, -1);
+
+       entry.signal_activate().connect (sigc::bind (sigc::mem_fun (d, &Dialog::response), RESPONSE_OK));
+
+       d.show_all ();
+
+       entry.grab_focus();
+
+       int const ret = d.run();
+
+       d.hide ();
+
+       if (ret != RESPONSE_OK) {
+               return;
+       }
+
+       std::string tagstr = entry.get_text();
+       strip_whitespace_edges (tagstr);
+       
+       if (!tagstr.empty()) {
+               for (RegionList::iterator r = regions.begin(); r != regions.end(); r++) {
+                       (*r)->set_tags(tagstr);
+               }
+                       
+               _regions->redisplay ();
+       }
+}
+
+void
+Editor::tag_selected_region ()
+{
+       std::list<boost::shared_ptr<Region> > rlist;
+
+       RegionSelection rs = get_regions_from_selection_and_entered ();
+       for (RegionSelection::iterator r = rs.begin(); r != rs.end(); r++) {
+               rlist.push_back((*r)->region());
+       }
+
+       tag_regions(rlist);
+}
+
+void
+Editor::tag_last_capture ()
+{
+       if (!_session) {
+               return;
+       }
+
+       std::list<boost::shared_ptr<Region> > rlist;
+
+       std::list<boost::shared_ptr<Source> > srcs;
+       _session->get_last_capture_sources (srcs);
+       for (std::list<boost::shared_ptr<Source> >::iterator i = srcs.begin(); i != srcs.end(); ++i) {
+               boost::shared_ptr<ARDOUR::Source> source = (*i);
+               if (source) {
+
+                       set<boost::shared_ptr<Region> > regions;
+                       RegionFactory::get_regions_using_source (source, regions);
+                       for (set<boost::shared_ptr<Region> >::iterator r = regions.begin(); r != regions.end(); r++) {
+                               rlist.push_back(*r);
+                       }
+
+               }
+       }
+       
+       tag_regions(rlist);
+}
+
 void
 Editor::normalize_region ()
 {
@@ -5891,18 +6074,18 @@ Editor::toggle_record_enable ()
 }
 
 StripableList
-tracklist_to_stripables( TrackViewList list )
+tracklist_to_stripables (TrackViewList list)
 {
        StripableList ret;
-       
+
        for (TrackSelection::iterator i = list.begin(); i != list.end(); ++i) {
                RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> ((*i));
 
                if (rtv && rtv->is_track()) {
-                       ret.push_back( rtv->track() );
+                       ret.push_back (rtv->track());
                }
        }
-       
+
        return ret;
 }
 
@@ -5911,25 +6094,25 @@ Editor::play_solo_selection (bool restart)
 {
        //note: session::solo_selection takes care of invalidating the region playlist
 
-       if ( (!selection->tracks.empty()) && selection->time.length() > 0 ) {  //a range is selected; solo the tracks and roll
-       
+       if ((!selection->tracks.empty()) && selection->time.length() > 0) {  //a range is selected; solo the tracks and roll
+
                StripableList sl = tracklist_to_stripables (selection->tracks);
-               _session->solo_selection( sl, true );
+               _session->solo_selection (sl, true);
 
-               if ( restart ) {
+               if (restart) {
                        samplepos_t start = selection->time.start();
                        samplepos_t end = selection->time.end_sample();
                        _session->request_bounded_roll (start, end);
                }
-       } else if ( ! selection->tracks.empty() ) {  //no range is selected, but tracks are selected; solo the tracks and roll
+       } else if (! selection->tracks.empty()) {  //no range is selected, but tracks are selected; solo the tracks and roll
                StripableList sl = tracklist_to_stripables (selection->tracks);
-               _session->solo_selection( sl, true );
+               _session->solo_selection (sl, true);
                _session->request_cancel_play_range();
                transition_to_rolling (true);
-       
-       } else if ( ! selection->regions.empty() ) {  //solo any tracks with selected regions, and roll
-               StripableList sl = tracklist_to_stripables ( get_tracks_for_range_action() );
-               _session->solo_selection( sl, true );
+
+       } else if (! selection->regions.empty()) {  //solo any tracks with selected regions, and roll
+               StripableList sl = tracklist_to_stripables (get_tracks_for_range_action());
+               _session->solo_selection (sl, true);
                _session->request_cancel_play_range();
                transition_to_rolling (true);
        } else {
@@ -5982,7 +6165,9 @@ Editor::toggle_mute ()
                        first = false;
                }
 
-               cl->push_back (stav->stripable()->mute_control());
+               boost::shared_ptr<MuteControl> mc = stav->stripable()->mute_control();
+               cl->push_back (mc);
+               mc->start_touch (_session->audible_sample ());
        }
 
        _session->set_controls (cl, new_state, Controllable::UseGroup);
@@ -6361,7 +6546,10 @@ Editor::set_playhead_cursor ()
 void
 Editor::split_region ()
 {
-       if (_drags->active ()) {
+       if (_dragging_playhead) {
+               /*continue*/
+       } else if (_drags->active ()) {
+               /*any other kind of drag, bail out so we avoid Undo snafu*/
                return;
        }
 
@@ -6372,9 +6560,33 @@ Editor::split_region ()
        }
 
        //if no range was selected, try to find some regions to split
-       if (current_mouse_mode() == MouseObject) {  //don't try this for Internal Edit, Stretch, Draw, etc.
+       if (current_mouse_mode() == MouseObject || current_mouse_mode() == MouseRange ) {  //don't try this for Internal Edit, Stretch, Draw, etc.
+
+               RegionSelection rs;
+
+               //new behavior:  the Split action will prioritize the entered_regionview rather than selected regions.
+               //this fixes the unexpected case where you point at a region, but
+               //  * nothing happens OR
+               //  * some other region (maybe off-screen) is split.
+               //NOTE:  if the entered_regionview is /part of the selection/ then we should operate on the selection as usual
+               if (_edit_point == EditAtMouse && entered_regionview && !entered_regionview->selected()) {
+                       rs.add (entered_regionview);
+               } else {
+                       rs = selection->regions;   //might be empty
+               }
+
+               if (rs.empty()) {
+                       TrackViewList tracks = selection->tracks;
+
+                       if (!tracks.empty()) {
+                               /* no region selected or entered, but some selected tracks:
+                                * act on all regions on the selected tracks at the edit point
+                                */
+                               samplepos_t const where = get_preferred_edit_position (Editing::EDIT_IGNORE_NONE, false, false);
+                               get_regions_at(rs, where, tracks);
+                       }
+               }
 
-               RegionSelection rs = get_regions_from_selection_and_edit_point ();
                const samplepos_t pos = get_preferred_edit_position();
                const int32_t division = get_grid_music_divisions (0);
                MusicSample where (pos, division);
@@ -6384,86 +6596,19 @@ Editor::split_region ()
                }
 
                split_regions_at (where, rs);
-
        }
 }
 
 void
 Editor::select_next_stripable (bool routes_only)
 {
-       if (selection->tracks.empty()) {
-               selection->set (track_views.front());
-               return;
-       }
-
-       TimeAxisView* current = selection->tracks.front();
-
-       bool valid;
-       do {
-               for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
-
-                       if (*i == current) {
-                               ++i;
-                               if (i != track_views.end()) {
-                                       current = (*i);
-                               } else {
-                                       current = (*(track_views.begin()));
-                                       //selection->set (*(track_views.begin()));
-                               }
-                               break;
-                       }
-               }
-
-               if (routes_only) {
-                       RouteUI* rui = dynamic_cast<RouteUI *>(current);
-                       valid = rui && rui->route()->active();
-               } else {
-                       valid = 0 != current->stripable ().get();
-               }
-
-       } while (current->hidden() || !valid);
-
-       selection->set (current);
-
-       ensure_time_axis_view_is_visible (*current, false);
+       _session->selection().select_next_stripable (false, routes_only);
 }
 
 void
 Editor::select_prev_stripable (bool routes_only)
 {
-       if (selection->tracks.empty()) {
-               selection->set (track_views.front());
-               return;
-       }
-
-       TimeAxisView* current = selection->tracks.front();
-
-       bool valid;
-       do {
-               for (TrackViewList::reverse_iterator i = track_views.rbegin(); i != track_views.rend(); ++i) {
-
-                       if (*i == current) {
-                               ++i;
-                               if (i != track_views.rend()) {
-                                       current = (*i);
-                               } else {
-                                       current = *(track_views.rbegin());
-                               }
-                               break;
-                       }
-               }
-               if (routes_only) {
-                       RouteUI* rui = dynamic_cast<RouteUI *>(current);
-                       valid = rui && rui->route()->active();
-               } else {
-                       valid = 0 != current->stripable ().get();
-               }
-
-       } while (current->hidden() || !valid);
-
-       selection->set (current);
-
-       ensure_time_axis_view_is_visible (*current, false);
+       _session->selection().select_prev_stripable (false, routes_only);
 }
 
 void
@@ -6548,7 +6693,7 @@ Editor::set_auto_punch_range ()
                        set_punch_range (tpl->start(), now, _("Auto Punch In/Out"));
                        _session->config.set_punch_out(true);
                }
-       } else  {
+       } else {
                if (_session->config.get_punch_out()) {
                        _session->config.set_punch_out(false);
                }
@@ -6594,7 +6739,7 @@ Editor::set_session_extents_from_selection ()
                commit_reversible_command ();
        }
 
-       _session->set_end_is_free (false);
+       _session->set_session_range_is_free (false);
 }
 
 void
@@ -6617,11 +6762,8 @@ Editor::set_punch_start_from_edit_point ()
                        start.sample = get_preferred_edit_position();
                }
 
-               //snap the selection start/end
-               snap_to(start);
-
                //if there's not already a sensible selection endpoint, go "forever"
-               if (start.sample > end ) {
+               if (start.sample > end) {
                        end = max_samplepos;
                }
 
@@ -6650,9 +6792,6 @@ Editor::set_punch_end_from_edit_point ()
                        end.sample = get_preferred_edit_position();
                }
 
-               //snap the selection start/end
-               snap_to (end);
-
                set_punch_range (start, end.sample, _("set punch end from EP"));
 
        }
@@ -6678,11 +6817,8 @@ Editor::set_loop_start_from_edit_point ()
                        start.sample = get_preferred_edit_position();
                }
 
-               //snap the selection start/end
-               snap_to (start);
-
                //if there's not already a sensible selection endpoint, go "forever"
-               if (start.sample > end ) {
+               if (start.sample > end) {
                        end = max_samplepos;
                }
 
@@ -6711,9 +6847,6 @@ Editor::set_loop_end_from_edit_point ()
                        end.sample = get_preferred_edit_position();
                }
 
-               //snap the selection start/end
-               snap_to(end);
-
                set_loop_range (start, end.sample, _("set loop end from EP"));
        }
 }
@@ -6916,10 +7049,10 @@ Editor::split_region_at_points (boost::shared_ptr<Region> r, AnalysisFeatureList
 
        if (positions.size() > 20 && can_ferret) {
                std::string msgstr = string_compose (_("You are about to split\n%1\ninto %2 pieces.\nThis could take a long time."), r->name(), positions.size() + 1);
-               MessageDialog msg (msgstr,
-                                  false,
-                                  Gtk::MESSAGE_INFO,
-                                  Gtk::BUTTONS_OK_CANCEL);
+               ArdourMessageDialog msg (msgstr,
+                                        false,
+                                        Gtk::MESSAGE_INFO,
+                                        Gtk::BUTTONS_OK_CANCEL);
 
                if (can_ferret) {
                        msg.add_button (_("Call for the Ferret!"), RESPONSE_APPLY);
@@ -6929,8 +7062,6 @@ Editor::split_region_at_points (boost::shared_ptr<Region> r, AnalysisFeatureList
                }
 
                msg.set_title (_("Excessive split?"));
-               msg.present ();
-
                int response = msg.run();
                msg.hide ();
 
@@ -7132,7 +7263,7 @@ Editor::snap_regions_to_grid ()
                (*r)->region()->clear_changes ();
 
                MusicSample start ((*r)->region()->first_sample (), 0);
-               snap_to (start, RoundNearest, SnapToGrid );
+               snap_to (start, RoundNearest, SnapToGrid_Unscaled, true);
                (*r)->region()->set_position (start.sample, start.division);
                _session->add_command(new StatefulDiffCommand ((*r)->region()));
        }
@@ -7337,12 +7468,28 @@ Editor::playhead_forward_to_grid ()
                return;
        }
 
-       MusicSample pos (playhead_cursor->current_sample (), 0);
+       MusicSample pos  (playhead_cursor->current_sample (), 0);
 
-       if (pos.sample < max_samplepos - 1) {
-               pos.sample += 2;
-               snap_to_internal (pos, RoundUpAlways, SnapToGrid, false, true);
-               _session->request_locate (pos.sample);
+       if ( _grid_type == GridTypeNone) {
+               if (pos.sample < max_samplepos - current_page_samples()*0.1) {
+                       pos.sample += current_page_samples()*0.1;
+                       _session->request_locate (pos.sample);
+               } else {
+                       _session->request_locate (0);
+               }
+       } else {
+
+               if (pos.sample < max_samplepos - 1) {
+                       pos.sample += 2;
+                       pos = snap_to_grid (pos, RoundUpAlways, SnapToGrid_Scaled);
+                       _session->request_locate (pos.sample);
+               }
+       }
+
+
+       /* keep PH visible in window */
+       if (pos.sample > (_leftmost_sample + current_page_samples() *0.9)) {
+               reset_x_origin (pos.sample - (current_page_samples()*0.9));
        }
 }
 
@@ -7356,10 +7503,34 @@ Editor::playhead_backward_to_grid ()
 
        MusicSample pos  (playhead_cursor->current_sample (), 0);
 
-       if (pos.sample > 2) {
-               pos.sample -= 2;
-               snap_to_internal (pos, RoundDownAlways, SnapToGrid, false, true);
-               _session->request_locate (pos.sample);
+       if ( _grid_type == GridTypeNone) {
+               if ( pos.sample > current_page_samples()*0.1 ) {
+                       pos.sample -= current_page_samples()*0.1;
+                       _session->request_locate (pos.sample);
+               } else {
+                       _session->request_locate (0);
+               }
+       } else {
+
+               if (pos.sample > 2) {
+                       pos.sample -= 2;
+                       pos = snap_to_grid (pos, RoundDownAlways, SnapToGrid_Scaled);
+               }
+
+               //handle the case where we are rolling, and we're less than one-half second past the mark, we want to go to the prior mark...
+               //also see:  jump_backward_to_mark
+               if (_session->transport_rolling()) {
+                       if ((playhead_cursor->current_sample() - pos.sample) < _session->sample_rate()/2) {
+                               pos = snap_to_grid (pos, RoundDownAlways, SnapToGrid_Scaled);
+                       }
+               }
+
+               _session->request_locate (pos.sample, _session->transport_rolling());
+       }
+
+       /* keep PH visible in window */
+       if (pos.sample < (_leftmost_sample + current_page_samples() *0.1)) {
+               reset_x_origin (pos.sample - (current_page_samples()*0.1));
        }
 }
 
@@ -7424,6 +7595,10 @@ Editor::_remove_tracks ()
                return;
        }
 
+       if (!ARDOUR_UI_UTILS::engine_is_running ()) {
+               return;
+       }
+
        vector<string> choices;
        string prompt;
        int ntracks = 0;
@@ -7460,19 +7635,17 @@ Editor::_remove_tracks ()
        }
 
        if (special_bus && !Config->get_allow_special_bus_removal()) {
-               MessageDialog msg (_("That would be bad news ...."),
-                                  false,
-                                  Gtk::MESSAGE_INFO,
-                                  Gtk::BUTTONS_OK);
-               msg.set_secondary_text (string_compose (_(
-                                                               "Removing the master or monitor bus is such a bad idea\n\
+               ArdourMessageDialog msg (_("That would be bad news ...."),
+                                        false,
+                                        Gtk::MESSAGE_INFO,
+                                        Gtk::BUTTONS_OK);
+               msg.set_secondary_text (string_compose (_("Removing the master or monitor bus is such a bad idea\n\
 that %1 is not going to allow it.\n\
 \n\
 If you really want to do this sort of thing\n\
 edit your ardour.rc file to set the\n\
 \"allow-special-bus-removal\" option to be \"yes\""), PROGRAM_NAME));
 
-               msg.present ();
                msg.run ();
                return;
        }
@@ -7549,7 +7722,7 @@ edit your ardour.rc file to set the\n\
                /* Route deletion calls Editor::timeaxisview_deleted() iteratively (for each deleted
                 * route). If the deleted route is currently displayed in the Editor-Mixer (highly
                 * likely because deletion requires selection) this will call
-                * Editor::set_selected_mixer_strip () which is expensive ( MixerStrip::set_route() ).
+                * Editor::set_selected_mixer_strip () which is expensive (MixerStrip::set_route()).
                 * It's likewise likely that the route that has just been displayed in the
                 * Editor-Mixer will be next in line for deletion.
                 *
@@ -7588,6 +7761,16 @@ void
 Editor::do_insert_time ()
 {
        if (selection->tracks.empty()) {
+               ArdourMessageDialog msg (_("You must first select some tracks to Insert Time."),
+                                  true, MESSAGE_INFO, BUTTONS_OK, true);
+               msg.run ();
+               return;
+       }
+
+       if (Config->get_edit_mode() == Lock) {
+               ArdourMessageDialog msg (_("You cannot insert time in Lock Edit mode."),
+                                        true, MESSAGE_INFO, BUTTONS_OK, true);
+               msg.run ();
                return;
        }
 
@@ -7643,7 +7826,7 @@ Editor::insert_time (
                if (all_playlists) {
                        RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*x);
                        if (rtav && rtav->track ()) {
-                               vector<boost::shared_ptr<Playlist> > all = _session->playlists->playlists_for_track (rtav->track ());
+                               vector<boost::shared_ptr<Playlist> > all = _session->playlists()->playlists_for_track (rtav->track ());
                                for (vector<boost::shared_ptr<Playlist> >::iterator p = all.begin(); p != all.end(); ++p) {
                                        pl.insert (*p);
                                }
@@ -7751,6 +7934,16 @@ void
 Editor::do_remove_time ()
 {
        if (selection->tracks.empty()) {
+               ArdourMessageDialog msg (_("You must first select some tracks to Remove Time."),
+                                        true, MESSAGE_INFO, BUTTONS_OK, true);
+               msg.run ();
+               return;
+       }
+
+       if (Config->get_edit_mode() == Lock) {
+               ArdourMessageDialog msg (_("You cannot remove time in Lock Edit mode."),
+                                        true, MESSAGE_INFO, BUTTONS_OK, true);
+               msg.run ();
                return;
        }
 
@@ -7978,7 +8171,8 @@ Editor::fit_tracks (TrackViewList & tracks)
        double first_y_pos = DBL_MAX;
 
        if (h < TimeAxisView::preset_height (HeightSmall)) {
-               MessageDialog msg (_("There are too many tracks to fit in the current window"));
+               ArdourMessageDialog msg (_("There are too many tracks to fit in the current window"));
+               msg.run ();
                /* too small to be displayed */
                return;
        }