Fix some set-but-not-used variable warnings from gcc 4.6
[ardour.git] / gtk2_ardour / editor_drag.cc
index c13781c42c3d24aadd7a89add79277664c26e4be..c889921db774b7ed5ac9f29edd9c1859d2f4e0c1 100644 (file)
 
 */
 
-#define __STDC_LIMIT_MACROS 1
+#ifdef WAF_BUILD
+#include "gtk2ardour-config.h"
+#endif
+
 #include <stdint.h>
+
 #include "pbd/memento_command.h"
 #include "pbd/basename.h"
 #include "pbd/stateful_diff_command.h"
+
+#include "gtkmm2ext/utils.h"
+
 #include "ardour/session.h"
 #include "ardour/dB.h"
 #include "ardour/region_factory.h"
+#include "ardour/operations.h"
+
 #include "editor.h"
 #include "i18n.h"
 #include "keyboard.h"
 #include "selection.h"
 #include "midi_selection.h"
 #include "automation_time_axis.h"
+#include "debug.h"
+#include "editor_cursors.h"
+#include "mouse_cursors.h"
+#include "verbose_cursor.h"
 
 using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 using namespace Gtk;
+using namespace Gtkmm2ext;
 using namespace Editing;
 using namespace ArdourCanvas;
 
 using Gtkmm2ext::Keyboard;
 
-double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
+double ControlPointDrag::_zero_gain_fraction = -1.0;
 
 DragManager::DragManager (Editor* e)
        : _editor (e)
        , _ending (false)
        , _current_pointer_frame (0)
 {
-
 }
 
 DragManager::~DragManager ()
@@ -72,12 +85,16 @@ void
 DragManager::abort ()
 {
        _ending = true;
-       
+
        for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
                (*i)->abort ();
                delete *i;
        }
 
+       if (!_drags.empty ()) {
+               _editor->set_follow_playhead (_old_follow_playhead, false);
+       }
+
        _drags.clear ();
 
        _ending = false;
@@ -93,7 +110,6 @@ DragManager::add (Drag* d)
 void
 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
 {
-       assert (_drags.empty ());
        d->set_manager (this);
        _drags.push_back (d);
        start_grab (e, c);
@@ -102,8 +118,12 @@ DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
 void
 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
 {
+       /* Prevent follow playhead during the drag to be nice to the user */
+       _old_follow_playhead = _editor->follow_playhead ();
+       _editor->set_follow_playhead (false);
+
        _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
-       
+
        for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
                (*i)->start_grab (e, c);
        }
@@ -116,7 +136,7 @@ bool
 DragManager::end_grab (GdkEvent* e)
 {
        _ending = true;
-       
+
        bool r = false;
        for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
                bool const t = (*i)->end_grab (e);
@@ -129,7 +149,9 @@ DragManager::end_grab (GdkEvent* e)
        _drags.clear ();
 
        _ending = false;
-       
+
+       _editor->set_follow_playhead (_old_follow_playhead, false);
+
        return r;
 }
 
@@ -139,13 +161,13 @@ DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
        bool r = false;
 
        _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
-       
+
        for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
                bool const t = (*i)->motion_handler (e, from_autoscroll);
                if (t) {
                        r = true;
                }
-               
+
        }
 
        return r;
@@ -162,11 +184,12 @@ DragManager::have_item (ArdourCanvas::Item* i) const
        return j != _drags.end ();
 }
 
-Drag::Drag (Editor* e, ArdourCanvas::Item* i) 
+Drag::Drag (Editor* e, ArdourCanvas::Item* i)
        : _editor (e)
        , _item (i)
        , _pointer_frame_offset (0)
        , _move_threshold_passed (false)
+       , _raw_grab_frame (0)
        , _grab_frame (0)
        , _last_pointer_frame (0)
 {
@@ -208,15 +231,16 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
                _y_constrained = false;
        }
 
-       _grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
-       _grab_frame = adjusted_frame (_grab_frame, event);
+       _raw_grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
+       setup_pointer_frame_offset ();
+       _grab_frame = adjusted_frame (_raw_grab_frame, event);
        _last_pointer_frame = _grab_frame;
        _last_pointer_x = _grab_x;
        _last_pointer_y = _grab_y;
 
        _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
-                     *cursor,
-                     event->button.time);
+                    *cursor,
+                    event->button.time);
 
        if (_editor->session() && _editor->session()->transport_rolling()) {
                _was_rolling = true;
@@ -251,15 +275,15 @@ Drag::end_grab (GdkEvent* event)
 
        finished (event, _move_threshold_passed);
 
-       _editor->hide_verbose_canvas_cursor();
+       _editor->verbose_cursor()->hide ();
 
        return _move_threshold_passed;
 }
 
