Move Diskstream ownership to Track, so that Session no longer holds lists of Diskstre...
[ardour.git] / gtk2_ardour / editor_drag.cc
index 7aa4e3804b6c66ad3853371ec4f0cad8130d1a22..d9da60e0c8705f23242adea60209722ded17b62e 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2009 Paul Davis 
+    Copyright (C) 2009 Paul Davis
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 
 */
 
+#define __STDC_LIMIT_MACROS 1
+#include <stdint.h>
 #include "pbd/memento_command.h"
 #include "pbd/basename.h"
-#include "ardour/diskstream.h"
+#include "pbd/stateful_diff_command.h"
+#include "ardour/session.h"
 #include "ardour/dB.h"
 #include "ardour/region_factory.h"
-#include "ardour/midi_diskstream.h"
 #include "editor.h"
 #include "i18n.h"
 #include "keyboard.h"
 #include "audio_region_view.h"
 #include "midi_region_view.h"
 #include "ardour_ui.h"
+#include "gui_thread.h"
 #include "control_point.h"
 #include "utils.h"
 #include "region_gain_line.h"
 #include "editor_drag.h"
 #include "audio_time_axis.h"
 #include "midi_time_axis.h"
+#include "canvas-note.h"
+#include "selection.h"
+#include "midi_selection.h"
+#include "automation_time_axis.h"
 
 using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
-using namespace sigc;
 using namespace Gtk;
 using namespace Editing;
+using namespace ArdourCanvas;
+
+using Gtkmm2ext::Keyboard;
 
 double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
 
-Drag::Drag (Editor* e, ArdourCanvas::Item* i) :
-       _editor (e),
-       _item (i),
-       _pointer_frame_offset (0),
-       _grab_frame (0),
-       _last_pointer_frame (0),
-       _current_pointer_frame (0),
-       _had_movement (false),
-       _move_threshold_passed (false)
+DragManager::DragManager (Editor* e)
+       : _editor (e)
+       , _ending (false)
+       , _current_pointer_frame (0)
+{
+
+}
+
+DragManager::~DragManager ()
+{
+       abort ();
+}
+
+void
+DragManager::abort ()
+{
+       for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
+               (*i)->end_grab (0);
+               delete *i;
+       }
+
+       _drags.clear ();
+}
+
+void
+DragManager::break_drag ()
+{
+       _ending = true;
+       
+       for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
+               (*i)->break_drag ();
+               delete *i;
+       }
+
+       _drags.clear ();
+
+       _ending = false;
+}
+
+void
+DragManager::add (Drag* d)
+{
+       d->set_manager (this);
+       _drags.push_back (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);
+}
+
+void
+DragManager::start_grab (GdkEvent* e)
+{
+       _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);
+       }
+}
+
+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);
+               if (t) {
+                       r = true;
+               }
+               delete *i;
+       }
+
+       _drags.clear ();
+
+       _ending = false;
+       
+       return r;
+}
+
+bool
+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;
+}
+
+bool
+DragManager::have_item (ArdourCanvas::Item* i) const
+{
+       list<Drag*>::const_iterator j = _drags.begin ();
+       while (j != _drags.end() && (*j)->item () != i) {
+               ++j;
+       }
+
+       return j != _drags.end ();
+}
+
+Drag::Drag (Editor* e, ArdourCanvas::Item* i) 
+       : _editor (e)
+       , _item (i)
+       , _pointer_frame_offset (0)
+       , _move_threshold_passed (false)
+       , _grab_frame (0)
+       , _last_pointer_frame (0)
 {
 
 }
@@ -94,28 +216,22 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
        }
 
        _grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
+       _grab_frame = adjusted_frame (_grab_frame, event);
        _last_pointer_frame = _grab_frame;
-       _current_pointer_frame = _grab_frame;
-       _current_pointer_x = _grab_x;
-       _current_pointer_y = _grab_y;
-       _last_pointer_x = _current_pointer_x;
-       _last_pointer_y = _current_pointer_y;
-
-       _original_x = 0;
-       _original_y = 0;
-       _item->i2w (_original_x, _original_y);
+       _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 (_editor->session && _editor->session->transport_rolling()) {
+       if (_editor->session() && _editor->session()->transport_rolling()) {
                _was_rolling = true;
        } else {
                _was_rolling = false;
        }
 
-       switch (_editor->snap_type) {
+       switch (_editor->snap_type()) {
        case SnapToRegionStart:
        case SnapToRegionEnd:
        case SnapToRegionSync:
@@ -133,78 +249,73 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
 bool
 Drag::end_grab (GdkEvent* event)
 {
-       _ending = true;
-       
        _editor->stop_canvas_autoscroll ();
 
        _item->ungrab (event ? event->button.time : 0);
 
-       _last_pointer_x = _current_pointer_x;
-       _last_pointer_y = _current_pointer_y;
-       finished (event, _had_movement);
+       finished (event, _move_threshold_passed);
 
        _editor->hide_verbose_canvas_cursor();
 
-       update_selection ();
-       
-       _ending = false;
-
-       return _had_movement;
+       return _move_threshold_passed;
 }
 
 nframes64_t
-Drag::adjusted_current_frame () const
+Drag::adjusted_frame (nframes64_t f, GdkEvent const * event, bool snap) const
 {
-       if (_current_pointer_frame > _pointer_frame_offset) {
-               return _current_pointer_frame - _pointer_frame_offset;
+       nframes64_t pos = 0;
+
+       if (f > _pointer_frame_offset) {
+               pos = f - _pointer_frame_offset;
        }
 
-       return 0;
+       if (snap) {
+               _editor->snap_to_with_modifier (pos, event);
+       }
+
+       return pos;
+}
+
+nframes64_t
+Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
+{
+       return adjusted_frame (_drags->current_pointer_frame (), event, snap);
 }
 
 bool
 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
 {
-       _last_pointer_x = _current_pointer_x;
-       _last_pointer_y = _current_pointer_y;
-       _current_pointer_frame = _editor->event_frame (event, &_current_pointer_x, &_current_pointer_y);
+       /* 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 ()) ) {
+               return false;
+       }
 
-       if (!from_autoscroll && !_move_threshold_passed) {
-               
-               bool const xp = (::llabs ((nframes64_t) (_current_pointer_x - _grab_x)) > 4LL);
-               bool const yp = (::llabs ((nframes64_t) (_current_pointer_y - _grab_y)) > 4LL);
-                       
-               _move_threshold_passed = (xp || yp);
+       pair<nframes64_t, int> const threshold = move_threshold ();
 
-               if (apply_move_threshold() && _move_threshold_passed) {
-                       
-                       _grab_frame = _current_pointer_frame;
-                       _grab_x = _current_pointer_x;
-                       _grab_y = _current_pointer_y;
-                       _last_pointer_frame = _grab_frame;
-                       _pointer_frame_offset = _grab_frame - _last_frame_position;
-                       
-               }
-       }
+       bool const old_move_threshold_passed = _move_threshold_passed;
 
-       bool old_had_movement = _had_movement;
+       if (!from_autoscroll && !_move_threshold_passed) {
 
-       /* a motion event has happened, so we've had movement... */
-       _had_movement = true;
+               bool const xp = (::llabs (adjusted_current_frame (event) - _grab_frame) >= threshold.first);
+               bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);
 
-       /* ... unless we're using a move threshold and we've not yet passed it */
-       if (apply_move_threshold() && !_move_threshold_passed) {
-               _had_movement = false;
+               _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
        }
 
-       if (active (_editor->mouse_mode)) {
+       if (active (_editor->mouse_mode) && _move_threshold_passed) {
 
                if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
                        if (!from_autoscroll) {
-                               _editor->maybe_autoscroll (&event->motion);
+                               _editor->maybe_autoscroll (&event->motion, allow_vertical_autoscroll ());
                        }
 
-                       motion (event, _had_movement != old_had_movement);
+                       motion (event, _move_threshold_passed != old_move_threshold_passed);
+
+                       _last_pointer_x = _drags->current_pointer_x ();
+                       _last_pointer_y = _drags->current_pointer_y ();
+                       _last_pointer_frame = adjusted_current_frame (event);
+                       
                        return true;
                }
        }
@@ -212,56 +323,51 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
        return false;
 }
 
-
 void
 Drag::break_drag ()
 {
-       _editor->stop_canvas_autoscroll ();
-       _editor->hide_verbose_canvas_cursor ();
-
        if (_item) {
                _item->ungrab (0);
+       }
 
-               /* put it back where it came from */
+       aborted ();
 
-               double cxw, cyw;
-               cxw = 0;
-               cyw = 0;
-               _item->i2w (cxw, cyw);
-               _item->move (_original_x - cxw, _original_y - cyw);
-       }
+       _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),
-         _views (v)
+         _primary (p)
 {
-       RegionView::RegionViewGoingAway.connect (mem_fun (*this, &RegionDrag::region_going_away));
+       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());
 }
 
 void
 RegionDrag::region_going_away (RegionView* v)
 {
-       _views.remove (v);
-}
+       list<DraggingView>::iterator i = _views.begin ();
+       while (i != _views.end() && i->view != v) {
+               ++i;
+       }
 
-void
-RegionDrag::update_selection ()
-{
-       list<Selectable*> s;
-       copy (_views.begin(), _views.end(), back_inserter (s));
-       _editor->selection->set (s);
+       if (i != _views.end()) {
+               _views.erase (i);
+       }
 }
 
 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)
+         _brushing (b),
+         _total_x_delta (0)
 {
-       
+
 }
 
 
@@ -269,7 +375,7 @@ void
 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        Drag::start_grab (event);
-       
+
        _editor->show_verbose_time_cursor (_last_frame_position, 10);
 }
 
@@ -280,30 +386,30 @@ RegionMotionDrag::get_time_axis_view_summary ()
        TimeAxisViewSummary sum;
 
        _editor->visible_order_range (&sum.visible_y_low, &sum.visible_y_high);
-               
+
        /* get a bitmask representing the visible tracks */
 
-       for (Editor::TrackViewList::iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
+       for (TrackViewList::iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
                RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
                TimeAxisView::Children children_list;
-               
+
                /* zeroes are audio/MIDI tracks. ones are other types. */
-               
+
                if (!rtv->hidden()) {
-                       
+
                        if (!rtv->is_track()) {
                                /* not an audio nor MIDI track */
                                sum.tracks = sum.tracks |= (0x01 << rtv->order());
                        }
-                       
+
                        sum.height_list[rtv->order()] = (*i)->current_height();
                        children = 1;
 
                        if ((children_list = rtv->get_child_list()).size() > 0) {
-                               for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) { 
+                               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++;     
+                                       children++;
                                }
                        }
                }
@@ -328,7 +434,7 @@ RegionMotionDrag::compute_y_delta (
        }
 
        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;
@@ -356,9 +462,9 @@ RegionMotionDrag::compute_y_delta (
                        }
                }
 
