missing initialization
[ardour.git] / gtk2_ardour / editor_drag.cc
index 58bccf68d4b41028cfe4e4ef7daaa581e80df754..9c65892ebde46cdb2c140c194149756a712d3910 100644 (file)
 
 */
 
-#define __STDC_LIMIT_MACROS 1
+#ifdef WAF_BUILD
+#include "gtk2ardour-config.h"
+#endif
+
 #include <stdint.h>
+#include <algorithm>
+
 #include "pbd/memento_command.h"
 #include "pbd/basename.h"
 #include "pbd/stateful_diff_command.h"
-#include "ardour/session.h"
+
+#include "gtkmm2ext/utils.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/audioregion.h"
 #include "ardour/dB.h"
+#include "ardour/midi_region.h"
+#include "ardour/operations.h"
 #include "ardour/region_factory.h"
+#include "ardour/session.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 +89,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,19 +114,22 @@ 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);
+       start_grab (e, c);
 }
 
 void
-DragManager::start_grab (GdkEvent* e)
+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);
+               (*i)->start_grab (e, c);
        }
 }
 
@@ -116,7 +140,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 +153,9 @@ DragManager::end_grab (GdkEvent* e)
        _drags.clear ();
 
        _ending = false;
-       
+
+       _editor->set_follow_playhead (_old_follow_playhead, false);
+
        return r;
 }
 
@@ -139,13 +165,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 +188,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)
 {
@@ -180,19 +207,15 @@ Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t tim
        _item = new_item;
 
        if (cursor == 0) {
-               cursor = _editor->which_grabber_cursor ();
+               _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, time);
+       } else {
+               _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
        }
-
-       _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
 }
 
 void
 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
 {
-       if (cursor == 0) {
-               cursor = _editor->which_grabber_cursor ();
-       }
-
        // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
 
        if (Keyboard::is_button2_event (&event->button)) {
@@ -208,15 +231,21 @@ 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);
+       if (cursor == 0) {
+               _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
+                            event->button.time);
+       } else {
+               _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
+                            *cursor,
+                            event->button.time);
+       }
 
        if (_editor->session() && _editor->session()->transport_rolling()) {
                _was_rolling = true;
@@ -251,15 +280,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 +301,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 +311,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()));
@@ -303,7 +333,9 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
 
                if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
                        if (!from_autoscroll) {
-                               _editor->maybe_autoscroll (true, allow_vertical_autoscroll ());
+                               bool const moving_left = _drags->current_pointer_x() < _last_pointer_x;
+                               bool const moving_up = _drags->current_pointer_y() < _last_pointer_y;
+                               _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), moving_left, moving_up);
                        }
 
                        motion (event, _move_threshold_passed != old_move_threshold_passed);
@@ -311,7 +343,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,532 +358,506 @@ 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 ()
+boost::shared_ptr<Region>
+Drag::add_midi_region (MidiTimeAxisView* view)
 {
-       int32_t children = 0;
-       TimeAxisViewSummary sum;
+       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());
+               return view->add_region (grab_frame(), len, true);
+       }
 
-       _editor->visible_order_range (&sum.visible_y_low, &sum.visible_y_high);
+       return boost::shared_ptr<Region>();
+}
 
-       /* 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 (EditorSort) < rb->route()->order_key (EditorSort);
+    }
+};
 
-       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 tracks to refer to during the drag; we include hidden tracks,
+          as some of the regions we are dragging may be on such tracks.
+       */
 
-               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) {
+               _time_axis_views.push_back (*i);
+               
+               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());
+               }
+       }
 
-                       sum.height_list[rtv->order()] = (*i)->current_height();
-                       children = 1;
+       /* the list of views can be empty at this point if this is a region list-insert drag
+        */
 
-                       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++;
-                               }
-                       }
-               }
+       for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
+               _views.push_back (DraggingView (*i, this));
        }
 
-       return sum;
+       RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
 }
 
-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
-       )
+void
+RegionDrag::region_going_away (RegionView* v)
 {
-       if (_brushing) {
-               *pointer_order_span = 0;
-               *pointer_layer_span = 0;
-               return true;
+       list<DraggingView>::iterator i = _views.begin ();
+       while (i != _views.end() && i->view != v) {
+               ++i;
        }
 
-       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* rv = i->view;
-
-                       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 TimeAxisView, return the index of it into the _time_axis_views vector,
+ *  or -1 if it is not found.
+ */
+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*, double> 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 x_delta = 0;
+       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;
 
-               /* now compute the canvas unit distance we need to move the regionview
-                  to make it appear at the new location.
-               */
-
-               x_delta = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
+       if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
 
-               if (*pending_region_position <= _last_frame_position) {
+               /* x movement since last time (in pixels) */
+               dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
 
-                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+               /* total x movement */
+               framecnt_t total_dx = *pending_region_position;
+               if (regions_came_from_canvas()) {
+                       total_dx = total_dx - grab_frame ();
+               }
 
-                               RegionView* rv = i->view;
+               /* check that no regions have gone off the start of the session */
+               for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                       if ((i->view->region()->position() + total_dx) < 0) {
+                               dx = 0;
+                               *pending_region_position = _last_frame_position;
+                               break;
+                       }
+               }
 
-                               // If any regionview is at zero, we need to know so we can stop further leftward motion.
+               _last_frame_position = *pending_region_position;
+       }
 
-                               double ix1, ix2, iy1, iy2;
-                               rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
-                               rv->get_canvas_frame()->i2w (ix1, iy1);
+       return dx;
+}
 
-                               if (-x_delta > ix1 + _editor->horizontal_position()) {
-                                       x_delta = 0;
-                                       *pending_region_position = _last_frame_position;
-                                       break;
-                               }
-                       }
+bool
+RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const
+{
+       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;
+               }
 
+               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;
                }
 
-               _last_frame_position = *pending_region_position;
+               double const l = i->layer + delta_layer;
+
+               /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
+                  mode to allow the user to place a region below another on layer 0.
+               */
+               if (delta_track == 0 && (l < -0.5 || 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;
+               }
        }
 
-       return x_delta;
+       /* all regions being dragged are ok with this change */
+       return true;
 }
 
 void
 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 {
-       double y_delta = 0;
+       assert (!_views.empty ());
 
-       TimeAxisViewSummary tavs = get_time_axis_view_summary ();
+       /* Find the TimeAxisView that the pointer is now over */
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
 
-       vector<int32_t>::iterator j;
-
-       /* *pointer* variables reflect things about the pointer; as we may be moving
-          multiple regions, much detail must be computed per-region */
+       if (first_move && tv.first->view()->layer_display() == Stacked) {
+               tv.first->view()->set_layer_display (Expanded);
+       }
 
-       /* 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;
+       /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
 
-       int32_t pointer_order_span;
-       int32_t pointer_layer_span;
-       int32_t canvas_pointer_order_span;
-
-       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);
+       double 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;
+       double 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;
 
-               if (rv->region()->locked()) {
+               if (rv->region()->locked() || rv->region()->video_locked()) {
                        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 */
-
-               /* this x value is just a dummy value so that we have something
-                  to pass to i2w () */
-
-               double ix1 = 0;
-
-               /* distance from the top of this track view to the region area
-                  of our track view is always 1 */
-
-               double iy1 = 1;
-
-               /* convert to world coordinates, ie distance from the top of
-                  the ruler section */
-
-               rv->get_canvas_frame()->i2w (ix1, iy1);
-
-               /* compensate for the ruler section and the vertical scrollbar position */
-               iy1 += _editor->get_trackview_group_vertical_offset ();
-
                if (first_move) {
 
-                       // hide any dependent views
-
-                       rv->get_time_axis_view().hide_dependent_views (*rv);
+                       rv->drag_start (); 
 
-                       /*
-                          reparent to a non scrolling group so that we can keep the
+                       /* Absolutely no idea why this is necessary, but it is; without
+                          it, the region view disappears after the reparent.
+                       */
+                       _editor->update_canvas_now ();
+                       
+                       /* 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.
+                          Reparenting means that we will have to move the region view
+                          later, 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 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.
+               */
+               double this_delta_layer = delta_layer;
+               if (delta_time_axis_view != 0) {
+                       this_delta_layer = - i->layer;
+               }
 
-               if (pointer_order_span != 0 && !clamp_y_axis) {
+               /* The TimeAxisView that this region is now on */
+               TimeAxisView* tv = _time_axis_views[i->time_axis_view + delta_time_axis_view];
 
-                       /* INTER-TRACK MOVEMENT */
+               /* Ensure it is moved from stacked -> expanded if appropriate */
+               if (tv->view()->layer_display() == Stacked) {
+                       tv->view()->set_layer_display (Expanded);
+               }
+               
+               /* We're only allowed to go -ve in layer on Expanded views */
+               if (tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
+                       this_delta_layer = - i->layer;
+               }
+               
+               /* Set height */
+               rv->set_height (tv->view()->child_height ());
+               
+               /* Update show/hidden status as the region view may have come from a hidden track,
+                  or have moved to one.
+               */
+               if (tv->hidden ()) {
+                       rv->get_canvas_group()->hide ();
+               } else {
+                       rv->get_canvas_group()->show ();
+               }
 
-                       /* 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;
-                       }
+               /* Update the DraggingView */
+               i->time_axis_view += delta_time_axis_view;
+               i->layer += this_delta_layer;
 
-                       y_delta = 0;
-                       int32_t temp_pointer_order_span = canvas_pointer_order_span;
+               if (_brushing) {
+                       _editor->mouse_brush_insert_region (rv, pending_region_position);
+               } else {
+                       double x = 0;
+                       double y = 0;
 
-                       if (j != tavs.height_list.end ()) {
+                       /* Get the y coordinate of the top of the track that this region is now on */
+                       tv->canvas_display()->i2w (x, y);
+                       y += _editor->get_trackview_group_vertical_offset();
+                       
+                       /* And adjust for the layer that it should be on */
+                       StreamView* cv = tv->view ();
+                       switch (cv->layer_display ()) {
+                       case Overlaid:
+                               break;
+                       case Stacked:
+                               y += (cv->layers() - i->layer - 1) * cv->child_height ();
+                               break;
+                       case Expanded:
+                               y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
+                               break;
+                       }
 
-                               /* 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. */
+                       /* Now move the region view */
+                       rv->move (x_delta, y - rv->get_canvas_group()->property_y());
+               }
 
-                               StreamView* lv = last_pointer_view->view ();
-                               assert (lv);
+       } /* foreach region */
 
-                               /* move to the top of the last trackview */
-                               if (lv->layer_display () == Stacked) {
-                                       y_delta -= (lv->layers() - last_pointer_layer - 1) * lv->child_height ();
-                               }
+       _total_x_delta += x_delta;
 
-                               StreamView* cv = current_pointer_view->view ();
-                               assert (cv);
+       if (first_move) {
+               _editor->cursor_group->raise_to_top();
+       }
 
-                               /* 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 ();
-                               }
+       if (x_delta != 0 && !_brushing) {
+               show_verbose_cursor_time (_last_frame_position);
+       }
 
-                               /* And for being on a non-topmost layer on the new
-                                  track */
+       _last_pointer_time_axis_view += delta_time_axis_view;
+       _last_pointer_layer += delta_layer;
+}
 
-                               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--;
-                                       }
+void
+RegionMoveDrag::motion (GdkEvent* event, bool first_move)
+{
+       if (_copy && first_move) {
 
-                                       if (x != last_pointer_order) {
-                                               if ((*j) == 0) {
-                                                       ++temp_pointer_order_span;
-                                               }
-                                       }
+               /* duplicate the regionview(s) and region(s) */
 
-                                       y_delta -= (*j);
-                                       temp_pointer_order_span--;
-                               }
+               list<DraggingView> new_regionviews;
 
-                               while (temp_pointer_order_span < 0) {
+               for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
 
-                                       y_delta += (*j);
+                       RegionView* rv = i->view;
+                       AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
+                       MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
 
-                                       if (x != last_pointer_order) {
-                                               if ((*j) == 0) {
-                                                       --temp_pointer_order_span;
-                                               }
-                                       }
+                       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());
 
-                                       if (j != tavs.height_list.end()) {
-                                               j++;
-                                       }
+                       RegionView* nrv;
+                       if (arv) {
+                               boost::shared_ptr<AudioRegion> audioregion_copy
+                               = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
 
-                                       temp_pointer_order_span++;
-                               }
+                               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));
 