-nframes64_t
-Drag::adjusted_frame (nframes64_t f, GdkEvent const * event, bool snap) const
+framepos_t
+Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
 {
-       nframes64_t pos = 0;
+       framepos_t pos = 0;
 
        if (f > _pointer_frame_offset) {
                pos = f - _pointer_frame_offset;
@@ -272,7 +296,7 @@ Drag::adjusted_frame (nframes64_t f, GdkEvent const * event, bool snap) const
        return pos;
 }
 
-nframes64_t
+framepos_t
 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
 {
        return adjusted_frame (_drags->current_pointer_frame (), event, snap);
@@ -282,18 +306,19 @@ bool
 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
 {
        /* check to see if we have moved in any way that matters since the last motion event */
-       if ( (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
-            (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
+       if (_move_threshold_passed &&
+           (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
+           (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
                return false;
        }
 
-       pair<nframes64_t, int> const threshold = move_threshold ();
+       pair<framecnt_t, int> const threshold = move_threshold ();
 
        bool const old_move_threshold_passed = _move_threshold_passed;
 
        if (!from_autoscroll && !_move_threshold_passed) {
 
-               bool const xp = (::llabs (adjusted_current_frame (event) - _grab_frame) >= threshold.first);
+               bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
                bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);
 
                _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
@@ -311,7 +336,7 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
                        _last_pointer_x = _drags->current_pointer_x ();
                        _last_pointer_y = _drags->current_pointer_y ();
                        _last_pointer_frame = adjusted_current_frame (event);
-                       
+
                        return true;
                }
        }
@@ -326,228 +351,188 @@ Drag::abort ()
                _item->ungrab (0);
        }
 
-       aborted ();
+       aborted (_move_threshold_passed);
 
        _editor->stop_canvas_autoscroll ();
-       _editor->hide_verbose_canvas_cursor ();
-}
-
-RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
-       : Drag (e, i),
-         _primary (p)
-{
-       for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
-               _views.push_back (DraggingView (*i));
-       }
-       
-       RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
+       _editor->verbose_cursor()->hide ();
 }
 
 void
-RegionDrag::region_going_away (RegionView* v)
+Drag::show_verbose_cursor_time (framepos_t frame)
 {
-       list<DraggingView>::iterator i = _views.begin ();
-       while (i != _views.end() && i->view != v) {
-               ++i;
-       }
+       _editor->verbose_cursor()->set_time (
+               frame,
+               _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
+               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
+               );
 
-       if (i != _views.end()) {
-               _views.erase (i);
-       }
+       _editor->verbose_cursor()->show ();
 }
 
-RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
-       : RegionDrag (e, i, p, v),
-         _dest_trackview (0),
-         _dest_layer (0),
-         _brushing (b),
-         _total_x_delta (0)
+void
+Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double xoffset)
 {
+       _editor->verbose_cursor()->show (xoffset);
 
+       _editor->verbose_cursor()->set_duration (
+               start, end,
+               _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
+               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
+               );
 }
 
-
 void
-RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
+Drag::show_verbose_cursor_text (string const & text)
 {
-       Drag::start_grab (event);
+       _editor->verbose_cursor()->show ();
 
-       _editor->show_verbose_time_cursor (_last_frame_position, 10);
+       _editor->verbose_cursor()->set (
+               text,
+               _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
+               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
+               );
 }
 
-RegionMotionDrag::TimeAxisViewSummary
-RegionMotionDrag::get_time_axis_view_summary ()
-{
-       int32_t children = 0;
-       TimeAxisViewSummary sum;
-
-       _editor->visible_order_range (&sum.visible_y_low, &sum.visible_y_high);
 
-       /* get a bitmask representing the visible tracks */
+struct EditorOrderTimeAxisViewSorter {
+    bool operator() (TimeAxisView* a, TimeAxisView* b) {
+           RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
+           RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
+           assert (ra && rb);
+           return ra->route()->order_key (N_ ("editor")) < rb->route()->order_key (N_ ("editor"));
+    }
+};
 
-       for (TrackViewList::iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
-               RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
-               TimeAxisView::Children children_list;
+RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
+       : Drag (e, i),
+         _primary (p)
+{
+       _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
 
-               /* zeroes are audio/MIDI tracks. ones are other types. */
+       /* Make a list of non-hidden tracks to refer to during the drag */
 
-               if (!rtv->hidden()) {
+       TrackViewList track_views = _editor->track_views;
+       track_views.sort (EditorOrderTimeAxisViewSorter ());
 
-                       if (!rtv->is_track()) {
-                               /* not an audio nor MIDI track */
-                               sum.tracks = sum.tracks |= (0x01 << rtv->order());
-                       }
+       for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+               if (!(*i)->hidden()) {
 
-                       sum.height_list[rtv->order()] = (*i)->current_height();
-                       children = 1;
+                       _time_axis_views.push_back (*i);
 
-                       if ((children_list = rtv->get_child_list()).size() > 0) {
-                               for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
-                                       sum.tracks = sum.tracks |= (0x01 << (rtv->order() + children));
-                                       sum.height_list[rtv->order() + children] = (*j)->current_height();
-                                       children++;
-                               }
+                       TimeAxisView::Children children_list = (*i)->get_child_list ();
+                       for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
+                               _time_axis_views.push_back (j->get());
                        }
                }
        }
 
-       return sum;
-}
+       /* the list of views can be empty at this point if this is a region list-insert drag
+        */
 
-bool
-RegionMotionDrag::compute_y_delta (
-       TimeAxisView const * last_pointer_view, TimeAxisView* current_pointer_view,
-       int32_t last_pointer_layer, int32_t current_pointer_layer,
-       TimeAxisViewSummary const & tavs,
-       int32_t* pointer_order_span, int32_t* pointer_layer_span,
-       int32_t* canvas_pointer_order_span
-       )
-{
-       if (_brushing) {
-               *pointer_order_span = 0;
-               *pointer_layer_span = 0;
-               return true;
+       for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
+               _views.push_back (DraggingView (*i, this));
        }
 
-       bool clamp_y_axis = false;
-
-       /* the change in track order between this callback and the last */
-       *pointer_order_span = last_pointer_view->order() - current_pointer_view->order();
-       /* the change in layer between this callback and the last;
-          only meaningful if pointer_order_span == 0 (ie we've not moved tracks) */
-       *pointer_layer_span = last_pointer_layer - current_pointer_layer;
-
-       if (*pointer_order_span != 0) {
-
-               /* find the actual pointer span, in terms of the number of visible tracks;
-                  to do this, we reduce |pointer_order_span| by the number of hidden tracks
-                  over the span */
-
-               *canvas_pointer_order_span = *pointer_order_span;
-               if (last_pointer_view->order() >= current_pointer_view->order()) {
-                       for (int32_t y = current_pointer_view->order(); y < last_pointer_view->order(); y++) {
-                               if (tavs.height_list[y] == 0) {
-                                       *canvas_pointer_order_span--;
-                               }
-                       }
-               } else {
-                       for (int32_t y = last_pointer_view->order(); y <= current_pointer_view->order(); y++) {
-                               if (tavs.height_list[y] == 0) {
-                                       *canvas_pointer_order_span++;
-                               }
-                       }
-               }
-
-               for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+       RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
+}
 
-                       RegionView* rv = i->view;
+void
+RegionDrag::region_going_away (RegionView* v)
+{
+       list<DraggingView>::iterator i = _views.begin ();
+       while (i != _views.end() && i->view != v) {
+               ++i;
+       }
 
-                       if (rv->region()->locked()) {
-                               continue;
-                       }
+       if (i != _views.end()) {
+               _views.erase (i);
+       }
+}
 
-                       double ix1, ix2, iy1, iy2;
-                       rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
-                       rv->get_canvas_frame()->i2w (ix1, iy1);
-                       iy1 += _editor->vertical_adjustment.get_value() - _editor->canvas_timebars_vsize;
+/** Given a non-hidden TimeAxisView, return the index of it into the _time_axis_views vector */
+int
+RegionDrag::find_time_axis_view (TimeAxisView* t) const
+{
+       int i = 0;
+       int const N = _time_axis_views.size ();
+       while (i < N && _time_axis_views[i] != t) {
+               ++i;
+       }
 
-                       /* get the new trackview for this particular region */
-                       pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (iy1);
-                       assert (tvp.first);
-                       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
+       if (i == N) {
+               return -1;
+       }
 
-                       /* XXX: not sure that we should be passing canvas_pointer_order_span in here,
-                          as surely this is a per-region thing... */
+       return i;
+}
 
-                       clamp_y_axis = y_movement_disallowed (
-                               rtv->order(), last_pointer_view->order(), *canvas_pointer_order_span, tavs
-                               );
+RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
+       : RegionDrag (e, i, p, v),
+         _brushing (b),
+         _total_x_delta (0)
+{
 
-                       if (clamp_y_axis) {
-                               break;
-                       }
-               }
+}
 
-       } else if (_dest_trackview == current_pointer_view) {
 
-               if (current_pointer_layer == last_pointer_layer) {
-                       /* No movement; clamp */
-                       clamp_y_axis = true;
-               }
-       }
+void
+RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
 
-       if (!clamp_y_axis) {
-               _dest_trackview = current_pointer_view;
-               _dest_layer = current_pointer_layer;
-       }
+       show_verbose_cursor_time (_last_frame_position);
 
-       return clamp_y_axis;
+       pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       _last_pointer_time_axis_view = find_time_axis_view (tv.first);
+       _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
 }
 
-
 double
-RegionMotionDrag::compute_x_delta (GdkEvent const * event, nframes64_t* pending_region_position)
+RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
 {
        /* compute the amount of pointer motion in frames, and where
           the region would be if we moved it by that much.
        */
        *pending_region_position = adjusted_current_frame (event);
 
-       nframes64_t sync_frame;
-       nframes64_t sync_offset;
+       framepos_t sync_frame;
+       framecnt_t sync_offset;
        int32_t sync_dir;
-       
+
        sync_offset = _primary->region()->sync_offset (sync_dir);
-       
+
        /* we don't handle a sync point that lies before zero.
         */
        if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
-               
+
                sync_frame = *pending_region_position + (sync_dir*sync_offset);
-               
+
                _editor->snap_to_with_modifier (sync_frame, event);
-               
+
                *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
-               
+
        } else {
                *pending_region_position = _last_frame_position;
        }
 
-       if (*pending_region_position > max_frames - _primary->region()->length()) {
+       if (*pending_region_position > max_framepos - _primary->region()->length()) {
                *pending_region_position = _last_frame_position;
        }
 
        double dx = 0;
 
-       if ((*pending_region_position != _last_frame_position) && x_move_allowed ()) {
+       /* in locked edit mode, reverse the usual meaning of _x_constrained */
+       bool const x_move_allowed = Config->get_edit_mode() == Lock ? _x_constrained : !_x_constrained;
 
-               /* x movement since last time */
+       if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
+
+               /* x movement since last time (in pixels) */
                dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
 
                /* total x movement */
                framecnt_t total_dx = *pending_region_position;
                if (regions_came_from_canvas()) {
-                       total_dx = total_dx - grab_frame () + _pointer_frame_offset;
+                       total_dx = total_dx - grab_frame ();
                }
 
                /* check that no regions have gone off the start of the session */
@@ -565,67 +550,80 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, nframes64_t* pending_
        return dx;
 }
 
-void
-RegionMotionDrag::motion (GdkEvent* event, bool first_move)
+bool
+RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) const
 {
-       double y_delta = 0;
+       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+               int const n = i->time_axis_view + delta_track;
+               if (n < 0 || n >= int (_time_axis_views.size())) {
+                       /* off the top or bottom track */
+                       return false;
+               }
 
-       TimeAxisViewSummary tavs = get_time_axis_view_summary ();
+               RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
+               if (to == 0 || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
+                       /* not a track, or the wrong type */
+                       return false;
+               }
 
-       vector<int32_t>::iterator j;
+               int const l = i->layer + delta_layer;
+               if (delta_track == 0 && (l < 0 || l >= int (to->view()->layers()))) {
+                       /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
+                          If it has, the layers will be munged later anyway, so it's ok.
+                       */
+                       return false;
+               }
+       }
+
+       /* all regions being dragged are ok with this change */
+       return true;
+}
+
+void
+RegionMotionDrag::motion (GdkEvent* event, bool first_move)
+{
+       assert (!_views.empty ());
 
-       /* *pointer* variables reflect things about the pointer; as we may be moving
-          multiple regions, much detail must be computed per-region */
+       /* Find the TimeAxisView that the pointer is now over */
+       pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
 
-       /* current_pointer_view will become the TimeAxisView that we're currently pointing at, and
-          current_pointer_layer the current layer on that TimeAxisView; in this code layer numbers
-          are with respect to how the view's layers are displayed; if we are in Overlaid mode, layer
-          is always 0 regardless of what the region's "real" layer is */
-       RouteTimeAxisView* current_pointer_view;
-       layer_t current_pointer_layer;
-       if (!check_possible (&current_pointer_view, &current_pointer_layer)) {
+       /* Bail early if we're not over a track */
+       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv.first);
+       if (!rtv || !rtv->is_track()) {
+               _editor->verbose_cursor()->hide ();
                return;
        }
 
-       /* TimeAxisView that we were pointing at last time we entered this method */
-       TimeAxisView const * const last_pointer_view = _dest_trackview;
-       /* the order of the track that we were pointing at last time we entered this method */
-       int32_t const last_pointer_order = last_pointer_view->order ();
-       /* the layer that we were pointing at last time we entered this method */
-       layer_t const last_pointer_layer = _dest_layer;
-
-       int32_t pointer_order_span;
-       int32_t pointer_layer_span;
-       int32_t canvas_pointer_order_span;
+       /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
 
-       bool const clamp_y_axis = compute_y_delta (
-               last_pointer_view, current_pointer_view,
-               last_pointer_layer, current_pointer_layer, tavs,
-               &pointer_order_span, &pointer_layer_span,
-               &canvas_pointer_order_span
-               );
+       /* Here's the current pointer position in terms of time axis view and layer */
+       int const current_pointer_time_axis_view = find_time_axis_view (tv.first);
+       layer_t const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
 
-       nframes64_t pending_region_position;
+       /* Work out the change in x */
+       framepos_t pending_region_position;
        double const x_delta = compute_x_delta (event, &pending_region_position);
 
-       /*************************************************************
-           PREPARE TO MOVE
-       ************************************************************/
+       /* Work out the change in y */
+       int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
+       int delta_layer = current_pointer_layer - _last_pointer_layer;
 
-       if (x_delta == 0 && pointer_order_span == 0 && pointer_layer_span == 0 && !first_move) {
+       if (!y_movement_allowed (delta_time_axis_view, delta_layer)) {
+               /* this y movement is not allowed, so do no y movement this time */
+               delta_time_axis_view = 0;
+               delta_layer = 0;
+       }
+
+       if (x_delta == 0 && delta_time_axis_view == 0 && delta_layer == 0 && !first_move) {
                /* haven't reached next snap point, and we're not switching
                   trackviews nor layers. nothing to do.
                */
                return;
        }
 
-       /*************************************************************
-           MOTION
-       ************************************************************/
-
        pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
 
-       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
 
                RegionView* rv = i->view;
 
@@ -633,149 +631,91 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                        continue;
                }
 
-               /* here we are calculating the y distance from the
-                  top of the first track view to the top of the region
-                  area of the track view that we're working on */
+               if (first_move) {
 
-               /* this x value is just a dummy value so that we have something
-                  to pass to i2w () */
+                       /* here we are calculating the y distance from the
+                          top of the first track view to the top of the region
+                          area of the track view that we're working on */
 
-               double ix1 = 0;
+                       /* this x value is just a dummy value so that we have something
+                          to pass to i2w () */
 
-               /* distance from the top of this track view to the region area
-                  of our track view is always 1 */
+                       double ix1 = 0;
 
-               double iy1 = 1;
+                       /* distance from the top of this track view to the region area
+                          of our track view is always 1 */
 
-               /* convert to world coordinates, ie distance from the top of
-                  the ruler section */
+                       double iy1 = 1;
 
-               rv->get_canvas_frame()->i2w (ix1, iy1);
+                       /* convert to world coordinates, ie distance from the top of
+                          the ruler section */
 
-               /* compensate for the ruler section and the vertical scrollbar position */
-               iy1 += _editor->get_trackview_group_vertical_offset ();
+                       rv->get_canvas_frame()->i2w (ix1, iy1);
 
-               if (first_move) {
+                       /* compensate for the ruler section and the vertical scrollbar position */
+                       iy1 += _editor->get_trackview_group_vertical_offset ();
 
                        // hide any dependent views
 
                        rv->get_time_axis_view().hide_dependent_views (*rv);
 
                        /*
-                          reparent to a non scrolling group so that we can keep the
-                          region selection above all time axis views.
-                          reparenting means we have to move the rv as the two
-                          parent groups have different coordinates.
+                         reparent to a non scrolling group so that we can keep the
+                         region selection above all time axis views.
+                         reparenting means we have to move the rv as the two
+                         parent groups have different coordinates.
                        */
 
                        rv->get_canvas_group()->property_y() = iy1 - 1;
-                       rv->get_canvas_group()->reparent(*(_editor->_region_motion_group));
+                       rv->get_canvas_group()->reparent (*(_editor->_region_motion_group));
 
                        rv->fake_set_opaque (true);
                }
 
-               /* current view for this particular region */
-               pair<TimeAxisView*, int> pos = _editor->trackview_by_y_position (iy1);
-               RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (pos.first);
-
-               if (pointer_order_span != 0 && !clamp_y_axis) {
-
-                       /* INTER-TRACK MOVEMENT */
-
-                       /* move through the height list to the track that the region is currently on */
-                       vector<int32_t>::iterator j = tavs.height_list.begin ();
-                       int32_t x = 0;
-                       while (j != tavs.height_list.end () && x != rtv->order ()) {
-                               ++x;
-                               ++j;
-                       }
-
-                       y_delta = 0;
-                       int32_t temp_pointer_order_span = canvas_pointer_order_span;
-
-                       if (j != tavs.height_list.end ()) {
-
-                               /* Account for layers in the original and
-                                  destination tracks.  If we're moving around in layers we assume
-                                  that only one track is involved, so it's ok to use *pointer*
-                                  variables here. */
-
-                               StreamView* lv = last_pointer_view->view ();
-                               assert (lv);
-
-                               /* move to the top of the last trackview */
-                               if (lv->layer_display () == Stacked) {
-                                       y_delta -= (lv->layers() - last_pointer_layer - 1) * lv->child_height ();
-                               }
-
-                               StreamView* cv = current_pointer_view->view ();
-                               assert (cv);
-
-                               /* move to the right layer on the current trackview */
-                               if (cv->layer_display () == Stacked) {
-                                       y_delta += (cv->layers() - current_pointer_layer - 1) * cv->child_height ();
-                               }
-
-                               /* And for being on a non-topmost layer on the new
-                                  track */
-
-                               while (temp_pointer_order_span > 0) {
-                                       /* we're moving up canvas-wise,
-                                          so we need to find the next track height
-                                       */
-                                       if (j != tavs.height_list.begin()) {
-                                               j--;
-                                       }
-
-                                       if (x != last_pointer_order) {
-                                               if ((*j) == 0) {
-                                                       ++temp_pointer_order_span;
-                                               }
-                                       }
-
-                                       y_delta -= (*j);
-                                       temp_pointer_order_span--;
-                               }
-
-                               while (temp_pointer_order_span < 0) {
+               /* Work out the change in y position of this region view */
 
-                                       y_delta += (*j);
+               double y_delta = 0;
 
-                                       if (x != last_pointer_order) {
-                                               if ((*j) == 0) {
-                                                       --temp_pointer_order_span;
-                                               }
-                                       }
-
-                                       if (j != tavs.height_list.end()) {
-                                               j++;
-                                       }
-
-                                       temp_pointer_order_span++;
-                               }
-
-
-                               /* find out where we'll be when we move and set height accordingly */
+               /* If we have moved tracks, we'll fudge the layer delta so that the
+                  region gets moved back onto layer 0 on its new track; this avoids
+                  confusion when dragging regions from non-zero layers onto different
+                  tracks.
+               */
+               int this_delta_layer = delta_layer;
+               if (delta_time_axis_view != 0) {
+                       this_delta_layer = - i->layer;
+               }
 
-                               pair<TimeAxisView*, int> const pos = _editor->trackview_by_y_position (iy1 + y_delta);
-                               RouteTimeAxisView const * temp_rtv = dynamic_cast<RouteTimeAxisView*> (pos.first);
-                               rv->set_height (temp_rtv->view()->child_height());
+               /* Move this region to layer 0 on its old track */
+               StreamView* lv = _time_axis_views[i->time_axis_view]->view ();
+               if (lv->layer_display() == Stacked) {
+                       y_delta -= (lv->layers() - i->layer - 1) * lv->child_height ();
+               }
 
-                               /* if you un-comment the following, the region colours will follow
-                                  the track colours whilst dragging; personally
-                                  i think this can confuse things, but never mind.
-                               */
+               /* Now move it to its right layer on the current track */
+               StreamView* cv = _time_axis_views[i->time_axis_view + delta_time_axis_view]->view ();
+               if (cv->layer_display() == Stacked) {
+                       y_delta += (cv->layers() - (i->layer + this_delta_layer) - 1) * cv->child_height ();
+               }
 
-                               //const GdkColor& col (temp_rtv->view->get_region_color());
-                               //rv->set_color (const_cast<GdkColor&>(col));
+               /* Move tracks */
+               if (delta_time_axis_view > 0) {
+                       for (int j = 0; j < delta_time_axis_view; ++j) {
+                               y_delta += _time_axis_views[i->time_axis_view + j]->current_height ();
+                       }
+               } else {
+                       /* start by subtracting the height of the track above where we are now */
+                       for (int j = 1; j <= -delta_time_axis_view; ++j) {
+                               y_delta -= _time_axis_views[i->time_axis_view - j]->current_height ();
                        }
                }
 
-               if (pointer_order_span == 0 && pointer_layer_span != 0 && !clamp_y_axis) {
+               /* Set height */
+               rv->set_height (_time_axis_views[i->time_axis_view + delta_time_axis_view]->view()->child_height ());
 
-                       /* INTER-LAYER MOVEMENT in the same track */
-                       y_delta = rtv->view()->child_height () * pointer_layer_span;
-               }
+               /* Update the DraggingView */
+               i->time_axis_view += delta_time_axis_view;
+               i->layer += this_delta_layer;
 
                if (_brushing) {
                        _editor->mouse_brush_insert_region (rv, pending_region_position);
@@ -786,28 +726,90 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        } /* foreach region */
 
        _total_x_delta += x_delta;
-       
+
        if (first_move) {
                _editor->cursor_group->raise_to_top();
        }
 
        if (x_delta != 0 && !_brushing) {
-               _editor->show_verbose_time_cursor (_last_frame_position, 10);
+               show_verbose_cursor_time (_last_frame_position);
        }
+
+       _last_pointer_time_axis_view += delta_time_axis_view;
+       _last_pointer_layer += delta_layer;
 }
 
 void
 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
 {
        if (_copy && first_move) {
-               copy_regions (event);
+
+               /* duplicate the regionview(s) and region(s) */
+
+               list<DraggingView> new_regionviews;
+
+               for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+
+                       RegionView* rv = i->view;
+                       AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
+                       MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
+
+                       const boost::shared_ptr<const Region> original = rv->region();
+                       boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
+                       region_copy->set_position (original->position());
+
+                       RegionView* nrv;
+                       if (arv) {
+                               boost::shared_ptr<AudioRegion> audioregion_copy
+                               = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
+
+                               nrv = new AudioRegionView (*arv, audioregion_copy);
+                       } else if (mrv) {
+                               boost::shared_ptr<MidiRegion> midiregion_copy
+                                       = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
+                               nrv = new MidiRegionView (*mrv, midiregion_copy);
+                       } else {
+                               continue;
+                       }
+
+                       nrv->get_canvas_group()->show ();
+                       new_regionviews.push_back (DraggingView (nrv, this));
+
+                       /* swap _primary to the copy */
+
+                       if (rv == _primary) {
+                               _primary = nrv;
+                       }
+
+                       /* ..and deselect the one we copied */
+
+                       rv->set_selected (false);
+               }
+
+               if (!new_regionviews.empty()) {
+
+                       /* reflect the fact that we are dragging the copies */
+
+                       _views = new_regionviews;
+
+                       swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
+
+                       /*
+                         sync the canvas to what we think is its current state
+                         without it, the canvas seems to
+                         "forget" to update properly after the upcoming reparent()
+                         ..only if the mouse is in rapid motion at the time of the grab.
+                         something to do with regionview creation taking so long?
+                       */
+                       _editor->update_canvas_now();
+               }
        }
 
        RegionMotionDrag::motion (event, first_move);
 }
 
 void
-RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
+RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
 {
        if (!movement_occurred) {
                /* just a click */
@@ -822,40 +824,35 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                _x_constrained = !_x_constrained;
        }
 
-       bool const changed_position = (_last_frame_position != (nframes64_t) (_primary->region()->position()));
-       bool const changed_tracks = (_dest_trackview != &_primary->get_time_axis_view());
+       assert (!_views.empty ());
+
+       bool const changed_position = (_last_frame_position != _primary->region()->position());
+       bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
        framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
 
        _editor->update_canvas_now ();
-       
+
        if (_copy) {
-               
+
                finished_copy (
-                       find_time_axis_views_and_layers (),
                        changed_position,
                        changed_tracks,
                        drag_delta
                        );
-               
+
        } else {
-               
+
                finished_no_copy (
-                       find_time_axis_views_and_layers (),
                        changed_position,
                        changed_tracks,
                        drag_delta
                        );
-               
+
        }
 }
 
 void
-RegionMoveDrag::finished_copy (
-       map<RegionView*, pair<RouteTimeAxisView*, int> > const & final,
-       bool const changed_position,
-       bool const changed_tracks,
-       framecnt_t const drag_delta
-       )
+RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
 {
        RegionSelection new_views;
        PlaylistSet modified_playlists;
@@ -873,39 +870,36 @@ RegionMoveDrag::finished_copy (
        }
 
        if (_x_constrained) {
-               _editor->begin_reversible_command (_("fixed time region copy"));
+               _editor->begin_reversible_command (Operations::fixed_time_region_copy);
        } else {
-               _editor->begin_reversible_command (_("region copy"));
+               _editor->begin_reversible_command (Operations::region_copy);
        }
 
        /* insert the regions into their new playlists */
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
 
-               nframes64_t where;
-
                if (i->view->region()->locked()) {
                        continue;
                }
 
+               framepos_t where;
+
                if (changed_position && !_x_constrained) {
                        where = i->view->region()->position() - drag_delta;
                } else {
                        where = i->view->region()->position();
                }
 
-               map<RegionView*, pair<RouteTimeAxisView*, int> >::const_iterator j = final.find (i->view);
-               assert (j != final.end());
-
                RegionView* new_view = insert_region_into_playlist (
-                       i->view->region(), j->second.first, j->second.second, where, modified_playlists
+                       i->view->region(), dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]), i->layer, where, modified_playlists
                        );
-               
+
                if (new_view == 0) {
                        continue;
                }
 
                new_views.push_back (new_view);
-               
+
                /* we don't need the copied RegionView any more */
                views_to_delete.push_back (i->view);
        }