-               for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                       
-                       RegionView* rv = (*i);
+               for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+
+                       RegionView* rv = i->view;
 
                        if (rv->region()->locked()) {
                                continue;
@@ -376,7 +482,7 @@ RegionMotionDrag::compute_y_delta (
 
                        /* XXX: not sure that we should be passing canvas_pointer_order_span in here,
                           as surely this is a per-region thing... */
-                       
+
                        clamp_y_axis = y_movement_disallowed (
                                rtv->order(), last_pointer_view->order(), *canvas_pointer_order_span, tavs
                                );
@@ -391,7 +497,7 @@ RegionMotionDrag::compute_y_delta (
                if (current_pointer_layer == last_pointer_layer) {
                        /* No movement; clamp */
                        clamp_y_axis = true;
-               } 
+               }
        }
 
        if (!clamp_y_axis) {
@@ -406,77 +512,66 @@ RegionMotionDrag::compute_y_delta (
 double
 RegionMotionDrag::compute_x_delta (GdkEvent const * event, nframes64_t* pending_region_position)
 {
-       *pending_region_position = 0;
-       
        /* compute the amount of pointer motion in frames, and where
           the region would be if we moved it by that much.
        */
-       if (_current_pointer_frame >= _pointer_frame_offset) {
-
-               nframes64_t sync_frame;
-               nframes64_t sync_offset;
-               int32_t sync_dir;
+       *pending_region_position = adjusted_current_frame (event);
+       
+       nframes64_t sync_frame;
+       nframes64_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)) {
                
-               *pending_region_position = _current_pointer_frame - _pointer_frame_offset;
+               sync_frame = *pending_region_position + (sync_dir*sync_offset);
                
-               sync_offset = _primary->region()->sync_offset (sync_dir);
+               _editor->snap_to_with_modifier (sync_frame, event);
                
-               /* 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);
-                       
-                       /* we snap if the snap modifier is not enabled.
-                        */
-                       
-                       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-                               _editor->snap_to (sync_frame);  
-                       }
-                       
-                       *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
-                       
-               } else {
-                       *pending_region_position = _last_frame_position;
-               }
+               *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()) {
                *pending_region_position = _last_frame_position;
        }
 
        double x_delta = 0;
-       
+
        if ((*pending_region_position != _last_frame_position) && x_move_allowed ()) {
-               
+
                /* 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) {
-                       
-                       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               
-                               RegionView* rv = (*i);
-                               
+
+                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+
+                               RegionView* rv = i->view;
+
                                // If any regionview is at zero, we need to know so we can stop further leftward motion.
-                               
+
                                double ix1, ix2, iy1, iy2;
                                rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
                                rv->get_canvas_frame()->i2w (ix1, iy1);
-                               
+
                                if (-x_delta > ix1 + _editor->horizontal_adjustment.get_value()) {
                                        x_delta = 0;
                                        *pending_region_position = _last_frame_position;
                                        break;
                                }
                        }
-                       
+
                }
-               
+
                _last_frame_position = *pending_region_position;
        }
 
@@ -515,7 +610,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        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,
@@ -530,7 +625,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
            PREPARE TO MOVE
        ************************************************************/
 
-       if (x_delta == 0 && pointer_order_span == 0 && pointer_layer_span == 0) {
+       if (x_delta == 0 && pointer_order_span == 0 && pointer_layer_span == 0 && !first_move) {
                /* haven't reached next snap point, and we're not switching
                   trackviews nor layers. nothing to do.
                */
@@ -538,68 +633,68 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        }
 
        /*************************************************************
-           MOTION                                                                    
+           MOTION
        ************************************************************/
 
        pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
-       
-       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-               
-               RegionView* rv = (*i);
-               
+
+       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+
+               RegionView* rv = i->view;
+
                if (rv->region()->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 
-                       
+
+                       // hide any dependent views
+
                        rv->get_time_axis_view().hide_dependent_views (*rv);
-                       
-                       /* 
-                          reparent to a non scrolling group so that we can keep the 
+
+                       /*
+                          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 
+                          reparenting means we have to move the rv as the two
                           parent groups have different coordinates.
                        */
-                       
+
                        rv->get_canvas_group()->property_y() = iy1 - 1;
                        rv->get_canvas_group()->reparent(*(_editor->_region_motion_group));
-                       
+
                        rv->fake_set_opaque (true);
                }
-               
+
                /* current view for this particular region */
                pair<TimeAxisView*, int> pos = _editor->trackview_by_y_position (iy1);
                RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (pos.first);
-               
+
                if (pointer_order_span != 0 && !clamp_y_axis) {
-                       
+
                        /* INTER-TRACK MOVEMENT */
-                       
+
                        /* move through the height list to the track that the region is currently on */
                        vector<int32_t>::iterator j = tavs.height_list.begin ();
                        int32_t x = 0;
@@ -607,25 +702,25 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                                ++x;
                                ++j;
                        }
-                       
+
                        y_delta = 0;
                        int32_t temp_pointer_order_span = canvas_pointer_order_span;
-                       
+
                        if (j != tavs.height_list.end ()) {
-                               
+
                                /* Account for layers in the original and
                                   destination tracks.  If we're moving around in layers we assume
                                   that only one track is involved, so it's ok to use *pointer*
                                   variables here. */
-                               
+
                                StreamView* lv = last_pointer_view->view ();
                                assert (lv);
-                               
+
                                /* move to the top of the last trackview */
                                if (lv->layer_display () == Stacked) {
                                        y_delta -= (lv->layers() - last_pointer_layer - 1) * lv->child_height ();
                                }
-                                       
+
                                StreamView* cv = current_pointer_view->view ();
                                assert (cv);
 
@@ -633,77 +728,79 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                                if (cv->layer_display () == Stacked) {
                                        y_delta += (cv->layers() - current_pointer_layer - 1) * cv->child_height ();
                                }
-                               
+
                                /* And for being on a non-topmost layer on the new
                                   track */
-                               
+
                                while (temp_pointer_order_span > 0) {
                                        /* we're moving up canvas-wise,
                                           so we need to find the next track height
                                        */
-                                       if (j != tavs.height_list.begin()) {              
+                                       if (j != tavs.height_list.begin()) {
                                                j--;
                                        }
-                                       
+
                                        if (x != last_pointer_order) {
                                                if ((*j) == 0) {
                                                        ++temp_pointer_order_span;
                                                }
                                        }
-                                       
+
                                        y_delta -= (*j);
                                        temp_pointer_order_span--;
                                }
-                               
+
                                while (temp_pointer_order_span < 0) {
-                                       
+
                                        y_delta += (*j);
-                                       
+
                                        if (x != last_pointer_order) {
                                                if ((*j) == 0) {
                                                        --temp_pointer_order_span;
                                                }
                                        }
-                                       
-                                       if (j != tavs.height_list.end()) {                    
+
+                                       if (j != tavs.height_list.end()) {
                                                j++;
                                        }
-                                       
+
                                        temp_pointer_order_span++;
                                }
-                               
-                               
+
+
                                /* find out where we'll be when we move and set height accordingly */
-                               
+
                                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 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.
                                */
-                               
+
                                //const GdkColor& col (temp_rtv->view->get_region_color());
                                //rv->set_color (const_cast<GdkColor&>(col));
                        }
                }
-               
+
                if (pointer_order_span == 0 && pointer_layer_span != 0 && !clamp_y_axis) {
-                       
+
                        /* INTER-LAYER MOVEMENT in the same track */
                        y_delta = rtv->view()->child_height () * pointer_layer_span;
                }
-               
-               
+
+
                if (_brushing) {
                        _editor->mouse_brush_insert_region (rv, pending_region_position);
                } else {
                        rv->move (x_delta, y_delta);
                }
-               
+
        } /* foreach region */
 
+       _total_x_delta += x_delta;
+       
        if (first_move) {
                _editor->cursor_group->raise_to_top();
        }
@@ -724,14 +821,13 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move)
 }
 
 void
-RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
+RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 {
-       bool nocommit = true;
        vector<RegionView*> copies;
-       RouteTimeAxisView* source_tv;
-       boost::shared_ptr<Diskstream> ds;
+       boost::shared_ptr<Track> tr;
        boost::shared_ptr<Playlist> from_playlist;
-       list<RegionView*> new_views;
+       boost::shared_ptr<Playlist> to_playlist;
+       RegionSelection new_views;
        typedef set<boost::shared_ptr<Playlist> > PlaylistSet;
        PlaylistSet modified_playlists;
        PlaylistSet frozen_playlists;
@@ -739,26 +835,21 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
        pair<PlaylistSet::iterator,bool> insert_result, frozen_insert_result;
        nframes64_t drag_delta;
        bool changed_tracks, changed_position;
-       map<RegionView*, RouteTimeAxisView*> final;
+       map<RegionView*, pair<RouteTimeAxisView*, int> > final;
+       RouteTimeAxisView* source_tv;
+        vector<StatefulDiffCommand*> sdc;
 
        if (!movement_occurred) {
                /* just a click */
-               goto out;
-       }
-
-       nocommit = false;
-
-       if (Config->get_edit_mode() == Splice && !_editor->pre_drag_region_selection.empty()) {
-               _editor->selection->set (_editor->pre_drag_region_selection);
-               _editor->pre_drag_region_selection.clear ();
+               return;
        }
 
        if (_brushing) {
                /* all changes were made during motion event handlers */
-               
+
                if (_copy) {
-                       for (list<RegionView*>::iterator i = _views.begin(); i != _views.end(); ++i) {
-                               copies.push_back (*i);
+                       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+                               copies.push_back (i->view);
                        }
                }
 
@@ -768,8 +859,8 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
        /* reverse this here so that we have the correct logic to finalize
           the drag.
        */
-       
-       if (Config->get_edit_mode() == Lock && !_copy) {
+
+       if (Config->get_edit_mode() == Lock) {
                _x_constrained = !_x_constrained;
        }
 
@@ -778,7 +869,7 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
                        _editor->begin_reversible_command (_("fixed time region copy"));
                } else {
                        _editor->begin_reversible_command (_("region copy"));
-               } 
+               }
        } else {
                if (_x_constrained) {
                        _editor->begin_reversible_command (_("fixed time region drag"));
@@ -792,18 +883,24 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
 
        drag_delta = _primary->region()->position() - _last_frame_position;
 
-       _editor->track_canvas->update_now ();
+       _editor->update_canvas_now ();
 
        /* make a list of where each region ended up */
-       final = find_time_axis_views ();
+       final = find_time_axis_views_and_layers ();
 
-       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ) {
+        cerr << "Iterate over " << _views.size() << " views\n";
 
-               RegionView* rv = (*i);
-               RouteTimeAxisView* dest_rtv = final[*i];
+       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 ();
+
                if (rv->region()->locked()) {
                        ++i;
                        continue;
@@ -814,7 +911,7 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
                } else {
                        where = rv->region()->position();
                }
-                       
+
                boost::shared_ptr<Region> new_region;
 
                if (_copy) {
@@ -822,19 +919,19 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
                        new_region = rv->region();
 
                        /* undo the previous hide_dependent_views so that xfades don't
-                          disappear on copying regions 
+                          disappear on copying regions
                        */
-               
+
                        //rv->get_time_axis_view().reveal_dependent_views (*rv);
-               
+
                } else if (changed_tracks && dest_rtv->playlist()) {
                        new_region = RegionFactory::create (rv->region());
                }
 
                if (changed_tracks || _copy) {
 
-                       boost::shared_ptr<Playlist> to_playlist = dest_rtv->playlist();
-                       
+                       to_playlist = dest_rtv->playlist();
+
                        if (!to_playlist) {
                                ++i;
                                continue;
@@ -842,18 +939,30 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
 
                        _editor->latest_regionviews.clear ();
 
-                       sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (mem_fun(*_editor, &Editor::collect_new_region_view));
-                       
+                       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) {
-                               _editor->session->add_command (new MementoCommand<Playlist>(*to_playlist, &to_playlist->get_state(), 0));
+                                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);
 
+                       if (dest_rtv->view()->layer_display() == Stacked) {
+                               new_region->set_layer (dest_layer);
+                               new_region->set_pending_explicit_relayer (true);
+                       }
+
                        c.disconnect ();
-                                                             
+
                        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?
@@ -862,32 +971,40 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
                        }
 
                } else {
-                       /* 
-                          motion on the same track. plonk the previously reparented region 
+                       rv->region()->clear_history ();
+
+                       /*
+                          motion on the same track. plonk the previously reparented region
                           back to its original canvas group (its streamview).
                           No need to do anything for copies as they are fake regions which will be deleted.
                        */
 
                        rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
-                       rv->get_canvas_group()->property_y() = 0;
-                 
+                       rv->get_canvas_group()->property_y() = i->initial_y;
+                       rv->get_time_axis_view().reveal_dependent_views (*rv);
+
                        /* just change the model */
-                       
-                       boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
-                       insert_result = modified_playlists.insert (playlist);
+                       boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
                        
-                       if (insert_result.second) {
-                               _editor->session->add_command (new MementoCommand<Playlist>(*playlist, &playlist->get_state(), 0));
+                       if (dest_rtv->view()->layer_display() == Stacked) {
+                               rv->region()->set_layer (dest_layer);
+                               rv->region()->set_pending_explicit_relayer (true);
                        }
-                       /* freeze to avoid lots of relayering in the case of a multi-region drag */
-                       frozen_insert_result = frozen_playlists.insert(playlist);
                        
+                       /* 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();
                        }
 
+                        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) {
@@ -896,9 +1013,13 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
                           because we may have copied the region and it has not been attached to a playlist.
                        */
 
-                       assert ((source_tv = dynamic_cast<RouteTimeAxisView*> (&rv->get_time_axis_view())));
-                       assert ((ds = source_tv->get_diskstream()));
-                       assert ((from_playlist = ds->playlist()));
+                       source_tv = dynamic_cast<RouteTimeAxisView*> (&rv->get_time_axis_view());
+                       tr = source_tv->track();
+                       from_playlist = tr->playlist();
+
+                       assert (source_tv);
+                       assert (tr);
+                       assert (from_playlist);
 
                        /* moved to a different audio track, without copying */
 
@@ -906,21 +1027,27 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
                           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);
-                       
+
                        /* remove the region from the old playlist */
 
                        insert_result = modified_playlists.insert (from_playlist);
-                       
+
                        if (insert_result.second) {
-                               _editor->session->add_command (new MementoCommand<Playlist>(*from_playlist, &from_playlist->get_state(), 0));
+                               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";
+
+                        cerr << "removing region " << rv->region() << endl;
+                        
                        from_playlist->remove_region (rv->region());
-                       
+
                        /* 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,
@@ -939,70 +1066,107 @@ RegionMoveDrag::finished (GdkEvent* event, bool movement_occurred)
                        */
 
                        if (_views.empty()) {
-                               break;
-                       } else { 
+                                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;
+                       } else {
                                i = _views.begin();
                        }
 
                } 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 (new_views.empty()) {
-               if (_copy) {
-                       /* the region(view)s that are being dragged around are copies and do not
-                          belong to any track. remove them from our list
-                       */
-                       _views.clear ();
-               }
-       } else {
-               _views = new_views;
-               _primary = _views.front ();
+
+       /*
+          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);
        }
 
        for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
                (*p)->thaw();
        }
-                       
+
   out:
-       if (!nocommit) {
-               for (set<boost::shared_ptr<Playlist> >::iterator p = modified_playlists.begin(); p != modified_playlists.end(); ++p) {
-                       _editor->session->add_command (new MementoCommand<Playlist>(*(*p), 0, &(*p)->get_state()));     
-               }
+        for (vector<StatefulDiffCommand*>::iterator i = sdc.begin(); i != sdc.end(); ++i) {
+               _editor->session()->add_command (*i);
+        }
 
-               _editor->commit_reversible_command ();
-       }
+       _editor->commit_reversible_command ();
 
        for (vector<RegionView*>::iterator x = copies.begin(); x != copies.end(); ++x) {
                delete *x;
        }
 }
 
-
-bool
-RegionMoveDrag::x_move_allowed () const
+void
+RegionMoveDrag::aborted ()
 {
-       if (Config->get_edit_mode() == Lock) {
-               if (_copy) {
-                       return !_x_constrained;
-               } else {
-                       /* in locked edit mode, reverse the usual meaning of _x_constrained */
-                       return _x_constrained;
+       if (_copy) {
+
+               for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                       delete i->view;
                }
+
+               _views.clear ();
+
+       } else {
+               RegionMotionDrag::aborted ();
        }
-       
-       return !_x_constrained;
 }
 
+void
+RegionMotionDrag::aborted ()
+{
+       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+               RegionView* rv = i->view;
+               TimeAxisView* tv = &(rv->get_time_axis_view ());
+               RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
+               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->fake_set_opaque (false);
+               rv->move (-_total_x_delta, 0);
+               rv->set_height (rtv->view()->child_height ());
+       }
+
+       _editor->update_canvas_now ();
+}
+                                     
+
 bool
-RegionInsertDrag::x_move_allowed () const
+RegionMotionDrag::x_move_allowed () const
 {
        if (Config->get_edit_mode() == Lock) {
+               /* in locked edit mode, reverse the usual meaning of _x_constrained */
                return _x_constrained;
        }
 
@@ -1014,21 +1178,23 @@ RegionMotionDrag::copy_regions (GdkEvent* event)
 {
        /* duplicate the regionview(s) and region(s) */
 
-       list<RegionView*> new_regionviews;
-       
-       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-               
-               RegionView* rv = (*i);
+       list<DraggingView> new_regionviews;
+
+       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+
+               RegionView* rv = i->view;
                AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
                MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
-               
+
                const boost::shared_ptr<const Region> original = rv->region();
                boost::shared_ptr<Region> region_copy = RegionFactory::create (original);
+                region_copy->set_position (original->position(), this);
 
                RegionView* nrv;
                if (arv) {
                        boost::shared_ptr<AudioRegion> audioregion_copy
                                = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
+
                        nrv = new AudioRegionView (*arv, audioregion_copy);
                } else if (mrv) {
                        boost::shared_ptr<MidiRegion> midiregion_copy
@@ -1037,30 +1203,39 @@ RegionMotionDrag::copy_regions (GdkEvent* event)
                } else {
                        continue;
                }
-               
+
                nrv->get_canvas_group()->show ();
-               new_regionviews.push_back (nrv);
+               new_regionviews.push_back (DraggingView (nrv));
+
+               /* swap _primary to the copy */
+
+               if (rv == _primary) {
+                       _primary = nrv;
+               }
+
+               /* ..and deselect the one we copied */
+
+               rv->set_selected (false);
        }
-       
+
        if (new_regionviews.empty()) {
                return;
        }
-       
+
        /* reflect the fact that we are dragging the copies */
-       
-       _primary = new_regionviews.front();
+
        _views = new_regionviews;
-               
-       swap_grab (new_regionviews.front()->get_canvas_group (), 0, event ? event->motion.time : 0);
-       
-       /* 
-          sync the canvas to what we think is its current state 
-          without it, the canvas seems to 
-          "forget" to update properly after the upcoming reparent() 
-          ..only if the mouse is in rapid motion at the time of the grab. 
-          something to do with regionview creation raking so long?
+
+       swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
+
+       /*
+          sync the canvas to what we think is its current state
+          without it, the canvas seems to
+          "forget" to update properly after the upcoming reparent()
+          ..only if the mouse is in rapid motion at the time of the grab.
+          something to do with regionview creation taking so long?
        */
-       _editor->track_canvas->update_now();
+       _editor->update_canvas_now();
 }
 
 bool
@@ -1068,7 +1243,7 @@ RegionMotionDrag::check_possible (RouteTimeAxisView** tv, layer_t* layer)
 {
        /* Which trackview is this ? */
 
-       pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
+       pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        (*tv) = dynamic_cast<RouteTimeAxisView*> (tvp.first);
        (*layer) = tvp.second;
 
@@ -1079,10 +1254,10 @@ RegionMotionDrag::check_possible (RouteTimeAxisView** tv, layer_t* layer)
        /* 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. 
+               /* 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;
@@ -1101,7 +1276,7 @@ RegionMotionDrag::y_movement_disallowed (int new_order, int old_order, int y_spa
 {
        if (new_order != old_order) {
 
-               /* this isn't the pointer track */      
+               /* this isn't the pointer track */
 
                if (y_span > 0) {
 
@@ -1117,20 +1292,20 @@ RegionMotionDrag::y_movement_disallowed (int new_order, int old_order, int y_spa
                                        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 */
@@ -1140,30 +1315,30 @@ RegionMotionDrag::y_movement_disallowed (int new_order, int old_order, int y_spa
                                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;
@@ -1182,7 +1357,7 @@ RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p,
          _copy (c)
 {
        TimeAxisView* const tv = &_primary->get_time_axis_view ();
-       
+
        _dest_trackview = tv;
        if (tv->layer_display() == Overlaid) {
                _dest_layer = 0;
@@ -1193,7 +1368,7 @@ RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p,
        double speed = 1;
        RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
        if (rtv && rtv->is_track()) {
-               speed = rtv->get_diskstream()->speed ();
+               speed = rtv->track()->speed ();
        }
 
        _last_frame_position = static_cast<nframes64_t> (_primary->region()->position() / speed);
@@ -1203,8 +1378,8 @@ void
 RegionMoveDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
 {
        RegionMotionDrag::start_grab (event, c);
-       
-       _pointer_frame_offset = _grab_frame - _last_frame_position;
+
+       _pointer_frame_offset = grab_frame() - _last_frame_position;
 }
 
 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, nframes64_t pos)
@@ -1214,10 +1389,10 @@ RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, Rout
                (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 (_primary);
+       _views.push_back (DraggingView (_primary));
 
        _last_frame_position = pos;
 
@@ -1226,20 +1401,21 @@ RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, Rout
        _dest_layer = _primary->region()->layer ();
 }
 
-map<RegionView*, RouteTimeAxisView*>
-RegionMotionDrag::find_time_axis_views ()
+map<RegionView*, pair<RouteTimeAxisView*, int> >
+RegionMotionDrag::find_time_axis_views_and_layers ()
 {
-       map<RegionView*, RouteTimeAxisView*> tav;
-       
-       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+       map<RegionView*, pair<RouteTimeAxisView*, int> > tav;
+
+       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
 
                double ix1, ix2, iy1, iy2;
-               (*i)->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
-               (*i)->get_canvas_frame()->i2w (ix1, iy1);
+               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[*i] = dynamic_cast<RouteTimeAxisView*> (tv.first);
+               tav[rv] = make_pair (dynamic_cast<RouteTimeAxisView*> (tv.first), tv.second);
        }
 
        return tav;
@@ -1247,13 +1423,13 @@ RegionMotionDrag::find_time_axis_views ()
 
 
 void
-RegionInsertDrag::finished (GdkEvent* event, bool movement_occurred)
+RegionInsertDrag::finished (GdkEvent* /*event*/, bool /*movement_occurred*/)
 {
-       _editor->track_canvas->update_now ();
+       _editor->update_canvas_now ();
 
-       map<RegionView*, RouteTimeAxisView*> final = find_time_axis_views ();
-       
-       RouteTimeAxisView* dest_rtv = final[_primary];
+       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;
@@ -1261,9 +1437,9 @@ RegionInsertDrag::finished (GdkEvent* event, bool movement_occurred)
        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
        _editor->begin_reversible_command (_("insert region"));
-       XMLNode& before = playlist->get_state ();
+        playlist->clear_history ();
        playlist->add_region (_primary->region (), _last_frame_position);
-       _editor->session->add_command (new MementoCommand<Playlist> (*playlist, &before, &playlist->get_state()));
+       _editor->session()->add_command (new StatefulDiffCommand (playlist));
        _editor->commit_reversible_command ();
 
        delete _primary;
@@ -1271,6 +1447,12 @@ RegionInsertDrag::finished (GdkEvent* event, bool movement_occurred)
        _views.clear ();
 }
 
+void
+RegionInsertDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
        : RegionMoveDrag (e, i, p, v, false, false)
 {
@@ -1288,14 +1470,14 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
 {
        RouteTimeAxisView* tv;
        layer_t layer;
-       
+
        if (!check_possible (&tv, &layer)) {
                return;
        }
 
        int dir;
 
-       if (_current_pointer_x - _grab_x > 0) {
+       if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
                dir = 1;
        } else {
                dir = -1;
@@ -1306,6 +1488,8 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
        RegionSelectionByPosition cmp;
        copy.sort (cmp);
 
+       nframes64_t const pf = adjusted_current_frame (event);
+
        for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
 
                RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
@@ -1322,106 +1506,166 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
 
                if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
                        continue;
-               } 
+               }
 
                if (dir > 0) {
-                       if (_current_pointer_frame < (*i)->region()->last_frame() + 1) {
+                       if (pf < (*i)->region()->last_frame() + 1) {
                                continue;
                        }
                } else {
-                       if (_current_pointer_frame > (*i)->region()->first_frame()) {
+                       if (pf > (*i)->region()->first_frame()) {
                                continue;
                        }
                }
 
-               
-               playlist->shuffle ((*i)->region(), dir);
 
-               _grab_x = _current_pointer_x;
+               playlist->shuffle ((*i)->region(), dir);
        }
 }
 
 void
-RegionSpliceDrag::finished (GdkEvent* event, bool)
+RegionSpliceDrag::finished (GdkEvent* /*event*/, bool)
 {
-       
+
 }
 
+void
+RegionSpliceDrag::aborted ()
+{
+       /* XXX: TODO */
+}
 
 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
        : Drag (e, i),
          _view (v)
 {
-       
+
 }
 
 void
 RegionCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        _dest_trackview = _view;
-       
+
        Drag::start_grab (event);
 }
 
 
 void
-RegionCreateDrag::motion (GdkEvent* event, bool first_move)
+RegionCreateDrag::motion (GdkEvent* /*event*/, bool first_move)
 {
        if (first_move) {
                // TODO: create region-create-drag region view here
        }
 
        // TODO: resize region-create-drag region view here
-} 
+}
 
 void
 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
 {
        MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (_dest_trackview);
+
        if (!mtv) {
                return;
        }
 
-       const boost::shared_ptr<MidiDiskstream> diskstream =
-               boost::dynamic_pointer_cast<MidiDiskstream>(mtv->view()->trackview().track()->diskstream());
-       
-       if (!diskstream) {
-               warning << "Cannot create non-MIDI region" << endl;
-               return;
+       if (!movement_occurred) {
+               mtv->add_region (grab_frame ());
+       } else {
+               motion (event, false);
+               // TODO: create region-create-drag region here
        }
+}
 
-       if (!movement_occurred) {
-               _editor->begin_reversible_command (_("create region"));
-               XMLNode &before = mtv->playlist()->get_state();
+void
+RegionCreateDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
+NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+       , region (0)
+{
 
-               nframes64_t start = _grab_frame;
-               _editor->snap_to (start, -1);
-               const Meter& m = _editor->session->tempo_map().meter_at(start);
-               const Tempo& t = _editor->session->tempo_map().tempo_at(start);
-               double length = floor (m.frames_per_bar(t, _editor->session->frame_rate()));
+}
 
-               boost::shared_ptr<Source> src = _editor->session->create_midi_source_for_session(*diskstream.get());
-                               
-               mtv->playlist()->add_region (boost::dynamic_pointer_cast<MidiRegion>
-                                            (RegionFactory::create(src, 0, (nframes_t) length, 
-                                                                   PBD::basename_nosuffix(src->name()))), start);
-               XMLNode &after = mtv->playlist()->get_state();
-               _editor->session->add_command(new MementoCommand<Playlist>(*mtv->playlist().get(), &before, &after));
-               _editor->commit_reversible_command();
+void
+NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
+{
+       Gdk::Cursor     cursor;
+       ArdourCanvas::CanvasNote*     cnote = dynamic_cast<ArdourCanvas::CanvasNote*>(_item);
+
+       Drag::start_grab (event);
 
+       region = &cnote->region_view();
+
+       double region_start = region->get_position_pixels();
+       double middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;
+
+       if (grab_x() <= middle_point) {
+               cursor = Gdk::Cursor(Gdk::LEFT_SIDE);
+               at_front = true;
        } else {
-               motion (event, false);
-               // TODO: create region-create-drag region here
+               cursor = Gdk::Cursor(Gdk::RIGHT_SIDE);
+               at_front = false;
+       }
+
+       _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, cursor, event->motion.time);
+
+       if (event->motion.state & Keyboard::PrimaryModifier) {
+               relative = false;
+       } else {
+               relative = true;
+       }
+
+       MidiRegionSelection& ms (_editor->get_selection().midi_regions);
+
+       if (ms.size() > 1) {
+               /* has to be relative, may make no sense otherwise */
+               relative = true;
+       }
+
+       region->note_selected (cnote, true);
+
+       for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
+               MidiRegionSelection::iterator next;
+               next = r;
+               ++next;
+               (*r)->begin_resizing (at_front);
+               r = next;
        }
 }
 
+void
+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 (at_front, _drags->current_pointer_x() - grab_x(), relative);
+       }
+}
 
+void
+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 (at_front, _drags->current_pointer_x() - grab_x(), relative);
+       }
+}
 
+void
+NoteResizeDrag::aborted ()
+{
+       /* XXX: TODO */
+}
 
 void
-RegionGainDrag::motion (GdkEvent* event, bool)
+RegionGainDrag::motion (GdkEvent* /*event*/, bool)
 {
-       
+
 }
 
 void
@@ -1430,8 +1674,15 @@ RegionGainDrag::finished (GdkEvent *, bool)
 
 }
 