-                               /* find out where we'll be when we move and set height accordingly */
+                       /* swap _primary to the copy */
 
-                               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());
+                       if (rv == _primary) {
+                               _primary = nrv;
+                       }
 
-                               /* 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.
-                               */
+                       /* ..and deselect the one we copied */
 
-                               //const GdkColor& col (temp_rtv->view->get_region_color());
-                               //rv->set_color (const_cast<GdkColor&>(col));
-                       }
+                       rv->set_selected (false);
                }
 
-               if (pointer_order_span == 0 && pointer_layer_span != 0 && !clamp_y_axis) {
+               if (!new_regionviews.empty()) {
 
-                       /* INTER-LAYER MOVEMENT in the same track */
-                       y_delta = rtv->view()->child_height () * pointer_layer_span;
-               }
+                       /* reflect the fact that we are dragging the copies */
 
+                       _views = new_regionviews;
 
-               if (_brushing) {
-                       _editor->mouse_brush_insert_region (rv, pending_region_position);
-               } else {
-                       rv->move (x_delta, y_delta);
-               }
+                       swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
 
-       } /* foreach region */
+                       /*
+                         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();
+               }
+       }
 
-       _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);
-       }
+       RegionMotionDrag::motion (event, first_move);
 }
 
 void
-RegionMoveDrag::motion (GdkEvent* event, bool first_move)
+RegionMotionDrag::finished (GdkEvent *, bool)
 {
-       if (_copy && first_move) {
-               copy_regions (event);
-       }
+       for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
+               if (!(*i)->view()) {
+                       continue;
+               }
 
-       RegionMotionDrag::motion (event, first_move);
+               if ((*i)->view()->layer_display() == Expanded) {
+                       (*i)->view()->set_layer_display (Stacked);
+               }
+       }
 }
 
 void
-RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
+RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
 {
-       vector<RegionView*> copies;
-       boost::shared_ptr<Track> tr;
-       boost::shared_ptr<Playlist> from_playlist;
-       boost::shared_ptr<Playlist> to_playlist;
-       RegionSelection new_views;
-       typedef set<boost::shared_ptr<Playlist> > PlaylistSet;
-       PlaylistSet modified_playlists;
-       PlaylistSet frozen_playlists;
-       list <sigc::connection> modified_playlist_connections;
-       pair<PlaylistSet::iterator,bool> insert_result, frozen_insert_result;
-       nframes64_t drag_delta;
-       bool changed_tracks, changed_position;
-       map<RegionView*, pair<RouteTimeAxisView*, int> > final;
-       RouteTimeAxisView* source_tv;
-        vector<StatefulDiffCommand*> sdc;
-
+       RegionMotionDrag::finished (ev, movement_occurred);
+       
        if (!movement_occurred) {
                /* just a click */
                return;
        }
 
-       if (_brushing) {
-               /* all changes were made during motion event handlers */
-
-               if (_copy) {
-                       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
-                               copies.push_back (i->view);
-                       }
-               }
-
-               goto out;
-       }
-
        /* reverse this here so that we have the correct logic to finalize
           the drag.
        */
@@ -860,114 +866,194 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                _x_constrained = !_x_constrained;
        }
 
+       assert (!_views.empty ());
+
+       /* We might have hidden region views so that they weren't visible during the drag
+          (when they have been reparented).  Now everything can be shown again, as region
+          views are back in their track parent groups.
+       */
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->get_canvas_group()->show ();
+       }
+       
+       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) {
-               if (_x_constrained) {
-                       _editor->begin_reversible_command (_("fixed time region copy"));
-               } else {
-                       _editor->begin_reversible_command (_("region copy"));
+
+               finished_copy (
+                       changed_position,
+                       changed_tracks,
+                       drag_delta
+                       );
+
+       } else {
+
+               finished_no_copy (
+                       changed_position,
+                       changed_tracks,
+                       drag_delta
+                       );
+
+       }
+
+       _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
+}
+
+void
+RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
+{
+       RegionSelection new_views;
+       PlaylistSet modified_playlists;
+       list<RegionView*> views_to_delete;
+
+       if (_brushing) {
+               /* all changes were made during motion event handlers */
+
+               for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+                       delete i->view;
                }
+
+               _editor->commit_reversible_command ();
+               return;
+       }
+
+       if (_x_constrained) {
+               _editor->begin_reversible_command (Operations::fixed_time_region_copy);
        } else {
-               if (_x_constrained) {
-                       _editor->begin_reversible_command (_("fixed time region drag"));
+               _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) {
+
+               if (i->view->region()->locked() || i->view->region()->video_locked()) {
+                       continue;
+               }
+
+               framepos_t where;
+
+               if (changed_position && !_x_constrained) {
+                       where = i->view->region()->position() - drag_delta;
                } else {
-                       _editor->begin_reversible_command (_("region drag"));
+                       where = i->view->region()->position();
                }
+
+               RegionView* new_view = insert_region_into_playlist (
+                       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);
+       }
+
+       /* Delete views that are no longer needed; we can't do this directly in the iteration over _views
+          because when views are deleted they are automagically removed from _views, which messes
+          up the iteration.
+       */
+       for (list<RegionView*>::iterator i = views_to_delete.begin(); i != views_to_delete.end(); ++i) {
+               delete *i;
        }
 
-       changed_position = (_last_frame_position != (nframes64_t) (_primary->region()->position()));
-       changed_tracks = (_dest_trackview != &_primary->get_time_axis_view());
+       /* 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) {
+               _editor->selection->set (new_views);
+       }
 
-       drag_delta = _primary->region()->position() - _last_frame_position;
+       /* write commands for the accumulated diffs for all our modified playlists */
+       add_stateful_diff_commands_for_playlists (modified_playlists);
 
-       _editor->update_canvas_now ();
+       _editor->commit_reversible_command ();
+}
+
+void
+RegionMoveDrag::finished_no_copy (
+       bool const changed_position,
+       bool const changed_tracks,
+       framecnt_t const drag_delta
+       )
+{
+       RegionSelection new_views;
+       PlaylistSet modified_playlists;
+       PlaylistSet frozen_playlists;
+       set<RouteTimeAxisView*> views_to_update;
 
-       /* make a list of where each region ended up */
-       final = find_time_axis_views_and_layers ();
+       if (_brushing) {
+               /* all changes were made during motion event handlers */
+               _editor->commit_reversible_command ();
+               return;
+       }
 
-        cerr << "Iterate over " << _views.size() << " views\n";
+       if (_x_constrained) {
+               _editor->begin_reversible_command (_("fixed time region drag"));
+       } else {
+               _editor->begin_reversible_command (Operations::region_drag);
+       }
 
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
 
                RegionView* rv = i->view;
-               RouteTimeAxisView* dest_rtv = final[rv].first;
-               layer_t dest_layer = final[rv].second;
-
-               nframes64_t where;
 
-                from_playlist.reset ();
-                to_playlist.reset ();
+               RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
+               double const dest_layer = i->layer;
 
-               if (rv->region()->locked()) {
+               if (rv->region()->locked() || rv->region()->video_locked()) {
                        ++i;
                        continue;
                }
 
+               views_to_update.insert (dest_rtv);
+
+               framepos_t where;
+
                if (changed_position && !_x_constrained) {
                        where = rv->region()->position() - drag_delta;
                } else {
                        where = rv->region()->position();
                }
 
-               boost::shared_ptr<Region> new_region;
-
-               if (_copy) {
-                       /* we already made a copy */
-                       new_region = rv->region();
-
-                       /* undo the previous hide_dependent_views so that xfades don't
-                          disappear on copying regions
-                       */
-
-                       //rv->get_time_axis_view().reveal_dependent_views (*rv);
+               if (changed_tracks) {
 
-               } else if (changed_tracks && dest_rtv->playlist()) {
-                       new_region = RegionFactory::create (rv->region());
-               }
-
-               if (changed_tracks || _copy) {
+                       /* insert into new playlist */
 
-                       to_playlist = dest_rtv->playlist();
+                       RegionView* new_view = insert_region_into_playlist (
+                               RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
+                               );
 
-                       if (!to_playlist) {
+                       if (new_view == 0) {
                                ++i;
                                continue;
                        }
 
-                       _editor->latest_regionviews.clear ();
-
-                       sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun(*_editor, &Editor::collect_new_region_view));
-
-                       insert_result = modified_playlists.insert (to_playlist);
-
-                       if (insert_result.second) {
-                                to_playlist->clear_history ();
-                       }
-
-                        cerr << "To playlist " << to_playlist->name() << " region history contains "
-                             << to_playlist->region_list().change().added.size() << " adds and " 
-                             << to_playlist->region_list().change().removed.size() << " removes\n";
-
-                        cerr << "Adding new region " << new_region->id() << " based on "
-                             << rv->region()->id() << endl;
-                        
-                       to_playlist->add_region (new_region, where);
+                       new_views.push_back (new_view);
 
-                       if (dest_rtv->view()->layer_display() == Stacked) {
-                               new_region->set_layer (dest_layer);
-                               new_region->set_pending_explicit_relayer (true);
-                       }
+                       /* remove from old playlist */
 
-                       c.disconnect ();
+                       /* the region that used to be in the old playlist is not
+                          moved to the new one - we use a copy of it. as a result,
+                          any existing editor for the region should no longer be
+                          visible.
+                       */
+                       rv->hide_region_editor();
+                       rv->fake_set_opaque (false);
 
-                       if (!_editor->latest_regionviews.empty()) {
-                               // XXX why just the first one ? we only expect one
-                               // commented out in nick_m's canvas reworking. is that intended?
-                               //dest_atv->reveal_dependent_views (*latest_regionviews.front());
-                               new_views.push_back (_editor->latest_regionviews.front());
-                       }
+                       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
@@ -977,101 +1063,60 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 
                        rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
                        rv->get_canvas_group()->property_y() = i->initial_y;
-                       rv->get_time_axis_view().reveal_dependent_views (*rv);
+                       rv->drag_end ();
 
                        /* 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 */
 
-                       frozen_insert_result = frozen_playlists.insert(playlist);
-
-                       if (frozen_insert_result.second) {
-                               playlist->freeze();
+                       if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
+                               playlist->set_layer (rv->region(), dest_layer);
                        }
 
-                        cerr << "Moving region " << rv->region()->id() << endl;
-
-                       rv->region()->set_position (where, (void*) this);
-
-                       sdc.push_back (new StatefulDiffCommand (rv->region()));
-               }
-
-               if (changed_tracks && !_copy) {
-
-                       /* get the playlist where this drag started. we can't use rv->region()->playlist()
-                          because we may have copied the region and it has not been attached to a playlist.
-                       */
-
-                       source_tv = dynamic_cast<RouteTimeAxisView*> (&rv->get_time_axis_view());
-                       tr = source_tv->track();
-                       from_playlist = tr->playlist();
+                       /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
 
-                       assert (source_tv);
-                       assert (tr);
-                       assert (from_playlist);
+                       pair<PlaylistSet::iterator, bool> r = frozen_playlists.insert (playlist);
 
-                       /* moved to a different audio track, without copying */
+                       if (r.second) {
+                               playlist->freeze ();
+                       }
 
-                       /* the region that used to be in the old playlist is not
-                          moved to the new one - we use a copy of it. as a result,
-                          any existing editor for the region should no longer be
-                          visible.
+                       /* this movement may result in a crossfade being modified, so we need to get undo
+                          data from the playlist as well as the region.
                        */
 
-                       rv->hide_region_editor();
-                       rv->fake_set_opaque (false);
-
-                       /* remove the region from the old playlist */
+                       r = modified_playlists.insert (playlist);
+                       if (r.second) {
+                               playlist->clear_changes ();
+                       }
 
-                       insert_result = modified_playlists.insert (from_playlist);
+                       rv->region()->set_position (where);
 
-                       if (insert_result.second) {
-                               from_playlist->clear_history ();
-                       }
-                        
-                        cerr << "From playlist " << from_playlist->name() << " region history contains "
-                             << from_playlist->region_list().change().added.size() << " adds and " 
-                             << from_playlist->region_list().change().removed.size() << " removes\n";
+                       _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
+               }
 
-                        cerr << "removing region " << rv->region() << endl;
-                        
-                       from_playlist->remove_region (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 the selection (i.e. there were N regions selected, we removed 1,
+                          trace of it from _views (i.e. there were N regions selected, we removed 1,
                           but since its the same playlist for N tracks, all N tracks updated themselves, removed the
-                          corresponding regionview, and the selection is now empty).
+                          corresponding regionview, and _views is now empty).
 
-                          this could have invalidated any and all iterators into the region selection.
+                          This could have invalidated any and all iterators into _views.
 
-                          the heuristic we use here is: if the region selection is empty, break out of the loop
+                          The heuristic we use here is: if the region selection is empty, break out of the loop
                           here. if the region selection is not empty, then restart the loop because we know that
                           we must have removed at least the region(view) we've just been working on as well as any
                           that we processed on previous iterations.
 
-                          EXCEPT .... if we are doing a copy drag, then the selection hasn't been modified and
+                          EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
                           we can just iterate.
                        */
 
+
                        if (_views.empty()) {
-                                if (to_playlist) {
-                                        sdc.push_back (new StatefulDiffCommand (to_playlist));
-                                        cerr << "Saved diff for to:" << to_playlist->name() << endl;
-                                }
-                                
-                                if (from_playlist && (from_playlist != to_playlist)) {
-                                        sdc.push_back (new StatefulDiffCommand (from_playlist));
-                                        cerr << "Saved diff for from:" << from_playlist->name() << endl;
-                                }                              
-                                break;
+                               break;
                        } else {
                                i = _views.begin();
                        }
@@ -1079,27 +1124,10 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                } else {
                        ++i;
                }
-
-               if (_copy) {
-                       copies.push_back (rv);
-               }
-
-                cerr << "Done with TV, top = " << to_playlist << " from = " << from_playlist << endl;
-
-                if (to_playlist) {
-                        sdc.push_back (new StatefulDiffCommand (to_playlist));
-                        cerr << "Saved diff for to:" << to_playlist->name() << endl;
-                }
-
-                if (from_playlist && (from_playlist != to_playlist)) {
-                        sdc.push_back (new StatefulDiffCommand (from_playlist));
-                        cerr << "Saved diff for from:" << from_playlist->name() << endl;
-                }
        }
 
-       /*
-          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) {
@@ -1110,20 +1138,115 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                (*p)->thaw();
        }
 
-  out:
-        for (vector<StatefulDiffCommand*>::iterator i = sdc.begin(); i != sdc.end(); ++i) {
-               _editor->session()->add_command (*i);
-        }
+       /* write commands for the accumulated diffs for all our modified playlists */
+       add_stateful_diff_commands_for_playlists (modified_playlists);
 
        _editor->commit_reversible_command ();
 
-       for (vector<RegionView*>::iterator x = copies.begin(); x != copies.end(); ++x) {
-               delete *x;
+       /* We have futzed with the layering of canvas items on our streamviews.
+          If any region changed layer, this will have resulted in the stream
+          views being asked to set up their region views, and all will be well.
+          If not, we might now have badly-ordered region views.  Ask the StreamViews
+          involved to sort themselves out, just in case.
+       */
+
+       for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
+               (*i)->view()->playlist_layered ((*i)->track ());
+       }
+}
+
+/** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
+ *  @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_changes () is only called once per playlist.
+ */
+void
+RegionMoveDrag::remove_region_from_playlist (
+       boost::shared_ptr<Region> region,
+       boost::shared_ptr<Playlist> playlist,
+       PlaylistSet& modified_playlists
+       )
+{
+       pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
+
+       if (r.second) {
+               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.
+ *  @param region Region to insert.
+ *  @param dest_rtv Destination RouteTimeAxisView.
+ *  @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_changes () is only called once per playlist.
+ *  @return New RegionView, or 0 if no insert was performed.
+ */
+RegionView *
+RegionMoveDrag::insert_region_into_playlist (
+       boost::shared_ptr<Region> region,
+       RouteTimeAxisView* dest_rtv,
+       layer_t dest_layer,
+       framecnt_t where,
+       PlaylistSet& modified_playlists
+       )
+{
+       boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
+       if (!dest_playlist) {
+               return 0;
+       }
+
+       /* arrange to collect the new region view that will be created as a result of our playlist insertion */
+       _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 */
+       pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
+       if (r.second) {
+               dest_playlist->clear_changes ();
+       }
+
+       dest_playlist->add_region (region, where);
+
+       if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
+               dest_playlist->set_layer (region, dest_layer);
+       }
+
+       c.disconnect ();
+
+       assert (_new_region_view);
+
+       return _new_region_view;
+}
+
+void
+RegionMoveDrag::collect_new_region_view (RegionView* rv)
+{
+       _new_region_view = rv;
+}
+
+void
+RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
+{
+       for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++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) {
 
@@ -1134,13 +1257,19 @@ RegionMoveDrag::aborted ()
                _views.clear ();
 
        } else {
-               RegionMotionDrag::aborted ();
+               RegionMotionDrag::aborted (movement_occurred);
        }
 }
 
 void
-RegionMotionDrag::aborted ()
+RegionMotionDrag::aborted (bool)
 {
+       for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
+               if ((*i)->view()->layer_display() == Expanded) {
+                       (*i)->view()->set_layer_display (Stacked);
+               }
+       }
+       
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                RegionView* rv = i->view;
                TimeAxisView* tv = &(rv->get_time_axis_view ());
@@ -1148,7 +1277,7 @@ RegionMotionDrag::aborted ()
                assert (rtv);
                rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
                rv->get_canvas_group()->property_y() = 0;
-               rv->get_time_axis_view().reveal_dependent_views (*rv);
+               rv->drag_end ();
                rv->fake_set_opaque (false);
                rv->move (-_total_x_delta, 0);
                rv->set_height (rtv->view()->child_height ());
@@ -1156,295 +1285,75 @@ RegionMotionDrag::aborted ()
 
        _editor->update_canvas_now ();
 }