@@ -918,8 +912,8 @@ RegionMoveDrag::finished_copy (
                delete *i;
        }
 
-       /* If we've created new regions either by copying or moving 
-          to a new track, we want to replace the old selection with the new ones 
+       /* If we've created new regions either by copying or moving
+          to a new track, we want to replace the old selection with the new ones
        */
 
        if (new_views.size() > 0) {
@@ -934,7 +928,6 @@ RegionMoveDrag::finished_copy (
 
 void
 RegionMoveDrag::finished_no_copy (
-       map<RegionView*, pair<RouteTimeAxisView*, int> > const & final,
        bool const changed_position,
        bool const changed_tracks,
        framecnt_t const drag_delta
@@ -953,25 +946,22 @@ RegionMoveDrag::finished_no_copy (
        if (_x_constrained) {
                _editor->begin_reversible_command (_("fixed time region drag"));
        } else {
-               _editor->begin_reversible_command (_("region drag"));
+               _editor->begin_reversible_command (Operations::region_drag);
        }
 
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
 
                RegionView* rv = i->view;
 
-               map<RegionView*, pair<RouteTimeAxisView*, int> >::const_iterator j = final.find (rv);
-               assert (j != final.end());
-               
-               RouteTimeAxisView* dest_rtv = j->second.first;
-               layer_t dest_layer = j->second.second;
+               RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
+               layer_t const dest_layer = i->layer;
 
                if (rv->region()->locked()) {
                        ++i;
                        continue;
                }
 
-               nframes64_t where;
+               framepos_t where;
 
                if (changed_position && !_x_constrained) {
                        where = rv->region()->position() - drag_delta;
@@ -984,7 +974,7 @@ RegionMoveDrag::finished_no_copy (
                        /* insert into new playlist */
 
                        RegionView* new_view = insert_region_into_playlist (
-                               RegionFactory::create (rv->region ()), dest_rtv, dest_layer, where, modified_playlists
+                               RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
                                );
 
                        if (new_view == 0) {
@@ -1007,8 +997,8 @@ RegionMoveDrag::finished_no_copy (
                        remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
 
                } else {
-                       
-                       rv->region()->clear_history ();
+
+                       rv->region()->clear_changes ();
 
                        /*
                           motion on the same track. plonk the previously reparented region
@@ -1023,12 +1013,12 @@ RegionMoveDrag::finished_no_copy (
                        /* just change the model */
 
                        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
-                       
+
                        if (dest_rtv->view()->layer_display() == Stacked) {
                                rv->region()->set_layer (dest_layer);
                                rv->region()->set_pending_explicit_relayer (true);
                        }
-                       
+
                        /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
 
                        pair<PlaylistSet::iterator, bool> r = frozen_playlists.insert (playlist);
@@ -1037,13 +1027,22 @@ RegionMoveDrag::finished_no_copy (
                                playlist->freeze ();
                        }
 
-                       rv->region()->set_position (where, (void*) this);
+                       /* this movement may result in a crossfade being modified, so we need to get undo
+                          data from the playlist as well as the region.
+                       */
+
+                       r = modified_playlists.insert (playlist);
+                       if (r.second) {
+                               playlist->clear_changes ();
+                       }
+
+                       rv->region()->set_position (where);
 
                        _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
                }
 
                if (changed_tracks) {
-                       
+
                        /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
                           was selected in all of them, then removing it from a playlist will have removed all
                           trace of it from _views (i.e. there were N regions selected, we removed 1,
@@ -1061,9 +1060,9 @@ RegionMoveDrag::finished_no_copy (
                           we can just iterate.
                        */
 
-                       
+
                        if (_views.empty()) {
-                                break;
+                               break;
                        } else {
                                i = _views.begin();
                        }
@@ -1073,8 +1072,8 @@ RegionMoveDrag::finished_no_copy (
                }
        }
 
-       /* If we've created new regions either by copying or moving 
-          to a new track, we want to replace the old selection with the new ones 
+       /* If we've created new regions either by copying or moving
+          to a new track, we want to replace the old selection with the new ones
        */
 
        if (new_views.size() > 0) {
@@ -1095,7 +1094,7 @@ RegionMoveDrag::finished_no_copy (
  *  @param region Region to remove.
  *  @param playlist playlist To remove from.
  *  @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
- *  that clear_history () is only called once per playlist.
+ *  that clear_changes () is only called once per playlist.
  */
 void
 RegionMoveDrag::remove_region_from_playlist (
@@ -1107,12 +1106,12 @@ RegionMoveDrag::remove_region_from_playlist (
        pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
 
        if (r.second) {
-               playlist->clear_history ();
+               playlist->clear_changes ();
        }
 
        playlist->remove_region (region);
 }
-       
+
 
 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
  *  clearing the playlist's diff history first if necessary.
@@ -1121,7 +1120,7 @@ RegionMoveDrag::remove_region_from_playlist (
  *  @param dest_layer Destination layer.
  *  @param where Destination position.
  *  @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
- *  that clear_history () is only called once per playlist. 
+ *  that clear_changes () is only called once per playlist.
  *  @return New RegionView, or 0 if no insert was performed.
  */
 RegionView *
@@ -1142,10 +1141,10 @@ RegionMoveDrag::insert_region_into_playlist (
        _new_region_view = 0;
        sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
 
-       /* clear history for the playlist we are about to insert to, provided we haven't already done so */     
+       /* clear history for the playlist we are about to insert to, provided we haven't already done so */
        pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
        if (r.second) {
-               dest_playlist->clear_history ();
+               dest_playlist->clear_changes ();
        }
 
        dest_playlist->add_region (region, where);
@@ -1172,13 +1171,18 @@ void
 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
 {
        for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
-               _editor->session()->add_command (new StatefulDiffCommand (*i));
+               StatefulDiffCommand* c = new StatefulDiffCommand (*i);
+               if (!c->empty()) {
+                       _editor->session()->add_command (c);
+               } else {
+                       delete c;
+               }
        }
 }
 
 
 void
-RegionMoveDrag::aborted ()
+RegionMoveDrag::aborted (bool movement_occurred)
 {
        if (_copy) {
 
@@ -1189,12 +1193,12 @@ RegionMoveDrag::aborted ()
                _views.clear ();
 
        } else {
-               RegionMotionDrag::aborted ();
+               RegionMotionDrag::aborted (movement_occurred);
        }
 }
 
 void
-RegionMotionDrag::aborted ()
+RegionMotionDrag::aborted (bool)
 {
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                RegionView* rv = i->view;
@@ -1211,231 +1215,36 @@ RegionMotionDrag::aborted ()
 
        _editor->update_canvas_now ();
 }
-                                     
 
-bool
-RegionMotionDrag::x_move_allowed () const
-{
-       if (Config->get_edit_mode() == Lock) {
-               /* in locked edit mode, reverse the usual meaning of _x_constrained */
-               return _x_constrained;
-       }
-
-       return !_x_constrained;
-}
-
-void
-RegionMotionDrag::copy_regions (GdkEvent* event)
-{
-       /* duplicate the regionview(s) and region(s) */
-
-       list<DraggingView> new_regionviews;
-
-       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-
-               RegionView* rv = i->view;
-               AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
-               MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
-
-               const boost::shared_ptr<const Region> original = rv->region();
-               boost::shared_ptr<Region> region_copy = RegionFactory::create (original);
-                region_copy->set_position (original->position(), this);
-
-               RegionView* nrv;
-               if (arv) {
-                       boost::shared_ptr<AudioRegion> audioregion_copy
-                               = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
-
-                       nrv = new AudioRegionView (*arv, audioregion_copy);
-               } else if (mrv) {
-                       boost::shared_ptr<MidiRegion> midiregion_copy
-                               = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
-                       nrv = new MidiRegionView (*mrv, midiregion_copy);
-               } else {
-                       continue;
-               }
-
-               nrv->get_canvas_group()->show ();
-               new_regionviews.push_back (DraggingView (nrv));
-
-               /* swap _primary to the copy */
-
-               if (rv == _primary) {
-                       _primary = nrv;
-               }
-
-               /* ..and deselect the one we copied */
-
-               rv->set_selected (false);
-       }
-
-       if (new_regionviews.empty()) {
-               return;
-       }
-
-       /* reflect the fact that we are dragging the copies */
-
-       _views = new_regionviews;
-
-       swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
-
-       /*
-          sync the canvas to what we think is its current state
-          without it, the canvas seems to
-          "forget" to update properly after the upcoming reparent()
-          ..only if the mouse is in rapid motion at the time of the grab.
-          something to do with regionview creation taking so long?
-       */
-       _editor->update_canvas_now();
-}
-
-bool
-RegionMotionDrag::check_possible (RouteTimeAxisView** tv, layer_t* layer)
-{
-       /* Which trackview is this ? */
-
-       pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
-       (*tv) = dynamic_cast<RouteTimeAxisView*> (tvp.first);
-       (*layer) = tvp.second;
-
-       if (*tv && (*tv)->layer_display() == Overlaid) {
-               *layer = 0;
-       }
-
-       /* 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 and audiotrack.
-               */
-               _editor->hide_verbose_canvas_cursor ();
-               return false;
-       }
-
-       return true;
-}
-
-/** @param new_order New track order.
- *  @param old_order Old track order.
- *  @param visible_y_low Lowest visible order.
- *  @return true if y movement should not happen, otherwise false.
+/** @param b true to brush, otherwise false.
+ *  @param c true to make copies of the regions being moved, otherwise false.
  */
-bool
-RegionMotionDrag::y_movement_disallowed (int new_order, int old_order, int y_span, TimeAxisViewSummary const & tavs) const
-{
-       if (new_order != old_order) {
-
-               /* this isn't the pointer track */
-
-               if (y_span > 0) {
-
-                       /* moving up the canvas */
-                       if ( (new_order - y_span) >= tavs.visible_y_low) {
-
-                               int32_t n = 0;
-
-                               /* work out where we'll end up with this y span, taking hidden TimeAxisViews into account */
-                               int32_t visible_tracks = 0;
-                               while (visible_tracks < y_span ) {
-                                       visible_tracks++;
-                                       while (tavs.height_list[new_order - (visible_tracks - n)] == 0) {
-                                               /* passing through a hidden track */
-                                               n--;
-                                       }
-                               }
-
-                               if (tavs.tracks[new_order - (y_span - n)] != 0x00) {
-                                       /* moving to a non-track; disallow */
-                                       return true;
-                               }
-
-
-                       } else {
-                               /* moving beyond the lowest visible track; disallow */
-                               return true;
-                       }
-
-               } else if (y_span < 0) {
-
-                       /* moving down the canvas */
-                       if ((new_order - y_span) <= tavs.visible_y_high) {
-
-                               int32_t visible_tracks = 0;
-                               int32_t n = 0;
-                               while (visible_tracks > y_span ) {
-                                       visible_tracks--;
-
-                                       while (tavs.height_list[new_order - (visible_tracks - n)] == 0) {
-                                               /* passing through a hidden track */
-                                               n++;
-                                       }
-                               }
-
-                               if (tavs.tracks[new_order - (y_span - n)] != 0x00) {
-                                       /* moving to a non-track; disallow */
-                                       return true;
-                               }
-
-
-                       } else {
-
-                               /* moving beyond the highest visible track; disallow */
-                               return true;
-                       }
-               }
-
-       } else {
-
-               /* this is the pointer's track */
-
-               if ((new_order - y_span) > tavs.visible_y_high) {
-                       /* we will overflow */
-                       return true;
-               } else if ((new_order - y_span) < tavs.visible_y_low) {
-                       /* we will overflow */
-                       return true;
-               }
-       }
-
-       return false;
-}
-
-
 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
        : RegionMotionDrag (e, i, p, v, b),
          _copy (c)
 {
-       TimeAxisView* const tv = &_primary->get_time_axis_view ();
-
-       _dest_trackview = tv;
-       if (tv->layer_display() == Overlaid) {
-               _dest_layer = 0;
-       } else {
-               _dest_layer = _primary->region()->layer ();
-       }
+       DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
 
        double speed = 1;
-       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
+       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
        if (rtv && rtv->is_track()) {
                speed = rtv->track()->speed ();
        }
 
-       _last_frame_position = static_cast<nframes64_t> (_primary->region()->position() / speed);
+       _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
 }
 
 void
-RegionMoveDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
+RegionMoveDrag::setup_pointer_frame_offset ()
 {
-       RegionMotionDrag::start_grab (event, c);
-
-       _pointer_frame_offset = grab_frame() - _last_frame_position;
+       _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
 }
 
-RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, nframes64_t pos)
+RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
        : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
 {
+       DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
+
        assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
                (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
 
@@ -1443,52 +1252,27 @@ RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, Rout
 
        _primary->get_canvas_group()->show ();
        _primary->set_position (pos, 0);
-       _views.push_back (DraggingView (_primary));
+       _views.push_back (DraggingView (_primary, this));
 
        _last_frame_position = pos;
 
        _item = _primary->get_canvas_group ();
-       _dest_trackview = v;
-       _dest_layer = _primary->region()->layer ();
 }
 
-map<RegionView*, pair<RouteTimeAxisView*, int> >
-RegionMotionDrag::find_time_axis_views_and_layers ()
-{
-       map<RegionView*, pair<RouteTimeAxisView*, int> > tav;
-
-       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-
-               double ix1, ix2, iy1, iy2;
-               RegionView* rv = i->view;
-               rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
-               rv->get_canvas_frame()->i2w (ix1, iy1);
-               iy1 += _editor->vertical_adjustment.get_value() - _editor->canvas_timebars_vsize;
-
-               pair<TimeAxisView*, int> tv = _editor->trackview_by_y_position (iy1);
-               tav[rv] = make_pair (dynamic_cast<RouteTimeAxisView*> (tv.first), tv.second);
-       }
-
-       return tav;
-}
-
-
 void
-RegionInsertDrag::finished (GdkEvent* /*event*/, bool /*movement_occurred*/)
+RegionInsertDrag::finished (GdkEvent *, bool)
 {
        _editor->update_canvas_now ();
 
-       map<RegionView*, pair<RouteTimeAxisView*, int> > final = find_time_axis_views_and_layers ();
-
-       RouteTimeAxisView* dest_rtv = final[_primary].first;
+       RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[_views.front().time_axis_view]);
 
        _primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
        _primary->get_canvas_group()->property_y() = 0;
 
        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
-       _editor->begin_reversible_command (_("insert region"));
-        playlist->clear_history ();
+       _editor->begin_reversible_command (Operations::insert_region);
+       playlist->clear_changes ();
        playlist->add_region (_primary->region (), _last_frame_position);
        _editor->session()->add_command (new StatefulDiffCommand (playlist));
        _editor->commit_reversible_command ();
@@ -1499,7 +1283,7 @@ RegionInsertDrag::finished (GdkEvent* /*event*/, bool /*movement_occurred*/)
 }
 
 void
-RegionInsertDrag::aborted ()
+RegionInsertDrag::aborted (bool)
 {
        delete _primary;
        _primary = 0;
@@ -1509,7 +1293,7 @@ RegionInsertDrag::aborted ()
 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
        : RegionMoveDrag (e, i, p, v, false, false)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
 }
 
 struct RegionSelectionByPosition {
@@ -1521,10 +1305,20 @@ struct RegionSelectionByPosition {
 void
 RegionSpliceDrag::motion (GdkEvent* event, bool)
 {
-       RouteTimeAxisView* tv;
-       layer_t layer;
+       /* Which trackview is this ? */
 
-       if (!check_possible (&tv, &layer)) {
+       pair<TimeAxisView*, int> 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 and audiotrack.
+               */
+               _editor->verbose_cursor()->hide ();
                return;
        }
 
@@ -1541,7 +1335,7 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
        RegionSelectionByPosition cmp;
        copy.sort (cmp);
 
-       nframes64_t const pf = adjusted_current_frame (event);
+       framepos_t const pf = adjusted_current_frame (event);
 
        for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
 
@@ -1577,13 +1371,13 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
 }
 
 void
-RegionSpliceDrag::finished (GdkEvent* /*event*/, bool)
+RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
 {
-
+       RegionMoveDrag::finished (event, movement_occurred);
 }
 
 void
-RegionSpliceDrag::aborted ()
+RegionSpliceDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -1592,6 +1386,8 @@ RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisVi
        : Drag (e, i),
          _view (dynamic_cast<MidiTimeAxisView*> (v))
 {
+       DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
+
        assert (_view);
 }
 
@@ -1599,31 +1395,64 @@ void
 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
 {
        if (first_move) {
-               /* don't use a zero-length region otherwise its region view will be hidden when it is created */
-               _region = _view->add_region (grab_frame(), 1, false);
+               add_region();
+               _view->playlist()->freeze ();
        } else {
-               framepos_t const f = adjusted_current_frame (event);
-               if (f < grab_frame()) {
-                       _region->set_position (f, this);
-               }
+               if (_region) {
+                       framepos_t const f = adjusted_current_frame (event);
+                       if (f < grab_frame()) {
+                               _region->set_position (f);
+                       }
+
+                       /* Don't use a zero-length region, and subtract 1 frame from the snapped length
+                          so that if this region is duplicated, its duplicate starts on
+                          a snap point rather than 1 frame after a snap point.  Otherwise things get
+                          a bit confusing as if a region starts 1 frame after a snap point, one cannot
+                          place snapped notes at the start of the region.
+                       */
 
-               /* again, don't use a zero-length region (see above) */
-               framecnt_t const len = abs (f - grab_frame ());
-               _region->set_length (len < 1 ? 1 : len, this);
+                       framecnt_t const len = abs (f - grab_frame () - 1);
+                       _region->set_length (len < 1 ? 1 : len);
+               }
        }
 }
 
 void
-RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
+RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
 {
-       if (movement_occurred) {
+       if (!movement_occurred) {
+               add_region ();
+       } else {
+               _view->playlist()->thaw ();
+       }
+
+       if (_region) {
                _editor->commit_reversible_command ();
        }
 }
 
 void
-RegionCreateDrag::aborted ()
+RegionCreateDrag::add_region ()
+{
+       if (_editor->session()) {
+               const TempoMap& map (_editor->session()->tempo_map());
+               framecnt_t pos = grab_frame();
+               const Meter& m = map.meter_at (pos);
+               /* not that the frame rate used here can be affected by pull up/down which
+                  might be wrong.
+               */
+               framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
+               _region = _view->add_region (grab_frame(), len, false);
+       }
+}
+
+void
+RegionCreateDrag::aborted (bool)
 {
+       if (_region) {
+               _view->playlist()->thaw ();
+       }
+
        /* XXX */
 }
 
@@ -1631,16 +1460,23 @@ NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
        , region (0)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
 }
 
 void
 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
 {
        Gdk::Cursor* cursor;
-       ArdourCanvas::CanvasNote* cnote = dynamic_cast<ArdourCanvas::CanvasNote*>(_item);
+       ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item);
+       float x_fraction = cnote->mouse_x_fraction ();
 
-       Drag::start_grab (event);
+       if (x_fraction > 0.0 && x_fraction < 0.25) {
+               cursor = _editor->cursors()->left_side_trim;
+       } else  {
+               cursor = _editor->cursors()->right_side_trim;
+       }
+
+       Drag::start_grab (event, cursor);
 
        region = &cnote->region_view();
 
@@ -1648,10 +1484,10 @@ NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
        double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;
 
        if (grab_x() <= middle_point) {
-               cursor = _editor->left_side_trim_cursor;
+               cursor = _editor->cursors()->left_side_trim;
                at_front = true;
        } else {
-               cursor = _editor->right_side_trim_cursor;
+               cursor = _editor->cursors()->right_side_trim;
                at_front = false;
        }
 
@@ -1689,7 +1525,7 @@ NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
 {
        MidiRegionSelection& ms (_editor->get_selection().midi_regions);
        for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
-               (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNote*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
+               (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
        }
 }
 
@@ -1698,16 +1534,22 @@ NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
 {
        MidiRegionSelection& ms (_editor->get_selection().midi_regions);
        for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
-               (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNote*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
+               (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
        }
 }
 
 void
-NoteResizeDrag::aborted ()
+NoteResizeDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
 
+RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New RegionGainDrag\n");
+}
+
 void
 RegionGainDrag::motion (GdkEvent* /*event*/, bool)
 {
@@ -1721,20 +1563,19 @@ RegionGainDrag::finished (GdkEvent *, bool)
 }
 
 void
-RegionGainDrag::aborted ()
+RegionGainDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
 
 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
        : RegionDrag (e, i, p, v)
-       , _have_transaction (false)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
 }
 
 void
-TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
+TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
 {
        double speed = 1.0;
        TimeAxisView* tvp = &_primary->get_time_axis_view ();
@@ -1744,39 +1585,47 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
                speed = tv->track()->speed();
        }
 
-       nframes64_t const region_start = (nframes64_t) (_primary->region()->position() / speed);
-       nframes64_t const region_end = (nframes64_t) (_primary->region()->last_frame() / speed);
-       nframes64_t const region_length = (nframes64_t) (_primary->region()->length() / speed);
+       framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
+       framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
+       framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
 
-       nframes64_t const pf = adjusted_current_frame (event);
+       framepos_t const pf = adjusted_current_frame (event);
 
        if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
+               /* Move the contents of the region around without changing the region bounds */
                _operation = ContentsTrim;
-                Drag::start_grab (event, _editor->trimmer_cursor);
+               Drag::start_grab (event, _editor->cursors()->trimmer);
        } else {
                /* These will get overridden for a point trim.*/
                if (pf < (region_start + region_length/2)) {
-                       /* closer to start */
+                       /* closer to front */
                        _operation = StartTrim;
-                        Drag::start_grab (event, _editor->left_side_trim_cursor);
+                       Drag::start_grab (event, _editor->cursors()->left_side_trim);
                } else {
                        /* closer to end */
                        _operation = EndTrim;
-                        Drag::start_grab (event, _editor->right_side_trim_cursor);
-                }
+                       Drag::start_grab (event, _editor->cursors()->right_side_trim);
+               }
        }
 
        switch (_operation) {
        case StartTrim:
-               _editor->show_verbose_time_cursor (region_start, 10);
+               show_verbose_cursor_time (region_start);
+               for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+                       i->view->trim_front_starting ();
+               }
                break;
        case EndTrim:
-               _editor->show_verbose_time_cursor (region_end, 10);
+               show_verbose_cursor_time (region_end);
                break;
        case ContentsTrim:
-               _editor->show_verbose_time_cursor (pf, 10);
+               show_verbose_cursor_time (pf);
                break;
        }
+
+       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->region()->suspend_property_changes ();
+       }
 }
 
 void
@@ -1784,11 +1633,6 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
 {
        RegionView* rv = _primary;
 
-       /* snap modifier works differently here..
-          its current state has to be passed to the
-          various trim functions in order to work properly
-       */
-
        double speed = 1.0;
        TimeAxisView* tvp = &_primary->get_time_axis_view ();
        RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
@@ -1798,7 +1642,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                speed = tv->track()->speed();
        }
 
-       nframes64_t const pf = adjusted_current_frame (event);
+       framecnt_t const dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
 
        if (first_move) {
 
@@ -1817,18 +1661,16 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                }
 
                _editor->begin_reversible_command (trim_type);
-               _have_transaction = true;
 
                for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                        RegionView* rv = i->view;
-                       rv->fake_set_opaque(false);
+                       rv->fake_set_opaque (false);
                        rv->enable_display (false);
-                        rv->region()->clear_history ();
-                       rv->region()->suspend_property_changes ();
+                       rv->region()->playlist()->clear_owned_changes ();
 
                        AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
 
-                       if (arv){
+                       if (arv) {
                                arv->temporarily_hide_envelope ();
                        }
 
@@ -1850,13 +1692,13 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
        switch (_operation) {
        case StartTrim:
                for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                       _editor->single_start_trim (*i->view, pf, non_overlap_trim);
+                       i->view->trim_front (i->initial_position + dt, non_overlap_trim);
                }
                break;
 
        case EndTrim:
                for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                       _editor->single_end_trim (*i->view, pf, non_overlap_trim);
+                       i->view->trim_end (i->initial_end + dt, non_overlap_trim);
                }
                break;
 
@@ -1868,21 +1710,21 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                                swap_direction = true;
                        }
 
-                       nframes64_t frame_delta = 0;
-                       
+                       framecnt_t frame_delta = 0;
+
                        bool left_direction = false;
-                       if (last_pointer_frame() > pf) {
+                       if (last_pointer_frame() > adjusted_current_frame(event)) {
                                left_direction = true;
                        }
 
                        if (left_direction) {
-                               frame_delta = (last_pointer_frame() - pf);
+                               frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
                        } else {
-                               frame_delta = (pf - last_pointer_frame());
+                               frame_delta = (adjusted_current_frame(event) - last_pointer_frame());
                        }
 
                        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               _editor->single_contents_trim (*i->view, frame_delta, left_direction, swap_direction);
+                               i->view->trim_contents (frame_delta, left_direction, swap_direction);
                        }
                }
                break;
@@ -1890,13 +1732,13 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
 
        switch (_operation) {
        case StartTrim:
-               _editor->show_verbose_time_cursor((nframes64_t) (rv->region()->position()/speed), 10);
+               show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
                break;
        case EndTrim:
-               _editor->show_verbose_time_cursor((nframes64_t) (rv->region()->last_frame()/speed), 10);
+               show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed));
                break;
        case ContentsTrim:
-               _editor->show_verbose_time_cursor (pf, 10);
+               show_verbose_cursor_time (adjusted_current_frame (event));
                break;
        }
 }
@@ -1908,17 +1750,39 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
        if (movement_occurred) {
                motion (event, false);
 
+               /* This must happen before the region's StatefulDiffCommand is created, as it may
+                  `correct' (ahem) the region's _start from being negative to being zero.  It
+                  needs to be zero in the undo record.
+               */
+               if (_operation == StartTrim) {
+                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                               i->view->trim_front_ending ();
+                       }
+               }
+
                if (!_editor->selection->selected (_primary)) {
-                       _editor->thaw_region_after_trim (*_primary);
+                       _primary->thaw_after_trim ();
                } else {
 
+                       set<boost::shared_ptr<Playlist> > diffed_playlists;
+
                        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               _editor->thaw_region_after_trim (*i->view);
+                                i->view->thaw_after_trim ();
                                i->view->enable_display (true);
                                i->view->fake_set_opaque (true);
-                                if (_have_transaction) {
-                                        _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
-                                }
+
+                               /* Trimming one region may affect others on the playlist, so we need
+                                  to get undo Commands from the whole playlist rather than just the
+                                  region.  Use diffed_playlists to make sure we don't diff a given
+                                  playlist more than once.
+                               */
+                               boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
+                               if (diffed_playlists.find (p) == diffed_playlists.end()) {
+                                       vector<Command*> cmds;
+                                       p->rdiff (cmds);
+                                       _editor->session()->add_commands (cmds);
+                                       diffed_playlists.insert (p);
+                               }
                        }
                }
                for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