+void
+RegionGainDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
        : RegionDrag (e, i, p, v)
+       , _have_transaction (false)
 {
 
 }
@@ -1444,23 +1695,25 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
        RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
 
        if (tv && tv->is_track()) {
-               speed = tv->get_diskstream()->speed();
+               speed = tv->track()->speed();
        }
-       
+
        nframes64_t region_start = (nframes64_t) (_primary->region()->position() / speed);
        nframes64_t region_end = (nframes64_t) (_primary->region()->last_frame() / speed);
        nframes64_t region_length = (nframes64_t) (_primary->region()->length() / speed);
 
        Drag::start_grab (event, _editor->trimmer_cursor);
-       
+
+       nframes64_t const pf = adjusted_current_frame (event);
+
        if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
                _operation = ContentsTrim;
        } else {
                /* These will get overridden for a point trim.*/
-               if (_current_pointer_frame < (region_start + region_length/2)) {
+               if (pf < (region_start + region_length/2)) {
                        /* closer to start */
                        _operation = StartTrim;
-               } else if (_current_pointer_frame > (region_end - region_length/2)) {
+               } else if (pf > (region_end - region_length/2)) {
                        /* closer to end */
                        _operation = EndTrim;
                }
@@ -1468,13 +1721,13 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 
        switch (_operation) {
        case StartTrim:
-               _editor->show_verbose_time_cursor (region_start, 10);   
+               _editor->show_verbose_time_cursor (region_start, 10);
                break;
        case EndTrim:
-               _editor->show_verbose_time_cursor (region_end, 10);     
+               _editor->show_verbose_time_cursor (region_end, 10);
                break;
        case ContentsTrim:
-               _editor->show_verbose_time_cursor (_current_pointer_frame, 10); 
+               _editor->show_verbose_time_cursor (pf, 10);
                break;
        }
 }
@@ -1486,12 +1739,12 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
        nframes64_t frame_delta = 0;
 
        bool left_direction;
-       bool obey_snap = !Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier());
+       bool obey_snap = event ? !Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) : false;
 
        /* snap modifier works differently here..
-          its' current state has to be passed to the 
-          various trim functions in order to work properly 
-       */ 
+          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 ();
@@ -1499,25 +1752,19 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
        pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
 
        if (tv && tv->is_track()) {
-               speed = tv->get_diskstream()->speed();
+               speed = tv->track()->speed();
        }