-                                     
 
-bool
-RegionMotionDrag::x_move_allowed () const
+/** @param b true to brush, otherwise false.
+ *  @param c true to make copies of the regions being moved, otherwise 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)
 {
-       if (Config->get_edit_mode() == Lock) {
-               /* in locked edit mode, reverse the usual meaning of _x_constrained */
-               return _x_constrained;
+       DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
+
+       double speed = 1;
+       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
+       if (rtv && rtv->is_track()) {
+               speed = rtv->track()->speed ();
        }
 
-       return !_x_constrained;
+       _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
 }
 
 void
-RegionMotionDrag::copy_regions (GdkEvent* event)
+RegionMoveDrag::setup_pointer_frame_offset ()
 {
-       /* 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);
+       _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
+}
 
-               RegionView* nrv;
-               if (arv) {
-                       boost::shared_ptr<AudioRegion> audioregion_copy
-                               = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
+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");
 
-                       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;
-               }
+       assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
+               (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
 
-               nrv->get_canvas_group()->show ();
-               new_regionviews.push_back (DraggingView (nrv));
+       _primary = v->view()->create_region_view (r, false, false);
 
-               /* swap _primary to the copy */
+       _primary->get_canvas_group()->show ();
+       _primary->set_position (pos, 0);
+       _views.push_back (DraggingView (_primary, this));
 
-               if (rv == _primary) {
-                       _primary = nrv;
-               }
+       _last_frame_position = pos;
 
-               /* ..and deselect the one we copied */
+       _item = _primary->get_canvas_group ();
+}
 
-               rv->set_selected (false);
-       }
+void
+RegionInsertDrag::finished (GdkEvent *, bool)
+{
+       _editor->update_canvas_now ();
 
-       if (new_regionviews.empty()) {
-               return;
-       }
+       RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[_views.front().time_axis_view]);
 
-       /* reflect the fact that we are dragging the copies */
+       _primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
+       _primary->get_canvas_group()->property_y() = 0;
 
-       _views = new_regionviews;
+       boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
-       swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
+       _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 ();
 
-       /*
-          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();
+       delete _primary;
+       _primary = 0;
+       _views.clear ();
 }
 
-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.
- */
-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 ();
-       }
-
-       double speed = 1;
-       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
-       if (rtv && rtv->is_track()) {
-               speed = rtv->track()->speed ();
-       }
-
-       _last_frame_position = static_cast<nframes64_t> (_primary->region()->position() / speed);
-}
-
-void
-RegionMoveDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
-{
-       RegionMotionDrag::start_grab (event, c);
-
-       _pointer_frame_offset = grab_frame() - _last_frame_position;
-}
-
-RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, nframes64_t pos)
-       : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
-{
-       assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
-               (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
-
-       _primary = v->view()->create_region_view (r, false, false);
-
-       _primary->get_canvas_group()->show ();
-       _primary->set_position (pos, 0);
-       _views.push_back (DraggingView (_primary));
-
-       _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*/)
-{
-       _editor->update_canvas_now ();
-
-       map<RegionView*, pair<RouteTimeAxisView*, int> > final = find_time_axis_views_and_layers ();
-
-       RouteTimeAxisView* dest_rtv = final[_primary].first;
-
-       _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 ();
-       playlist->add_region (_primary->region (), _last_frame_position);
-       _editor->session()->add_command (new StatefulDiffCommand (playlist));
-       _editor->commit_reversible_command ();
-
-       delete _primary;
-       _primary = 0;
-       _views.clear ();
-}
-
-void
-RegionInsertDrag::aborted ()
+void
+RegionInsertDrag::aborted (bool)
 {
        delete _primary;
        _primary = 0;
@@ -1454,7 +1363,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 {
@@ -1466,10 +1375,20 @@ struct RegionSelectionByPosition {
 void
 RegionSpliceDrag::motion (GdkEvent* event, bool)
 {
-       RouteTimeAxisView* tv;
-       layer_t layer;
+       /* Which trackview is this ? */
+
+       pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
+
+       /* The region motion is only processed if the pointer is over
+          an audio track.
+       */
 
-       if (!check_possible (&tv, &layer)) {
+       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;
        }
 
@@ -1486,7 +1405,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) {
 
@@ -1522,80 +1441,93 @@ 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 */
 }
 
 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
        : Drag (e, i),
-         _view (v)
+         _view (dynamic_cast<MidiTimeAxisView*> (v))
 {
+       DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
 
+       assert (_view);
 }
 
 void
-RegionCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
+RegionCreateDrag::motion (GdkEvent* event, bool first_move)
 {
-       _dest_trackview = _view;
-
-       Drag::start_grab (event);
-}
+       if (first_move) {
+               _region = add_midi_region (_view);
+               _view->playlist()->freeze ();
+       } else {
+               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.
+                       */
 
-void
-RegionCreateDrag::motion (GdkEvent* /*event*/, bool first_move)
-{
-       if (first_move) {
-               // TODO: create region-create-drag region view here
+                       framecnt_t const len = (framecnt_t) fabs (f - grab_frame () - 1);
+                       _region->set_length (len < 1 ? 1 : len);
+               }
        }
-
-       // TODO: resize region-create-drag region view here
 }
 
 void
-RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
+RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
 {
-       MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (_dest_trackview);
-
-       if (!mtv) {
-               return;
-       }
-
        if (!movement_occurred) {
-               mtv->add_region (grab_frame ());
+               add_midi_region (_view);
        } else {
-               motion (event, false);
-               // TODO: create region-create-drag region here
+               _view->playlist()->thaw ();
        }
 }
 
 void
-RegionCreateDrag::aborted ()
+RegionCreateDrag::aborted (bool)
 {
-       /* XXX: TODO */
+       if (_region) {
+               _view->playlist()->thaw ();
+       }
+
+       /* XXX */
 }
 
 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();
 
@@ -1603,10 +1535,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;
        }
 
@@ -1644,7 +1576,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);
        }
 }
 
