changes to help strp silence
[ardour.git] / gtk2_ardour / editor_drag.cc
index 86fbe134bb141571c934f57c668318f56eb88174..c436822ae3220d01394379e0d29a0cd9202ef933 100644 (file)
 
 */
 
+#define __STDC_LIMIT_MACROS 1
+#include <stdint.h>
 #include "pbd/memento_command.h"
 #include "pbd/basename.h"
+#include "pbd/stateful_diff_command.h"
 #include "ardour/diskstream.h"
 #include "ardour/session.h"
 #include "ardour/dB.h"
@@ -53,15 +56,128 @@ using Gtkmm2ext::Keyboard;
 
 double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
 
+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)
-       , _have_transaction (false)
        , _move_threshold_passed (false)
        , _grab_frame (0)
        , _last_pointer_frame (0)
-       , _current_pointer_frame (0)
 {
 
 }
@@ -104,15 +220,8 @@ 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,
@@ -142,20 +251,14 @@ 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, _move_threshold_passed);
 
        _editor->hide_verbose_canvas_cursor();
 
-       _ending = false;
-
        return _move_threshold_passed;
 }
 
@@ -178,20 +281,15 @@ Drag::adjusted_frame (nframes64_t f, GdkEvent const * event, bool snap) const
 nframes64_t
 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
 {
-       return adjusted_frame (_current_pointer_frame, event, snap);
+       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;
-       _last_pointer_frame = adjusted_current_frame (event);
-       _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 == _current_pointer_y) ) {
+            (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
                return false;
        }
 
@@ -202,7 +300,7 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
        if (!from_autoscroll && !_move_threshold_passed) {
 
                bool const xp = (::llabs (adjusted_current_frame (event) - _grab_frame) >= threshold.first);
-               bool const yp = (::fabs ((_current_pointer_y - _grab_y)) >= threshold.second);
+               bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);
 
                _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
        }
@@ -215,6 +313,11 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
                        }
 
                        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;
                }
        }
@@ -222,31 +325,17 @@ 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 */
-
-               double cxw, cyw;
-               cxw = 0;
-               cyw = 0;
-               _item->i2w (cxw, cyw);
-               _item->move (_original_x - cxw, _original_y - cyw);
        }
-}
 
-pair<nframes64_t, nframes64_t>
-Drag::extent () const
-{
-       nframes64_t const f = adjusted_current_frame (0);
-       return make_pair (f, f);
+       aborted ();
+
+       _editor->stop_canvas_autoscroll ();
+       _editor->hide_verbose_canvas_cursor ();
 }
 
 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
@@ -263,19 +352,12 @@ RegionDrag::region_going_away (RegionView* v)
        _views.remove (v);
 }
 
-pair<nframes64_t, nframes64_t>
-RegionDrag::extent () const
-{
-       nframes64_t const f = adjusted_current_frame (0);
-       return make_pair (f, f + _primary->region()->length ());
-}
-
-
 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)
 {
 
 }
@@ -709,6 +791,8 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
        } /* foreach region */
 
+       _total_x_delta += x_delta;
+       
        if (first_move) {
                _editor->cursor_group->raise_to_top();
        }
@@ -734,6 +818,7 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        vector<RegionView*> copies;
        boost::shared_ptr<Diskstream> ds;
        boost::shared_ptr<Playlist> from_playlist;
+       boost::shared_ptr<Playlist> to_playlist;
        RegionSelection new_views;
        typedef set<boost::shared_ptr<Playlist> > PlaylistSet;
        PlaylistSet modified_playlists;
@@ -744,6 +829,7 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        bool changed_tracks, changed_position;
        map<RegionView*, pair<RouteTimeAxisView*, int> > final;
        RouteTimeAxisView* source_tv;
+        vector<StatefulDiffCommand*> sdc;
 
        if (!movement_occurred) {
                /* just a click */
@@ -784,8 +870,6 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                }
        }
 
-       _have_transaction = true;
-
        changed_position = (_last_frame_position != (nframes64_t) (_primary->region()->position()));
        changed_tracks = (_dest_trackview != &_primary->get_time_axis_view());
 
@@ -796,6 +880,8 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        /* make a list of where each region ended up */
        final = find_time_axis_views_and_layers ();
 