@@ -1926,19 +1790,24 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                }
 
                _editor->motion_frozen_playlists.clear ();
-
-               if (_have_transaction) {
-                       _editor->commit_reversible_command();
-               }
+               _editor->commit_reversible_command();
 
        } else {
                /* no mouse movement */
                _editor->point_trim (event, adjusted_current_frame (event));
        }
+
+       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+               if (_operation == StartTrim) {
+                       i->view->trim_front_ending ();
+               }
+
+               i->view->region()->resume_property_changes ();
+       }
 }
 
 void
-TrimDrag::aborted ()
+TrimDrag::aborted (bool movement_occurred)
 {
        /* Our motion method is changing model state, so use the Undo system
           to cancel.  Perhaps not ideal, as this will leave an Undo point
@@ -1946,16 +1815,46 @@ TrimDrag::aborted ()
        */
 
        finished (0, true);
-       
-       if (_have_transaction) {
+
+       if (movement_occurred) {
                _editor->undo ();
        }
+
+       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->region()->resume_property_changes ();
+       }
+}
+
+void
+TrimDrag::setup_pointer_frame_offset ()
+{
+       list<DraggingView>::iterator i = _views.begin ();
+       while (i != _views.end() && i->view != _primary) {
+               ++i;
+       }
+
+       if (i == _views.end()) {
+               return;
+       }
+
+       switch (_operation) {
+       case StartTrim:
+               _pointer_frame_offset = raw_grab_frame() - i->initial_position;
+               break;
+       case EndTrim:
+               _pointer_frame_offset = raw_grab_frame() - i->initial_end;
+               break;
+       case ContentsTrim:
+               break;
+       }
 }
 
 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
        : Drag (e, i),
          _copy (c)
 {
+       DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
+
        _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
        assert (_marker);
 }