@@ -1653,43 +1585,189 @@ 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 */
+       MidiRegionSelection& ms (_editor->get_selection().midi_regions);
+       for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
+               (*r)->abort_resizing ();
+       }
+}
+
+AVDraggingView::AVDraggingView (RegionView* v)
+       : view (v)
+{
+       initial_position = v->region()->position ();
+}
+
+VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
+
+       RegionSelection rs;
+       TrackViewList empty;
+       empty.clear();
+       _editor->get_regions_after(rs, (framepos_t) 0, empty);
+       std::list<RegionView*> views = rs.by_layer();
+
+       for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
+               RegionView* rv = (*i);
+               if (!rv->region()->video_locked()) {
+                       continue;
+               }
+               _views.push_back (AVDraggingView (rv));
+       }
+}
+
+void
+VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
+{
+       Drag::start_grab (event);
+       if (_editor->session() == 0) {
+               return;
+       }
+
+       _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
+       _max_backwards_drag = (
+                         ARDOUR_UI::instance()->video_timeline->get_duration()
+                       + ARDOUR_UI::instance()->video_timeline->get_offset()
+                       - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
+                       );
+
+       for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
+                       _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
+               }
+       }
+       DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
+
+       char buf[128];
+       Timecode::Time timecode;
+       _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
+       snprintf (buf, sizeof (buf), "Video Start:\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, (_startdrag_video_offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
+       _editor->verbose_cursor()->set(buf, event->button.x + 10, event->button.y + 10);
+       _editor->verbose_cursor()->show ();
 }
 
 void
-RegionGainDrag::motion (GdkEvent* /*event*/, bool)
+VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
 {
+       if (_editor->session() == 0) {
+               return;
+       }
+       if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+               return;
+       }
 
+       framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
+       dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
+
+       if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
+               dt = - _max_backwards_drag;
+       }
+
+       ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
+       ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+
+       for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               RegionView* rv = i->view;
+               DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
+               if (first_move) {
+                       rv->drag_start ();
+                       _editor->update_canvas_now ();
+                       rv->fake_set_opaque (true);
+                       rv->region()->clear_changes ();
+                       rv->region()->suspend_property_changes();
+               }
+               rv->region()->set_position(i->initial_position + dt);
+               rv->region_changed(ARDOUR::Properties::position);
+       }
+
+       const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
+       Timecode::Time timecode;
+       Timecode::Time timediff;
+       char buf[128];
+       _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
+       _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
+       snprintf (buf, sizeof (buf),
+                       "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
+                       "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
+                       , _("Video Start:"),
+                               (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
+                       , _("Diff:"),
+                               (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
+                               );
+       _editor->verbose_cursor()->set(buf, event->button.x + 10, event->button.y + 10);
+       _editor->verbose_cursor()->show ();
 }
 
 void
-RegionGainDrag::finished (GdkEvent *, bool)
+VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
 {
+       if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+               return;
+       }
+
+       if (!movement_occurred || ! _editor->session()) {
+               return;
+       }
+
+       ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+
+       _editor->begin_reversible_command (_("Move Video"));
 
+       XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
+       ARDOUR_UI::instance()->video_timeline->save_undo();
+       XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
+       _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
+
+       for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->drag_end();
+               i->view->fake_set_opaque (false);
+               i->view->region()->resume_property_changes ();
+
+               _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
+       }
+
+       _editor->session()->maybe_update_session_range(
+                       std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
+                       std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
+                       );
+
+
+       _editor->commit_reversible_command ();
+       _editor->update_canvas_now ();
 }
 
 void
-RegionGainDrag::aborted ()
+VideoTimeLineDrag::aborted (bool)
 {
-       /* XXX: TODO */
+       if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+               return;
+       }
+       ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
+       ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+
+       for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->region()->resume_property_changes ();
+               i->view->region()->set_position(i->initial_position);
+       }
 }
 
-TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
+TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
        : RegionDrag (e, i, p, v)
-       , _have_transaction (false)
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
+       _preserve_fade_anchor = preserve_fade_anchor;
 }
 
 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 ();
@@ -1699,39 +1777,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
@@ -1739,11 +1825,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);
@@ -1753,7 +1834,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) {
 
@@ -1772,19 +1853,18 @@ 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 ();
+                               arv->drag_start ();
                        }
 
                        boost::shared_ptr<Playlist> pl = rv->region()->playlist();
@@ -1805,13 +1885,41 @@ 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);
+                       bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim);
+                       if (changed && _preserve_fade_anchor) {
+                               AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
+                               if (arv) {
+                                       double distance;
+                                       double new_length;
+                                       framecnt_t len;
+                                       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+                                       distance = _drags->current_pointer_x() - grab_x();
+                                       len = ar->fade_in()->back()->when;
+                                       new_length = len - _editor->unit_to_frame (distance);
+                                       new_length = ar->verify_xfade_bounds (new_length, true  /*START*/ );
+                                       arv->reset_fade_in_shape_width (ar, new_length);  //the grey shape
+                               }
+                       }
                }
                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);
+                       bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim);
+                       if (changed && _preserve_fade_anchor) {
+                               AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
+                               if (arv) {
+                                       double distance;
+                                       double new_length;
+                                       framecnt_t len;
+                                       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+                                       distance = grab_x() - _drags->current_pointer_x();
+                                       len = ar->fade_out()->back()->when;
+                                       new_length = len - _editor->unit_to_frame (distance);
+                                       new_length = ar->verify_xfade_bounds (new_length, false  /*END*/ );
+                                       arv->reset_fade_out_shape_width (ar, new_length);  //the grey shape
+                               }
+                       }
                }
                break;
 
@@ -1823,21 +1931,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;
@@ -1845,13 +1953,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;
        }
 }
@@ -1863,37 +1971,110 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
        if (movement_occurred) {
                motion (event, false);
 
-               if (!_editor->selection->selected (_primary)) {
-                       _editor->thaw_region_after_trim (*_primary);
-               } else {
-
+               if (_operation == StartTrim) {
                        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               _editor->thaw_region_after_trim (*i->view);
-                               i->view->enable_display (true);
-                               i->view->fake_set_opaque (true);
-                                if (_have_transaction) {
-                                        _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
-                                }
+                               {
+                                       /* 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.
+                                       */
+                                       i->view->trim_front_ending ();
+                               }
+                               if (_preserve_fade_anchor) {
+                                       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
+                                       if (arv) {
+                                               double distance;
+                                               double new_length;
+                                               framecnt_t len;
+                                               boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+                                               distance = _drags->current_pointer_x() - grab_x();
+                                               len = ar->fade_in()->back()->when;
+                                               new_length = len - _editor->unit_to_frame (distance);
+                                               new_length = ar->verify_xfade_bounds (new_length, true  /*START*/ );
+                                               ar->set_fade_in_length(new_length);
+                                       }
+                               }
+                       }
+               } else if (_operation == EndTrim) {
+                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                               if (_preserve_fade_anchor) {
+                                       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
+                                       if (arv) {
+                                               double distance;
+                                               double new_length;
+                                               framecnt_t len;
+                                               boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+                                               distance = _drags->current_pointer_x() - grab_x();
+                                               len = ar->fade_out()->back()->when;
+                                               new_length = len - _editor->unit_to_frame (distance);
+                                               new_length = ar->verify_xfade_bounds (new_length, false  /*END*/ );
+                                               ar->set_fade_out_length(new_length);
+                                       }
+                               }
                        }
-               }
-               for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
-                       (*p)->thaw ();
                }
 
-               _editor->motion_frozen_playlists.clear ();
+               if (!_views.empty()) {
+                       if (_operation == StartTrim) {
+                               _editor->maybe_locate_with_edit_preroll(
+                                       _views.begin()->view->region()->position());
+                       }
+                       if (_operation == EndTrim) {
+                               _editor->maybe_locate_with_edit_preroll(
+                                       _views.begin()->view->region()->position() +
+                                       _views.begin()->view->region()->length());
+                       }
+               }
+       
+               if (!_editor->selection->selected (_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) {
+                                i->view->thaw_after_trim ();
+                               i->view->enable_display (true);
+                               i->view->fake_set_opaque (true);
 
-               if (_have_transaction) {
-                       _editor->commit_reversible_command();
+                               /* 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) {
+                       (*p)->thaw ();
                }
 
+               _editor->motion_frozen_playlists.clear ();
+               _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
@@ -1901,16 +2082,45 @@ 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);
 }
@@ -1918,42 +2128,58 @@ MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 MeterMarkerDrag::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), "%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()));
+       Drag::start_grab (event, cursor);
+       show_verbose_cursor_time (adjusted_current_frame(event));
+}
 
-               _item = &new_marker->the_item ();
-               _marker = new_marker;
+void
+MeterMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
+}
 
-       } else {
+void
+MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
+{
+       if (first_move) {
 
-               MetricSection& section (_marker->meter());
+               // create a dummy marker for visual representation of moving the
+               // section, because whether its a copy or not, we're going to 
+               // leave or lose the original marker (leave if its a copy; lose if its
+               // not, because we'll remove it from the map).
+               
+               MeterSection section (_marker->meter());
 
                if (!section.movable()) {
                        return;
                }
-
+               
+               char name[64];
+               snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
+               
+               _marker = new MeterMarker (
+                       *_editor,
+                       *_editor->meter_group,
+                       ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
+                       name,
+                       *new MeterSection (_marker->meter())
+               );
+               
+               /* use the new marker for the grab */
+               swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
+
+               if (!_copy) {
+                       TempoMap& map (_editor->session()->tempo_map());
+                       /* get current state */
+                       before_state = &map.get_state();
+                       /* remove the section while we drag it */
+                       map.remove_meter (section, true);
+               }
        }
 
-       Drag::start_grab (event, cursor);
-
-       _pointer_frame_offset = grab_frame() - _marker->meter().frame();
-
-       _editor->show_verbose_time_cursor (adjusted_current_frame(event), 10);
-}
-
-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
@@ -1965,11 +2191,11 @@ 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);
-
+       
        if (_copy == true) {
                _editor->begin_reversible_command (_("copy meter mark"));
                XMLNode &before = map.get_state();
@@ -1978,29 +2204,43 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
                _editor->commit_reversible_command ();
 
-               // delete the dummy marker we used for visual representation of copying.
-               // a new visual marker will show up automatically.
-               delete _marker;
        } else {
                _editor->begin_reversible_command (_("move meter mark"));
-               XMLNode &before = map.get_state();
-               map.move_meter (_marker->meter(), when);
+
+               /* we removed it before, so add it back now */
+               
+               map.add_meter (_marker->meter(), when);
                XMLNode &after = map.get_state();
-               _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
+               _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
                _editor->commit_reversible_command ();
        }