+        cerr << "Iterate over " << _views.size() << " views\n";
+
        for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ) {
 
                RegionView* rv = (*i);
@@ -804,6 +890,9 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 
                nframes64_t where;
 
+                from_playlist.reset ();
+                to_playlist.reset ();
+
                if (rv->region()->locked()) {
                        ++i;
                        continue;
@@ -833,7 +922,7 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 
                if (changed_tracks || _copy) {
 
-                       boost::shared_ptr<Playlist> to_playlist = dest_rtv->playlist();
+                       to_playlist = dest_rtv->playlist();
 
                        if (!to_playlist) {
                                ++i;
@@ -847,10 +936,18 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                        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);
@@ -866,6 +963,8 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                        }
 
                } else {
+                       rv->region()->clear_history ();
+
                        /*
                           motion on the same track. plonk the previously reparented region
                           back to its original canvas group (its streamview).
@@ -879,25 +978,25 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                        /* just change the model */
 
                        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
-
+                       
                        if (dest_rtv->view()->layer_display() == Stacked) {
                                rv->region()->set_layer (dest_layer);
                                rv->region()->set_pending_explicit_relayer (true);
                        }
+                       
+                       /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
 
-                       insert_result = modified_playlists.insert (playlist);
-
-                       if (insert_result.second) {
-                               _editor->session()->add_command (new MementoCommand<Playlist>(*playlist, &playlist->get_state(), 0));
-                       }
-                       /* freeze 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) {
@@ -930,9 +1029,15 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                        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
@@ -953,7 +1058,16 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                        */
 
                        if (_views.empty()) {
-                               break;
+                                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();
                        }
@@ -965,11 +1079,25 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
                if (_copy) {
                        copies.push_back (rv);
                }
+
+                cerr << "Done with TV, top = " << to_playlist << " from = " << from_playlist << endl;
+
+                if (to_playlist) {
+                        sdc.push_back (new StatefulDiffCommand (to_playlist));
+                        cerr << "Saved diff for to:" << to_playlist->name() << endl;
+                }
+
+                if (from_playlist && (from_playlist != to_playlist)) {
+                        sdc.push_back (new StatefulDiffCommand (from_playlist));
+                        cerr << "Saved diff for from:" << from_playlist->name() << endl;
+                }
        }
+
        /*
           if we've created new regions either by copying or moving 
           to a new track, we want to replace the old selection with the new ones 
        */
+
        if (new_views.size() > 0) {
                _editor->selection->set (new_views);
        }
@@ -979,9 +1107,9 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 
   out:
-       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 ();
 
@@ -990,6 +1118,40 @@ RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 }
 
+void
+RegionMoveDrag::aborted ()
+{
+       if (_copy) {
+
+               for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                       delete *i;
+               }
+
+               _views.clear ();
+
+       } else {
+               RegionMotionDrag::aborted ();
+       }
+}
+
+void
+RegionMotionDrag::aborted ()
+{
+       for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+               TimeAxisView* tv = &(*i)->get_time_axis_view ();
+               RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
+               assert (rtv);
+               (*i)->get_canvas_group()->reparent (*rtv->view()->canvas_item());
+               (*i)->get_canvas_group()->property_y() = 0;
+               (*i)->get_time_axis_view().reveal_dependent_views (**i);
+               (*i)->fake_set_opaque (false);
+               (*i)->move (-_total_x_delta, 0);
+               (*i)->set_height (rtv->view()->child_height ());
+       }
+
+       _editor->update_canvas_now ();
+}
+                                     
 
 bool
 RegionMotionDrag::x_move_allowed () const
@@ -1017,11 +1179,13 @@ RegionMotionDrag::copy_regions (GdkEvent* event)
 
                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
@@ -1060,7 +1224,7 @@ RegionMotionDrag::copy_regions (GdkEvent* event)
           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?
+          something to do with regionview creation taking so long?
        */
        _editor->update_canvas_now();
 }
@@ -1070,7 +1234,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;
 
@@ -1263,9 +1427,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;
@@ -1273,6 +1437,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)
 {
@@ -1297,7 +1467,7 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
 
        int dir;
 
-       if ((current_pointer_x() - last_pointer_x()) > 0) {
+       if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
                dir = 1;
        } else {
                dir = -1;
@@ -1349,6 +1519,11 @@ RegionSpliceDrag::finished (GdkEvent* /*event*/, bool)
 
 }
 
+void
+RegionSpliceDrag::aborted ()
+{
+       /* XXX: TODO */
+}
 
 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
        : Drag (e, i),
@@ -1393,6 +1568,12 @@ RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
        }
 }
 
+void
+RegionCreateDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
        , region (0)
@@ -1452,7 +1633,7 @@ NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
 {
        MidiRegionSelection& ms (_editor->get_selection().midi_regions);
        for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
-               (*r)->update_resizing (at_front, current_pointer_x() - grab_x(), relative);
+               (*r)->update_resizing (at_front, _drags->current_pointer_x() - grab_x(), relative);
        }
 }
 