@@ -1968,8 +1867,14 @@ MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                // The actual copying is not done before we reach the finish callback.
                char name[64];
                snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
-               MeterMarker* new_marker = new MeterMarker(*_editor, *_editor->meter_group, ARDOUR_UI::config()->canvasvar_MeterMarker.get(), name,
-                                                         *new MeterSection (_marker->meter()));
+
+               MeterMarker* new_marker = new MeterMarker (
+                       *_editor,
+                       *_editor->meter_group,
+                       ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
+                       name,
+                       *new MeterSection (_marker->meter())
+                       );
 
                _item = &new_marker->the_item ();
                _marker = new_marker;
@@ -1986,19 +1891,23 @@ MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        Drag::start_grab (event, cursor);
 
-       _pointer_frame_offset = grab_frame() - _marker->meter().frame();
+       show_verbose_cursor_time (adjusted_current_frame(event));
+}
 
-       _editor->show_verbose_time_cursor (adjusted_current_frame(event), 10);
+void
+MeterMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
 }
 
 void
 MeterMarkerDrag::motion (GdkEvent* event, bool)
 {
-       nframes64_t const pf = adjusted_current_frame (event);
+       framepos_t const pf = adjusted_current_frame (event);
 
        _marker->set_position (pf);
-       
-       _editor->show_verbose_time_cursor (pf, 10);
+
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -2010,7 +1919,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
-       BBT_Time when;
+       Timecode::BBT_Time when;
 
        TempoMap& map (_editor->session()->tempo_map());
        map.bbt_time (last_pointer_frame(), when);
@@ -2037,7 +1946,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-MeterMarkerDrag::aborted ()
+MeterMarkerDrag::aborted (bool)
 {
        _marker->set_position (_marker->meter().frame ());
 }
@@ -2046,6 +1955,8 @@ TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
        : Drag (e, i),
          _copy (c)
 {
+       DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
+
        _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
        assert (_marker);
 }
@@ -2053,40 +1964,43 @@ TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
-
        if (_copy) {
 
                // create a dummy marker for visual representation of moving the copy.
                // The actual copying is not done before we reach the finish callback.
                char name[64];
                snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
-               TempoMarker* new_marker = new TempoMarker(*_editor, *_editor->tempo_group, ARDOUR_UI::config()->canvasvar_TempoMarker.get(), name,
-                                                         *new TempoSection (_marker->tempo()));
+
+               TempoMarker* new_marker = new TempoMarker (
+                       *_editor,
+                       *_editor->tempo_group,
+                       ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
+                       name,
+                       *new TempoSection (_marker->tempo())
+                       );
 
                _item = &new_marker->the_item ();
                _marker = new_marker;
 
-       } else {
-
-               MetricSection& section (_marker->tempo());
-
-               if (!section.movable()) {
-                       return;
-               }
        }
 
        Drag::start_grab (event, cursor);
 
-       _pointer_frame_offset = grab_frame() - _marker->tempo().frame();
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
+}
+
+void
+TempoMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
 }
 
 void
 TempoMarkerDrag::motion (GdkEvent* event, bool)
 {
-       nframes64_t const pf = adjusted_current_frame (event);
+       framepos_t const pf = adjusted_current_frame (event);
        _marker->set_position (pf);
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -2098,7 +2012,7 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
-       BBT_Time when;
+       Timecode::BBT_Time when;
 
        TempoMap& map (_editor->session()->tempo_map());
        map.bbt_time (last_pointer_frame(), when);
@@ -2125,7 +2039,7 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-TempoMarkerDrag::aborted ()
+TempoMarkerDrag::aborted (bool)
 {
        _marker->set_position (_marker->tempo().frame());
 }
@@ -2134,78 +2048,104 @@ CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
        : Drag (e, i),
          _stop (s)
 {
-       _cursor = reinterpret_cast<EditorCursor*> (_item->get_data ("cursor"));
-       assert (_cursor);
+       DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
 }
 
+/** Do all the things we do when dragging the playhead to make it look as though
+ *  we have located, without actually doing the locate (because that would cause
+ *  the diskstream buffers to be refilled, which is too slow).
+ */
 void
-CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
+CursorDrag::fake_locate (framepos_t t)
 {
-       Drag::start_grab (event, c);
+       _editor->playhead_cursor->set_position (t);
 
-       if (!_stop) {
+       Session* s = _editor->session ();
+       if (s->timecode_transmission_suspended ()) {
+               framepos_t const f = _editor->playhead_cursor->current_frame;
+               s->send_mmc_locate (f);
+               s->send_full_time_code (f);
+       }
 
-               nframes64_t where = _editor->event_frame (event, 0, 0);
+       show_verbose_cursor_time (t);
+       _editor->UpdateAllTransportClocks (t);
+}
 
-               _editor->snap_to_with_modifier (where, event);
-               _editor->playhead_cursor->set_position (where);
+void
+CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
+{
+       Drag::start_grab (event, c);
 
-       }
+       _grab_zoom = _editor->frames_per_unit;
 
-       if (_cursor == _editor->playhead_cursor) {
-               _editor->_dragging_playhead = true;
+       framepos_t where = _editor->event_frame (event, 0, 0);
+       _editor->snap_to_with_modifier (where, event);
 
-               Session* s = _editor->session ();
+       _editor->_dragging_playhead = true;
 
-               if (s) {
-                       if (_was_rolling && _stop) {
-                               s->request_stop ();
-                       }
+       Session* s = _editor->session ();
 
-                       if (s->is_auditioning()) {
-                               s->cancel_audition ();
-                       }
+       if (s) {
+               if (_was_rolling && _stop) {
+                       s->request_stop ();
+               }
 
-                       s->request_suspend_timecode_transmission ();
+               if (s->is_auditioning()) {
+                       s->cancel_audition ();
+               }
 
-                       if (s->timecode_transmission_suspended ()) {
-                               nframes64_t const f = _editor->playhead_cursor->current_frame;
-                               s->send_mmc_locate (f);
-                               s->send_full_time_code (f);
-                       }
+               s->request_suspend_timecode_transmission ();
+               while (!s->timecode_transmission_suspended ()) {
+                       /* twiddle our thumbs */
                }
        }
 
-       _pointer_frame_offset = grab_frame() - _cursor->current_frame;
-
-       _editor->show_verbose_time_cursor (_cursor->current_frame, 10);
+       fake_locate (where);
 }
 
 void
 CursorDrag::motion (GdkEvent* event, bool)
 {
-       nframes64_t const adjusted_frame = adjusted_current_frame (event);
+       if (_drags->current_pointer_y() != last_pointer_y()) {
 
-       if (adjusted_frame == last_pointer_frame()) {
-               return;
-       }
+               /* zoom when we move the pointer up and down */
 
-       _cursor->set_position (adjusted_frame);
+               /* y range to operate over (pixels) */
+               double const y_range = 512;
+               /* we will multiply the grab zoom by a factor between scale_range and scale_range^-1 */
+               double const scale_range = 4;
+               /* dead zone around the grab point in which to do no zooming (pixels) */
+               double const dead_zone = 100;
 
-       _editor->show_verbose_time_cursor (_cursor->current_frame, 10);
+               /* current dy */
+               double dy = _drags->current_pointer_y() - grab_y();
 
-       Session* s = _editor->session ();
-       if (s && _item == &_editor->playhead_cursor->canvas_item && s->timecode_transmission_suspended ()) {
-               nframes64_t const f = _editor->playhead_cursor->current_frame;
-               s->send_mmc_locate (f);
-               s->send_full_time_code (f);
+               if (dy < -dead_zone || dy > dead_zone) {
+                       /* we are outside the dead zone; remove it from our calculation */
+                       if (dy < 0) {
+                               dy += dead_zone;
+                       } else {
+                               dy -= dead_zone;
+                       }
+
+                       /* get a number from -1 to 1 as dy ranges from -y_range to y_range */
+                       double udy = max (min (dy / y_range, 1.0), -1.0);
+
+                       /* and zoom, using playhead focus temporarily */
+                       Editing::ZoomFocus const zf = _editor->get_zoom_focus ();
+                       _editor->set_zoom_focus (Editing::ZoomFocusPlayhead);
+                       _editor->temporal_zoom (_grab_zoom * pow (scale_range, -udy));
+                       _editor->set_zoom_focus (zf);
+               }
        }
-       
 
+       framepos_t const adjusted_frame = adjusted_current_frame (event);
+       if (adjusted_frame != last_pointer_frame()) {
+               fake_locate (adjusted_frame);
 #ifdef GTKOSX
-       _editor->update_canvas_now ();
+               _editor->update_canvas_now ();
 #endif
-       _editor->UpdateAllTransportClocks (_cursor->current_frame);
+       }
 }
 
 void
@@ -2219,31 +2159,29 @@ CursorDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
-       if (_item == &_editor->playhead_cursor->canvas_item) {
-               Session* s = _editor->session ();
-               if (s) {
-                       s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
-                       _editor->_pending_locate_request = true;
-                       s->request_resume_timecode_transmission ();
-               }
+       Session* s = _editor->session ();
+       if (s) {
+               s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
+               _editor->_pending_locate_request = true;
+               s->request_resume_timecode_transmission ();
        }
 }
 
 void
-CursorDrag::aborted ()
+CursorDrag::aborted (bool)
 {
        if (_editor->_dragging_playhead) {
                _editor->session()->request_resume_timecode_transmission ();
                _editor->_dragging_playhead = false;
        }
-       
-       _cursor->set_position (adjusted_frame (grab_frame (), 0, false));
+
+       _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
 }
 
 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
        : RegionDrag (e, i, p, v)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
 }
 
 void
@@ -2251,20 +2189,28 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
        Drag::start_grab (event, cursor);
 
-       AudioRegionView* a = dynamic_cast<AudioRegionView*> (_primary);
-       boost::shared_ptr<AudioRegion> const r = a->audio_region ();
+       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
+       boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
+
+       show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
+
+       arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
+}
 
-       _pointer_frame_offset = grab_frame() - ((nframes64_t) r->fade_in()->back()->when + r->position());
-       _editor->show_verbose_duration_cursor (r->position(), r->position() + r->fade_in()->back()->when, 10);
-       
+void
+FadeInDrag::setup_pointer_frame_offset ()
+{
+       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
+       boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
+       _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
 }
 
 void
 FadeInDrag::motion (GdkEvent* event, bool)
 {
-       nframes64_t fade_length;
+       framecnt_t fade_length;
 
-       nframes64_t const pos = adjusted_current_frame (event);
+       framepos_t const pos = adjusted_current_frame (event);
 
        boost::shared_ptr<Region> region = _primary->region ();
 
@@ -2285,9 +2231,10 @@ FadeInDrag::motion (GdkEvent* event, bool)
                }
 
                tmp->reset_fade_in_shape_width (fade_length);
+               tmp->show_fade_line((framecnt_t) fade_length);
        }
 
-       _editor->show_verbose_duration_cursor (region->position(), region->position() + fade_length, 10);
+       show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
 }
 
 void