-       
-       if (_last_pointer_frame > _current_pointer_frame) {
+
+       nframes64_t const pf = adjusted_current_frame (event);
+
+       if (last_pointer_frame() > pf) {
                left_direction = true;
        } else {
                left_direction = false;
        }
 
-       if (obey_snap) {
-               _editor->snap_to (_current_pointer_frame);
-       }
-
-       if (_current_pointer_frame == _last_pointer_frame) {
-               return;
-       }
-
        if (first_move) {
-       
+
                string trim_type;
 
                switch (_operation) {
@@ -1533,73 +1780,74 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                }
 
                _editor->begin_reversible_command (trim_type);
+               _have_transaction = true;
 
-               for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                       (*i)->fake_set_opaque(false);
-                       (*i)->region()->freeze ();
-               
-                       AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*i);
+               for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                       RegionView* rv = i->view;
+                       rv->fake_set_opaque(false);
+                        rv->region()->clear_history ();
+                       rv->region()->suspend_property_changes ();
+
+                       AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
 
                        if (arv){
                                arv->temporarily_hide_envelope ();
                        }
 
-                       boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
+                       boost::shared_ptr<Playlist> pl = rv->region()->playlist();
                        insert_result = _editor->motion_frozen_playlists.insert (pl);
 
                        if (insert_result.second) {
-                               _editor->session->add_command(new MementoCommand<Playlist>(*pl, &pl->get_state(), 0));
                                pl->freeze();
                        }
                }
        }
 
        if (left_direction) {
-               frame_delta = (_last_pointer_frame - _current_pointer_frame);
+               frame_delta = (last_pointer_frame() - pf);
        } else {
-               frame_delta = (_current_pointer_frame - _last_pointer_frame);
+               frame_delta = (pf - last_pointer_frame());
        }
 
        bool non_overlap_trim = false;
 
-       if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
+       if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
                non_overlap_trim = true;
        }
 
-       switch (_operation) {           
+       switch (_operation) {
        case StartTrim:
-               if ((left_direction == false) && (_current_pointer_frame <= rv->region()->first_frame()/speed)) {
+               if ((left_direction == false) && (pf <= rv->region()->first_frame()/speed)) {
                        break;
                } else {
 
-                       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               _editor->single_start_trim (**i, frame_delta, left_direction, obey_snap, non_overlap_trim);
+                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                               _editor->single_start_trim (*i->view, frame_delta, left_direction, obey_snap, non_overlap_trim);
                        }
                        break;
                }
-               
+
        case EndTrim:
-               if ((left_direction == true) && (_current_pointer_frame > (nframes64_t) (rv->region()->last_frame()/speed))) {
+               if ((left_direction == true) && (pf > (nframes64_t) (rv->region()->last_frame()/speed))) {
                        break;
                } else {
 
-                       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               _editor->single_end_trim (**i, frame_delta, left_direction, obey_snap, non_overlap_trim);
+                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                               _editor->single_end_trim (*i->view, frame_delta, left_direction, obey_snap, non_overlap_trim);
                        }
                        break;
                }
-               
+
        case ContentsTrim:
                {
                        bool swap_direction = false;
 
-                       if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
+                       if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
                                swap_direction = true;
                        }
-                       
-                       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i)
-                       {
-                               _editor->single_contents_trim (**i, frame_delta, left_direction, swap_direction, obey_snap);
+
+                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                               _editor->single_contents_trim (*i->view, frame_delta, left_direction, swap_direction, obey_snap);
                        }
                }
                break;
@@ -1607,17 +1855,15 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
 
        switch (_operation) {
        case StartTrim:
-               _editor->show_verbose_time_cursor((nframes64_t) (rv->region()->position()/speed), 10);  
+               _editor->show_verbose_time_cursor((nframes64_t) (rv->region()->position()/speed), 10);
                break;
        case EndTrim:
-               _editor->show_verbose_time_cursor((nframes64_t) (rv->region()->last_frame()/speed), 10);        
+               _editor->show_verbose_time_cursor((nframes64_t) (rv->region()->last_frame()/speed), 10);
                break;
        case ContentsTrim:
-               _editor->show_verbose_time_cursor(_current_pointer_frame, 10);  
+               _editor->show_verbose_time_cursor (pf, 10);
                break;
        }
-
-       _last_pointer_frame = _current_pointer_frame;
 }
 
 
@@ -1626,28 +1872,47 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
 {
        if (movement_occurred) {
                motion (event, false);
-               
+
                if (!_editor->selection->selected (_primary)) {
-                       _editor->thaw_region_after_trim (*_primary);            
+                       _editor->thaw_region_after_trim (*_primary);
                } else {
-                       
-                       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               _editor->thaw_region_after_trim (**i);
-                               (*i)->fake_set_opaque (true);
+
+                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                               _editor->thaw_region_after_trim (*i->view);
+                               i->view->fake_set_opaque (true);
+                                if (_have_transaction) {
+                                        _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
+                                }
                        }
                }
-               
                for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
                        (*p)->thaw ();
-                       _editor->session->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
                }
-               
+
                _editor->motion_frozen_playlists.clear ();
 
-               _editor->commit_reversible_command();
+               if (_have_transaction) {
+                       _editor->commit_reversible_command();
+               }
+
        } else {
                /* no mouse movement */
-               _editor->point_trim (event);
+               _editor->point_trim (event, adjusted_current_frame (event));
+       }
+}
+
+void
+TrimDrag::aborted ()
+{
+       /* Our motion method is changing model state, so use the Undo system
+          to cancel.  Perhaps not ideal, as this will leave an Undo point
+          behind which may be slightly odd from the user's point of view.
+       */
+
+       finished (0, true);
+       
+       if (_have_transaction) {
+               _editor->undo ();
        }
 }
 
@@ -1667,47 +1932,37 @@ MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                // The actual copying is not done before we reach the finish callback.
                char name[64];
                snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
-               MeterMarker* new_marker = new MeterMarker(*_editor, *_editor->meter_group, ARDOUR_UI::config()->canvasvar_MeterMarker.get(), name, 
+               MeterMarker* new_marker = new MeterMarker(*_editor, *_editor->meter_group, ARDOUR_UI::config()->canvasvar_MeterMarker.get(), name,
                                                          *new MeterSection (_marker->meter()));
 
                _item = &new_marker->the_item ();
                _marker = new_marker;
-               
+
        } else {
 
                MetricSection& section (_marker->meter());
-               
+
                if (!section.movable()) {
                        return;
                }
-               
+
        }
 
        Drag::start_grab (event, cursor);
-       
-       _pointer_frame_offset = _grab_frame - _marker->meter().frame(); 
 