@@ -1461,10 +1642,16 @@ 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, current_pointer_x() - grab_x(), relative);
+               (*r)->commit_resizing (at_front, _drags->current_pointer_x() - grab_x(), relative);
        }
 }
 
+void
+NoteResizeDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 void
 RegionGainDrag::motion (GdkEvent* /*event*/, bool)
 {
@@ -1477,8 +1664,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)
 {
 
 }
@@ -1535,7 +1729,7 @@ 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
@@ -1580,7 +1774,8 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
 
                for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                        (*i)->fake_set_opaque(false);
-                       (*i)->region()->freeze ();
+                        (*i)->region()->clear_history ();
+                       (*i)->region()->suspend_property_changes ();
 
                        AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*i);
 
@@ -1592,19 +1787,11 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                        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 (pf == last_pointer_frame()) {
-               return;
-       }
-
-       /* XXX i hope to god that we can really conclude this ... */
-       _have_transaction = true;
-
        if (left_direction) {
                frame_delta = (last_pointer_frame() - pf);
        } else {
@@ -1613,7 +1800,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
 
        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;
        }
 
@@ -1644,7 +1831,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                {
                        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;
                        }
 
@@ -1682,13 +1869,13 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                        for (list<RegionView*>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                                _editor->thaw_region_after_trim (**i);
                                (*i)->fake_set_opaque (true);
+                                if (_have_transaction) {
+                                        _editor->session()->add_command (new StatefulDiffCommand ((*i)->region()));
+                                }
                        }
                }
                for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
                        (*p)->thaw ();
-                       if (_have_transaction) {
-                               _editor->session()->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
-                       }
                }
 
                _editor->motion_frozen_playlists.clear ();
@@ -1699,7 +1886,22 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
 
        } 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 ();
        }
 }
 
@@ -1747,10 +1949,6 @@ MeterMarkerDrag::motion (GdkEvent* event, bool)
 {
        nframes64_t const pf = adjusted_current_frame (event);
 
-       if (pf == last_pointer_frame()) {
-               return;
-       }
-
        _marker->set_position (pf);
        
        _editor->show_verbose_time_cursor (pf, 10);
@@ -1791,6 +1989,12 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        }
 }
 
+void
+MeterMarkerDrag::aborted ()
+{
+       _marker->set_position (_marker->meter().frame ());
+}
+
 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
        : Drag (e, i),
          _copy (c)
@@ -1834,15 +2038,7 @@ void
 TempoMarkerDrag::motion (GdkEvent* event, bool)
 {
        nframes64_t const pf = adjusted_current_frame (event);
-
-       if (pf == last_pointer_frame()) {
-               return;
-       }
-
-       /* OK, we've moved far enough to make it worth actually move the thing. */
-
        _marker->set_position (pf);
-
        _editor->show_verbose_time_cursor (pf, 10);
 }
 
@@ -1881,6 +2077,11 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        }
 }
 
+void
+TempoMarkerDrag::aborted ()
+{
+       _marker->set_position (_marker->tempo().frame());
+}
 
 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
        : Drag (e, i),
@@ -1916,6 +2117,8 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
                }
        }
 
+       _pointer_frame_offset = grab_frame() - _cursor->current_frame;
+
        _editor->show_verbose_time_cursor (_cursor->current_frame, 10);
 }
 
@@ -1957,6 +2160,13 @@ CursorDrag::finished (GdkEvent* event, bool movement_occurred)
        }
 }
 
+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)
 {
@@ -2050,6 +2260,20 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
        _editor->commit_reversible_command ();
 }
 
+void
+FadeInDrag::aborted ()
+{
+       for (RegionSelection::iterator i = _views.begin(); i != _views.end(); ++i) {
+               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
+
+               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)
 {
@@ -2147,6 +2371,20 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
        _editor->commit_reversible_command ();
 }
 
+void
+FadeOutDrag::aborted ()
+{
+       for (RegionSelection::iterator i = _views.begin(); i != _views.end(); ++i) {
+               AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
+
+               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)
 {
@@ -2459,6 +2697,12 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        _line->hide();
 }
 
+void
+MarkerDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 void
 MarkerDrag::update_item (Location* location)
 {
@@ -2488,11 +2732,10 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
        // the point doesn't 'jump' to the mouse after the first drag
        _time_axis_view_grab_x = _point->get_x();
        _time_axis_view_grab_y = _point->get_y();
-       nframes64_t grab_frame = _editor->pixel_to_frame (_time_axis_view_grab_x);
 
        float const fraction = 1 - (_point->get_y() / _point->line().height());
 
-       _point->line().start_drag_single (_point, grab_frame, fraction);
+       _point->line().start_drag_single (_point, _time_axis_view_grab_x, fraction);
 
        _editor->set_verbose_canvas_cursor (_point->line().get_verbose_cursor_string (fraction),
                                            event->button.x + 10, event->button.y + 10);
@@ -2503,8 +2746,8 @@ 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;
@@ -2548,7 +2791,7 @@ ControlPointDrag::motion (GdkEvent* event, bool)
 
        bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
 
-       _point->line().drag_motion (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));
 }
@@ -2570,6 +2813,12 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
        _point->line().end_drag ();
 }
 