@@ -2297,9 +2244,9 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
                return;
        }
 
-       nframes64_t fade_length;
+       framecnt_t fade_length;
 
-       nframes64_t const pos = adjusted_current_frame (event);
+       framepos_t const pos = adjusted_current_frame (event);
 
        boost::shared_ptr<Region> region = _primary->region ();
 
@@ -2326,6 +2273,7 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
 
                tmp->audio_region()->set_fade_in_length (fade_length);
                tmp->audio_region()->set_fade_in_active (true);
+               tmp->hide_fade_line();
 
                XMLNode &after = alist->get_state();
                _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
@@ -2335,7 +2283,7 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-FadeInDrag::aborted ()
+FadeInDrag::aborted (bool)
 {
        for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
                AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
@@ -2345,13 +2293,14 @@ FadeInDrag::aborted ()
                }
 
                tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
+               tmp->hide_fade_line();
        }
 }
 
 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
        : RegionDrag (e, i, p, v)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
 }
 
 void
@@ -2359,19 +2308,28 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
        Drag::start_grab (event, cursor);
 
-       AudioRegionView* a = dynamic_cast<AudioRegionView*> (_primary);
-       boost::shared_ptr<AudioRegion> r = a->audio_region ();
+       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
+       boost::shared_ptr<AudioRegion> r = arv->audio_region ();
 
-       _pointer_frame_offset = grab_frame() - (r->length() - (nframes64_t) r->fade_out()->back()->when + r->position());
-       _editor->show_verbose_duration_cursor (r->last_frame() - r->fade_out()->back()->when, r->last_frame(), 10);
+       show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
+
+       arv->show_fade_line(r->length() - r->fade_out()->back()->when);
+}
+
+void
+FadeOutDrag::setup_pointer_frame_offset ()
+{
+       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
+       boost::shared_ptr<AudioRegion> r = arv->audio_region ();
+       _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
 }
 
 void
 FadeOutDrag::motion (GdkEvent* event, bool)
 {
-       nframes64_t fade_length;
+       framecnt_t fade_length;
 
-       nframes64_t const pos = adjusted_current_frame (event);
+       framepos_t const pos = adjusted_current_frame (event);
 
        boost::shared_ptr<Region> region = _primary->region ();
 
@@ -2394,9 +2352,10 @@ FadeOutDrag::motion (GdkEvent* event, bool)
                }
 
                tmp->reset_fade_out_shape_width (fade_length);
+               tmp->show_fade_line(region->length() - fade_length);
        }
 
-       _editor->show_verbose_duration_cursor (region->last_frame() - fade_length, region->last_frame(), 10);
+       show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
 }
 
 void
@@ -2406,9 +2365,9 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
                return;
        }
 
-       nframes64_t fade_length;
+       framecnt_t fade_length;
 
-       nframes64_t const pos = adjusted_current_frame (event);
+       framepos_t const pos = adjusted_current_frame (event);
 
        boost::shared_ptr<Region> region = _primary->region ();
 
@@ -2437,6 +2396,7 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
 
                tmp->audio_region()->set_fade_out_length (fade_length);
                tmp->audio_region()->set_fade_out_active (true);
+               tmp->hide_fade_line();
 
                XMLNode &after = alist->get_state();
                _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
@@ -2446,7 +2406,7 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-FadeOutDrag::aborted ()
+FadeOutDrag::aborted (bool)
 {
        for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
                AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
@@ -2456,24 +2416,20 @@ FadeOutDrag::aborted ()
                }
 
                tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
+               tmp->hide_fade_line();
        }
 }
 
 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
 {
+       DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
+
        _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
        assert (_marker);
 
        _points.push_back (Gnome::Art::Point (0, 0));
        _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
-
-       _line = new ArdourCanvas::Line (*_editor->timebar_group);
-       _line->property_width_pixels() = 1;
-       _line->property_points () = _points;
-       _line->hide ();
-
-       _line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MarkerDragLine.get();
 }
 
 MarkerDrag::~MarkerDrag ()
@@ -2493,17 +2449,15 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        Location *location = _editor->find_location_from_marker (_marker, is_start);
        _editor->_dragging_edit_point = true;
 
-       _pointer_frame_offset = grab_frame() - (is_start ? location->start() : location->end());
-
        update_item (location);
 
        // _drag_line->show();
        // _line->raise_to_top();
 
        if (is_start) {
-               _editor->show_verbose_time_cursor (location->start(), 10);
+               show_verbose_cursor_time (location->start());
        } else {
-               _editor->show_verbose_time_cursor (location->end(), 10);
+               show_verbose_cursor_time (location->end());
        }
 
        Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
@@ -2521,13 +2475,13 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        {
                Locations::LocationList ll;
                list<Marker*> to_add;
-               nframes64_t s, e;
+               framepos_t s, e;
                _editor->selection->markers.range (s, e);
                s = min (_marker->position(), s);
                e = max (_marker->position(), e);
                s = min (s, e);
                e = max (s, e);
-               if (e < max_frames) {
+               if (e < max_framepos) {
                        ++e;
                }
                _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
@@ -2560,23 +2514,27 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        }
 }
 
+void
+MarkerDrag::setup_pointer_frame_offset ()
+{
+       bool is_start;
+       Location *location = _editor->find_location_from_marker (_marker, is_start);
+       _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
+}
+
 void
 MarkerDrag::motion (GdkEvent* event, bool)
 {
-       nframes64_t f_delta = 0;
+       framecnt_t f_delta = 0;
        bool is_start;
        bool move_both = false;
        Marker* marker;
        Location *real_location;
        Location *copy_location = 0;
 
-       nframes64_t const newframe = adjusted_current_frame (event);
-
-       nframes64_t next = newframe;
+       framepos_t const newframe = adjusted_current_frame (event);
 
-       if (newframe == last_pointer_frame()) {
-               return;
-       }
+       framepos_t next = newframe;
 
        if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
                move_both = true;
@@ -2607,13 +2565,15 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
 
                                switch (marker->type()) {
-                               case Marker::Start:
+                               case Marker::SessionStart:
+                               case Marker::RangeStart:
                                case Marker::LoopStart:
                                case Marker::PunchIn:
                                        f_delta = newframe - copy_location->start();
                                        break;
 
-                               case Marker::End:
+                               case Marker::SessionEnd:
+                               case Marker::RangeEnd:
                                case Marker::LoopEnd:
                                case Marker::PunchOut:
                                        f_delta = newframe - copy_location->end();
@@ -2659,8 +2619,8 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
                } else {
 
-                       nframes64_t new_start = copy_location->start() + f_delta;
-                       nframes64_t new_end = copy_location->end() + f_delta;
+                       framepos_t new_start = copy_location->start() + f_delta;
+                       framepos_t new_end = copy_location->end() + f_delta;
 
                        if (is_start) { // start-of-range marker
 
@@ -2669,7 +2629,7 @@ MarkerDrag::motion (GdkEvent* event, bool)
                                        copy_location->set_end (new_end);
                                } else  if (new_start < copy_location->end()) {
                                        copy_location->set_start (new_start);
-                               } else {
+                               } else if (newframe > 0) {
                                        _editor->snap_to (next, 1, true);
                                        copy_location->set_end (next);
                                        copy_location->set_start (newframe);
@@ -2701,7 +2661,7 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
        assert (!_copied_locations.empty());
 
-       _editor->show_verbose_time_cursor (newframe, 10);
+       show_verbose_cursor_time (newframe);
 
 #ifdef GTKOSX
        _editor->update_canvas_now ();
@@ -2767,12 +2727,10 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        XMLNode &after = _editor->session()->locations()->get_state();
        _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
        _editor->commit_reversible_command ();
-
-       _line->hide();
 }
 
 void
-MarkerDrag::aborted ()
+MarkerDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -2780,11 +2738,7 @@ MarkerDrag::aborted ()
 void
 MarkerDrag::update_item (Location* location)
 {
-       double const x1 = _editor->frame_to_pixel (location->start());
-
-       _points.front().set_x(x1);
-       _points.back().set_x(x1);
-       _line->property_points() = _points;
+        /* noop */
 }
 
 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
@@ -2792,6 +2746,12 @@ ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
          _cumulative_x_drag (0),
          _cumulative_y_drag (0)
 {
+       if (_zero_gain_fraction < 0.0) {
+               _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
+       }
+
+       DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
+
        _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
        assert (_point);
 }
@@ -2800,7 +2760,7 @@ ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
 void
 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 {
-       Drag::start_grab (event, _editor->fader_cursor);
+       Drag::start_grab (event, _editor->cursors()->fader);
 
        // start the grab at the center of the control point so
        // the point doesn't 'jump' to the mouse after the first drag
@@ -2811,10 +2771,10 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
 
-       _editor->set_verbose_canvas_cursor (_point->line().get_verbose_cursor_string (fraction),
-                                           event->button.x + 10, event->button.y + 10);
+       _editor->verbose_cursor()->set (_point->line().get_verbose_cursor_string (fraction),
+                                       event->button.x + 10, event->button.y + 10);
 
-       _editor->show_verbose_canvas_cursor ();
+       _editor->verbose_cursor()->show ();
 }
 
 void
@@ -2857,7 +2817,7 @@ ControlPointDrag::motion (GdkEvent* event, bool)
        cy = min ((double) _point->line().height(), cy);
 
        framepos_t cx_frames = _editor->unit_to_frame (cx);
-       
+
        if (!_x_constrained) {
                _editor->snap_to_with_modifier (cx_frames, event);
        }
@@ -2870,7 +2830,7 @@ ControlPointDrag::motion (GdkEvent* event, bool)
 
        _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
 
-       _editor->set_verbose_canvas_cursor_text (_point->line().get_verbose_cursor_string (fraction));
+       _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
 }
 
 void
@@ -2887,11 +2847,13 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
        } else {
                motion (event, false);
        }
+
        _point->line().end_drag ();
+       _editor->session()->commit_reversible_command ();
 }
 
 void
-ControlPointDrag::aborted ()
+ControlPointDrag::aborted (bool)
 {
        _point->line().reset ();
 }
@@ -2913,8 +2875,9 @@ LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
          _line (0),
          _cumulative_y_drag (0)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
 }
+
 void
 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 {
@@ -2932,17 +2895,17 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        _line->parent_group().w2i (cx, cy);
 
-       nframes64_t const frame_within_region = (nframes64_t) floor (cx * _editor->frames_per_unit);
+       framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit);
 
        uint32_t before;
        uint32_t after;