-       _editor->show_verbose_time_cursor (_current_pointer_frame, 10);
+       _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 adjusted_frame = adjusted_current_frame ();
-       
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (adjusted_frame);
-       }
-       
-       if (adjusted_frame == _last_pointer_frame) {
-               return;
-       }
+       nframes64_t const pf = adjusted_current_frame (event);
 
-       _marker->set_position (adjusted_frame);
+       _marker->set_position (pf);
        
-       _last_pointer_frame = adjusted_frame;
-
-       _editor->show_verbose_time_cursor (adjusted_frame, 10);
+       _editor->show_verbose_time_cursor (pf, 10);
 }
 
 void
@@ -1718,18 +1973,18 @@ MeterMarkerDrag::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);
-       
+
+       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();
                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, &after));
                _editor->commit_reversible_command ();
 
                // delete the dummy marker we used for visual representation of copying.
@@ -1740,11 +1995,17 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                XMLNode &before = map.get_state();
                map.move_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, &after));
                _editor->commit_reversible_command ();
        }
 }
 
+void
+MeterMarkerDrag::aborted ()
+{
+       _marker->set_position (_marker->meter().frame ());
+}
+
 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
        : Drag (e, i),
          _copy (c)
@@ -1758,12 +2019,12 @@ TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
 
        if (_copy) {
-               
+
                // create a dummy marker for visual representation of moving the copy.
                // The actual copying is not done before we reach the finish callback.
                char name[64];
                snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
-               TempoMarker* new_marker = new TempoMarker(*_editor, *_editor->tempo_group, ARDOUR_UI::config()->canvasvar_TempoMarker.get(), name, 
+               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 ();
@@ -1772,7 +2033,7 @@ TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        } else {
 
                MetricSection& section (_marker->tempo());
-               
+
                if (!section.movable()) {
                        return;
                }
@@ -1780,30 +2041,16 @@ TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        Drag::start_grab (event, cursor);
 
-       _pointer_frame_offset = _grab_frame - _marker->tempo().frame(); 
-       _editor->show_verbose_time_cursor (_current_pointer_frame, 10);
+       _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 adjusted_frame = adjusted_current_frame ();
-
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (adjusted_frame);
-       }
-       
-       if (adjusted_frame == _last_pointer_frame) {
-               return;
-       }
-
-       /* OK, we've moved far enough to make it worth actually move the thing. */
-               
-       _marker->set_position (adjusted_frame);
-       
-       _editor->show_verbose_time_cursor (adjusted_frame, 10);
-
-       _last_pointer_frame = adjusted_frame;
+       nframes64_t const pf = adjusted_current_frame (event);
+       _marker->set_position (pf);
+       _editor->show_verbose_time_cursor (pf, 10);
 }
 
 void
@@ -1812,22 +2059,22 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        if (!movement_occurred) {
                return;
        }
-       
+
        motion (event, false);
-       
+
        BBT_Time when;
-       
-       TempoMap& map (_editor->session->tempo_map());
-       map.bbt_time (_last_pointer_frame, when);
+
+       TempoMap& map (_editor->session()->tempo_map());
+       map.bbt_time (last_pointer_frame(), when);
 
        if (_copy == true) {
                _editor->begin_reversible_command (_("copy tempo mark"));
                XMLNode &before = map.get_state();
                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, &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;
@@ -1836,11 +2083,16 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                XMLNode &before = map.get_state();
                map.move_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, &after));
                _editor->commit_reversible_command ();
        }
 }
 
+void
+TempoMarkerDrag::aborted ()
+{
+       _marker->set_position (_marker->tempo().frame());
+}
 
 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
        : Drag (e, i),
@@ -1856,10 +2108,10 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
        Drag::start_grab (event, c);
 
        if (!_stop) {
-               
+
                nframes64_t where = _editor->event_frame (event, 0, 0);
 
-               _editor->snap_to (where);
+               _editor->snap_to_with_modifier (where, event);
                _editor->playhead_cursor->set_position (where);
 
        }
@@ -1867,32 +2119,26 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
        if (_cursor == _editor->playhead_cursor) {
                _editor->_dragging_playhead = true;
 
-               if (_editor->session && _was_rolling && _stop) {
-                       _editor->session->request_stop ();
+               if (_editor->session() && _was_rolling && _stop) {
+                       _editor->session()->request_stop ();
                }
 
-               if (_editor->session && _editor->session->is_auditioning()) {
-                       _editor->session->cancel_audition ();
+               if (_editor->session() && _editor->session()->is_auditioning()) {
+                       _editor->session()->cancel_audition ();
                }
        }
 
-       _pointer_frame_offset = _grab_frame - _cursor->current_frame;   
-       
+       _pointer_frame_offset = grab_frame() - _cursor->current_frame;
+
        _editor->show_verbose_time_cursor (_cursor->current_frame, 10);
 }
 
 void
 CursorDrag::motion (GdkEvent* event, bool)
 {
-       nframes64_t adjusted_frame = adjusted_current_frame ();
-       
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               if (_cursor == _editor->playhead_cursor) {
-                       _editor->snap_to (adjusted_frame);
-               }
-       }
-       
-       if (adjusted_frame == _last_pointer_frame) {
+       nframes64_t const adjusted_frame = adjusted_current_frame (event);
+
+       if (adjusted_frame == last_pointer_frame()) {
                return;
        }
 
@@ -1901,11 +2147,9 @@ CursorDrag::motion (GdkEvent* event, bool)
        _editor->show_verbose_time_cursor (_cursor->current_frame, 10);
 
 #ifdef GTKOSX
-       _editor->track_canvas->update_now ();
+       _editor->update_canvas_now ();
 #endif
        _editor->UpdateAllTransportClocks (_cursor->current_frame);
-
-       _last_pointer_frame = adjusted_frame;
 }
 
 void
@@ -1916,21 +2160,28 @@ CursorDrag::finished (GdkEvent* event, bool movement_occurred)
        if (!movement_occurred && _stop) {
                return;
        }
-       
+
        motion (event, false);
-       
+
        if (_item == &_editor->playhead_cursor->canvas_item) {
-               if (_editor->session) {
-                       _editor->session->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
+               if (_editor->session()) {
+                       _editor->session()->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
                        _editor->_pending_locate_request = true;
                }
-       } 
+       }
+}
+
+void
+CursorDrag::aborted ()
+{
+       _editor->_dragging_playhead = false;
+       _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)
 {
-       
+
 }
 
 void
@@ -1940,8 +2191,9 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        AudioRegionView* a = dynamic_cast<AudioRegionView*> (_primary);
        boost::shared_ptr<AudioRegion> const r = a->audio_region ();
-       
-       _pointer_frame_offset = _grab_frame - ((nframes64_t) r->fade_in()->back()->when + r->position());       
+
+       _pointer_frame_offset = grab_frame() - ((nframes64_t) r->fade_in()->back()->when + r->position());
+       _editor->show_verbose_duration_cursor (r->position(), r->position() + r->fade_in()->back()->when, 10);
 }
 
 void
@@ -1949,12 +2201,8 @@ FadeInDrag::motion (GdkEvent* event, bool)
 {
        nframes64_t fade_length;
 
-       nframes64_t pos = adjusted_current_frame ();
+       nframes64_t const pos = adjusted_current_frame (event);
 
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (pos);
-       }
-       
        boost::shared_ptr<Region> region = _primary->region ();
 
        if (pos < (region->position() + 64)) {
@@ -1963,16 +2211,16 @@ FadeInDrag::motion (GdkEvent* event, bool)
                fade_length = region->length();
        } else {
                fade_length = pos - region->position();
-       }               
+       }
 
-       for (RegionSelection::iterator i = _views.begin(); i != _views.end(); ++i) {
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+
+               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
 
-               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
-               
                if (!tmp) {
                        continue;
                }
-       
+
                tmp->reset_fade_in_shape_width (fade_length);
        }
 
@@ -1988,7 +2236,7 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
 
        nframes64_t fade_length;
 
-       nframes64_t const pos = adjusted_current_frame ();
+       nframes64_t const pos = adjusted_current_frame (event);
 
        boost::shared_ptr<Region> region = _primary->region ();
 
@@ -1999,34 +2247,48 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
        } else {
                fade_length = pos - region->position();
        }
-               
+
        _editor->begin_reversible_command (_("change fade in length"));
 
-       for (RegionSelection::iterator i = _views.begin(); i != _views.end(); ++i) {
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+
+               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
 
-               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
-               
                if (!tmp) {
                        continue;
                }
-       
+
                boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
                XMLNode &before = alist->get_state();
 
                tmp->audio_region()->set_fade_in_length (fade_length);
                tmp->audio_region()->set_fade_in_active (true);
-               
+
                XMLNode &after = alist->get_state();
-               _editor->session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
+               _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
        }
 
        _editor->commit_reversible_command ();
 }
 
+void
+FadeInDrag::aborted ()
+{
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
+
+               if (!tmp) {
+                       continue;
+               }
+
+               tmp->reset_fade_in_shape_width (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)
 {
-       
+
 }
 
 void
@@ -2036,8 +2298,9 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        AudioRegionView* a = dynamic_cast<AudioRegionView*> (_primary);
        boost::shared_ptr<AudioRegion> r = a->audio_region ();
-       
-       _pointer_frame_offset = _grab_frame - (r->length() - (nframes64_t) r->fade_out()->back()->when + r->position());
+
+       _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
@@ -2045,14 +2308,10 @@ FadeOutDrag::motion (GdkEvent* event, bool)
 {
        nframes64_t fade_length;
 
-       nframes64_t pos = adjusted_current_frame ();
-
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (pos);
-       }
+       nframes64_t const pos = adjusted_current_frame (event);
 
        boost::shared_ptr<Region> region = _primary->region ();
-       
+
        if (pos > (region->last_frame() - 64)) {
                fade_length = 64; // this should really be a minimum fade defined somewhere
        }
@@ -2062,15 +2321,15 @@ FadeOutDrag::motion (GdkEvent* event, bool)
        else {
                fade_length = region->last_frame() - pos;
        }
-               
-       for (RegionSelection::iterator i = _views.begin(); i != _views.end(); ++i) {
 
-               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
-               
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+
+               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
+
                if (!tmp) {
                        continue;
                }
-       
+
                tmp->reset_fade_out_shape_width (fade_length);
        }
 
@@ -2086,11 +2345,7 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
 
        nframes64_t fade_length;
 
-       nframes64_t pos = adjusted_current_frame ();
-
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (pos);
-       }
+       nframes64_t const pos = adjusted_current_frame (event);
 
        boost::shared_ptr<Region> region = _primary->region ();
 
@@ -2106,27 +2361,41 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
 
        _editor->begin_reversible_command (_("change fade out length"));
 
-       for (RegionSelection::iterator i = _views.begin(); i != _views.end(); ++i) {
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+
+               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
 
-               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
-               
                if (!tmp) {
                        continue;
                }
-       
+
                boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
                XMLNode &before = alist->get_state();
-               
+
                tmp->audio_region()->set_fade_out_length (fade_length);
                tmp->audio_region()->set_fade_out_active (true);
 
                XMLNode &after = alist->get_state();
-               _editor->session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
+               _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
        }
 
        _editor->commit_reversible_command ();
 }
 
+void
+FadeOutDrag::aborted ()
+{
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
+
+               if (!tmp) {
+                       continue;
+               }
+
+               tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
+       }
+}
+
 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
 {
@@ -2155,13 +2424,13 @@ void
 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
        Drag::start_grab (event, cursor);
-       
+
        bool is_start;
 
        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()); 
+       _pointer_frame_offset = grab_frame() - (is_start ? location->start() : location->end());
 
        update_item (location);
 
@@ -2174,7 +2443,7 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                _editor->show_verbose_time_cursor (location->end(), 10);
        }
 
-       Selection::Operation op = Keyboard::selection_type (event->button.state);
+       Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
 
        switch (op) {
        case Selection::Toggle:
@@ -2198,7 +2467,7 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                if (e < max_frames) {
                        ++e;
                }
-               _editor->session->locations()->find_all_between (s, e, ll, Location::Flags (0));
+               _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
                for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
                        Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
                        if (lm) {
@@ -2220,10 +2489,10 @@ 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);
+               Locationl = _editor->find_location_from_marker (*i, is_start);
                _copied_locations.push_back (new Location (*l));
        }
 }
@@ -2235,18 +2504,14 @@ MarkerDrag::motion (GdkEvent* event, bool)
        bool is_start;
        bool move_both = false;
        Marker* marker;