+void
+ControlPointDrag::aborted ()
+{
+       _point->line().reset ();
+}
+
 bool
 ControlPointDrag::active (Editing::MouseMode m)
 {
@@ -2636,7 +2885,7 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*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;
@@ -2659,8 +2908,8 @@ LineDrag::motion (GdkEvent* event, bool)
                push = true;
        }
 
-       /* we are ignoring x position for this drag, so we can just pass in 0 */
-       _line->drag_motion (0, 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));
 }
@@ -2672,6 +2921,12 @@ LineDrag::finished (GdkEvent* event, bool)
        _line->end_drag ();
 }
 
+void
+LineDrag::aborted ()
+{
+       _line->reset ();
+}
+
 void
 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
@@ -2687,14 +2942,8 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
        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) (pf - grab_frame())) < 8) {
-               return;
-       }
-
        nframes64_t grab = grab_frame ();
        if (Config->get_rubberbanding_snaps_to_grid ()) {
                _editor->snap_to_with_modifier (grab, event);
@@ -2710,11 +2959,11 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
                start = grab;
        }
 
-       if (current_pointer_y() < grab_y()) {
-               y1 = current_pointer_y();
+       if (_drags->current_pointer_y() < grab_y()) {
+               y1 = _drags->current_pointer_y();
                y2 = grab_y();
        } else {
-               y2 = current_pointer_y();
+               y2 = _drags->current_pointer_y();
                y1 = grab_y();
        }
 
@@ -2744,11 +2993,11 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
                motion (event, false);
 
                double y1,y2;
-               if (current_pointer_y() < grab_y()) {
-                       y1 = current_pointer_y();
+               if (_drags->current_pointer_y() < grab_y()) {
+                       y1 = _drags->current_pointer_y();
                        y2 = grab_y();
                } else {
-                       y2 = current_pointer_y();
+                       y2 = _drags->current_pointer_y();
                        y1 = grab_y();
                }
 
@@ -2780,6 +3029,12 @@ 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 *)
 {
@@ -2795,10 +3050,6 @@ TimeFXDrag::motion (GdkEvent* event, bool)
 
        nframes64_t const pf = adjusted_current_frame (event);
 
-       if (pf == last_pointer_frame()) {
-               return;
-       }
-
        if (pf > rv->region()->position()) {
                rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
        }
@@ -2843,6 +3094,13 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 }
 
+void
+TimeFXDrag::aborted ()
+{
+       _primary->get_time_axis_view().hide_timestretch ();
+}
+
+
 void
 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
@@ -2852,7 +3110,7 @@ ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 void
 ScrubDrag::motion (GdkEvent* /*event*/, bool)
 {
-       _editor->scrub ();
+       _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
 }
 
 void
@@ -2864,6 +3122,12 @@ ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 }
 
+void
+ScrubDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
        : Drag (e, i)
        , _operation (o)
@@ -2928,7 +3192,7 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
                _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
        }
 
-       _original_pointer_time_axis = _editor->trackview_by_y_position (current_pointer_y ()).first->order ();
+       _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
 }
 
 void
@@ -2938,7 +3202,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        nframes64_t end = 0;
        nframes64_t length;
 
-       pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (current_pointer_y ());
+       pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        if (pending_time_axis.first == 0) {
                return;
        }