-       
+
        if (!_line->control_points_adjacent (frame_within_region, before, after)) {
                /* no adjacent points */
                return;
        }
 
-       Drag::start_grab (event, _editor->fader_cursor);
+       Drag::start_grab (event, _editor->cursors()->fader);
 
        /* store grab start in parent frame */
 
@@ -2953,10 +2916,10 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        _line->start_drag_line (before, after, fraction);
 
-       _editor->set_verbose_canvas_cursor (_line->get_verbose_cursor_string (fraction),
-                                           event->button.x + 10, event->button.y + 10);
+       _editor->verbose_cursor()->set (_line->get_verbose_cursor_string (fraction),
+                                       event->button.x + 10, event->button.y + 10);
 
-       _editor->show_verbose_canvas_cursor ();
+       _editor->verbose_cursor()->show ();
 }
 
 void
@@ -2988,7 +2951,7 @@ LineDrag::motion (GdkEvent* event, bool)
        /* we are ignoring x position for this drag, so we can just pass in anything */
        _line->drag_motion (0, fraction, true, push);
 
-       _editor->set_verbose_canvas_cursor_text (_line->get_verbose_cursor_string (fraction));
+       _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
 }
 
 void
@@ -2996,10 +2959,11 @@ LineDrag::finished (GdkEvent* event, bool)
 {
        motion (event, false);
        _line->end_drag ();
+       _editor->session()->commit_reversible_command ();
 }
 
 void
-LineDrag::aborted ()
+LineDrag::aborted (bool)
 {
        _line->reset ();
 }
@@ -3009,15 +2973,15 @@ FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
          _line (0),
          _cumulative_x_drag (0)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
 }
+
 void
 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 {
-  
        Drag::start_grab (event);
-       
-       _line = reinterpret_cast<SimpleLine*> (_item);
+
+       _line = reinterpret_cast<Line*> (_item);
        assert (_line);
 
        /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
@@ -3029,69 +2993,88 @@ FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        /* store grab start in parent frame */
        _region_view_grab_x = cx;
-       
-       _before = _line->property_x1();
-       
+
+       _before = *(float*) _item->get_data ("position");
+
        _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
-               
+
        _max_x = _editor->frame_to_pixel(_arv->get_duration());
 }
 
 void
-FeatureLineDrag::motion (GdkEvent* event, bool)
+FeatureLineDrag::motion (GdkEvent*, bool)
 {
        double dx = _drags->current_pointer_x() - last_pointer_x();
-       
+
        double cx = _region_view_grab_x + _cumulative_x_drag + dx;
-       
+
        _cumulative_x_drag += dx;
-               
+
        /* Clamp the min and max extent of the drag to keep it within the region view bounds */
-       
+
        if (cx > _max_x){
                cx = _max_x;
        }
        else if(cx < 0){
                cx = 0;
        }
-       
-       _line->property_x1() = cx; 
-       _line->property_x2() = cx;
 
-       _before = _line->property_x1();
+       ArdourCanvas::Points points;
+
+       double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
+
+       _line->get_bounds(x1, y2, x2, y2);
+
+       points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
+       points.push_back(Gnome::Art::Point(cx, y2 - y1));
+
+       _line->property_points() = points;
+
+       float *pos = new float;
+       *pos = cx;
+
+       _line->set_data ("position", pos);
+
+       _before = cx;
 }
 
 void
-FeatureLineDrag::finished (GdkEvent* event, bool)
+FeatureLineDrag::finished (GdkEvent*, bool)
 {
        _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
-       _arv->update_transient(_before, _line->property_x1());
+       _arv->update_transient(_before, _before);
 }
 
 void
-FeatureLineDrag::aborted ()
+FeatureLineDrag::aborted (bool)
 {
        //_line->reset ();
 }
 
+RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
+}
+
 void
 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        Drag::start_grab (event);
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
 RubberbandSelectDrag::motion (GdkEvent* event, bool)
 {
-       nframes64_t start;
-       nframes64_t end;
+       framepos_t start;
+       framepos_t end;
        double y1;
        double y2;
 
-       nframes64_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
+       framepos_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
 
-       nframes64_t grab = grab_frame ();
+       framepos_t grab = grab_frame ();
        if (Config->get_rubberbanding_snaps_to_grid ()) {
                _editor->snap_to_with_modifier (grab, event);
        }
@@ -3128,7 +3111,7 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
                _editor->rubberband_rect->show();
                _editor->rubberband_rect->raise_to_top();
 
-               _editor->show_verbose_time_cursor (pf, 10);
+               show_verbose_cursor_time (pf);
        }
 }
 
@@ -3150,19 +3133,16 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
 
 
                Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
-               bool committed;
 
                _editor->begin_reversible_command (_("rubberband selection"));
 
                if (grab_frame() < last_pointer_frame()) {
-                       committed = _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
+                       _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
                } else {
-                       committed = _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
+                       _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
                }
 
-               if (!committed) {
-                       _editor->commit_reversible_command ();
-               }
+               _editor->commit_reversible_command ();
 
        } else {
                if (!getenv("ARDOUR_SAE")) {
@@ -3177,17 +3157,23 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RubberbandSelectDrag::aborted ()
+RubberbandSelectDrag::aborted (bool)
 {
        _editor->rubberband_rect->hide ();
 }
 
+TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
+       : RegionDrag (e, i, p, v)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
+}
+
 void
-TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
+TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
-       Drag::start_grab (event);
+       Drag::start_grab (event, cursor);
 
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
@@ -3195,13 +3181,13 @@ TimeFXDrag::motion (GdkEvent* event, bool)
 {
        RegionView* rv = _primary;
 
-       nframes64_t const pf = adjusted_current_frame (event);
+       framepos_t const pf = adjusted_current_frame (event);
 
        if (pf > rv->region()->position()) {
                rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
        }
 
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -3218,7 +3204,7 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                return;
        }
 
-       nframes64_t newlen = last_pointer_frame() - _primary->region()->position();
+       framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
 
        float percentage = (double) newlen / (double) _primary->region()->length();
 
@@ -3229,8 +3215,6 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 #endif
 
-       _editor->begin_reversible_command (_("timestretch"));
-
        // XXX how do timeFX on multiple regions ?
 
        RegionSelection rs;
@@ -3242,11 +3226,16 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 }
 
 void
-TimeFXDrag::aborted ()
+TimeFXDrag::aborted (bool)
 {
        _primary->get_time_axis_view().hide_timestretch ();
 }
 
+ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
+}
 
 void
 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
@@ -3270,7 +3259,7 @@ ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 }
 
 void
-ScrubDrag::aborted ()
+ScrubDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3282,15 +3271,12 @@ SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
        , _original_pointer_time_axis (-1)
        , _last_pointer_time_axis (-1)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
 }
 
 void
 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
 {
-       nframes64_t start = 0;
-       nframes64_t end = 0;
-
        if (_editor->session() == 0) {
                return;
        }
@@ -3304,7 +3290,7 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
                } else {
                        _copy = false;
                }
-               cursor = _editor->selector_cursor;
+               cursor = _editor->cursors()->selector;
                Drag::start_grab (event, cursor);
                break;
 
@@ -3312,49 +3298,62 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
                if (_editor->clicked_axisview) {
                        _editor->clicked_axisview->order_selection_trims (_item, true);
                }
-               Drag::start_grab (event, _editor->left_side_trim_cursor);
-               start = _editor->selection->time[_editor->clicked_selection].start;
-               _pointer_frame_offset = grab_frame() - start;
+               Drag::start_grab (event, _editor->cursors()->left_side_trim);
                break;
 
        case SelectionEndTrim:
                if (_editor->clicked_axisview) {
                        _editor->clicked_axisview->order_selection_trims (_item, false);
                }
-               Drag::start_grab (event, _editor->right_side_trim_cursor);
-               end = _editor->selection->time[_editor->clicked_selection].end;
-               _pointer_frame_offset = grab_frame() - end;
+               Drag::start_grab (event, _editor->cursors()->right_side_trim);
                break;
 
        case SelectionMove:
-               start = _editor->selection->time[_editor->clicked_selection].start;
                Drag::start_grab (event, cursor);
-               _pointer_frame_offset = grab_frame() - start;
                break;
        }
 
        if (_operation == SelectionMove) {
-               _editor->show_verbose_time_cursor (start, 10);
+               show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
        } else {
-               _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+               show_verbose_cursor_time (adjusted_current_frame (event));
        }
 
        _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
 }
 
+void
+SelectionDrag::setup_pointer_frame_offset ()
+{
+       switch (_operation) {
+       case CreateSelection:
+               _pointer_frame_offset = 0;
+               break;
+
+       case SelectionStartTrim:
+       case SelectionMove:
+               _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
+               break;
+
+       case SelectionEndTrim:
+               _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
+               break;
+       }
+}
+
 void
 SelectionDrag::motion (GdkEvent* event, bool first_move)
 {
-       nframes64_t start = 0;
-       nframes64_t end = 0;
-       nframes64_t length;
+       framepos_t start = 0;
+       framepos_t end = 0;
+       framecnt_t length;
 
        pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        if (pending_time_axis.first == 0) {
                return;
        }
-       
-       nframes64_t const pending_position = adjusted_current_frame (event);
+
+       framepos_t const pending_position = adjusted_current_frame (event);
 
        /* only alter selection if things have changed */
 
@@ -3365,7 +3364,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        switch (_operation) {
        case CreateSelection:
        {
-               nframes64_t grab = grab_frame ();
+               framepos_t grab = grab_frame ();
 
                if (first_move) {
                        _editor->snap_to (grab);
@@ -3387,22 +3386,25 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
                        if (_copy) {
                                /* adding to the selection */
-                               _editor->selection->add (_editor->clicked_axisview);
+                               _editor->set_selected_track_as_side_effect (Selection::Add);
+                               //_editor->selection->add (_editor->clicked_axisview);
                                _editor->clicked_selection = _editor->selection->add (start, end);
                                _copy = false;
                        } else {
                                /* new selection */
 
                                if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
-                                       _editor->selection->set (_editor->clicked_axisview);
+                                       //_editor->selection->set (_editor->clicked_axisview);
+                                       _editor->set_selected_track_as_side_effect (Selection::Set);
                                }
-                               
+
                                _editor->clicked_selection = _editor->selection->set (start, end);
                        }
                }
 
                /* select the track that we're in */
                if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
+                       // _editor->set_selected_track_as_side_effect (Selection::Add);
                        _editor->selection->add (pending_time_axis.first);
                        _added_time_axes.push_back (pending_time_axis.first);
                }
@@ -3410,7 +3412,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                /* deselect any tracks that this drag no longer includes, being careful to only deselect
                   tracks that we selected in the first place.
                */
-               
+
                int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
                int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
 
@@ -3419,7 +3421,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
                        list<TimeAxisView*>::iterator tmp = i;
                        ++tmp;
-                       
+
                        if ((*i)->order() < min_order || (*i)->order() > max_order) {
                                _editor->selection->remove (*i);
                                _added_time_axes.remove (*i);
@@ -3480,9 +3482,9 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        }
 
        if (_operation == SelectionMove) {
-               _editor->show_verbose_time_cursor(start, 10);
+               show_verbose_cursor_time(start);
        } else {
-               _editor->show_verbose_time_cursor(pending_position, 10);
+               show_verbose_cursor_time(pending_position);
        }
 }
 
@@ -3514,7 +3516,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
                if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
                        _editor->selection->set (_editor->clicked_axisview);
                }
-               
+
                if (s && s->get_play_range () && s->transport_rolling()) {
                        s->request_stop (false, false);
                }
@@ -3525,7 +3527,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-SelectionDrag::aborted ()
+SelectionDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3535,8 +3537,10 @@ RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operat
          _operation (o),
          _copy (false)
 {
-       _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0, 
-                                                   physical_screen_height (_editor->get_window()));
+       DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
+
+       _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
+                                                  physical_screen_height (_editor->get_window()));
        _drag_rect->hide ();
 
        _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
@@ -3566,20 +3570,20 @@ RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
                } else {
                        _copy = false;
                }
-               cursor = _editor->selector_cursor;
+               cursor = _editor->cursors()->selector;
                break;
        }
 
        Drag::start_grab (event, cursor);
 
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
 {
-       nframes64_t start = 0;
-       nframes64_t end = 0;
+       framepos_t start = 0;
+       framepos_t end = 0;
        ArdourCanvas::SimpleRect *crect;
 
        switch (_operation) {
@@ -3598,12 +3602,12 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
                break;
        }
 
-       nframes64_t const pf = adjusted_current_frame (event);
+       framepos_t const pf = adjusted_current_frame (event);
 
        if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
-               nframes64_t grab = grab_frame ();
+               framepos_t grab = grab_frame ();
                _editor->snap_to (grab);
-               
+
                if (pf < grab_frame()) {
                        start = pf;
                        end = grab;
@@ -3644,7 +3648,7 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
                update_item (_editor->temp_location);
        }
 
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 
 }
 
@@ -3677,7 +3681,7 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
                        newloc = new Location (
                                *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
                                );
-                       
+
                        _editor->session()->locations()->add (newloc, true);
                        XMLNode &after = _editor->session()->locations()->get_state();
                        _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