+
+       // delete the dummy marker we used for visual representation while moving.
+       // a new visual marker will show up automatically.
+       delete _marker;
 }
 
 void
-MeterMarkerDrag::aborted ()
+MeterMarkerDrag::aborted (bool moved)
 {
        _marker->set_position (_marker->meter().frame ());
+
+       if (moved) {
+               TempoMap& map (_editor->session()->tempo_map());
+               /* we removed it before, so add it back now */
+               map.add_meter (_marker->meter(), _marker->meter().frame());
+               // delete the dummy marker we used for visual representation while moving.
+               // a new visual marker will show up automatically.
+               delete _marker;
+       }
 }
 
 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);
 }
@@ -2008,40 +2248,57 @@ TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
+       Drag::start_grab (event, cursor);
+       show_verbose_cursor_time (adjusted_current_frame (event));
+}
 
-       if (_copy) {
+void
+TempoMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
+}
+
+void
+TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
+{
+       if (first_move) {
 
+               // create a dummy marker for visual representation of moving the
+               // section, because whether its a copy or not, we're going to 
+               // leave or lose the original marker (leave if its a copy; lose if its
+               // not, because we'll remove it from the map).
+               
                // 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()));
 
-               _item = &new_marker->the_item ();
-               _marker = new_marker;
-
-       } else {
-
-               MetricSection& section (_marker->tempo());
-
-               if (!section.movable()) {
-                       return;
+               TempoSection section (_marker->tempo());
+
+               _marker = new TempoMarker (
+                       *_editor,
+                       *_editor->tempo_group,
+                       ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
+                       name,
+                       *new TempoSection (_marker->tempo())
+                       );
+
+               /* use the new marker for the grab */
+               swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
+
+               if (!_copy) {
+                       TempoMap& map (_editor->session()->tempo_map());
+                       /* get current state */
+                       before_state = &map.get_state();
+                       /* remove the section while we drag it */
+                       map.remove_tempo (section, true);
                }
        }
 
-       Drag::start_grab (event, cursor);
-
-       _pointer_frame_offset = grab_frame() - _marker->tempo().frame();
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
-}
-
-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
@@ -2053,10 +2310,11 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
-       BBT_Time when;
-
        TempoMap& map (_editor->session()->tempo_map());
-       map.bbt_time (last_pointer_frame(), when);
+       framepos_t beat_time = map.round_to_beat (last_pointer_frame(), 0);
+       Timecode::BBT_Time when;
+
+       map.bbt_time (beat_time, when);
 
        if (_copy == true) {
                _editor->begin_reversible_command (_("copy tempo mark"));
@@ -2066,31 +2324,65 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
                _editor->commit_reversible_command ();
 
-               // delete the dummy marker we used for visual representation of copying.
-               // a new visual marker will show up automatically.
-               delete _marker;
        } else {
                _editor->begin_reversible_command (_("move tempo mark"));
-               XMLNode &before = map.get_state();
-               map.move_tempo (_marker->tempo(), when);
+               /* we removed it before, so add it back now */
+               map.add_tempo (_marker->tempo(), when);
                XMLNode &after = map.get_state();
-               _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
+               _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
                _editor->commit_reversible_command ();
        }
+
+       // delete the dummy marker we used for visual representation while moving.
+       // a new visual marker will show up automatically.
+       delete _marker;
 }
 
 void
-TempoMarkerDrag::aborted ()
+TempoMarkerDrag::aborted (bool moved)
 {
        _marker->set_position (_marker->tempo().frame());
+       if (moved) {
+               TempoMap& map (_editor->session()->tempo_map());
+               /* we removed it before, so add it back now */
+               map.add_tempo (_marker->tempo(), _marker->tempo().start());
+               // delete the dummy marker we used for visual representation while moving.
+               // a new visual marker will show up automatically.
+               delete _marker;
+       }
 }
 
 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::fake_locate (framepos_t t)
+{
+       _editor->playhead_cursor->set_position (t);
+
+       Session* s = _editor->session ();
+       if (s->timecode_transmission_suspended ()) {
+               framepos_t const f = _editor->playhead_cursor->current_frame;
+               /* This is asynchronous so it will be sent "now"
+                */
+               s->send_mmc_locate (f);
+               /* These are synchronous and will be sent during the next
+                  process cycle
+               */
+               s->queue_full_time_code ();
+               s->queue_song_position_pointer ();
+       }
+
+       show_verbose_cursor_time (t);
+       _editor->UpdateAllTransportClocks (t);
 }
 
 void
@@ -2098,69 +2390,54 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
 {
        Drag::start_grab (event, c);
 
-       if (!_stop) {
+       _grab_zoom = _editor->frames_per_unit;
 
-               nframes64_t where = _editor->event_frame (event, 0, 0);
+       framepos_t where = _editor->event_frame (event, 0, 0);
+       _editor->snap_to_with_modifier (where, event);
 
-               _editor->snap_to_with_modifier (where, event);
-               _editor->playhead_cursor->set_position (where);
+       _editor->_dragging_playhead = true;
 
-       }
+       Session* s = _editor->session ();
 
-       if (_cursor == _editor->playhead_cursor) {
-               _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 ();
-                       }
 
-                       if (s->is_auditioning()) {
-                               s->cancel_audition ();
-                       }
+               if (AudioEngine::instance()->connected()) {
+                       
+                       /* do this only if we're the engine is connected
+                        * because otherwise this request will never be
+                        * serviced and we'll busy wait forever. likewise,
+                        * notice if we are disconnected while waiting for the
+                        * request to be serviced.
+                        */
 
                        s->request_suspend_timecode_transmission ();
-
-                       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);
+                       while (AudioEngine::instance()->connected() && !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 (adjusted_frame == last_pointer_frame()) {
-               return;
-       }
-
-       _cursor->set_position (adjusted_frame);
-
-       _editor->show_verbose_time_cursor (_cursor->current_frame, 10);
-
-       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);
-       }
-       
-
+       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
@@ -2174,31 +2451,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
@@ -2206,20 +2481,26 @@ 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 ();
 
-       _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);
-       
+       show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
+}
+
+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 ();
 
@@ -2239,10 +2520,10 @@ FadeInDrag::motion (GdkEvent* event, bool)
                        continue;
                }
 
-               tmp->reset_fade_in_shape_width (fade_length);
+               tmp->reset_fade_in_shape_width (tmp->audio_region(), 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
@@ -2252,9 +2533,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 ();
 
@@ -2290,7 +2571,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);
@@ -2299,14 +2580,14 @@ FadeInDrag::aborted ()
                        continue;
                }
 
-               tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
+               tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
        }
 }
 
 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
@@ -2314,19 +2595,26 @@ 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 ();
+
+       show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
+}
 
-       _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);
+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 ();
 
@@ -2348,10 +2636,10 @@ FadeOutDrag::motion (GdkEvent* event, bool)
                        continue;
                }
 
-               tmp->reset_fade_out_shape_width (fade_length);
+               tmp->reset_fade_out_shape_width (tmp->audio_region(), 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
@@ -2361,9 +2649,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 ();
 
@@ -2401,7 +2689,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);
@@ -2410,34 +2698,36 @@ FadeOutDrag::aborted ()
                        continue;
                }
 
-               tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
+               tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
        }
 }
 
 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, _editor->physical_screen_height));
-
-       _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();
+       _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
 }
 
 MarkerDrag::~MarkerDrag ()
 {
-       for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
-               delete *i;
+       for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
+               delete i->location;
        }
 }
 
+MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, Marker* m)
+{
+       location = new Location (*l);
+       markers.push_back (m);
+       move_both = false;
+}
+
 void
 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
@@ -2448,24 +2738,22 @@ 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);
 
        switch (op) {
        case Selection::Toggle:
-               _editor->selection->toggle (_marker);
+               /* we toggle on the button release */
                break;
        case Selection::Set:
                if (!_editor->selection->selected (_marker)) {
@@ -2476,13 +2764,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));
@@ -2507,51 +2795,79 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                break;
        }
 
-       /* Set up copies for us to manipulate during the drag */
+       /* Set up copies for us to manipulate during the drag 
+        */
 
        for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
+
                Location* l = _editor->find_location_from_marker (*i, is_start);
-               _copied_locations.push_back (new Location (*l));
+
+               if (!l) {
+                       continue;
+               }
+
+               if (l->is_mark()) {
+                       _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
+               } else {
+                       /* range: check that the other end of the range isn't
+                          already there.
+                       */
+                       CopiedLocationInfo::iterator x;
+                       for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
+                               if (*(*x).location == *l) {
+                                       break;
+                               }
+                       }
+                       if (x == _copied_locations.end()) {
+                               _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
+                       } else {
+                               (*x).markers.push_back (*i);
+                               (*x).move_both = true;
+                       }
+               }
+                       
        }
 }
 
+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;
-
-       if (newframe == last_pointer_frame()) {
-               return;
-       }
+       framepos_t const newframe = adjusted_current_frame (event);
+       framepos_t next = newframe;
 
        if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
                move_both = true;
        }
 
-       MarkerSelection::iterator i;
-       list<Location*>::iterator x;
+       CopiedLocationInfo::iterator x;
 
        /* find the marker we're dragging, and compute the delta */
 
-       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
-            x != _copied_locations.end() && i != _editor->selection->markers.end();
-            ++i, ++x) {
+       for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
+
+               copy_location = (*x).location;
 
-               copy_location = *x;
-               marker = *i;
+               if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
 
-               if (marker == _marker) {
+                       /* this marker is represented by this
+                        * CopiedLocationMarkerInfo 
+                        */
 
-                       if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
+                       if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
                                /* que pasa ?? */
                                return;
                        }
@@ -2561,14 +2877,16 @@ MarkerDrag::motion (GdkEvent* event, bool)
                        } else {
 
 
-                               switch (marker->type()) {
-                               case Marker::Start:
+                               switch (_marker->type()) {
+                               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();
@@ -2578,27 +2896,25 @@ MarkerDrag::motion (GdkEvent* event, bool)
                                        return;
                                }
                        }
+
                        break;
                }
        }
 
-       if (i == _editor->selection->markers.end()) {
+       if (x == _copied_locations.end()) {
                /* hmm, impossible - we didn't find the dragged marker */
                return;
        }
 
        /* now move them all */
 
-       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
-            x != _copied_locations.end() && i != _editor->selection->markers.end();
-            ++i, ++x) {
+       for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
 
-               copy_location = *x;
-               marker = *i;
+               copy_location = x->location;
 
                /* call this to find out if its the start or end */
 
-               if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
+               if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
                        continue;
                }
 
@@ -2613,18 +2929,18 @@ MarkerDrag::motion (GdkEvent* event, bool)
                        copy_location->set_start (copy_location->start() + f_delta);
 
                } 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