@@ -2974,9 +3238,6 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
                if (first_move) {
 
-                       _editor->begin_reversible_command (_("range selection"));
-                       _have_transaction = true;
-
                        if (_copy) {
                                /* adding to the selection */
                                _editor->selection->add (_editor->clicked_axisview);
@@ -3025,11 +3286,6 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
        case SelectionStartTrim:
 
-               if (first_move) {
-                       _editor->begin_reversible_command (_("trim selection start"));
-                       _have_transaction = true;
-               }
-               
                start = _editor->selection->time[_editor->clicked_selection].start;
                end = _editor->selection->time[_editor->clicked_selection].end;
 
@@ -3042,11 +3298,6 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
        case SelectionEndTrim:
 
-               if (first_move) {
-                       _editor->begin_reversible_command (_("trim selection end"));
-                       _have_transaction = true;
-               }
-
                start = _editor->selection->time[_editor->clicked_selection].start;
                end = _editor->selection->time[_editor->clicked_selection].end;
 
@@ -3060,11 +3311,6 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
        case SelectionMove:
 
-               if (first_move) {
-                       _editor->begin_reversible_command (_("move selection"));
-                       _have_transaction = true;
-               }
-
                start = _editor->selection->time[_editor->clicked_selection].start;
                end = _editor->selection->time[_editor->clicked_selection].end;
 
@@ -3105,10 +3351,6 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
                        _editor->selection->TimeChanged ();
                }
 
-               if (_have_transaction) {
-                       _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);
@@ -3135,6 +3377,12 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
        _editor->stop_canvas_autoscroll ();
 }
 
+void
+SelectionDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
        : Drag (e, i),
          _operation (o),
@@ -3204,14 +3452,6 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
 
        nframes64_t const pf = adjusted_current_frame (event);
 
-       /* only alter selection if the current frame is
-          different from the last frame position.
-        */
-
-       if (pf == last_pointer_frame()) {
-               return;
-       }
-
        if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
                nframes64_t grab = grab_frame ();
                _editor->snap_to (grab);
@@ -3337,7 +3577,11 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
        _editor->stop_canvas_autoscroll ();
 }
 
-
+void
+RangeMarkerBarDrag::aborted ()
+{
+       /* XXX: TODO */
+}
 
 void
 RangeMarkerBarDrag::update_item (Location* location)
@@ -3364,10 +3608,6 @@ MouseZoomDrag::motion (GdkEvent* event, bool first_move)
 
        nframes64_t const pf = adjusted_current_frame (event);
 
-       if (pf == last_pointer_frame()) {
-               return;
-       }
-
        nframes64_t grab = grab_frame ();
        _editor->snap_to_with_modifier (grab, event);
 
@@ -3415,6 +3655,12 @@ MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
        _editor->zoom_rect->hide();
 }
 
+void
+MouseZoomDrag::aborted ()
+{
+       _editor->zoom_rect->hide ();
+}
+
 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
 {
@@ -3433,8 +3679,8 @@ NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
        double event_x;
        double event_y;
 
-       event_x = current_pointer_x();
-       event_y = current_pointer_y();
+       event_x = _drags->current_pointer_x();
+       event_y = _drags->current_pointer_y();
 
        _item->property_parent().get_value()->w2i(event_x, event_y);
 
@@ -3471,8 +3717,8 @@ NoteDrag::motion (GdkEvent*, bool)
        double event_x;
        double event_y;
 
-       event_x = current_pointer_x();
-       event_y = current_pointer_y();
+       event_x = _drags->current_pointer_x();
+       event_y = _drags->current_pointer_y();
 
        _item->property_parent().get_value()->w2i(event_x, event_y);
 
@@ -3542,6 +3788,12 @@ NoteDrag::finished (GdkEvent* ev, bool moved)
        }
 }
 
+void
+NoteDrag::aborted ()
+{
+       /* XXX: TODO */
+}
+
 AutomationRangeDrag::AutomationRangeDrag (Editor* e, ArdourCanvas::Item* i, list<AudioRange> const & r)
        : Drag (e, i)
        , _ranges (r)
@@ -3618,7 +3870,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                return;
        }
 
-       _line->start_drag_multiple (points, 1 - (current_pointer_y() / _line->height ()), state);
+       _line->start_drag_multiple (points, 1 - (_drags->current_pointer_y() / _line->height ()), state);
 }
 
 void
@@ -3628,10 +3880,10 @@ AutomationRangeDrag::motion (GdkEvent* event, bool first_move)
                return;
        }
        
-       float const f = 1 - (current_pointer_y() / _line->height());
+       float const f = 1 - (_drags->current_pointer_y() / _line->height());
 
-       /* we are ignoring x position for this drag, so we can just pass in 0 */
-       _line->drag_motion (0, f, false);
+       /* we are ignoring x position for this drag, so we can just pass in anything */
+       _line->drag_motion (0, f, true, false);
 }
 
 void
@@ -3645,3 +3897,10 @@ AutomationRangeDrag::finished (GdkEvent* event, bool)
        _line->end_drag ();
        _line->clear_always_in_view ();
 }
+
+void
+AutomationRangeDrag::aborted ()
+{
+       _line->clear_always_in_view ();
+       _line->reset ();
+}