@@ -3695,16 +3699,16 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
 
                if (Keyboard::no_modifier_keys_pressed (&event->button) && _operation != CreateCDMarker) {
 
-                       nframes64_t start;
-                       nframes64_t end;
+                       framepos_t start;
+                       framepos_t end;
 
                        _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
 
-                       if (end == max_frames) {
+                       if (end == max_framepos) {
                                end = _editor->session()->current_end_frame ();
                        }
 
-                       if (start == max_frames) {
+                       if (start == max_framepos) {
                                start = _editor->session()->current_start_frame ();
                        }
 
@@ -3729,7 +3733,7 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RangeMarkerBarDrag::aborted ()
+RangeMarkerBarDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3744,22 +3748,36 @@ RangeMarkerBarDrag::update_item (Location* location)
        _drag_rect->property_x2() = x2;
 }
 
+MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+       , _zoom_out (false)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New MouseZoomDrag\n");
+}
+
 void
 MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
-       Drag::start_grab (event, _editor->zoom_cursor);
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
+               Drag::start_grab (event, _editor->cursors()->zoom_out);
+               _zoom_out = true;
+       } else {
+               Drag::start_grab (event, _editor->cursors()->zoom_in);
+               _zoom_out = false;
+       }
+
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
 MouseZoomDrag::motion (GdkEvent* event, bool first_move)
 {
-       nframes64_t start;
-       nframes64_t end;
+       framepos_t start;
+       framepos_t end;
 
-       nframes64_t const pf = adjusted_current_frame (event);
+       framepos_t const pf = adjusted_current_frame (event);
 
-       nframes64_t grab = grab_frame ();
+       framepos_t grab = grab_frame ();
        _editor->snap_to_with_modifier (grab, event);
 
        /* base start and end on initial click position */
@@ -3780,7 +3798,7 @@ MouseZoomDrag::motion (GdkEvent* event, bool first_move)
 
                _editor->reposition_zoom_rect(start, end);
 
-               _editor->show_verbose_time_cursor (pf, 10);
+               show_verbose_cursor_time (pf);
        }
 }
 
@@ -3796,18 +3814,18 @@ MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
                        _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
                }
        } else {
-               _editor->temporal_zoom_to_frame (false, grab_frame());
-               /*
-               temporal_zoom_step (false);
-               center_screen (grab_frame());
-               */
+               if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
+                       _editor->tav_zoom_step (_zoom_out);
+               } else {
+                       _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
+               }
        }
 
        _editor->zoom_rect->hide();
 }
 
 void
-MouseZoomDrag::aborted ()
+MouseZoomDrag::aborted (bool)
 {
        _editor->zoom_rect->hide ();
 }
@@ -3817,6 +3835,8 @@ NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
        , _cumulative_dx (0)
        , _cumulative_dy (0)
 {
+       DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
+
        _primary = dynamic_cast<CanvasNoteEvent*> (_item);
        _region = &_primary->region_view ();
        _note_height = _region->midi_stream_view()->note_height ();
@@ -3857,9 +3877,14 @@ NoteDrag::total_dx () const
 
        /* primary note time */
        frameoffset_t const n = _region->beats_to_frames (_primary->note()->time ());
-       
+
        /* new time of the primary note relative to the region position */
-       frameoffset_t const st = n + dx;
+       frameoffset_t st = n + dx;
+
+       /* prevent the note being dragged earlier than the region's position */
+       if (st < 0) {
+               st = 0;
+       }
 
        /* snap and return corresponding delta */
        return _region->snap_frame_to_frame (st) - n;
@@ -3883,32 +3908,38 @@ NoteDrag::total_dy () const
                }
        }
 
-       return ndy;
+       /* more positive value = higher pitch and higher y-axis position on track,
+          which is the inverse of the X-centric geometric universe
+       */
+
+       return -ndy;
 }
-       
 
 void
 NoteDrag::motion (GdkEvent *, bool)
 {
        /* Total change in x and y since the start of the drag */
        frameoffset_t const dx = total_dx ();
-       int8_t const dy = total_dy ();
+       int8_t const dy = -total_dy ();
 
        /* Now work out what we have to do to the note canvas items to set this new drag delta */
        double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx;
        double const tdy = dy * _note_height - _cumulative_dy;
 
        if (tdx || tdy) {
-               _region->move_selection (tdx, tdy);
                _cumulative_dx += tdx;
                _cumulative_dy += tdy;
 
-                char buf[12];
-                snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (_primary->note()->note() + dy).c_str(),
-                          (int) floor (_primary->note()->note() + dy));
-               
-               _editor->show_verbose_canvas_cursor_with (buf);
-        }
+               int8_t note_delta = total_dy();
+
+               _region->move_selection (tdx, tdy, note_delta);
+
+               char buf[12];
+               snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (_primary->note()->note() + note_delta).c_str(),
+                         (int) floor (_primary->note()->note() + note_delta));
+
+               show_verbose_cursor_text (buf);
+       }
 }
 
 void
@@ -3936,25 +3967,59 @@ NoteDrag::finished (GdkEvent* ev, bool moved)
                        }
                }
        } else {
-               _region->note_dropped (_primary, total_dx(), total_dy());
+               _region->note_dropped (_primary, total_dx(), total_dy());
        }
 }
 
 void
-NoteDrag::aborted ()
+NoteDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
 
-AutomationRangeDrag::AutomationRangeDrag (Editor* e, ArdourCanvas::Item* i, list<AudioRange> const & r)
-       : Drag (e, i)
+AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* item, list<AudioRange> const & r)
+       : Drag (editor, item)
        , _ranges (r)
        , _nothing_to_drag (false)
 {
+       DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
+
        _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
        assert (_atav);
 
-       _line = _atav->line ();
+       /* get all lines in the automation view */
+       list<boost::shared_ptr<AutomationLine> > lines = _atav->lines ();
+
+       /* find those that overlap the ranges being dragged */
+       list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
+       while (i != lines.end ()) {
+               list<boost::shared_ptr<AutomationLine> >::iterator j = i;
+               ++j;
+
+               pair<framepos_t, framepos_t> const r = (*i)->get_point_x_range ();
+
+               /* check this range against all the AudioRanges that we are using */
+               list<AudioRange>::const_iterator k = _ranges.begin ();
+               while (k != _ranges.end()) {
+                       if (k->coverage (r.first, r.second) != OverlapNone) {
+                               break;
+                       }
+                       ++k;
+               }
+
+               /* add it to our list if it overlaps at all */
+               if (k != _ranges.end()) {
+                       Line n;
+                       n.line = *i;
+                       n.state = 0;
+                       n.range = r;
+                       _lines.push_back (n);
+               }
+
+               i = j;
+       }
+
+       /* Now ::lines contains the AutomationLines that somehow overlap our drag */
 }
 
 void
@@ -3962,80 +4027,135 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
        Drag::start_grab (event, cursor);
 
-       list<ControlPoint*> points;
+       /* Get line states before we start changing things */
+       for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
+               i->state = &i->line->get_state ();
+       }
 
-       XMLNode* state = &_line->get_state ();
-       
        if (_ranges.empty()) {
-               
-               uint32_t const N = _line->npoints ();
-               for (uint32_t i = 0; i < N; ++i) {
-                       points.push_back (_line->nth (i));
+
+               /* No selected time ranges: drag all points */
+               for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
+                       uint32_t const N = i->line->npoints ();
+                       for (uint32_t j = 0; j < N; ++j) {
+                               i->points.push_back (i->line->nth (j));
+                       }
                }
-               
+
        } else {
 
-               boost::shared_ptr<AutomationList> the_list = _line->the_list ();
-               for (list<AudioRange>::const_iterator j = _ranges.begin(); j != _ranges.end(); ++j) {
+               for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
 
-                       /* fade into and out of the region that we're dragging;
-                          64 samples length plucked out of thin air.
-                       */
-                       nframes64_t const h = (j->start + j->end) / 2;
-                       nframes64_t a = j->start + 64;
-                       if (a > h) {
-                               a = h;
-                       }
-                       nframes64_t b = j->end - 64;
-                       if (b < h) {
-                               b = h;
+                       framecnt_t const half = (i->start + i->end) / 2;
+
+                       /* find the line that this audio range starts in */
+                       list<Line>::iterator j = _lines.begin();
+                       while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
+                               ++j;
                        }
-                       
-                       the_list->add (j->start, the_list->eval (j->start));
-                       _line->add_always_in_view (j->start);
-                       the_list->add (a, the_list->eval (a));
-                       _line->add_always_in_view (a);
-                       the_list->add (b, the_list->eval (b));
-                       _line->add_always_in_view (b);
-                       the_list->add (j->end, the_list->eval (j->end));
-                       _line->add_always_in_view (j->end);
-               }
 
-               uint32_t const N = _line->npoints ();
-               for (uint32_t i = 0; i < N; ++i) {
+                       if (j != _lines.end()) {
+                               boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
+
+                               /* j is the line that this audio range starts in; fade into it;
+                                  64 samples length plucked out of thin air.
+                               */
+
+                               framepos_t a = i->start + 64;
+                               if (a > half) {
+                                       a = half;
+                               }
+
+                               double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
+                               double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
+
+                               the_list->add (p, the_list->eval (p));
+                               j->line->add_always_in_view (p);
+                               the_list->add (q, the_list->eval (q));
+                               j->line->add_always_in_view (q);
+                       }
 
-                       ControlPoint* p = _line->nth (i);
+                       /* same thing for the end */
 
-                       list<AudioRange>::const_iterator j = _ranges.begin ();
-                       while (j != _ranges.end() && (j->start >= (*p->model())->when || j->end <= (*p->model())->when)) {
+                       j = _lines.begin();
+                       while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
                                ++j;
                        }
 
-                       if (j != _ranges.end()) {
-                               points.push_back (p);
+                       if (j != _lines.end()) {
+                               boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
+
+                               /* j is the line that this audio range starts in; fade out of it;
+                                  64 samples length plucked out of thin air.
+                               */
+
+                               framepos_t b = i->end - 64;
+                               if (b < half) {
+                                       b = half;
+                               }
+
+                               double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
+                               double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
+
+                               the_list->add (p, the_list->eval (p));
+                               j->line->add_always_in_view (p);
+                               the_list->add (q, the_list->eval (q));
+                               j->line->add_always_in_view (q);
                        }
                }
-       }
 
-       if (points.empty()) {
                _nothing_to_drag = true;
+
+               /* Find all the points that should be dragged and put them in the relevant
+                  points lists in the Line structs.
+               */
+
+               for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
+
+                       uint32_t const N = i->line->npoints ();
+                       for (uint32_t j = 0; j < N; ++j) {
+
+                               /* here's a control point on this line */
+                               ControlPoint* p = i->line->nth (j);
+                               double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
+
+                               /* see if it's inside a range */
+                               list<AudioRange>::const_iterator k = _ranges.begin ();
+                               while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
+                                       ++k;
+                               }
+
+                               if (k != _ranges.end()) {
+                                       /* dragging this point */
+                                       _nothing_to_drag = false;
+                                       i->points.push_back (p);
+                               }
+                       }
+               }
+       }
+
+       if (_nothing_to_drag) {
                return;
        }
 
-       _line->start_drag_multiple (points, 1 - (_drags->current_pointer_y() / _line->height ()), state);
+       for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
+               i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state);
+       }
 }
 
 void
-AutomationRangeDrag::motion (GdkEvent* event, bool first_move)
+AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
 {
        if (_nothing_to_drag) {
                return;
        }
-       
-       float const f = 1 - (_drags->current_pointer_y() / _line->height());
 
-       /* we are ignoring x position for this drag, so we can just pass in anything */
-       _line->drag_motion (0, f, true, false);
+       for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
+               float const f = 1 - (_drags->current_pointer_y() / i->line->height());
+
+               /* we are ignoring x position for this drag, so we can just pass in anything */
+               i->line->drag_motion (0, f, true, false);
+       }
 }
 
 void
@@ -4044,22 +4164,88 @@ AutomationRangeDrag::finished (GdkEvent* event, bool)
        if (_nothing_to_drag) {
                return;
        }
-       
+
        motion (event, false);
-       _line->end_drag ();
-       _line->clear_always_in_view ();
+       for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
+               i->line->end_drag ();
+               i->line->clear_always_in_view ();
+       }
+
+       _editor->session()->commit_reversible_command ();
 }
 
 void
-AutomationRangeDrag::aborted ()
+AutomationRangeDrag::aborted (bool)
 {
-       _line->clear_always_in_view ();
-       _line->reset ();
+       for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
+               i->line->clear_always_in_view ();
+               i->line->reset ();
+       }
 }
 
-DraggingView::DraggingView (RegionView* v)
+DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
        : view (v)
 {
+       time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
+       layer = v->region()->layer ();
        initial_y = v->get_canvas_group()->property_y ();
        initial_playlist = v->region()->playlist ();
+       initial_position = v->region()->position ();
+       initial_end = v->region()->position () + v->region()->length ();
+}
+
+PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
+       : Drag (e, i)
+       , _region_view (r)
+       , _patch_change (i)
+       , _cumulative_dx (0)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
+}
+
+void
+PatchChangeDrag::motion (GdkEvent* ev, bool)
+{
+       framepos_t f = adjusted_current_frame (ev);
+       boost::shared_ptr<Region> r = _region_view->region ();
+       f = max (f, r->position ());
+       f = min (f, r->last_frame ());
+
+       framecnt_t const dxf = f - grab_frame();
+       double const dxu = _editor->frame_to_unit (dxf);
+       _patch_change->move (dxu - _cumulative_dx, 0);
+       _cumulative_dx = dxu;
+}
+
+void
+PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
+{
+       if (!movement_occurred) {
+               return;
+       }
+
+       boost::shared_ptr<Region> r (_region_view->region ());
+
+       framepos_t f = adjusted_current_frame (ev);
+       f = max (f, r->position ());
+       f = min (f, r->last_frame ());
+
+       _region_view->move_patch_change (
+               *_patch_change,
+               _region_view->frames_to_beats (f - r->position() - r->start())
+               );
+}
+
+void
+PatchChangeDrag::aborted (bool)
+{
+       _patch_change->move (-_cumulative_dx, 0);
+}
+
+void
+PatchChangeDrag::setup_pointer_frame_offset ()
+{
+       boost::shared_ptr<Region> region = _region_view->region ();
+       _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_patch_change->patch()->time()) - region->position() + region->start();
 }
+