-       Location  *real_location;
+       Location *real_location;
        Location *copy_location = 0;
 
-       nframes64_t newframe = adjusted_current_frame ();
+       nframes64_t const newframe = adjusted_current_frame (event);
 
        nframes64_t next = newframe;
 
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (newframe, 0, true);
-       }
-       
-       if (_current_pointer_frame == _last_pointer_frame) { 
+       if (newframe == last_pointer_frame()) {
                return;
        }
 
@@ -2259,8 +2524,8 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
        /* 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(); 
+       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
+            x != _copied_locations.end() && i != _editor->selection->markers.end();
             ++i, ++x) {
 
                copy_location = *x;
@@ -2306,49 +2571,49 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
        /* now move them all */
 
-       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin(); 
-            x != _copied_locations.end() && i != _editor->selection->markers.end(); 
+       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
+            x != _copied_locations.end() && i != _editor->selection->markers.end();
             ++i, ++x) {
 
                copy_location = *x;
                marker = *i;
 
                /* call this to find out if its the start or end */
-               
+
                if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
                        continue;
                }
-               
+
                if (real_location->locked()) {
                        continue;
                }
 
                if (copy_location->is_mark()) {
 
-                       /* just move it */
-                       
+                       /* now move it */
+
                        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;
-                       
+
                        if (is_start) { // start-of-range marker
-                               
+
                                if (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 {
                                        _editor->snap_to (next, 1, true);
                                        copy_location->set_end (next);
                                        copy_location->set_start (newframe);
                                }
-                               
+
                        } else { // end marker
-                               
+
                                if (move_both) {
                                        copy_location->set_end (new_end);
                                        copy_location->set_start (new_start);
@@ -2371,17 +2636,13 @@ MarkerDrag::motion (GdkEvent* event, bool)
                }
        }
 
-       _last_pointer_frame = _current_pointer_frame;
-
        assert (!_copied_locations.empty());
 
-       _editor->edit_point_clock.set (_copied_locations.front()->start());
        _editor->show_verbose_time_cursor (newframe, 10);
 
 #ifdef GTKOSX
-       _editor->track_canvas->update_now ();
+       _editor->update_canvas_now ();
 #endif
-       _editor->edit_point_clock.set (copy_location->start());
 }
 
 void
@@ -2393,7 +2654,7 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                   off the selection process
                */
 
-               Selection::Operation op = Keyboard::selection_type (event->button.state);
+               Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
 
                switch (op) {
                case Selection::Set:
@@ -2407,31 +2668,31 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                case Selection::Add:
                        break;
                }
-               
+
                return;
        }
 
        _editor->_dragging_edit_point = false;
-       
+
        _editor->begin_reversible_command ( _("move marker") );
-       XMLNode &before = _editor->session->locations()->get_state();
+       XMLNode &before = _editor->session()->locations()->get_state();
 
        MarkerSelection::iterator i;
        list<Location*>::iterator x;
        bool is_start;
 
-       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin(); 
-            x != _copied_locations.end() && i != _editor->selection->markers.end(); 
+       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
+            x != _copied_locations.end() && i != _editor->selection->markers.end();
             ++i, ++x) {
-       
+
                Location * location = _editor->find_location_from_marker (*i, is_start);
-               
+
                if (location) {
-                       
+
                        if (location->locked()) {
                                return;
                        }
-                       
+
                        if (location->is_mark()) {
                                location->set_start ((*x)->start());
                        } else {
@@ -2440,13 +2701,19 @@ 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));
+       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 ()
+{
+       /* XXX: TODO */
+}
+
 void
 MarkerDrag::update_item (Location* location)
 {
@@ -2462,31 +2729,27 @@ ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
          _cumulative_x_drag (0),
          _cumulative_y_drag (0)
 {
-       ControlPoint* _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
+       _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
        assert (_point);
 }
 
 
 void
-ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 {
        Drag::start_grab (event, _editor->fader_cursor);
 
        // start the grab at the center of the control point so
        // the point doesn't 'jump' to the mouse after the first drag
-       _grab_x = _point->get_x();
-       _grab_y = _point->get_y();
+       _time_axis_view_grab_x = _point->get_x();
+       _time_axis_view_grab_y = _point->get_y();
 
-       _point->line().parent_group().i2w (_grab_x, _grab_y);
-       _editor->track_canvas->w2c (_grab_x, _grab_y, _grab_x, _grab_y);
+       float const fraction = 1 - (_point->get_y() / _point->line().height());
 
-       _grab_frame = _editor->pixel_to_frame (_grab_x);
+       _point->line().start_drag_single (_point, _time_axis_view_grab_x, fraction);
 
-       _point->line().start_drag (_point, _grab_frame, 0);
-
-       float fraction = 1.0 - (_point->get_y() / _point->line().height());
-       _editor->set_verbose_canvas_cursor (_point->line().get_verbose_cursor_string (fraction), 
-                                           _current_pointer_x + 10, _current_pointer_y + 10);
+       _editor->set_verbose_canvas_cursor (_point->line().get_verbose_cursor_string (fraction),
+                                           event->button.x + 10, event->button.y + 10);
 
        _editor->show_verbose_canvas_cursor ();
 }
@@ -2494,58 +2757,53 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 void
 ControlPointDrag::motion (GdkEvent* event, bool)
 {
-       double dx = _current_pointer_x - _last_pointer_x;
-       double dy = _current_pointer_y - _last_pointer_y;
+       double dx = _drags->current_pointer_x() - last_pointer_x();
+       double dy = _drags->current_pointer_y() - last_pointer_y();
 
        if (event->button.state & Keyboard::SecondaryModifier) {
                dx *= 0.1;
                dy *= 0.1;
        }
 
-       double cx = _grab_x + _cumulative_x_drag + dx;
-       double cy = _grab_y + _cumulative_y_drag + dy;
+       /* 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;
 
        // calculate zero crossing point. back off by .01 to stay on the
        // positive side of zero
-       double _unused = 0;
-       double zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
-       _point->line().parent_group().i2w(_unused, zero_gain_y);
+       double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
 
        // make sure we hit zero when passing through
-       if ((cy < zero_gain_y and (cy - dy) > zero_gain_y)
-                       or (cy > zero_gain_y and (cy - dy) < zero_gain_y)) {
+       if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
                cy = zero_gain_y;
        }
 
        if (_x_constrained) {
-               cx = _grab_x;
+               cx = _time_axis_view_grab_x;
        }
        if (_y_constrained) {
-               cy = _grab_y;
+               cy = _time_axis_view_grab_y;
        }
 
-       _cumulative_x_drag = cx - _grab_x;
-       _cumulative_y_drag = cy - _grab_y;
-
-       _point->line().parent_group().w2i (cx, cy);
+       _cumulative_x_drag = cx - _time_axis_view_grab_x;
+       _cumulative_y_drag = cy - _time_axis_view_grab_y;
 
        cx = max (0.0, cx);
        cy = max (0.0, cy);
        cy = min ((double) _point->line().height(), cy);
 
-       //translate cx to frames
        nframes64_t cx_frames = _editor->unit_to_frame (cx);
 
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) && !_x_constrained) {
-               _editor->snap_to (cx_frames);
+       if (!_x_constrained) {
+               _editor->snap_to_with_modifier (cx_frames, event);
        }
 
        float const fraction = 1.0 - (cy / _point->line().height());
 
        bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
 
-       _point->line().point_drag (*_point, cx_frames, fraction, push);
-       
+       _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
+
        _editor->set_verbose_canvas_cursor_text (_point->line().get_verbose_cursor_string (fraction));
 }
 
@@ -2555,7 +2813,7 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
        if (!movement_occurred) {
 
                /* just a click */
-               
+
                if ((event->type == GDK_BUTTON_RELEASE) && (event->button.button == 1) && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
                        _editor->reset_point_selection ();
                }
@@ -2563,7 +2821,25 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
        } else {
                motion (event, false);
        }
-       _point->line().end_drag (_point);
+       _point->line().end_drag ();
+}
+
+void
+ControlPointDrag::aborted ()
+{
+       _point->line().reset ();
+}
+
+bool
+ControlPointDrag::active (Editing::MouseMode m)
+{
+       if (m == Editing::MouseGain) {
+               /* always active in mouse gain */
+               return true;
+       }
+
+       /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
+       return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
 }
 
 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
@@ -2574,7 +2850,7 @@ LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
 
 }
 void
-LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 {
        _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
        assert (_line);
@@ -2592,7 +2868,10 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        nframes64_t const frame_within_region = (nframes64_t) floor (cx * _editor->frames_per_unit);
 
-       if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
+       uint32_t before;
+       uint32_t after;
+       
+       if (!_line->control_points_adjacent (frame_within_region, before, after)) {
                /* no adjacent points */
                return;
        }
@@ -2601,31 +2880,31 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        /* store grab start in parent frame */
 
-       _grab_x = cx;
-       _grab_y = cy;
+       _time_axis_view_grab_x = cx;
+       _time_axis_view_grab_y = cy;
 
        double fraction = 1.0 - (cy / _line->height());
 
-       _line->start_drag (0, _grab_frame, fraction);
-       
+       _line->start_drag_line (before, after, fraction);
+
        _editor->set_verbose_canvas_cursor (_line->get_verbose_cursor_string (fraction),
-                                           _current_pointer_x + 10, _current_pointer_y + 10);
-       
+                                           event->button.x + 10, event->button.y + 10);
+
        _editor->show_verbose_canvas_cursor ();
 }
 
 void
 LineDrag::motion (GdkEvent* event, bool)
 {
-       double dy = _current_pointer_y - _last_pointer_y;
-       
+       double dy = _drags->current_pointer_y() - last_pointer_y();
+
        if (event->button.state & Keyboard::SecondaryModifier) {
                dy *= 0.1;
        }
 
-       double cy = _grab_y + _cumulative_y_drag + dy;
+       double cy = _time_axis_view_grab_y + _cumulative_y_drag + dy;
 
-       _cumulative_y_drag = cy - _grab_y;
+       _cumulative_y_drag = cy - _time_axis_view_grab_y;
 
        cy = max (0.0, cy);
        cy = min ((double) _line->height(), cy);
@@ -2640,8 +2919,9 @@ LineDrag::motion (GdkEvent* event, bool)
                push = true;
        }
 
-       _line->line_drag (_before, _after, fraction, push);
-       
+       /* we are ignoring x position for this drag, so we can just pass in anything */
+       _line->drag_motion (0, fraction, true, push);
+
        _editor->set_verbose_canvas_cursor_text (_line->get_verbose_cursor_string (fraction));
 }
 
@@ -2649,61 +2929,61 @@ void
 LineDrag::finished (GdkEvent* event, bool)
 {
        motion (event, false);
-       _line->end_drag (0);
+       _line->end_drag ();
+}
+
+void
+LineDrag::aborted ()
+{
+       _line->reset ();
 }
 
 void
 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        Drag::start_grab (event);
-       _editor->show_verbose_time_cursor (_current_pointer_frame, 10);
+       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
 }
 
 void
-RubberbandSelectDrag::motion (GdkEvent* event, bool first_move)
+RubberbandSelectDrag::motion (GdkEvent* event, bool)
 {
        nframes64_t start;
        nframes64_t end;
        double y1;
        double y2;
 
-       /* use a bigger drag threshold than the default */
+       nframes64_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
 
-       if (abs ((int) (_current_pointer_frame - _grab_frame)) < 8) {
-               return;
+       nframes64_t grab = grab_frame ();
+       if (Config->get_rubberbanding_snaps_to_grid ()) {
+               _editor->snap_to_with_modifier (grab, event);
        }
 
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) && Config->get_rubberbanding_snaps_to_grid()) {
-               if (first_move) {
-                       _editor->snap_to (_grab_frame);
-               } 
-               _editor->snap_to (_current_pointer_frame);
-       }
-
        /* base start and end on initial click position */
 