-
-                               if (move_both) {
+                               
+                               if (move_both || (*x).move_both) {
                                        copy_location->set_start (new_start);
                                        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);
@@ -2632,7 +2948,7 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
                        } else { // end marker
 
-                               if (move_both) {
+                               if (move_both || (*x).move_both) {
                                        copy_location->set_end (new_end);
                                        copy_location->set_start (new_start);
                                } else if (new_end > copy_location->start()) {
@@ -2646,17 +2962,25 @@ MarkerDrag::motion (GdkEvent* event, bool)
                }
 
                update_item (copy_location);
-
+               
+               /* now lookup the actual GUI items used to display this
+                * location and move them to wherever the copy of the location
+                * is now. This means that the logic in ARDOUR::Location is
+                * still enforced, even though we are not (yet) modifying 
+                * the real Location itself.
+                */
+               
                Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
 
                if (lm) {
                        lm->set_position (copy_location->start(), copy_location->end());
                }
+
        }
 
        assert (!_copied_locations.empty());
 
-       _editor->show_verbose_time_cursor (newframe, 10);
+       show_verbose_cursor_time (newframe);
 
 #ifdef GTKOSX
        _editor->update_canvas_now ();
@@ -2682,6 +3006,10 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                        break;
 
                case Selection::Toggle:
+                       /* we toggle on the button release, click only */
+                       _editor->selection->toggle (_marker);
+                       break;
+
                case Selection::Extend:
                case Selection::Add:
                        break;
@@ -2696,7 +3024,7 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        XMLNode &before = _editor->session()->locations()->get_state();
 
        MarkerSelection::iterator i;
-       list<Location*>::iterator x;
+       CopiedLocationInfo::iterator x;
        bool is_start;
 
        for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
@@ -2712,9 +3040,9 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                        }
 
                        if (location->is_mark()) {
-                               location->set_start ((*x)->start());
+                               location->set_start (((*x).location)->start());
                        } else {
-                               location->set ((*x)->start(), (*x)->end());
+                               location->set (((*x).location)->start(), ((*x).location)->end());
                        }
                }
        }
@@ -2722,24 +3050,18 @@ 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 */
 }
 
 void
-MarkerDrag::update_item (Location* location)
+MarkerDrag::update_item (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)
@@ -2747,6 +3069,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);
 }
@@ -2755,21 +3083,27 @@ 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
-       _time_axis_view_grab_x = _point->get_x();
-       _time_axis_view_grab_y = _point->get_y();
+       _fixed_grab_x = _point->get_x();
+       _fixed_grab_y = _point->get_y();
 
        float const fraction = 1 - (_point->get_y() / _point->line().height());
 
-       _point->line().start_drag_single (_point, _time_axis_view_grab_x, fraction);
+       _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 ();
+
+       _pushing = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
+
+       if (!_point->can_slide ()) {
+               _x_constrained = true;
+       }
 }
 
 void
@@ -2783,9 +3117,10 @@ ControlPointDrag::motion (GdkEvent* event, bool)
                dy *= 0.1;
        }
 
-       /* coordinate in TimeAxisView's space */
-       double cx = _time_axis_view_grab_x + _cumulative_x_drag + dx;
-       double cy = _time_axis_view_grab_y + _cumulative_y_drag + dy;
+       /* coordinate in pixels relative to the start of the region (for region-based automation)
+          or track (for track-based automation) */
+       double cx = _fixed_grab_x + _cumulative_x_drag + dx;
+       double cy = _fixed_grab_y + _cumulative_y_drag + dy;
 
        // calculate zero crossing point. back off by .01 to stay on the
        // positive side of zero
@@ -2797,32 +3132,32 @@ ControlPointDrag::motion (GdkEvent* event, bool)
        }
 
        if (_x_constrained) {
-               cx = _time_axis_view_grab_x;
+               cx = _fixed_grab_x;
        }
        if (_y_constrained) {
-               cy = _time_axis_view_grab_y;
+               cy = _fixed_grab_y;
        }
 
-       _cumulative_x_drag = cx - _time_axis_view_grab_x;
-       _cumulative_y_drag = cy - _time_axis_view_grab_y;
+       _cumulative_x_drag = cx - _fixed_grab_x;
+       _cumulative_y_drag = cy - _fixed_grab_y;
 
        cx = max (0.0, cx);
        cy = max (0.0, cy);
        cy = min ((double) _point->line().height(), cy);
 
-       nframes64_t cx_frames = _editor->unit_to_frame (cx);
+       framepos_t cx_frames = _editor->unit_to_frame (cx);
 
        if (!_x_constrained) {
                _editor->snap_to_with_modifier (cx_frames, event);
        }
 
-       float const fraction = 1.0 - (cy / _point->line().height());
+       cx_frames = min (cx_frames, _point->line().maximum_time());
 
-       bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
+       float const fraction = 1.0 - (cy / _point->line().height());
 
-       _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
+       _point->line().drag_motion (_editor->frame_to_unit_unrounded (cx_frames), fraction, false, _pushing, _final_index);
 
-       _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
@@ -2839,11 +3174,13 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
        } else {
                motion (event, false);
        }
-       _point->line().end_drag ();
+
+       _point->line().end_drag (_pushing, _final_index);
+       _editor->session()->commit_reversible_command ();
 }
 
 void
-ControlPointDrag::aborted ()
+ControlPointDrag::aborted (bool)
 {
        _point->line().reset ();
 }
@@ -2865,8 +3202,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*/)
 {
@@ -2884,31 +3222,31 @@ 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 */
 
-       _time_axis_view_grab_x = cx;
-       _time_axis_view_grab_y = cy;
+       _fixed_grab_x = cx;
+       _fixed_grab_y = cy;
 
        double fraction = 1.0 - (cy / _line->height());
 
        _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
@@ -2920,38 +3258,32 @@ LineDrag::motion (GdkEvent* event, bool)
                dy *= 0.1;
        }
 
-       double cy = _time_axis_view_grab_y + _cumulative_y_drag + dy;
+       double cy = _fixed_grab_y + _cumulative_y_drag + dy;
 
-       _cumulative_y_drag = cy - _time_axis_view_grab_y;
+       _cumulative_y_drag = cy - _fixed_grab_y;
 
        cy = max (0.0, cy);
        cy = min ((double) _line->height(), cy);
 
        double const fraction = 1.0 - (cy / _line->height());
-
-       bool push;
-
-       if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
-               push = false;
-       } else {
-               push = true;
-       }
+       uint32_t ignored;
 
        /* we are ignoring x position for this drag, so we can just pass in anything */
-       _line->drag_motion (0, fraction, true, push);
+       _line->drag_motion (0, fraction, true, false, ignored);
 
-       _editor->set_verbose_canvas_cursor_text (_line->get_verbose_cursor_string (fraction));
+       _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
 }
 
 void
 LineDrag::finished (GdkEvent* event, bool)
 {
        motion (event, false);
-       _line->end_drag ();
+       _line->end_drag (false, 0);
+       _editor->session()->commit_reversible_command ();
 }
 
 void
-LineDrag::aborted ()
+LineDrag::aborted (bool)
 {
        _line->reset ();
 }
@@ -2961,15 +3293,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. */
@@ -2981,69 +3313,89 @@ 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)
+       , _vertical_only (false)
+{
+       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);
        }
@@ -3073,87 +3425,129 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
                double x2 = _editor->frame_to_pixel (end);
 
                _editor->rubberband_rect->property_x1() = x1;
+               if (_vertical_only) {
+                       /* fixed 10 pixel width */
+                       _editor->rubberband_rect->property_x2() = x1 + 10;
+               } else {
+                       _editor->rubberband_rect->property_x2() = x2;
+               } 
+
                _editor->rubberband_rect->property_y1() = y1;
-               _editor->rubberband_rect->property_x2() = x2;
                _editor->rubberband_rect->property_y2() = y2;
 
                _editor->rubberband_rect->show();
                _editor->rubberband_rect->raise_to_top();
 
-               _editor->show_verbose_time_cursor (pf, 10);
+               show_verbose_cursor_time (pf);
+
+               do_select_things (event, true);
        }
 }
 
+void
+RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
+{
+       framepos_t x1;
+       framepos_t x2;
+       
+       if (grab_frame() < last_pointer_frame()) {
+               x1 = grab_frame ();
+               x2 = last_pointer_frame ();
+       } else {
+               x2 = grab_frame ();
+               x1 = last_pointer_frame ();
+       }
+
+       double y1;
+       double y2;
+       
+       if (_drags->current_pointer_y() < grab_y()) {
+               y1 = _drags->current_pointer_y();
+               y2 = grab_y();
+       } else {
+               y2 = _drags->current_pointer_y();
+               y1 = grab_y();
+       }
+
+       select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
+}
+
 void
 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
 {
        if (movement_occurred) {
 
                motion (event, false);
+               do_select_things (event, false);
 
-               double y1,y2;
-               if (_drags->current_pointer_y() < grab_y()) {
-                       y1 = _drags->current_pointer_y();
-                       y2 = grab_y();
-               } else {
-                       y2 = _drags->current_pointer_y();
-                       y1 = grab_y();
-               }
+       } else {
 
+               /* just a click */
 
-               Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
-               bool committed;
+               bool do_deselect = true;
+               MidiTimeAxisView* mtv;
 
-               _editor->begin_reversible_command (_("rubberband selection"));
+               if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
+                       /* MIDI track */
+                       if (_editor->selection->empty()) {
+                               /* nothing selected */
+                               add_midi_region (mtv);
+                               do_deselect = false;
+                       }
+               } 
 
-               if (grab_frame() < last_pointer_frame()) {
-                       committed = _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op);
-               } else {
-                       committed = _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op);
-               }
+               /* do not deselect if Primary or Tertiary (toggle-select or
+                * extend-select are pressed.
+                */
 
-               if (!committed) {
-                       _editor->commit_reversible_command ();
+               if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) && 
+                   !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) && 
+                   do_deselect) {
+                       deselect_things ();
                }
 
-       } else {
-               if (!getenv("ARDOUR_SAE")) {
-                       _editor->selection->clear_tracks();
-               }
-               _editor->selection->clear_regions();
-               _editor->selection->clear_points ();
-               _editor->selection->clear_lines ();
        }
 
        _editor->rubberband_rect->hide();
 }
 
 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
 TimeFXDrag::motion (GdkEvent* event, bool)
 {
        RegionView* rv = _primary;
+       StreamView* cv = rv->get_time_axis_view().view ();
+
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
+       int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
+       int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
 
-       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);
+               rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
        }
 
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -3170,7 +3564,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();
 
@@ -3181,24 +3575,30 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 #endif
 
-       _editor->begin_reversible_command (_("timestretch"));
-
-       // XXX how do timeFX on multiple regions ?
-
-       RegionSelection rs;
-       rs.add (_primary);
+       if (!_editor->get_selection().regions.empty()) {
+               /* primary will already be included in the selection, and edit
+                  group shared editing will propagate selection across
+                  equivalent regions, so just use the current region
+                  selection.
+               */
 
-       if (_editor->time_stretch (rs, percentage) == -1) {
-               error << _("An error occurred while executing time stretch operation") << endmsg;
+               if (_editor->time_stretch (_editor->get_selection().regions, percentage) == -1) {
+                       error << _("An error occurred while executing time stretch operation") << endmsg;
+               }
        }
 }
 
 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 *)
@@ -3222,7 +3622,7 @@ ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 }
 
 void
-ScrubDrag::aborted ()
+ScrubDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3230,19 +3630,23 @@ ScrubDrag::aborted ()
 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
        : Drag (e, i)
        , _operation (o)