-       if (_current_pointer_frame < _grab_frame) {
-               start = _current_pointer_frame;
-               end = _grab_frame;
+       if (pf < grab) {
+               start = pf;
+               end = grab;
        } else {
-               end = _current_pointer_frame;
-               start = _grab_frame;
+               end = pf;
+               start = grab;
        }
 
-       if (_current_pointer_y < _grab_y) {
-               y1 = _current_pointer_y;
-               y2 = _grab_y;
+       if (_drags->current_pointer_y() < grab_y()) {
+               y1 = _drags->current_pointer_y();
+               y2 = grab_y();
        } else {
-               y2 = _current_pointer_y;
-               y1 = _grab_y;
+               y2 = _drags->current_pointer_y();
+               y1 = grab_y();
        }
 
-       
+
        if (start != end || y1 != y2) {
 
                double x1 = _editor->frame_to_pixel (start);
                double x2 = _editor->frame_to_pixel (end);
-               
+
                _editor->rubberband_rect->property_x1() = x1;
                _editor->rubberband_rect->property_y1() = y1;
                _editor->rubberband_rect->property_x2() = x2;
@@ -2711,10 +2991,8 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool first_move)
 
                _editor->rubberband_rect->show();
                _editor->rubberband_rect->raise_to_top();
-               
-               _last_pointer_frame = _current_pointer_frame;
 
-               _editor->show_verbose_time_cursor (_current_pointer_frame, 10);
+               _editor->show_verbose_time_cursor (pf, 10);
        }
 }
 
@@ -2726,30 +3004,30 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
                motion (event, false);
 
                double y1,y2;
-               if (_current_pointer_y < _grab_y) {
-                       y1 = _current_pointer_y;
-                       y2 = _grab_y;
+               if (_drags->current_pointer_y() < grab_y()) {
+                       y1 = _drags->current_pointer_y();
+                       y2 = grab_y();
                } else {
-                       y2 = _current_pointer_y;
-                       y1 = _grab_y;
+                       y2 = _drags->current_pointer_y();
+                       y1 = grab_y();
                }
 
 
-               Selection::Operation op = Keyboard::selection_type (event->button.state);
-               bool commit;
+               Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
+               bool committed;
 
                _editor->begin_reversible_command (_("rubberband selection"));
 
-               if (_grab_frame < _last_pointer_frame) {
-                       commit = _editor->select_all_within (_grab_frame, _last_pointer_frame, y1, y2, _editor->track_views, op);
+               if (grab_frame() < last_pointer_frame()) {
+                       committed = _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op);
                } else {
-                       commit = _editor->select_all_within (_last_pointer_frame, _grab_frame, y1, y2, _editor->track_views, op);
-               }               
+                       committed = _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op);
+               }
 
-               if (commit) {
+               if (!committed) {
                        _editor->commit_reversible_command ();
                }
-               
+
        } else {
                if (!getenv("ARDOUR_SAE")) {
                        _editor->selection->clear_tracks();
@@ -2762,12 +3040,18 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
        _editor->rubberband_rect->hide();
 }
 
+void
+RubberbandSelectDrag::aborted ()
+{
+       _editor->rubberband_rect->hide ();
+}
+
 void
 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        Drag::start_grab (event);
-       
-       _editor->show_verbose_time_cursor (_current_pointer_frame, 10);
+
+       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
 }
 
 void
@@ -2775,75 +3059,103 @@ TimeFXDrag::motion (GdkEvent* event, bool)
 {
        RegionView* rv = _primary;
 
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (_current_pointer_frame);
-       }
-
-       if (_current_pointer_frame == _last_pointer_frame) {
-               return;
-       }
+       nframes64_t const pf = adjusted_current_frame (event);
 
-       if (_current_pointer_frame > rv->region()->position()) {
-               rv->get_time_axis_view().show_timestretch (rv->region()->position(), _current_pointer_frame);
+       if (pf > rv->region()->position()) {
+               rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
        }
 
-       _last_pointer_frame = _current_pointer_frame;
-
-       _editor->show_verbose_time_cursor (_current_pointer_frame, 10);
+       _editor->show_verbose_time_cursor (pf, 10);
 }
 
 void
-TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
+TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 {
        _primary->get_time_axis_view().hide_timestretch ();
 
-       if (!movement_occurred) {
+       if (!movement_occurred) {
                return;
        }
 
-       if (_last_pointer_frame < _primary->region()->position()) {
+       if (last_pointer_frame() < _primary->region()->position()) {
                /* backwards drag of the left edge - not usable */
                return;
        }
-       
-       nframes64_t newlen = _last_pointer_frame - _primary->region()->position();
+
+       nframes64_t newlen = last_pointer_frame() - _primary->region()->position();
 
        float percentage = (double) newlen / (double) _primary->region()->length();
-       
+
 #ifndef USE_RUBBERBAND
-       // Soundtouch uses percentage / 100 instead of normal (/ 1) 
+       // Soundtouch uses percentage / 100 instead of normal (/ 1)
        if (_primary->region()->data_type() == DataType::AUDIO) {
                percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
        }
-#endif 
-       
+#endif
+
        _editor->begin_reversible_command (_("timestretch"));
-       
+
        // XXX how do timeFX on multiple regions ?
-       
+
        RegionSelection rs;
        rs.add (_primary);
 
-       if (_editor->time_stretch (rs, percentage) == 0) {
-               _editor->session->commit_reversible_command ();
+       if (!_editor->time_stretch (rs, percentage) == 0) {
+               error << _("An error occurred while executing time stretch operation") << endmsg;
        }
 }
 
-SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
-       : Drag (e, i),
-         _operation (o),
-         _copy (false)
+void
+TimeFXDrag::aborted ()
 {
+       _primary->get_time_axis_view().hide_timestretch ();
+}
 
+
+void
+ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
+{
+       Drag::start_grab (event);
 }
 
 void
-SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
+ScrubDrag::motion (GdkEvent* /*event*/, bool)
 {
-       nframes64_t start = 0;
-       nframes64_t end = 0;
+       _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
+}
 
-       if (_editor->session == 0) {
+void
+ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
+{
+       if (movement_occurred && _editor->session()) {
+               /* make sure we stop */
+               _editor->session()->request_transport_speed (0.0);
+       }
+}
+
+void
+ScrubDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
+SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
+       : Drag (e, i)
+       , _operation (o)
+       , _copy (false)
+       , _original_pointer_time_axis (-1)
+       , _last_pointer_time_axis (-1)
+{
+
+}
+
+void
+SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
+{
+       nframes64_t start = 0;
+       nframes64_t end = 0;
+
+       if (_editor->session() == 0) {
                return;
        }
 
@@ -2863,35 +3175,35 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
        case SelectionStartTrim:
                if (_editor->clicked_axisview) {
                        _editor->clicked_axisview->order_selection_trims (_item, true);
-               } 
-               Drag::start_grab (event, cursor);
-               cursor = _editor->trimmer_cursor;
+               }
+               Drag::start_grab (event, _editor->trimmer_cursor);
                start = _editor->selection->time[_editor->clicked_selection].start;
-               _pointer_frame_offset = _grab_frame - start;    
+               _pointer_frame_offset = grab_frame() - start;
                break;
-               
+
        case SelectionEndTrim:
                if (_editor->clicked_axisview) {
                        _editor->clicked_axisview->order_selection_trims (_item, false);
                }
-               Drag::start_grab (event, cursor);
-               cursor = _editor->trimmer_cursor;
+               Drag::start_grab (event, _editor->trimmer_cursor);
                end = _editor->selection->time[_editor->clicked_selection].end;
-               _pointer_frame_offset = _grab_frame - end;      
+               _pointer_frame_offset = grab_frame() - end;
                break;
 
        case SelectionMove:
                start = _editor->selection->time[_editor->clicked_selection].start;
                Drag::start_grab (event, cursor);
-               _pointer_frame_offset = _grab_frame - start;    
+               _pointer_frame_offset = grab_frame() - start;
                break;
        }
 
        if (_operation == SelectionMove) {
-               _editor->show_verbose_time_cursor (start, 10);  
+               _editor->show_verbose_time_cursor (start, 10);
        } else {
-               _editor->show_verbose_time_cursor (_current_pointer_frame, 10); 
+               _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
        }
+
+       _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
 }
 
 void
@@ -2901,60 +3213,90 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        nframes64_t end = 0;
        nframes64_t length;
 
-       nframes64_t pending_position = adjusted_current_frame ();
-       
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (pending_position);
+       pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       if (pending_time_axis.first == 0) {
+               return;
        }
-
-       /* only alter selection if the current frame is 
-          different from the last frame position (adjusted)
-        */
        
-       if (pending_position == _last_pointer_frame) {
+       nframes64_t const pending_position = adjusted_current_frame (event);
+
+       /* only alter selection if things have changed */
+
+       if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
                return;
        }
-       
+
        switch (_operation) {
        case CreateSelection:
-               
+       {
+               nframes64_t grab = grab_frame ();
+
                if (first_move) {
-                       _editor->snap_to (_grab_frame);
+                       _editor->snap_to (grab);
                }
-               
-               if (pending_position < _grab_frame) {
+
+               if (pending_position < grab_frame()) {
                        start = pending_position;
-                       end = _grab_frame;
+                       end = grab;
                } else {
                        end = pending_position;
-                       start = _grab_frame;
+                       start = grab;
                }
-               
+
                /* first drag: Either add to the selection
-                  or create a new selection->
+                  or create a new selection
                */
-               
+
                if (first_move) {
-                       
-                       _editor->begin_reversible_command (_("range selection"));
-                       
+
                        if (_copy) {
                                /* adding to the selection */
+                               _editor->selection->add (_editor->clicked_axisview);
                                _editor->clicked_selection = _editor->selection->add (start, end);
                                _copy = false;
                        } else {
-                               /* new selection-> */
-                               _editor->clicked_selection = _editor->selection->set (_editor->clicked_axisview, start, end);
+                               /* new selection */
+
+                               if (!_editor->selection->selected (_editor->clicked_axisview)) {
+                                       _editor->selection->set (_editor->clicked_axisview);
+                               }
+                               
+                               _editor->clicked_selection = _editor->selection->set (start, end);
                        }
-               } 
-               break;
-               
-       case SelectionStartTrim:
-               
-               if (first_move) {
-                       _editor->begin_reversible_command (_("trim selection start"));
                }
+
+               /* 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->selection->add (pending_time_axis.first);
+                       _added_time_axes.push_back (pending_time_axis.first);
+               }
+
+               /* 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());
+
+               list<TimeAxisView*>::iterator i = _added_time_axes.begin();
+               while (i != _added_time_axes.end()) {
+
+                       list<TimeAxisView*>::iterator tmp = i;
+                       ++tmp;
+                       
+                       if ((*i)->order() < min_order || (*i)->order() > max_order) {
+                               _editor->selection->remove (*i);
+                               _added_time_axes.remove (*i);
+                       }
+
+                       i = tmp;
+               }
+
+       }
+       break;
+
+       case SelectionStartTrim:
+
                start = _editor->selection->time[_editor->clicked_selection].start;
                end = _editor->selection->time[_editor->clicked_selection].end;
 
@@ -2964,13 +3306,9 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                        start = pending_position;
                }
                break;
-               
+
        case SelectionEndTrim:
-               
-               if (first_move) {
-                       _editor->begin_reversible_command (_("trim selection end"));
-               }
-               
+
                start = _editor->selection->time[_editor->clicked_selection].start;
                end = _editor->selection->time[_editor->clicked_selection].end;
 
@@ -2979,29 +3317,25 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                } else {
                        end = pending_position;
                }
-               
+
                break;
-               
+
        case SelectionMove:
-               
-               if (first_move) {
-                       _editor->begin_reversible_command (_("move selection"));
-               }
-               
+
                start = _editor->selection->time[_editor->clicked_selection].start;
                end = _editor->selection->time[_editor->clicked_selection].end;
-               
+
                length = end - start;
-               
+
                start = pending_position;
                _editor->snap_to (start);
-               
+
                end = start + length;
-               
+
                break;
        }
-       
-       if (event->button.x >= _editor->horizontal_adjustment.get_value() + _editor->canvas_width) {
+
+       if (event->button.x >= _editor->horizontal_adjustment.get_value() + _editor->_canvas_width) {
                _editor->start_canvas_autoscroll (1, 0);
        }
 
@@ -3009,40 +3343,57 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                _editor->selection->replace (_editor->clicked_selection, start, end);
        }
 
-       _last_pointer_frame = pending_position;
-
        if (_operation == SelectionMove) {
-               _editor->show_verbose_time_cursor(start, 10);   
+               _editor->show_verbose_time_cursor(start, 10);
        } else {
-               _editor->show_verbose_time_cursor(pending_position, 10);        
+               _editor->show_verbose_time_cursor(pending_position, 10);
        }
 }
 
 void
 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
 {
+       Session* s = _editor->session();
+
        if (movement_occurred) {
                motion (event, false);
                /* XXX this is not object-oriented programming at all. ick */
                if (_editor->selection->time.consolidate()) {
                        _editor->selection->TimeChanged ();
                }
-               _editor->commit_reversible_command ();
+
+               /* 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);
+               }
+
+
        } else {
                /* just a click, no pointer movement.*/
 
                if (Keyboard::no_modifier_keys_pressed (&event->button)) {
-
                        _editor->selection->clear_time();
+               }
+
+               if (!_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);
+               }
 
-               } 
        }
 
-       /* XXX what happens if its a music selection? */
-       _editor->session->set_audio_range (_editor->selection->time);
        _editor->stop_canvas_autoscroll ();
 }
 
+void
+SelectionDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
        : Drag (e, i),
          _operation (o),
@@ -3058,7 +3409,7 @@ RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operat
 void
 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
-       if (_editor->session == 0) {
+       if (_editor->session() == 0) {
                return;
        }
 
@@ -3067,12 +3418,12 @@ RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
        if (!_editor->temp_location) {
                _editor->temp_location = new Location;
        }
-       
+
        switch (_operation) {
        case CreateRangeMarker:
        case CreateTransportMarker:
        case CreateCDMarker:
-       
+
                if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
                        _copy = true;
                } else {
@@ -3084,7 +3435,7 @@ RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 
        Drag::start_grab (event, cursor);
 
-       _editor->show_verbose_time_cursor (_current_pointer_frame, 10); 
+       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
 }
 
 void
@@ -3109,57 +3460,42 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
                return;
                break;
        }
-       
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (_current_pointer_frame);
-       }
 