-       , _copy (false)
+       , _add (false)
+       , _extend (false)
        , _original_pointer_time_axis (-1)
        , _last_pointer_time_axis (-1)
+       , _time_selection_at_start (!_editor->get_selection().time.empty())
 {
-
+       DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
+       
+       if (_time_selection_at_start) {
+               start_at_start = _editor->get_selection().time.start();
+               end_at_start = _editor->get_selection().time.end_frame();
+       }
 }
 
 void
 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
 {
-       nframes64_t start = 0;
-       nframes64_t end = 0;
-
        if (_editor->session() == 0) {
                return;
        }
@@ -3251,12 +3655,12 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
 
        switch (_operation) {
        case CreateSelection:
-               if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
-                       _copy = true;
+               if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
+                       _add = true;
                } else {
-                       _copy = false;
+                       _add = false;
                }
-               cursor = _editor->selector_cursor;
+               cursor = _editor->cursors()->selector;
                Drag::start_grab (event, cursor);
                break;
 
@@ -3264,49 +3668,70 @@ 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;
+
+       case SelectionExtend:
+               Drag::start_grab (event, cursor);
                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;
+
+       case SelectionExtend:
+               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 = 0;
+       framecnt_t distance = 0;
 
        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 */
 
@@ -3317,13 +3742,18 @@ 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);
+                       grab = adjusted_current_frame (event, false);
+                       if (grab < pending_position) {
+                               _editor->snap_to (grab, -1);
+                       }  else {
+                               _editor->snap_to (grab, 1);
+                       }
                }
 
-               if (pending_position < grab_frame()) {
+               if (pending_position < grab) {
                        start = pending_position;
                        end = grab;
                } else {
@@ -3337,24 +3767,27 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
                if (first_move) {
 
-                       if (_copy) {
+                       if (_add) {
                                /* 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;
+                               _add = 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);
                }
@@ -3362,7 +3795,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());
 
@@ -3371,7 +3804,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);
@@ -3407,20 +3840,23 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                }
 
                break;
-
+               
        case SelectionMove:
 
                start = _editor->selection->time[_editor->clicked_selection].start;
                end = _editor->selection->time[_editor->clicked_selection].end;
 
                length = end - start;
-
+               distance = pending_position - start;
                start = pending_position;
                _editor->snap_to (start);
 
                end = start + length;
 
                break;
+
+       case SelectionExtend:
+               break;
        }
 
        if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
@@ -3428,13 +3864,21 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        }
 
        if (start != end) {
-               _editor->selection->replace (_editor->clicked_selection, start, end);
+               switch (_operation) {
+               case SelectionMove:     
+                       if (_time_selection_at_start) {
+                               _editor->selection->move_time (distance);
+                       }
+                       break;
+               default:
+                       _editor->selection->replace (_editor->clicked_selection, start, end);
+               }
        }
 
        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);
        }
 }
 
@@ -3451,22 +3895,43 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
                }
 
                /* XXX what if its a music time selection? */
-               if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
-                       s->request_play_range (&_editor->selection->time, true);
+               if (s) {
+                       if ( s->get_play_range() && s->transport_rolling() ) {
+                               s->request_play_range (&_editor->selection->time, true);
+                       } else {
+                               if (Config->get_always_play_range() && !s->transport_rolling()) {
+                                       s->request_locate (_editor->get_selection().time.start());
+                               }
+                       }
                }
 
-
        } else {
-               /* just a click, no pointer movement.*/
-
-               if (Keyboard::no_modifier_keys_pressed (&event->button)) {
-                       _editor->selection->clear_time();
+               /* just a click, no pointer movement.
+                */
+
+               if (_operation == SelectionExtend) {
+                       if (_time_selection_at_start) {
+                               framepos_t pos = adjusted_current_frame (event, false);
+                               framepos_t start = min (pos, start_at_start);
+                               framepos_t end = max (pos, end_at_start);
+                               _editor->selection->set (start, end);
+                       }
+               } else {
+                       if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
+                               if (_editor->clicked_selection) {
+                                       _editor->selection->remove (_editor->clicked_selection);
+                               }
+                       } else {
+                               if (!_editor->clicked_selection) {
+                                       _editor->selection->clear_time();
+                               }
+                       }
                }
 
                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);
                }
@@ -3474,10 +3939,11 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
        }
 
        _editor->stop_canvas_autoscroll ();
+       _editor->clicked_selection = 0;
 }
 
 void
-SelectionDrag::aborted ()
+SelectionDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3487,7 +3953,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, _editor->physical_screen_height);
+       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();
@@ -3504,7 +3973,7 @@ RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
        Gdk::Cursor* cursor = 0;
 
        if (!_editor->temp_location) {
-               _editor->temp_location = new Location;
+               _editor->temp_location = new Location (*_editor->session());
        }
 
        switch (_operation) {
@@ -3517,20 +3986,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) {
@@ -3544,17 +4013,17 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
                crect = _editor->cd_marker_bar_drag_rect;
                break;
        default:
-               cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
+               error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
                return;
                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;
@@ -3595,7 +4064,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);
 
 }
 
@@ -3625,7 +4094,10 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
                                flags = Location::IsRangeMarker;
                                _editor->range_bar_drag_rect->hide();
                        }
-                       newloc = new Location(_editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags);
+                       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));
@@ -3643,23 +4115,23 @@ 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 ();
                        }
 
                        switch (_editor->mouse_mode) {
                        case MouseObject:
                                /* find the two markers on either side and then make the selection from it */
-                               _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set);
+                               _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
                                break;
 
                        case MouseRange:
@@ -3677,7 +4149,7 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RangeMarkerBarDrag::aborted ()
+RangeMarkerBarDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3692,22 +4164,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 */
@@ -3728,7 +4214,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);
        }
 }
 
@@ -3739,32 +4225,37 @@ MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
                motion (event, false);
 
                if (grab_frame() < last_pointer_frame()) {
-                       _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
+                       _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame());
                } else {
-                       _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
+                       _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame());
                }
        } 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 ();
 }
 
 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
+       , _cumulative_dx (0)
+       , _cumulative_dy (0)
 {
-       CanvasNoteEvent*     cnote = dynamic_cast<CanvasNoteEvent*>(_item);
-       region = &cnote->region_view();
+       DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
+
+       _primary = dynamic_cast<CanvasNoteEvent*> (_item);
+       _region = &_primary->region_view ();
+       _note_height = _region->midi_stream_view()->note_height ();
 }
 
 void
@@ -3772,23 +4263,7 @@ NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        Drag::start_grab (event);
 
-       drag_delta_x = 0;
-       drag_delta_note = 0;
-
-       double event_x;
-       double event_y;
-
-       event_x = _drags->current_pointer_x();
-       event_y = _drags->current_pointer_y();
-
-       _item->property_parent().get_value()->w2i(event_x, event_y);
-
-       last_x = region->snap_to_pixel(event_x);
-       last_y = event_y;
-
-       CanvasNoteEvent* cnote = dynamic_cast<CanvasNoteEvent*>(_item);
-
-       if (!(was_selected = cnote->selected())) {
+       if (!(_was_selected = _primary->selected())) {
 
                /* tertiary-click means extend selection - we'll do that on button release,
                   so don't add it here, because otherwise we make it hard to figure
@@ -3801,129 +4276,191 @@ NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
                        bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
 
                        if (add) {
-                               region->note_selected (cnote, true);
+                               _region->note_selected (_primary, true);
                        } else {
-                               region->unique_select (cnote);
+                               _region->unique_select (_primary);
                        }
                }
        }
 }
 
+/** @return Current total drag x change in frames */
+frameoffset_t
+NoteDrag::total_dx () const
+{
+       /* dx in frames */
+       frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
+
+       /* primary note time */
+       frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
+
+       /* new time of the primary note in session frames */
+       frameoffset_t st = n + dx;
+
+       framepos_t const rp = _region->region()->position ();
+
+       /* prevent the note being dragged earlier than the region's position */
+       st = max (st, rp);
+
+       /* snap and return corresponding delta */
+       return _region->snap_frame_to_frame (st - rp) + rp - n;
+}
+
+/** @return Current total drag y change in note number */
+int8_t
+NoteDrag::total_dy () const
+{
+       MidiStreamView* msv = _region->midi_stream_view ();
+       double const y = _region->midi_view()->y_position ();
+       /* new current note */
+       uint8_t n = msv->y_to_note (_drags->current_pointer_y () - y);
+       /* clamp */
+       n = max (msv->lowest_note(), n);
+       n = min (msv->highest_note(), n);
+       /* and work out delta */
+       return n - msv->y_to_note (grab_y() - y);
+}
+
 void
-NoteDrag::motion (GdkEvent*, bool)
+NoteDrag::motion (GdkEvent *, bool)
 {
-       MidiStreamView* streamview = region->midi_stream_view();
-       double event_x;
-       double event_y;
+       /* Total change in x and y since the start of the drag */
+       frameoffset_t const dx = total_dx ();
+       int8_t const dy = total_dy ();
 
-       event_x = _drags->current_pointer_x();
-       event_y = _drags->current_pointer_y();
+       /* 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;
 
-       _item->property_parent().get_value()->w2i(event_x, event_y);
+       if (tdx || tdy) {
+               _cumulative_dx += tdx;
+               _cumulative_dy += tdy;
 
-       event_x = region->snap_to_pixel(event_x);
+               int8_t note_delta = total_dy();
 
-       double dx     = event_x - last_x;
-       double dy     = event_y - last_y;
-       last_x = event_x;
+               _region->move_selection (tdx, tdy, note_delta);
 
-       drag_delta_x += dx;
+               /* the new note value may be the same as the old one, but we
+                * don't know what that means because the selection may have
+                * involved more than one note and we might be doing something
+                * odd with them. so show the note value anyway, always.
+                */
 
-       // Snap to note rows
+               char buf[12];
+               uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
+               
+               snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
+                         (int) floor (new_note));
 
-       if (abs (dy) < streamview->note_height()) {
-               dy = 0.0;
-       } else {
-               int8_t this_delta_note;
-               if (dy > 0) {
-                       this_delta_note = (int8_t)ceil(dy / streamview->note_height() / 2.0);
-               } else {
-                       this_delta_note = (int8_t)floor(dy / streamview->note_height() / 2.0);
-               }
-               drag_delta_note -= this_delta_note;
-               dy = streamview->note_height() * this_delta_note;
-               last_y = last_y + dy;
-       }
-
-       if (dx || dy) {
-                
-               CanvasNoteEvent* cnote = dynamic_cast<CanvasNoteEvent*>(_item);
-                Evoral::MusicalTime new_time;
-                
-                if (drag_delta_x) {
-                        nframes64_t start_frames = region->beats_to_frames(cnote->note()->time());
-                        if (drag_delta_x >= 0) {
-                                start_frames += region->snap_frame_to_frame(_editor->pixel_to_frame(drag_delta_x));
-                        } else {
-                                start_frames -= region->snap_frame_to_frame(_editor->pixel_to_frame(-drag_delta_x));
-                        }
-                        new_time = region->frames_to_beats(start_frames);
-                } else {
-                        new_time = cnote->note()->time();
-                }
-                
-                boost::shared_ptr<Evoral::Note<Evoral::MusicalTime> > check_note (
-                        new Evoral::Note<Evoral::MusicalTime> (cnote->note()->channel(), 
-                                                               new_time,
-                                                               cnote->note()->length(), 
-                                                               cnote->note()->note() + drag_delta_note,
-                                                               cnote->note()->velocity()));
-
-               region->move_selection (dx, dy);
-
-                char buf[12];
-                snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (cnote->note()->note() + drag_delta_note).c_str(),
-                          (int) floor ((cnote->note()->note() + drag_delta_note)));
-               _editor->show_verbose_canvas_cursor_with (buf);
-        }
+               show_verbose_cursor_text (buf);
+       }
 }
 
 void
 NoteDrag::finished (GdkEvent* ev, bool moved)
 {
-       ArdourCanvas::CanvasNote* cnote = dynamic_cast<ArdourCanvas::CanvasNote*>(_item);
-
        if (!moved) {
-               if (_editor->current_mouse_mode() == Editing::MouseObject) {
-
-                       if (was_selected) {
+               /* no motion - select note */
+               
+               if (_editor->current_mouse_mode() == Editing::MouseObject ||
+                   _editor->current_mouse_mode() == Editing::MouseDraw) {
+                       
+                       if (_was_selected) {
                                bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
                                if (add) {
-                                       region->note_deselected (cnote);
+                                       _region->note_deselected (_primary);
                                }
                        } else {
                                bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
                                bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
 
-                               if (!extend && !add && region->selection_size() > 1) {
-                                       region->unique_select(cnote);
+                               if (!extend && !add && _region->selection_size() > 1) {
+                                       _region->unique_select (_primary);
                                } else if (extend) {
-                                       region->note_selected (cnote, true, true);
+                                       _region->note_selected (_primary, true, true);
                                } else {
                                        /* it was added during button press */
                                }
                        }
                }
        } else {
-               region->note_dropped (cnote, drag_delta_x, drag_delta_note);
+               _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)
+/** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
+AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
+       : Drag (editor, atv->base_item ())
        , _ranges (r)
        , _nothing_to_drag (false)
 {
-       _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
-       assert (_atav);
+       DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
+       y_origin = atv->y_position();
+       setup (atv->lines ());
+}
 
-       _line = _atav->line ();
+/** Make an AutomationRangeDrag for region gain lines */
+AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AudioRegionView* rv, list<AudioRange> const & r)
+       : Drag (editor, rv->get_canvas_group ())
+       , _ranges (r)
+       , _nothing_to_drag (false)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
+
+       list<boost::shared_ptr<AutomationLine> > lines;
+       lines.push_back (rv->get_gain_line ());
+       y_origin = rv->get_time_axis_view().y_position();
+       setup (lines);
+}
+
+/** @param lines AutomationLines to drag.
+ *  @param offset Offset from the session start to the points in the AutomationLines.
+ */
+void
+AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
+{
+       /* find the lines that overlap the ranges being dragged */
+       list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
+       while (i != lines.end ()) {
+               list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
+               ++j;
+
+               pair<framepos_t, framepos_t> 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) != Evoral::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 */
+}
+
+double
+AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
+{
+       return 1.0 - ((global_y - y_origin) / line->height());
 }
 
 void
@@ -3931,80 +4468,133 @@ 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 ();
+               i->original_fraction = y_fraction (i->line, _drags->current_pointer_y());
+       }
 
-       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));
+                               the_list->add (q, the_list->eval (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));
+                               the_list->add (q, the_list->eval (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, y_fraction (i->line, _drags->current_pointer_y()), 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 l = _lines.begin(); l != _lines.end(); ++l) {
+               float const f = y_fraction (l->line, _drags->current_pointer_y());
+               /* we are ignoring x position for this drag, so we can just pass in anything */
+               uint32_t ignored;
+               l->line->drag_motion (0, f, true, false, ignored);
+               show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, f));
+       }
 }
 
 void
@@ -4013,21 +4603,382 @@ 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 (false, 0);
+       }
+
+       _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->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, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
+                                                  _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
+                                                  grab_frame()));
 }
+
+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(); // permitted dx in frames
+       double const dxu = _editor->frame_to_unit (dxf); // permitted fx in units
+       _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->region_frames_to_region_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->source_beats_to_absolute_frames (_patch_change->patch()->time());
+}
+
+MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
+       : RubberbandSelectDrag (e, rv->get_canvas_frame ())
+       , _region_view (rv)
+{
+
+}
+
+void
+MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
+{
+       framepos_t const p = _region_view->region()->position ();
+       double const y = _region_view->midi_view()->y_position ();
+
+       x1 = max ((framepos_t) 0, x1 - p);
+       x2 = max ((framepos_t) 0, x2 - p);
+       y1 = max (0.0, y1 - y);
+       y2 = max (0.0, y2 - y);
+       
+       _region_view->update_drag_selection (
+               _editor->frame_to_pixel (x1),
+               _editor->frame_to_pixel (x2),
+               y1,
+               y2,
+               Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
+               );
+}
+
+void
+MidiRubberbandSelectDrag::deselect_things ()
+{
+       /* XXX */
+}
+
+MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
+       : RubberbandSelectDrag (e, rv->get_canvas_frame ())
+       , _region_view (rv)
+{
+       _vertical_only = true;
+}
+
+void
+MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
+{
+       double const y = _region_view->midi_view()->y_position ();
+
+       y1 = max (0.0, y1 - y);
+       y2 = max (0.0, y2 - y);
+       
+       _region_view->update_vertical_drag_selection (
+               y1,
+               y2,
+               Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
+               );
+}
+
+void
+MidiVerticalSelectDrag::deselect_things ()
+{
+       /* XXX */
+}
+
+EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
+       : RubberbandSelectDrag (e, i)
+{
+
+}
+
+void
+EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
+{
+       if (drag_in_progress) {
+               /* We just want to select things at the end of the drag, not during it */
+               return;
+       }
+       
+       Selection::Operation op = ArdourKeyboard::selection_type (button_state);
+       
+       _editor->begin_reversible_command (_("rubberband selection"));
+       _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
+       _editor->commit_reversible_command ();
+}
+
+void
+EditorRubberbandSelectDrag::deselect_things ()
+{
+       if (!getenv("ARDOUR_SAE")) {
+               _editor->selection->clear_tracks();
+       }
+       _editor->selection->clear_regions();
+       _editor->selection->clear_points ();
+       _editor->selection->clear_lines ();
+}
+
+NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
+       : Drag (e, i)
+       , _region_view (rv)
+       , _drag_rect (0)
+{
+       
+}
+
+NoteCreateDrag::~NoteCreateDrag ()
+{
+       delete _drag_rect;
+}
+
+framecnt_t
+NoteCreateDrag::grid_frames (framepos_t t) const
+{
+       bool success;
+       Evoral::MusicalTime grid_beats = _editor->get_grid_type_as_beats (success, t);
+       if (!success) {
+               grid_beats = 1;
+       }
+
+       return _region_view->region_beats_to_region_frames (grid_beats);
+}
+
+void
+NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
+                                                
+       _drag_rect = new ArdourCanvas::SimpleRect (*_region_view->get_canvas_group ());
+
+       framepos_t pf = _drags->current_pointer_frame ();
+       framecnt_t const g = grid_frames (pf);
+
+       /* Hack so that we always snap to the note that we are over, instead of snapping
+          to the next one if we're more than halfway through the one we're over.
+       */
+       if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
+               pf -= g / 2;
+       }
+
+       _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
+
+       MidiStreamView* sv = _region_view->midi_stream_view ();
+       double const x = _editor->frame_to_pixel (_note[0]);
+       double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
+
+       _drag_rect->property_x1() = x;
+       _drag_rect->property_y1() = y;
+       _drag_rect->property_x2() = x;
+       _drag_rect->property_y2() = y + floor (_region_view->midi_stream_view()->note_height ());
+
+       _drag_rect->property_outline_what() = 0xff;
+       _drag_rect->property_outline_color_rgba() = 0xffffff99;
+       _drag_rect->property_fill_color_rgba()    = 0xffffff66;
+}
+
+void
+NoteCreateDrag::motion (GdkEvent* event, bool)
+{
+       _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
+       double const x = _editor->frame_to_pixel (_note[1]);
+       if (_note[1] > _note[0]) {
+               _drag_rect->property_x2() = x;
+       } else {
+               _drag_rect->property_x1() = x;
+       }
+}
+
+void
+NoteCreateDrag::finished (GdkEvent*, bool had_movement)
+{
+       if (!had_movement) {
+               return;
+       }
+       
+       framepos_t const start = min (_note[0], _note[1]);
+       framecnt_t length = (framecnt_t) fabs (_note[0] - _note[1]);
+
+       framecnt_t const g = grid_frames (start);
+       double const one_tick = 1 / Timecode::BBT_Time::ticks_per_beat;
+       
+       if (_editor->snap_mode() == SnapNormal && length < g) {
+               length = g - one_tick;
+       }
+
+       double const length_beats = max (one_tick, _region_view->region_frames_to_region_beats (length));
+
+       _region_view->create_note_at (start, _drag_rect->property_y1(), length_beats, false);
+}
+
+double
+NoteCreateDrag::y_to_region (double y) const
+{
+       double x = 0;
+       _region_view->get_canvas_group()->w2i (x, y);
+       return y;
+}
+
+void
+NoteCreateDrag::aborted (bool)
+{
+       
+}
+
+CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
+       : Drag (e, i)
+       , arv (rv)
+       , start (start_yn)
+{
+       std::cout << ("CrossfadeEdgeDrag is DEPRECATED.  See TrimDrag::preserve_fade_anchor") << endl;
+}
+
+void
+CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
+{
+       Drag::start_grab (event, cursor);
+}
+
+void
+CrossfadeEdgeDrag::motion (GdkEvent*, bool)
+{
+       double distance;
+       double new_length;
+       framecnt_t len;
+
+       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+
+       if (start) {
+               distance = _drags->current_pointer_x() - grab_x();
+               len = ar->fade_in()->back()->when;
+       } else {
+               distance = grab_x() - _drags->current_pointer_x();
+               len = ar->fade_out()->back()->when;
+       }
+
+       /* how long should it be ? */
+
+       new_length = len + _editor->unit_to_frame (distance);
+
+       /* now check with the region that this is legal */
+
+       new_length = ar->verify_xfade_bounds (new_length, start);
+
+       if (start) {
+               arv->redraw_start_xfade_to (ar, new_length);
+       } else {
+               arv->redraw_end_xfade_to (ar, new_length);
+       }
+}
+
+void
+CrossfadeEdgeDrag::finished (GdkEvent*, bool)
+{
+       double distance;
+       double new_length;
+       framecnt_t len;
+
+       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+
+       if (start) {
+               distance = _drags->current_pointer_x() - grab_x();
+               len = ar->fade_in()->back()->when;
+       } else {
+               distance = grab_x() - _drags->current_pointer_x();
+               len = ar->fade_out()->back()->when;
+       }
+
+       new_length = ar->verify_xfade_bounds (len + _editor->unit_to_frame (distance), start);
+       
+       _editor->begin_reversible_command ("xfade trim");
+       ar->playlist()->clear_owned_changes (); 
+
+       if (start) {
+               ar->set_fade_in_length (new_length);
+       } else {
+               ar->set_fade_out_length (new_length);
+       }
+
+       /* Adjusting the xfade may affect other regions in the playlist, so we need
+          to get undo Commands from the whole playlist rather than just the
+          region.
+       */
+
+       vector<Command*> cmds;
+       ar->playlist()->rdiff (cmds);
+       _editor->session()->add_commands (cmds);
+       _editor->commit_reversible_command ();
+
+}
+
+void
+CrossfadeEdgeDrag::aborted (bool)
+{
+       if (start) {
+               arv->redraw_start_xfade ();
+       } else {
+               arv->redraw_end_xfade ();
+       }
+}
+