-       /* only alter selection if the current frame is 
-          different from the last frame position.
-        */
-       
-       if (_current_pointer_frame == _last_pointer_frame) {
-               return;
-       }
-       
-       switch (_operation) {
-       case CreateRangeMarker:
-       case CreateTransportMarker:
-       case CreateCDMarker:
-               if (first_move) {
-                       _editor->snap_to (_grab_frame);
-               }
+       nframes64_t const pf = adjusted_current_frame (event);
+
+       if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
+               nframes64_t grab = grab_frame ();
+               _editor->snap_to (grab);
                
-               if (_current_pointer_frame < _grab_frame) {
-                       start = _current_pointer_frame;
-                       end = _grab_frame;
+               if (pf < grab_frame()) {
+                       start = pf;
+                       end = grab;
                } else {
-                       end = _current_pointer_frame;
-                       start = _grab_frame;
+                       end = pf;
+                       start = grab;
                }
-               
+
                /* first drag: Either add to the selection
                   or create a new selection.
                */
-               
+
                if (first_move) {
-                       
+
                        _editor->temp_location->set (start, end);
-                       
+
                        crect->show ();
 
                        update_item (_editor->temp_location);
                        _drag_rect->show();
                        //_drag_rect->raise_to_top();
-                       
-               } 
-               break;          
+
+               }
        }
-       
-       if (event->button.x >= _editor->horizontal_adjustment.get_value() + _editor->canvas_width) {
+
+       if (event->button.x >= _editor->horizontal_adjustment.get_value() + _editor->_canvas_width) {
                _editor->start_canvas_autoscroll (1, 0);
        }
-       
+
        if (start != end) {
                _editor->temp_location->set (start, end);
 
@@ -3171,10 +3507,8 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
                update_item (_editor->temp_location);
        }
 
-       _last_pointer_frame = _current_pointer_frame;
+       _editor->show_verbose_time_cursor (pf, 10);
 
-       _editor->show_verbose_time_cursor (_current_pointer_frame, 10); 
-       
 }
 
 void
@@ -3183,17 +3517,18 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
        Location * newloc = 0;
        string rangename;
        int flags;
-       
+
        if (movement_occurred) {
                motion (event, false);
+               _drag_rect->hide();
 
                switch (_operation) {
                case CreateRangeMarker:
                case CreateCDMarker:
                    {
                        _editor->begin_reversible_command (_("new range marker"));
-                       XMLNode &before = _editor->session->locations()->get_state();
-                       _editor->session->locations()->next_available_name(rangename,"unnamed");
+                       XMLNode &before = _editor->session()->locations()->get_state();
+                       _editor->session()->locations()->next_available_name(rangename,"unnamed");
                        if (_operation == CreateCDMarker) {
                                flags = Location::IsRangeMarker | Location::IsCDMarker;
                                _editor->cd_marker_bar_drag_rect->hide();
@@ -3203,19 +3538,16 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
                                _editor->range_bar_drag_rect->hide();
                        }
                        newloc = new Location(_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));
+                       _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));
                        _editor->commit_reversible_command ();
-                       
-                       _drag_rect->hide();
                        break;
                    }
 
                case CreateTransportMarker:
                        // popup menu to pick loop or punch
                        _editor->new_transport_marker_context_menu (&event->button, _item);
-                       
                        break;
                }
        } else {
@@ -3226,15 +3558,14 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
                        nframes64_t start;
                        nframes64_t end;
 
-                       start = _editor->session->locations()->first_mark_before (_grab_frame);
-                       end = _editor->session->locations()->first_mark_after (_grab_frame);
-                       
+                       _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
+
                        if (end == max_frames) {
-                               end = _editor->session->current_end_frame ();
+                               end = _editor->session()->current_end_frame ();
                        }
 
-                       if (start == 0) {
-                               start = _editor->session->current_start_frame ();
+                       if (start == max_frames) {
+                               start = _editor->session()->current_start_frame ();
                        }
 
                        switch (_editor->mouse_mode) {
@@ -3245,19 +3576,23 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
 
                        case MouseRange:
                                /* find the two markers on either side of the click and make the range out of it */
-                               _editor->selection->set (0, start, end);
+                               _editor->selection->set (start, end);
                                break;
 
                        default:
                                break;
                        }
-               } 
+               }
        }
 
        _editor->stop_canvas_autoscroll ();
 }
 
-
+void
+RangeMarkerBarDrag::aborted ()
+{
+       /* XXX: TODO */
+}
 
 void
 RangeMarkerBarDrag::update_item (Location* location)
@@ -3273,7 +3608,7 @@ void
 MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        Drag::start_grab (event, _editor->zoom_cursor);
-       _editor->show_verbose_time_cursor (_current_pointer_frame, 10);
+       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
 }
 
 void
@@ -3282,27 +3617,20 @@ MouseZoomDrag::motion (GdkEvent* event, bool first_move)
        nframes64_t start;
        nframes64_t end;
 
-       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-               _editor->snap_to (_current_pointer_frame);
-               
-               if (first_move) {
-                       _editor->snap_to (_grab_frame);
-               }
-       }
-               
-       if (_current_pointer_frame == _last_pointer_frame) {
-               return;
-       }
+       nframes64_t const pf = adjusted_current_frame (event);
+
+       nframes64_t grab = grab_frame ();
+       _editor->snap_to_with_modifier (grab, event);
 
        /* base start and end on initial click position */
-       if (_current_pointer_frame < _grab_frame) {
-               start = _current_pointer_frame;
-               end = _grab_frame;
+       if (pf < grab) {
+               start = pf;
+               end = grab;
        } else {
-               end = _current_pointer_frame;
-               start = _grab_frame;
+               end = pf;
+               start = grab;
        }
-       
+
        if (start != end) {
 
                if (first_move) {
@@ -3312,9 +3640,7 @@ MouseZoomDrag::motion (GdkEvent* event, bool first_move)
 
                _editor->reposition_zoom_rect(start, end);
 
-               _last_pointer_frame = _current_pointer_frame;
-
-               _editor->show_verbose_time_cursor (_current_pointer_frame, 10);
+               _editor->show_verbose_time_cursor (pf, 10);
        }
 }
 
@@ -3323,19 +3649,275 @@ MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
 {
        if (movement_occurred) {
                motion (event, false);
-               
-               if (_grab_frame < _last_pointer_frame) {
-                       _editor->temporal_zoom_by_frame (_grab_frame, _last_pointer_frame, "mouse zoom");
+
+               if (grab_frame() < last_pointer_frame()) {
+                       _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
                } else {
-                       _editor->temporal_zoom_by_frame (_last_pointer_frame, _grab_frame, "mouse zoom");
-               }               
+                       _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
+               }
        } else {
-               _editor->temporal_zoom_to_frame (false, _grab_frame);
+               _editor->temporal_zoom_to_frame (false, grab_frame());
                /*
                temporal_zoom_step (false);
-               center_screen (_grab_frame);
+               center_screen (grab_frame());
                */
        }
 
        _editor->zoom_rect->hide();
 }
+
+void
+MouseZoomDrag::aborted ()
+{
+       _editor->zoom_rect->hide ();
+}
+
+NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+{
+       CanvasNoteEvent*     cnote = dynamic_cast<CanvasNoteEvent*>(_item);
+       region = &cnote->region_view();
+}
+
+void
+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())) {
+
+               /* 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
+                  out the "extend-to" range.
+               */
+
+               bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
+
+               if (!extend) {
+                       bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
+
+                       if (add) {
+                               region->note_selected (cnote, true);
+                       } else {
+                               region->unique_select (cnote);
+                       }
+               }
+       }
+}
+
+void
+NoteDrag::motion (GdkEvent*, bool)
+{
+       MidiStreamView* streamview = region->midi_stream_view();
+       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);
+
+       event_x = region->snap_to_pixel(event_x);
+
+       double dx     = event_x - last_x;
+       double dy     = event_y - last_y;
+       last_x = event_x;
+
+       drag_delta_x += dx;
+
+       // Snap to note rows
+
+       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) {
+               region->move_selection (dx, dy);
+
+               CanvasNoteEvent* cnote = dynamic_cast<CanvasNoteEvent*>(_item);
+                char buf[4];
+               snprintf (buf, sizeof (buf), "%g", (int) cnote->note()->note() + drag_delta_note);
+               //editor.show_verbose_canvas_cursor_with (Evoral::midi_note_name (ev->note()->note()));
+               _editor->show_verbose_canvas_cursor_with (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) {
+                               bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
+                               if (add) {
+                                       region->note_deselected (cnote);
+                               }
+                       } 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);
+                               } else if (extend) {
+                                       region->note_selected (cnote, true, true);
+                               } else {
+                                       /* it was added during button press */
+                               }
+                       }
+               }
+       } else {
+               region->note_dropped (cnote, drag_delta_x, drag_delta_note);
+       }
+}
+
+void
+NoteDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
+AutomationRangeDrag::AutomationRangeDrag (Editor* e, ArdourCanvas::Item* i, list<AudioRange> const & r)
+       : Drag (e, i)
+       , _ranges (r)
+       , _nothing_to_drag (false)
+{
+       _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
+       assert (_atav);
+
+       _line = _atav->line ();
+}
+
+void
+AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
+
+       list<ControlPoint*> points;
+
+       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));
+               }
+               
+       } else {
+
+               boost::shared_ptr<AutomationList> the_list = _line->the_list ();
+               for (list<AudioRange>::const_iterator j = _ranges.begin(); j != _ranges.end(); ++j) {
+
+                       /* 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;
+                       }
+                       
+                       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) {
+
+                       ControlPoint* p = _line->nth (i);
+
+                       list<AudioRange>::const_iterator j = _ranges.begin ();
+                       while (j != _ranges.end() && (j->start >= (*p->model())->when || j->end <= (*p->model())->when)) {
+                               ++j;
+                       }
+
+                       if (j != _ranges.end()) {
+                               points.push_back (p);
+                       }
+               }
+       }
+
+       if (points.empty()) {
+               _nothing_to_drag = true;
+               return;
+       }
+
+       _line->start_drag_multiple (points, 1 - (_drags->current_pointer_y() / _line->height ()), state);
+}
+
+void
+AutomationRangeDrag::motion (GdkEvent* event, 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);
+}
+
+void
+AutomationRangeDrag::finished (GdkEvent* event, bool)
+{
+       if (_nothing_to_drag) {
+               return;
+       }
+       
+       motion (event, false);
+       _line->end_drag ();
+       _line->clear_always_in_view ();
+}
+
+void
+AutomationRangeDrag::aborted ()
+{
+       _line->clear_always_in_view ();
+       _line->reset ();
+}
+
+DraggingView::DraggingView (RegionView* v)
+       : view (v)
+{
+       initial_y = v->get_canvas_group()->property_y ();
+}