missing initialization
[ardour.git] / gtk2_ardour / editor_drag.cc
index 70f705d0b635db1d850d054c84c724496bfd9567..9c65892ebde46cdb2c140c194149756a712d3910 100644 (file)
@@ -22,6 +22,7 @@
 #endif
 
 #include <stdint.h>
+#include <algorithm>
 
 #include "pbd/memento_command.h"
 #include "pbd/basename.h"
@@ -29,6 +30,7 @@
 
 #include "gtkmm2ext/utils.h"
 
+#include "ardour/audioengine.h"
 #include "ardour/audioregion.h"
 #include "ardour/dB.h"
 #include "ardour/midi_region.h"
@@ -205,19 +207,15 @@ Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t tim
        _item = new_item;
 
        if (cursor == 0) {
-               cursor = _editor->which_grabber_cursor ();
+               _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, time);
+       } else {
+               _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
        }
-
-       _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
 }
 
 void
 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
 {
-       if (cursor == 0) {
-               cursor = _editor->which_grabber_cursor ();
-       }
-
        // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
 
        if (Keyboard::is_button2_event (&event->button)) {
@@ -240,9 +238,14 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
        _last_pointer_x = _grab_x;
        _last_pointer_y = _grab_y;
 
-       _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
-                    *cursor,
-                    event->button.time);
+       if (cursor == 0) {
+               _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
+                            event->button.time);
+       } else {
+               _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
+                            *cursor,
+                            event->button.time);
+       }
 
        if (_editor->session() && _editor->session()->transport_rolling()) {
                _was_rolling = true;
@@ -330,7 +333,9 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
 
                if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
                        if (!from_autoscroll) {
-                               _editor->maybe_autoscroll (true, allow_vertical_autoscroll ());
+                               bool const moving_left = _drags->current_pointer_x() < _last_pointer_x;
+                               bool const moving_up = _drags->current_pointer_y() < _last_pointer_y;
+                               _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), moving_left, moving_up);
                        }
 
                        motion (event, _move_threshold_passed != old_move_threshold_passed);
@@ -395,13 +400,29 @@ Drag::show_verbose_cursor_text (string const & text)
                );
 }
 
+boost::shared_ptr<Region>
+Drag::add_midi_region (MidiTimeAxisView* view)
+{
+       if (_editor->session()) {
+               const TempoMap& map (_editor->session()->tempo_map());
+               framecnt_t pos = grab_frame();
+               const Meter& m = map.meter_at (pos);
+               /* not that the frame rate used here can be affected by pull up/down which
+                  might be wrong.
+               */
+               framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
+               return view->add_region (grab_frame(), len, true);
+       }
+
+       return boost::shared_ptr<Region>();
+}
 
 struct EditorOrderTimeAxisViewSorter {
     bool operator() (TimeAxisView* a, TimeAxisView* b) {
            RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
            RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
            assert (ra && rb);
-           return ra->route()->order_key (N_ ("editor")) < rb->route()->order_key (N_ ("editor"));
+           return ra->route()->order_key (EditorSort) < rb->route()->order_key (EditorSort);
     }
 };
 
@@ -411,20 +432,19 @@ RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<Re
 {
        _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
 
-       /* Make a list of non-hidden tracks to refer to during the drag */
+       /* Make a list of tracks to refer to during the drag; we include hidden tracks,
+          as some of the regions we are dragging may be on such tracks.
+       */
 
        TrackViewList track_views = _editor->track_views;
        track_views.sort (EditorOrderTimeAxisViewSorter ());
 
        for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
-               if (!(*i)->hidden()) {
-
-                       _time_axis_views.push_back (*i);
-
-                       TimeAxisView::Children children_list = (*i)->get_child_list ();
-                       for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
-                               _time_axis_views.push_back (j->get());
-                       }
+               _time_axis_views.push_back (*i);
+               
+               TimeAxisView::Children children_list = (*i)->get_child_list ();
+               for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
+                       _time_axis_views.push_back (j->get());
                }
        }
 
@@ -435,7 +455,7 @@ RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<Re
                _views.push_back (DraggingView (*i, this));
        }
 
-       RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
+       RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
 }
 
 void
@@ -451,7 +471,9 @@ RegionDrag::region_going_away (RegionView* v)
        }
 }
 
-/** Given a non-hidden TimeAxisView, return the index of it into the _time_axis_views vector */
+/** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
+ *  or -1 if it is not found.
+ */
 int
 RegionDrag::find_time_axis_view (TimeAxisView* t) const
 {
@@ -484,7 +506,7 @@ RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        show_verbose_cursor_time (_last_frame_position);
 
-       pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        _last_pointer_time_axis_view = find_time_axis_view (tv.first);
        _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
 }
@@ -553,7 +575,7 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r
 }
 
 bool
-RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) const
+RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const
 {
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                int const n = i->time_axis_view + delta_track;
@@ -568,8 +590,12 @@ RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) cons
                        return false;
                }
 
-               int const l = i->layer + delta_layer;
-               if (delta_track == 0 && (l < 0 || l >= int (to->view()->layers()))) {
+               double const l = i->layer + delta_layer;
+
+               /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
+                  mode to allow the user to place a region below another on layer 0.
+               */
+               if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
                        /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
                           If it has, the layers will be munged later anyway, so it's ok.
                        */
@@ -587,7 +613,11 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        assert (!_views.empty ());
 
        /* Find the TimeAxisView that the pointer is now over */
-       pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+
+       if (first_move && tv.first->view()->layer_display() == Stacked) {
+               tv.first->view()->set_layer_display (Expanded);
+       }
 
        /* Bail early if we're not over a track */
        RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv.first);
@@ -600,7 +630,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
        /* Here's the current pointer position in terms of time axis view and layer */
        int const current_pointer_time_axis_view = find_time_axis_view (tv.first);
-       layer_t const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
+       double const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
 
        /* Work out the change in x */
        framepos_t pending_region_position;
@@ -608,7 +638,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
        /* Work out the change in y */
        int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
-       int delta_layer = current_pointer_layer - _last_pointer_layer;
+       double delta_layer = current_pointer_layer - _last_pointer_layer;
 
        if (!y_movement_allowed (delta_time_axis_view, delta_layer)) {
                /* this y movement is not allowed, so do no y movement this time */
@@ -629,91 +659,64 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
                RegionView* rv = i->view;
 
-               if (rv->region()->locked()) {
+               if (rv->region()->locked() || rv->region()->video_locked()) {
                        continue;
                }
 
                if (first_move) {
 
-                       /* 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;
+                       rv->drag_start (); 
 
-                       /* 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 ();
-
-                       // hide any dependent views
-
-                       rv->get_time_axis_view().hide_dependent_views (*rv);
-
-                       /*
-                         reparent to a non scrolling group so that we can keep the
-                         region selection above all time axis views.
-                         reparenting means we have to move the rv as the two
-                         parent groups have different coordinates.
+                       /* Absolutely no idea why this is necessary, but it is; without
+                          it, the region view disappears after the reparent.
+                       */
+                       _editor->update_canvas_now ();
+                       
+                       /* Reparent to a non scrolling group so that we can keep the
+                          region selection above all time axis views.
+                          Reparenting means that we will have to move the region view
+                          later, as the two parent groups have different coordinates.
                        */
 
-                       rv->get_canvas_group()->property_y() = iy1 - 1;
                        rv->get_canvas_group()->reparent (*(_editor->_region_motion_group));
-
+                       
                        rv->fake_set_opaque (true);
                }
 
-               /* Work out the change in y position of this region view */
-
-               double y_delta = 0;
-
                /* If we have moved tracks, we'll fudge the layer delta so that the
                   region gets moved back onto layer 0 on its new track; this avoids
                   confusion when dragging regions from non-zero layers onto different
                   tracks.
                */
-               int this_delta_layer = delta_layer;
+               double this_delta_layer = delta_layer;
                if (delta_time_axis_view != 0) {
                        this_delta_layer = - i->layer;
                }
 
-               /* Move this region to layer 0 on its old track */
-               StreamView* lv = _time_axis_views[i->time_axis_view]->view ();
-               if (lv->layer_display() == Stacked) {
-                       y_delta -= (lv->layers() - i->layer - 1) * lv->child_height ();
-               }
+               /* The TimeAxisView that this region is now on */
+               TimeAxisView* tv = _time_axis_views[i->time_axis_view + delta_time_axis_view];
 
-               /* Now move it to its right layer on the current track */
-               StreamView* cv = _time_axis_views[i->time_axis_view + delta_time_axis_view]->view ();
-               if (cv->layer_display() == Stacked) {
-                       y_delta += (cv->layers() - (i->layer + this_delta_layer) - 1) * cv->child_height ();
+               /* Ensure it is moved from stacked -> expanded if appropriate */
+               if (tv->view()->layer_display() == Stacked) {
+                       tv->view()->set_layer_display (Expanded);
                }
-
-               /* Move tracks */
-               if (delta_time_axis_view > 0) {
-                       for (int j = 0; j < delta_time_axis_view; ++j) {
-                               y_delta += _time_axis_views[i->time_axis_view + j]->current_height ();
-                       }
-               } else {
-                       /* start by subtracting the height of the track above where we are now */
-                       for (int j = 1; j <= -delta_time_axis_view; ++j) {
-                               y_delta -= _time_axis_views[i->time_axis_view - j]->current_height ();
-                       }
+               
+               /* We're only allowed to go -ve in layer on Expanded views */
+               if (tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
+                       this_delta_layer = - i->layer;
                }
-
+               
                /* Set height */
-               rv->set_height (_time_axis_views[i->time_axis_view + delta_time_axis_view]->view()->child_height ());
+               rv->set_height (tv->view()->child_height ());
+               
+               /* Update show/hidden status as the region view may have come from a hidden track,
+                  or have moved to one.
+               */
+               if (tv->hidden ()) {
+                       rv->get_canvas_group()->hide ();
+               } else {
+                       rv->get_canvas_group()->show ();
+               }
 
                /* Update the DraggingView */
                i->time_axis_view += delta_time_axis_view;
@@ -722,7 +725,28 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                if (_brushing) {
                        _editor->mouse_brush_insert_region (rv, pending_region_position);
                } else {
-                       rv->move (x_delta, y_delta);
+                       double x = 0;
+                       double y = 0;
+
+                       /* Get the y coordinate of the top of the track that this region is now on */
+                       tv->canvas_display()->i2w (x, y);
+                       y += _editor->get_trackview_group_vertical_offset();
+                       
+                       /* And adjust for the layer that it should be on */
+                       StreamView* cv = tv->view ();
+                       switch (cv->layer_display ()) {
+                       case Overlaid:
+                               break;
+                       case Stacked:
+                               y += (cv->layers() - i->layer - 1) * cv->child_height ();
+                               break;
+                       case Expanded:
+                               y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
+                               break;
+                       }
+
+                       /* Now move the region view */
+                       rv->move (x_delta, y - rv->get_canvas_group()->property_y());
                }
 
        } /* foreach region */
@@ -811,8 +835,24 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move)
 }
 
 void
-RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
+RegionMotionDrag::finished (GdkEvent *, bool)
+{
+       for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
+               if (!(*i)->view()) {
+                       continue;
+               }
+
+               if ((*i)->view()->layer_display() == Expanded) {
+                       (*i)->view()->set_layer_display (Stacked);
+               }
+       }
+}
+
+void
+RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
 {
+       RegionMotionDrag::finished (ev, movement_occurred);
+       
        if (!movement_occurred) {
                /* just a click */
                return;
@@ -828,10 +868,18 @@ RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
 
        assert (!_views.empty ());
 
+       /* We might have hidden region views so that they weren't visible during the drag
+          (when they have been reparented).  Now everything can be shown again, as region
+          views are back in their track parent groups.
+       */
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->get_canvas_group()->show ();
+       }
+       
        bool const changed_position = (_last_frame_position != _primary->region()->position());
        bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
        framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
-
+       
        _editor->update_canvas_now ();
 
        if (_copy) {
@@ -851,6 +899,8 @@ RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
                        );
 
        }
+
+       _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
 }
 
 void
@@ -880,7 +930,7 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed
        /* insert the regions into their new playlists */
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
 
-               if (i->view->region()->locked()) {
+               if (i->view->region()->locked() || i->view->region()->video_locked()) {
                        continue;
                }
 
@@ -938,6 +988,7 @@ RegionMoveDrag::finished_no_copy (
        RegionSelection new_views;
        PlaylistSet modified_playlists;
        PlaylistSet frozen_playlists;
+       set<RouteTimeAxisView*> views_to_update;
 
        if (_brushing) {
                /* all changes were made during motion event handlers */
@@ -956,13 +1007,15 @@ RegionMoveDrag::finished_no_copy (
                RegionView* rv = i->view;
 
                RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
-               layer_t const dest_layer = i->layer;
+               double const dest_layer = i->layer;
 
-               if (rv->region()->locked()) {
+               if (rv->region()->locked() || rv->region()->video_locked()) {
                        ++i;
                        continue;
                }
 
+               views_to_update.insert (dest_rtv);
+
                framepos_t where;
 
                if (changed_position && !_x_constrained) {
@@ -1010,15 +1063,14 @@ RegionMoveDrag::finished_no_copy (
 
                        rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
                        rv->get_canvas_group()->property_y() = i->initial_y;
-                       rv->get_time_axis_view().reveal_dependent_views (*rv);
+                       rv->drag_end ();
 
                        /* just change the model */
 
                        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
-                       if (dest_rtv->view()->layer_display() == Stacked) {
-                               rv->region()->set_layer (dest_layer);
-                               rv->region()->set_pending_explicit_relayer (true);
+                       if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
+                               playlist->set_layer (rv->region(), dest_layer);
                        }
 
                        /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
@@ -1090,6 +1142,17 @@ RegionMoveDrag::finished_no_copy (
        add_stateful_diff_commands_for_playlists (modified_playlists);
 
        _editor->commit_reversible_command ();
+
+       /* We have futzed with the layering of canvas items on our streamviews.
+          If any region changed layer, this will have resulted in the stream
+          views being asked to set up their region views, and all will be well.
+          If not, we might now have badly-ordered region views.  Ask the StreamViews
+          involved to sort themselves out, just in case.
+       */
+
+       for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
+               (*i)->view()->playlist_layered ((*i)->track ());
+       }
 }
 
 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
@@ -1151,9 +1214,8 @@ RegionMoveDrag::insert_region_into_playlist (
 
        dest_playlist->add_region (region, where);
 
-       if (dest_rtv->view()->layer_display() == Stacked) {
-               region->set_layer (dest_layer);
-               region->set_pending_explicit_relayer (true);
+       if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
+               dest_playlist->set_layer (region, dest_layer);
        }
 
        c.disconnect ();
@@ -1202,6 +1264,12 @@ RegionMoveDrag::aborted (bool movement_occurred)
 void
 RegionMotionDrag::aborted (bool)
 {
+       for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
+               if ((*i)->view()->layer_display() == Expanded) {
+                       (*i)->view()->set_layer_display (Stacked);
+               }
+       }
+       
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                RegionView* rv = i->view;
                TimeAxisView* tv = &(rv->get_time_axis_view ());
@@ -1209,7 +1277,7 @@ RegionMotionDrag::aborted (bool)
                assert (rtv);
                rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
                rv->get_canvas_group()->property_y() = 0;
-               rv->get_time_axis_view().reveal_dependent_views (*rv);
+               rv->drag_end ();
                rv->fake_set_opaque (false);
                rv->move (-_total_x_delta, 0);
                rv->set_height (rtv->view()->child_height ());
@@ -1309,7 +1377,7 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
 {
        /* Which trackview is this ? */
 
-       pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
 
        /* The region motion is only processed if the pointer is over
@@ -1397,7 +1465,7 @@ void
 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
 {
        if (first_move) {
-               add_region();
+               _region = add_midi_region (_view);
                _view->playlist()->freeze ();
        } else {
                if (_region) {
@@ -1413,7 +1481,7 @@ RegionCreateDrag::motion (GdkEvent* event, bool first_move)
                           place snapped notes at the start of the region.
                        */
 
-                       framecnt_t const len = abs (f - grab_frame () - 1);
+                       framecnt_t const len = (framecnt_t) fabs (f - grab_frame () - 1);
                        _region->set_length (len < 1 ? 1 : len);
                }
        }
@@ -1423,29 +1491,10 @@ void
 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
 {
        if (!movement_occurred) {
-               add_region ();
+               add_midi_region (_view);
        } else {
                _view->playlist()->thaw ();
        }
-
-       if (_region) {
-               _editor->commit_reversible_command ();
-       }
-}
-
-void
-RegionCreateDrag::add_region ()
-{
-       if (_editor->session()) {
-               const TempoMap& map (_editor->session()->tempo_map());
-               framecnt_t pos = grab_frame();
-               const Meter& m = map.meter_at (pos);
-               /* not that the frame rate used here can be affected by pull up/down which
-                  might be wrong.
-               */
-               framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
-               _region = _view->add_region (grab_frame(), len, false);
-       }
 }
 
 void
@@ -1543,37 +1592,178 @@ NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
 void
 NoteResizeDrag::aborted (bool)
 {
-       /* XXX: TODO */
+       MidiRegionSelection& ms (_editor->get_selection().midi_regions);
+       for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
+               (*r)->abort_resizing ();
+       }
 }
 
-RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
+AVDraggingView::AVDraggingView (RegionView* v)
+       : view (v)
+{
+       initial_position = v->region()->position ();
+}
+
+VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
 {
-       DEBUG_TRACE (DEBUG::Drags, "New RegionGainDrag\n");
+       DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
+
+       RegionSelection rs;
+       TrackViewList empty;
+       empty.clear();
+       _editor->get_regions_after(rs, (framepos_t) 0, empty);
+       std::list<RegionView*> views = rs.by_layer();
+
+       for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
+               RegionView* rv = (*i);
+               if (!rv->region()->video_locked()) {
+                       continue;
+               }
+               _views.push_back (AVDraggingView (rv));
+       }
+}
+
+void
+VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
+{
+       Drag::start_grab (event);
+       if (_editor->session() == 0) {
+               return;
+       }
+
+       _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
+       _max_backwards_drag = (
+                         ARDOUR_UI::instance()->video_timeline->get_duration()
+                       + ARDOUR_UI::instance()->video_timeline->get_offset()
+                       - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
+                       );
+
+       for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
+                       _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
+               }
+       }
+       DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
+
+       char buf[128];
+       Timecode::Time timecode;
+       _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
+       snprintf (buf, sizeof (buf), "Video Start:\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, (_startdrag_video_offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
+       _editor->verbose_cursor()->set(buf, event->button.x + 10, event->button.y + 10);
+       _editor->verbose_cursor()->show ();
 }
 
 void
-RegionGainDrag::motion (GdkEvent* /*event*/, bool)
+VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
 {
+       if (_editor->session() == 0) {
+               return;
+       }
+       if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+               return;
+       }
+
+       framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
+       dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
+
+       if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
+               dt = - _max_backwards_drag;
+       }
+
+       ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
+       ARDOUR_UI::instance()->flush_videotimeline_cache(true);
 
+       for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               RegionView* rv = i->view;
+               DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
+               if (first_move) {
+                       rv->drag_start ();
+                       _editor->update_canvas_now ();
+                       rv->fake_set_opaque (true);
+                       rv->region()->clear_changes ();
+                       rv->region()->suspend_property_changes();
+               }
+               rv->region()->set_position(i->initial_position + dt);
+               rv->region_changed(ARDOUR::Properties::position);
+       }
+
+       const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
+       Timecode::Time timecode;
+       Timecode::Time timediff;
+       char buf[128];
+       _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
+       _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
+       snprintf (buf, sizeof (buf),
+                       "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
+                       "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
+                       , _("Video Start:"),
+                               (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
+                       , _("Diff:"),
+                               (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
+                               );
+       _editor->verbose_cursor()->set(buf, event->button.x + 10, event->button.y + 10);
+       _editor->verbose_cursor()->show ();
 }
 
 void
-RegionGainDrag::finished (GdkEvent *, bool)
+VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
 {
+       if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+               return;
+       }
+
+       if (!movement_occurred || ! _editor->session()) {
+               return;
+       }
+
+       ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+
+       _editor->begin_reversible_command (_("Move Video"));
+
+       XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
+       ARDOUR_UI::instance()->video_timeline->save_undo();
+       XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
+       _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
+
+       for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->drag_end();
+               i->view->fake_set_opaque (false);
+               i->view->region()->resume_property_changes ();
+
+               _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
+       }
+
+       _editor->session()->maybe_update_session_range(
+                       std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
+                       std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
+                       );
+
 
+       _editor->commit_reversible_command ();
+       _editor->update_canvas_now ();
 }
 
 void
-RegionGainDrag::aborted (bool)
+VideoTimeLineDrag::aborted (bool)
 {
-       /* XXX: TODO */
+       if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
+               return;
+       }
+       ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
+       ARDOUR_UI::instance()->flush_videotimeline_cache(true);
+
+       for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->region()->resume_property_changes ();
+               i->view->region()->set_position(i->initial_position);
+       }
 }
 
-TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
+TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
        : RegionDrag (e, i, p, v)
 {
        DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
+       _preserve_fade_anchor = preserve_fade_anchor;
 }
 
 void
@@ -1674,6 +1864,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
 
                        if (arv) {
                                arv->temporarily_hide_envelope ();
+                               arv->drag_start ();
                        }
 
                        boost::shared_ptr<Playlist> pl = rv->region()->playlist();
@@ -1694,13 +1885,41 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
        switch (_operation) {
        case StartTrim:
                for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                       i->view->trim_front (i->initial_position + dt, non_overlap_trim);
+                       bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim);
+                       if (changed && _preserve_fade_anchor) {
+                               AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
+                               if (arv) {
+                                       double distance;
+                                       double new_length;
+                                       framecnt_t len;
+                                       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+                                       distance = _drags->current_pointer_x() - grab_x();
+                                       len = ar->fade_in()->back()->when;
+                                       new_length = len - _editor->unit_to_frame (distance);
+                                       new_length = ar->verify_xfade_bounds (new_length, true  /*START*/ );
+                                       arv->reset_fade_in_shape_width (ar, new_length);  //the grey shape
+                               }
+                       }
                }
                break;
 
        case EndTrim:
                for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                       i->view->trim_end (i->initial_end + dt, non_overlap_trim);
+                       bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim);
+                       if (changed && _preserve_fade_anchor) {
+                               AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
+                               if (arv) {
+                                       double distance;
+                                       double new_length;
+                                       framecnt_t len;
+                                       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+                                       distance = grab_x() - _drags->current_pointer_x();
+                                       len = ar->fade_out()->back()->when;
+                                       new_length = len - _editor->unit_to_frame (distance);
+                                       new_length = ar->verify_xfade_bounds (new_length, false  /*END*/ );
+                                       arv->reset_fade_out_shape_width (ar, new_length);  //the grey shape
+                               }
+                       }
                }
                break;
 
@@ -1752,16 +1971,61 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
        if (movement_occurred) {
                motion (event, false);
 
-               /* This must happen before the region's StatefulDiffCommand is created, as it may
-                  `correct' (ahem) the region's _start from being negative to being zero.  It
-                  needs to be zero in the undo record.
-               */
                if (_operation == StartTrim) {
                        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               i->view->trim_front_ending ();
+                               {
+                                       /* This must happen before the region's StatefulDiffCommand is created, as it may
+                                          `correct' (ahem) the region's _start from being negative to being zero.  It
+                                          needs to be zero in the undo record.
+                                       */
+                                       i->view->trim_front_ending ();
+                               }
+                               if (_preserve_fade_anchor) {
+                                       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
+                                       if (arv) {
+                                               double distance;
+                                               double new_length;
+                                               framecnt_t len;
+                                               boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+                                               distance = _drags->current_pointer_x() - grab_x();
+                                               len = ar->fade_in()->back()->when;
+                                               new_length = len - _editor->unit_to_frame (distance);
+                                               new_length = ar->verify_xfade_bounds (new_length, true  /*START*/ );
+                                               ar->set_fade_in_length(new_length);
+                                       }
+                               }
+                       }
+               } else if (_operation == EndTrim) {
+                       for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                               if (_preserve_fade_anchor) {
+                                       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
+                                       if (arv) {
+                                               double distance;
+                                               double new_length;
+                                               framecnt_t len;
+                                               boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+                                               distance = _drags->current_pointer_x() - grab_x();
+                                               len = ar->fade_out()->back()->when;
+                                               new_length = len - _editor->unit_to_frame (distance);
+                                               new_length = ar->verify_xfade_bounds (new_length, false  /*END*/ );
+                                               ar->set_fade_out_length(new_length);
+                                       }
+                               }
                        }
                }
 
+               if (!_views.empty()) {
+                       if (_operation == StartTrim) {
+                               _editor->maybe_locate_with_edit_preroll(
+                                       _views.begin()->view->region()->position());
+                       }
+                       if (_operation == EndTrim) {
+                               _editor->maybe_locate_with_edit_preroll(
+                                       _views.begin()->view->region()->position() +
+                                       _views.begin()->view->region()->length());
+                       }
+               }
+       
                if (!_editor->selection->selected (_primary)) {
                        _primary->thaw_after_trim ();
                } else {
@@ -1787,6 +2051,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                                }
                        }
                }
+
                for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
                        (*p)->thaw ();
                }
@@ -1856,7 +2121,6 @@ MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
          _copy (c)
 {
        DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
-
        _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
        assert (_marker);
 }
@@ -1864,35 +2128,7 @@ MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
-       if (_copy) {
-               // create a dummy marker for visual representation of moving the copy.
-               // The actual copying is not done before we reach the finish callback.
-               char name[64];
-               snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
-
-               MeterMarker* new_marker = new MeterMarker (
-                       *_editor,
-                       *_editor->meter_group,
-                       ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
-                       name,
-                       *new MeterSection (_marker->meter())
-                       );
-
-               _item = &new_marker->the_item ();
-               _marker = new_marker;
-
-       } else {
-
-               MetricSection& section (_marker->meter());
-
-               if (!section.movable()) {
-                       return;
-               }
-
-       }
-
        Drag::start_grab (event, cursor);
-
        show_verbose_cursor_time (adjusted_current_frame(event));
 }
 
@@ -1903,12 +2139,46 @@ MeterMarkerDrag::setup_pointer_frame_offset ()
 }
 
 void
-MeterMarkerDrag::motion (GdkEvent* event, bool)
+MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
 {
-       framepos_t const pf = adjusted_current_frame (event);
+       if (first_move) {
 
-       _marker->set_position (pf);
+               // create a dummy marker for visual representation of moving the
+               // section, because whether its a copy or not, we're going to 
+               // leave or lose the original marker (leave if its a copy; lose if its
+               // not, because we'll remove it from the map).
+               
+               MeterSection section (_marker->meter());
+
+               if (!section.movable()) {
+                       return;
+               }
+               
+               char name[64];
+               snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
+               
+               _marker = new MeterMarker (
+                       *_editor,
+                       *_editor->meter_group,
+                       ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
+                       name,
+                       *new MeterSection (_marker->meter())
+               );
+               
+               /* use the new marker for the grab */
+               swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
+
+               if (!_copy) {
+                       TempoMap& map (_editor->session()->tempo_map());
+                       /* get current state */
+                       before_state = &map.get_state();
+                       /* remove the section while we drag it */
+                       map.remove_meter (section, true);
+               }
+       }
 
+       framepos_t const pf = adjusted_current_frame (event);
+       _marker->set_position (pf);
        show_verbose_cursor_time (pf);
 }
 
@@ -1925,7 +2195,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        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();
@@ -1934,23 +2204,35 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
                _editor->commit_reversible_command ();
 
-               // delete the dummy marker we used for visual representation of copying.
-               // a new visual marker will show up automatically.
-               delete _marker;
        } else {
                _editor->begin_reversible_command (_("move meter mark"));
-               XMLNode &before = map.get_state();
-               map.move_meter (_marker->meter(), when);
+
+               /* we removed it before, so add it back now */
+               
+               map.add_meter (_marker->meter(), when);
                XMLNode &after = map.get_state();
-               _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
+               _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
                _editor->commit_reversible_command ();
        }
+
+       // delete the dummy marker we used for visual representation while moving.
+       // a new visual marker will show up automatically.
+       delete _marker;
 }
 
 void
-MeterMarkerDrag::aborted (bool)
+MeterMarkerDrag::aborted (bool moved)
 {
        _marker->set_position (_marker->meter().frame ());
+
+       if (moved) {
+               TempoMap& map (_editor->session()->tempo_map());
+               /* we removed it before, so add it back now */
+               map.add_meter (_marker->meter(), _marker->meter().frame());
+               // delete the dummy marker we used for visual representation while moving.
+               // a new visual marker will show up automatically.
+               delete _marker;
+       }
 }
 
 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
@@ -1966,14 +2248,35 @@ TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
-       if (_copy) {
+       Drag::start_grab (event, cursor);
+       show_verbose_cursor_time (adjusted_current_frame (event));
+}
+
+void
+TempoMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
+}
+
+void
+TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
+{
+       if (first_move) {
 
+               // create a dummy marker for visual representation of moving the
+               // section, because whether its a copy or not, we're going to 
+               // leave or lose the original marker (leave if its a copy; lose if its
+               // not, because we'll remove it from the map).
+               
                // create a dummy marker for visual representation of moving the copy.
                // The actual copying is not done before we reach the finish callback.
+
                char name[64];
                snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
 
-               TempoMarker* new_marker = new TempoMarker (
+               TempoSection section (_marker->tempo());
+
+               _marker = new TempoMarker (
                        *_editor,
                        *_editor->tempo_group,
                        ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
@@ -1981,25 +2284,18 @@ TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                        *new TempoSection (_marker->tempo())
                        );
 
-               _item = &new_marker->the_item ();
-               _marker = new_marker;
+               /* use the new marker for the grab */
+               swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
 
+               if (!_copy) {
+                       TempoMap& map (_editor->session()->tempo_map());
+                       /* get current state */
+                       before_state = &map.get_state();
+                       /* remove the section while we drag it */
+                       map.remove_tempo (section, true);
+               }
        }
 
-       Drag::start_grab (event, cursor);
-
-       show_verbose_cursor_time (adjusted_current_frame (event));
-}
-
-void
-TempoMarkerDrag::setup_pointer_frame_offset ()
-{
-       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
-}
-
-void
-TempoMarkerDrag::motion (GdkEvent* event, bool)
-{
        framepos_t const pf = adjusted_current_frame (event);
        _marker->set_position (pf);
        show_verbose_cursor_time (pf);
@@ -2014,10 +2310,11 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
+       TempoMap& map (_editor->session()->tempo_map());
+       framepos_t beat_time = map.round_to_beat (last_pointer_frame(), 0);
        Timecode::BBT_Time when;
 
-       TempoMap& map (_editor->session()->tempo_map());
-       map.bbt_time (last_pointer_frame(), when);
+       map.bbt_time (beat_time, when);
 
        if (_copy == true) {
                _editor->begin_reversible_command (_("copy tempo mark"));
@@ -2027,23 +2324,32 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
                _editor->commit_reversible_command ();
 
-               // delete the dummy marker we used for visual representation of copying.
-               // a new visual marker will show up automatically.
-               delete _marker;
        } else {
                _editor->begin_reversible_command (_("move tempo mark"));
-               XMLNode &before = map.get_state();
-               map.move_tempo (_marker->tempo(), when);
+               /* we removed it before, so add it back now */
+               map.add_tempo (_marker->tempo(), when);
                XMLNode &after = map.get_state();
-               _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
+               _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
                _editor->commit_reversible_command ();
        }
+
+       // delete the dummy marker we used for visual representation while moving.
+       // a new visual marker will show up automatically.
+       delete _marker;
 }
 
 void
-TempoMarkerDrag::aborted (bool)
+TempoMarkerDrag::aborted (bool moved)
 {
        _marker->set_position (_marker->tempo().frame());
+       if (moved) {
+               TempoMap& map (_editor->session()->tempo_map());
+               /* we removed it before, so add it back now */
+               map.add_tempo (_marker->tempo(), _marker->tempo().start());
+               // delete the dummy marker we used for visual representation while moving.
+               // a new visual marker will show up automatically.
+               delete _marker;
+       }
 }
 
 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
@@ -2065,8 +2371,14 @@ CursorDrag::fake_locate (framepos_t t)
        Session* s = _editor->session ();
        if (s->timecode_transmission_suspended ()) {
                framepos_t const f = _editor->playhead_cursor->current_frame;
+               /* This is asynchronous so it will be sent "now"
+                */
                s->send_mmc_locate (f);
-               s->send_full_time_code (f);
+               /* These are synchronous and will be sent during the next
+                  process cycle
+               */
+               s->queue_full_time_code ();
+               s->queue_song_position_pointer ();
        }
 
        show_verbose_cursor_time (t);
@@ -2096,9 +2408,20 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
                        s->cancel_audition ();
                }
 
-               s->request_suspend_timecode_transmission ();
-               while (!s->timecode_transmission_suspended ()) {
-                       /* twiddle our thumbs */
+
+               if (AudioEngine::instance()->connected()) {
+                       
+                       /* do this only if we're the engine is connected
+                        * because otherwise this request will never be
+                        * serviced and we'll busy wait forever. likewise,
+                        * notice if we are disconnected while waiting for the
+                        * request to be serviced.
+                        */
+
+                       s->request_suspend_timecode_transmission ();
+                       while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
+                               /* twiddle our thumbs */
+                       }
                }
        }
 
@@ -2108,39 +2431,6 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
 void
 CursorDrag::motion (GdkEvent* event, bool)
 {
-       if (_drags->current_pointer_y() != last_pointer_y()) {
-
-               /* zoom when we move the pointer up and down */
-
-               /* y range to operate over (pixels) */
-               double const y_range = 512;
-               /* we will multiply the grab zoom by a factor between scale_range and scale_range^-1 */
-               double const scale_range = 4;
-               /* dead zone around the grab point in which to do no zooming (pixels) */
-               double const dead_zone = 100;
-
-               /* current dy */
-               double dy = _drags->current_pointer_y() - grab_y();
-
-               if (dy < -dead_zone || dy > dead_zone) {
-                       /* we are outside the dead zone; remove it from our calculation */
-                       if (dy < 0) {
-                               dy += dead_zone;
-                       } else {
-                               dy -= dead_zone;
-                       }
-
-                       /* get a number from -1 to 1 as dy ranges from -y_range to y_range */
-                       double udy = max (min (dy / y_range, 1.0), -1.0);
-
-                       /* and zoom, using playhead focus temporarily */
-                       Editing::ZoomFocus const zf = _editor->get_zoom_focus ();
-                       _editor->set_zoom_focus (Editing::ZoomFocusPlayhead);
-                       _editor->temporal_zoom (_grab_zoom * pow (scale_range, -udy));
-                       _editor->set_zoom_focus (zf);
-               }
-       }
-
        framepos_t const adjusted_frame = adjusted_current_frame (event);
        if (adjusted_frame != last_pointer_frame()) {
                fake_locate (adjusted_frame);
@@ -2195,8 +2485,6 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
 
        show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
-
-       arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
 }
 
 void
@@ -2232,8 +2520,7 @@ FadeInDrag::motion (GdkEvent* event, bool)
                        continue;
                }
 
-               tmp->reset_fade_in_shape_width (fade_length);
-               tmp->show_fade_line((framecnt_t) fade_length);
+               tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
        }
 
        show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
@@ -2275,7 +2562,6 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
 
                tmp->audio_region()->set_fade_in_length (fade_length);
                tmp->audio_region()->set_fade_in_active (true);
-               tmp->hide_fade_line();
 
                XMLNode &after = alist->get_state();
                _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
@@ -2294,8 +2580,7 @@ FadeInDrag::aborted (bool)
                        continue;
                }
 
-               tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
-               tmp->hide_fade_line();
+               tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
        }
 }
 
@@ -2314,8 +2599,6 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        boost::shared_ptr<AudioRegion> r = arv->audio_region ();
 
        show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
-
-       arv->show_fade_line(r->length() - r->fade_out()->back()->when);
 }
 
 void
@@ -2353,8 +2636,7 @@ FadeOutDrag::motion (GdkEvent* event, bool)
                        continue;
                }
 
-               tmp->reset_fade_out_shape_width (fade_length);
-               tmp->show_fade_line(region->length() - fade_length);
+               tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
        }
 
        show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
@@ -2398,7 +2680,6 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
 
                tmp->audio_region()->set_fade_out_length (fade_length);
                tmp->audio_region()->set_fade_out_active (true);
-               tmp->hide_fade_line();
 
                XMLNode &after = alist->get_state();
                _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
@@ -2417,8 +2698,7 @@ FadeOutDrag::aborted (bool)
                        continue;
                }
 
-               tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
-               tmp->hide_fade_line();
+               tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
        }
 }
 
@@ -2436,11 +2716,18 @@ MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
 
 MarkerDrag::~MarkerDrag ()
 {
-       for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
-               delete *i;
+       for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
+               delete i->location;
        }
 }
 
+MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, Marker* m)
+{
+       location = new Location (*l);
+       markers.push_back (m);
+       move_both = false;
+}
+
 void
 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
@@ -2466,7 +2753,7 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        switch (op) {
        case Selection::Toggle:
-               _editor->selection->toggle (_marker);
+               /* we toggle on the button release */
                break;
        case Selection::Set:
                if (!_editor->selection->selected (_marker)) {
@@ -2508,11 +2795,37 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                break;
        }
 
-       /* Set up copies for us to manipulate during the drag */
+       /* Set up copies for us to manipulate during the drag 
+        */
 
        for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
+
                Location* l = _editor->find_location_from_marker (*i, is_start);
-               _copied_locations.push_back (new Location (*l));
+
+               if (!l) {
+                       continue;
+               }
+
+               if (l->is_mark()) {
+                       _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
+               } else {
+                       /* range: check that the other end of the range isn't
+                          already there.
+                       */
+                       CopiedLocationInfo::iterator x;
+                       for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
+                               if (*(*x).location == *l) {
+                                       break;
+                               }
+                       }
+                       if (x == _copied_locations.end()) {
+                               _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
+                       } else {
+                               (*x).markers.push_back (*i);
+                               (*x).move_both = true;
+                       }
+               }
+                       
        }
 }
 
@@ -2530,33 +2843,31 @@ MarkerDrag::motion (GdkEvent* event, bool)
        framecnt_t f_delta = 0;
        bool is_start;
        bool move_both = false;
-       Marker* marker;
        Location *real_location;
        Location *copy_location = 0;
 
        framepos_t const newframe = adjusted_current_frame (event);
-
        framepos_t next = newframe;
 
        if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
                move_both = true;
        }
 
-       MarkerSelection::iterator i;
-       list<Location*>::iterator x;
+       CopiedLocationInfo::iterator x;
 
        /* find the marker we're dragging, and compute the delta */
 
-       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
-            x != _copied_locations.end() && i != _editor->selection->markers.end();
-            ++i, ++x) {
+       for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
 
-               copy_location = *x;
-               marker = *i;
+               copy_location = (*x).location;
 
-               if (marker == _marker) {
+               if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
 
-                       if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
+                       /* this marker is represented by this
+                        * CopiedLocationMarkerInfo 
+                        */
+
+                       if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
                                /* que pasa ?? */
                                return;
                        }
@@ -2566,7 +2877,7 @@ MarkerDrag::motion (GdkEvent* event, bool)
                        } else {
 
 
-                               switch (marker->type()) {
+                               switch (_marker->type()) {
                                case Marker::SessionStart:
                                case Marker::RangeStart:
                                case Marker::LoopStart:
@@ -2585,27 +2896,25 @@ MarkerDrag::motion (GdkEvent* event, bool)
                                        return;
                                }
                        }
+
                        break;
                }
        }
 
-       if (i == _editor->selection->markers.end()) {
+       if (x == _copied_locations.end()) {
                /* hmm, impossible - we didn't find the dragged marker */
                return;
        }
 
        /* now move them all */
 
-       for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
-            x != _copied_locations.end() && i != _editor->selection->markers.end();
-            ++i, ++x) {
+       for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
 
-               copy_location = *x;
-               marker = *i;
+               copy_location = x->location;
 
                /* call this to find out if its the start or end */
 
-               if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
+               if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
                        continue;
                }
 
@@ -2620,13 +2929,13 @@ MarkerDrag::motion (GdkEvent* event, bool)
                        copy_location->set_start (copy_location->start() + f_delta);
 
                } else {
-
+                       
                        framepos_t new_start = copy_location->start() + f_delta;
                        framepos_t new_end = copy_location->end() + f_delta;
-
+                       
                        if (is_start) { // start-of-range marker
-
-                               if (move_both) {
+                               
+                               if (move_both || (*x).move_both) {
                                        copy_location->set_start (new_start);
                                        copy_location->set_end (new_end);
                                } else  if (new_start < copy_location->end()) {
@@ -2639,7 +2948,7 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
                        } else { // end marker
 
-                               if (move_both) {
+                               if (move_both || (*x).move_both) {
                                        copy_location->set_end (new_end);
                                        copy_location->set_start (new_start);
                                } else if (new_end > copy_location->start()) {
@@ -2653,12 +2962,20 @@ MarkerDrag::motion (GdkEvent* event, bool)
                }
 
                update_item (copy_location);
-
+               
+               /* now lookup the actual GUI items used to display this
+                * location and move them to wherever the copy of the location
+                * is now. This means that the logic in ARDOUR::Location is
+                * still enforced, even though we are not (yet) modifying 
+                * the real Location itself.
+                */
+               
                Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
 
                if (lm) {
                        lm->set_position (copy_location->start(), copy_location->end());
                }
+
        }
 
        assert (!_copied_locations.empty());
@@ -2689,6 +3006,10 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                        break;
 
                case Selection::Toggle:
+                       /* we toggle on the button release, click only */
+                       _editor->selection->toggle (_marker);
+                       break;
+
                case Selection::Extend:
                case Selection::Add:
                        break;
@@ -2703,7 +3024,7 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        XMLNode &before = _editor->session()->locations()->get_state();
 
        MarkerSelection::iterator i;
-       list<Location*>::iterator x;
+       CopiedLocationInfo::iterator x;
        bool is_start;
 
        for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
@@ -2719,9 +3040,9 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                        }
 
                        if (location->is_mark()) {
-                               location->set_start ((*x)->start());
+                               location->set_start (((*x).location)->start());
                        } else {
-                               location->set ((*x)->start(), (*x)->end());
+                               location->set (((*x).location)->start(), ((*x).location)->end());
                        }
                }
        }
@@ -2777,6 +3098,12 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
                                        event->button.x + 10, event->button.y + 10);
 
        _editor->verbose_cursor()->show ();
+
+       _pushing = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
+
+       if (!_point->can_slide ()) {
+               _x_constrained = true;
+       }
 }
 
 void
@@ -2828,9 +3155,7 @@ ControlPointDrag::motion (GdkEvent* event, bool)
 
        float const fraction = 1.0 - (cy / _point->line().height());
 
-       bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
-
-       _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
+       _point->line().drag_motion (_editor->frame_to_unit_unrounded (cx_frames), fraction, false, _pushing, _final_index);
 
        _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
 }
@@ -2850,7 +3175,7 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
                motion (event, false);
        }
 
-       _point->line().end_drag ();
+       _point->line().end_drag (_pushing, _final_index);
        _editor->session()->commit_reversible_command ();
 }
 
@@ -2941,17 +3266,10 @@ LineDrag::motion (GdkEvent* event, bool)
        cy = min ((double) _line->height(), cy);
 
        double const fraction = 1.0 - (cy / _line->height());
-
-       bool push;
-
-       if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
-               push = false;
-       } else {
-               push = true;
-       }
+       uint32_t ignored;
 
        /* we are ignoring x position for this drag, so we can just pass in anything */
-       _line->drag_motion (0, fraction, true, push);
+       _line->drag_motion (0, fraction, true, false, ignored);
 
        _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
 }
@@ -2960,7 +3278,7 @@ void
 LineDrag::finished (GdkEvent* event, bool)
 {
        motion (event, false);
-       _line->end_drag ();
+       _line->end_drag (false, 0);
        _editor->session()->commit_reversible_command ();
 }
 
@@ -3055,6 +3373,7 @@ FeatureLineDrag::aborted (bool)
 
 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
+       , _vertical_only (false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
 }
@@ -3106,15 +3425,51 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
                double x2 = _editor->frame_to_pixel (end);
 
                _editor->rubberband_rect->property_x1() = x1;
+               if (_vertical_only) {
+                       /* fixed 10 pixel width */
+                       _editor->rubberband_rect->property_x2() = x1 + 10;
+               } else {
+                       _editor->rubberband_rect->property_x2() = x2;
+               } 
+
                _editor->rubberband_rect->property_y1() = y1;
-               _editor->rubberband_rect->property_x2() = x2;
                _editor->rubberband_rect->property_y2() = y2;
 
                _editor->rubberband_rect->show();
                _editor->rubberband_rect->raise_to_top();
 
                show_verbose_cursor_time (pf);
+
+               do_select_things (event, true);
+       }
+}
+
+void
+RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
+{
+       framepos_t x1;
+       framepos_t x2;
+       
+       if (grab_frame() < last_pointer_frame()) {
+               x1 = grab_frame ();
+               x2 = last_pointer_frame ();
+       } else {
+               x2 = grab_frame ();
+               x1 = last_pointer_frame ();
+       }
+
+       double y1;
+       double y2;
+       
+       if (_drags->current_pointer_y() < grab_y()) {
+               y1 = _drags->current_pointer_y();
+               y2 = grab_y();
+       } else {
+               y2 = _drags->current_pointer_y();
+               y1 = grab_y();
        }
+
+       select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
 }
 
 void
@@ -3123,36 +3478,34 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
        if (movement_occurred) {
 
                motion (event, false);
+               do_select_things (event, false);
 
-               double y1,y2;
-               if (_drags->current_pointer_y() < grab_y()) {
-                       y1 = _drags->current_pointer_y();
-                       y2 = grab_y();
-               } else {
-                       y2 = _drags->current_pointer_y();
-                       y1 = grab_y();
-               }
-
+       } else {
 
-               Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
+               /* just a click */
 
-               _editor->begin_reversible_command (_("rubberband selection"));
+               bool do_deselect = true;
+               MidiTimeAxisView* mtv;
 
-               if (grab_frame() < last_pointer_frame()) {
-                       _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
-               } else {
-                       _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
-               }
+               if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
+                       /* MIDI track */
+                       if (_editor->selection->empty()) {
+                               /* nothing selected */
+                               add_midi_region (mtv);
+                               do_deselect = false;
+                       }
+               } 
 
-               _editor->commit_reversible_command ();
+               /* do not deselect if Primary or Tertiary (toggle-select or
+                * extend-select are pressed.
+                */
 
-       } else {
-               if (!getenv("ARDOUR_SAE")) {
-                       _editor->selection->clear_tracks();
+               if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) && 
+                   !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) && 
+                   do_deselect) {
+                       deselect_things ();
                }
-               _editor->selection->clear_regions();
-               _editor->selection->clear_points ();
-               _editor->selection->clear_lines ();
+
        }
 
        _editor->rubberband_rect->hide();
@@ -3182,11 +3535,16 @@ void
 TimeFXDrag::motion (GdkEvent* event, bool)
 {
        RegionView* rv = _primary;
+       StreamView* cv = rv->get_time_axis_view().view ();
+
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
+       int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
+       int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
 
        framepos_t const pf = adjusted_current_frame (event);
 
        if (pf > rv->region()->position()) {
-               rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
+               rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
        }
 
        show_verbose_cursor_time (pf);
@@ -3217,13 +3575,16 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 #endif
 
-       // XXX how do timeFX on multiple regions ?
-
-       RegionSelection rs;
-       rs.add (_primary);
+       if (!_editor->get_selection().regions.empty()) {
+               /* primary will already be included in the selection, and edit
+                  group shared editing will propagate selection across
+                  equivalent regions, so just use the current region
+                  selection.
+               */
 
-       if (_editor->time_stretch (rs, percentage) == -1) {
-               error << _("An error occurred while executing time stretch operation") << endmsg;
+               if (_editor->time_stretch (_editor->get_selection().regions, percentage) == -1) {
+                       error << _("An error occurred while executing time stretch operation") << endmsg;
+               }
        }
 }
 
@@ -3269,11 +3630,18 @@ ScrubDrag::aborted (bool)
 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
        : Drag (e, i)
        , _operation (o)
-       , _copy (false)
+       , _add (false)
+       , _extend (false)
        , _original_pointer_time_axis (-1)
        , _last_pointer_time_axis (-1)
+       , _time_selection_at_start (!_editor->get_selection().time.empty())
 {
        DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
+       
+       if (_time_selection_at_start) {
+               start_at_start = _editor->get_selection().time.start();
+               end_at_start = _editor->get_selection().time.end_frame();
+       }
 }
 
 void
@@ -3287,10 +3655,10 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
 
        switch (_operation) {
        case CreateSelection:
-               if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
-                       _copy = true;
+               if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
+                       _add = true;
                } else {
-                       _copy = false;
+                       _add = false;
                }
                cursor = _editor->cursors()->selector;
                Drag::start_grab (event, cursor);
@@ -3313,6 +3681,10 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
        case SelectionMove:
                Drag::start_grab (event, cursor);
                break;
+
+       case SelectionExtend:
+               Drag::start_grab (event, cursor);
+               break;
        }
 
        if (_operation == SelectionMove) {
@@ -3340,6 +3712,9 @@ SelectionDrag::setup_pointer_frame_offset ()
        case SelectionEndTrim:
                _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
                break;
+
+       case SelectionExtend:
+               break;
        }
 }
 
@@ -3348,7 +3723,8 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 {
        framepos_t start = 0;
        framepos_t end = 0;
-       framecnt_t length;
+       framecnt_t length = 0;
+       framecnt_t distance = 0;
 
        pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        if (pending_time_axis.first == 0) {
@@ -3369,10 +3745,15 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                framepos_t grab = grab_frame ();
 
                if (first_move) {
-                       _editor->snap_to (grab);
+                       grab = adjusted_current_frame (event, false);
+                       if (grab < pending_position) {
+                               _editor->snap_to (grab, -1);
+                       }  else {
+                               _editor->snap_to (grab, 1);
+                       }
                }
 
-               if (pending_position < grab_frame()) {
+               if (pending_position < grab) {
                        start = pending_position;
                        end = grab;
                } else {
@@ -3386,12 +3767,12 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
                if (first_move) {
 
-                       if (_copy) {
+                       if (_add) {
                                /* adding to the selection */
                                _editor->set_selected_track_as_side_effect (Selection::Add);
                                //_editor->selection->add (_editor->clicked_axisview);
                                _editor->clicked_selection = _editor->selection->add (start, end);
-                               _copy = false;
+                               _add = false;
                        } else {
                                /* new selection */
 
@@ -3459,20 +3840,23 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                }
 
                break;
-
+               
        case SelectionMove:
 
                start = _editor->selection->time[_editor->clicked_selection].start;
                end = _editor->selection->time[_editor->clicked_selection].end;
 
                length = end - start;
-
+               distance = pending_position - start;
                start = pending_position;
                _editor->snap_to (start);
 
                end = start + length;
 
                break;
+
+       case SelectionExtend:
+               break;
        }
 
        if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
@@ -3480,7 +3864,15 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        }
 
        if (start != end) {
-               _editor->selection->replace (_editor->clicked_selection, start, end);
+               switch (_operation) {
+               case SelectionMove:     
+                       if (_time_selection_at_start) {
+                               _editor->selection->move_time (distance);
+                       }
+                       break;
+               default:
+                       _editor->selection->replace (_editor->clicked_selection, start, end);
+               }
        }
 
        if (_operation == SelectionMove) {
@@ -3503,22 +3895,43 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
                }
 
                /* XXX what if its a music time selection? */
-               if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
-                       s->request_play_range (&_editor->selection->time, true);
+               if (s) {
+                       if ( s->get_play_range() && s->transport_rolling() ) {
+                               s->request_play_range (&_editor->selection->time, true);
+                       } else {
+                               if (Config->get_always_play_range() && !s->transport_rolling()) {
+                                       s->request_locate (_editor->get_selection().time.start());
+                               }
+                       }
                }
 
-
        } else {
-               /* just a click, no pointer movement.*/
+               /* just a click, no pointer movement.
+                */
 
-               if (Keyboard::no_modifier_keys_pressed (&event->button)) {
-                       _editor->selection->clear_time();
+               if (_operation == SelectionExtend) {
+                       if (_time_selection_at_start) {
+                               framepos_t pos = adjusted_current_frame (event, false);
+                               framepos_t start = min (pos, start_at_start);
+                               framepos_t end = max (pos, end_at_start);
+                               _editor->selection->set (start, end);
+                       }
+               } else {
+                       if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
+                               if (_editor->clicked_selection) {
+                                       _editor->selection->remove (_editor->clicked_selection);
+                               }
+                       } else {
+                               if (!_editor->clicked_selection) {
+                                       _editor->selection->clear_time();
+                               }
+                       }
                }
 
                if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
                        _editor->selection->set (_editor->clicked_axisview);
                }
-
+                       
                if (s && s->get_play_range () && s->transport_rolling()) {
                        s->request_stop (false, false);
                }
@@ -3526,6 +3939,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
        }
 
        _editor->stop_canvas_autoscroll ();
+       _editor->clicked_selection = 0;
 }
 
 void
@@ -3599,7 +4013,7 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
                crect = _editor->cd_marker_bar_drag_rect;
                break;
        default:
-               cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
+               error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
                return;
                break;
        }
@@ -3811,9 +4225,9 @@ MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
                motion (event, false);
 
                if (grab_frame() < last_pointer_frame()) {
-                       _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
+                       _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame());
                } else {
-                       _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
+                       _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame());
                }
        } else {
                if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
@@ -3880,23 +4294,31 @@ NoteDrag::total_dx () const
        /* primary note time */
        frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
 
-       /* new time of the primary note relative to the region position */
+       /* new time of the primary note in session frames */
        frameoffset_t st = n + dx;
 
+       framepos_t const rp = _region->region()->position ();
+
        /* prevent the note being dragged earlier than the region's position */
-       if (st < 0) {
-               st = 0;
-       }
+       st = max (st, rp);
 
        /* snap and return corresponding delta */
-       return _region->snap_frame_to_frame (st) - n;
+       return _region->snap_frame_to_frame (st - rp) + rp - n;
 }
 
 /** @return Current total drag y change in note number */
 int8_t
 NoteDrag::total_dy () const
 {
-       return ((int8_t) (grab_y() / _note_height)) - ((int8_t) (_drags->current_pointer_y() / _note_height));
+       MidiStreamView* msv = _region->midi_stream_view ();
+       double const y = _region->midi_view()->y_position ();
+       /* new current note */
+       uint8_t n = msv->y_to_note (_drags->current_pointer_y () - y);
+       /* clamp */
+       n = max (msv->lowest_note(), n);
+       n = min (msv->highest_note(), n);
+       /* and work out delta */
+       return n - msv->y_to_note (grab_y() - y);
 }
 
 void
@@ -3938,8 +4360,11 @@ void
 NoteDrag::finished (GdkEvent* ev, bool moved)
 {
        if (!moved) {
-               if (_editor->current_mouse_mode() == Editing::MouseObject) {
-
+               /* no motion - select note */
+               
+               if (_editor->current_mouse_mode() == Editing::MouseObject ||
+                   _editor->current_mouse_mode() == Editing::MouseDraw) {
+                       
                        if (_was_selected) {
                                bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
                                if (add) {
@@ -3969,31 +4394,49 @@ NoteDrag::aborted (bool)
        /* XXX: TODO */
 }
 
-AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* item, list<AudioRange> const & r)
-       : Drag (editor, item)
+/** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
+AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
+       : Drag (editor, atv->base_item ())
        , _ranges (r)
        , _nothing_to_drag (false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
+       y_origin = atv->y_position();
+       setup (atv->lines ());
+}
 
-       _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
-       assert (_atav);
+/** Make an AutomationRangeDrag for region gain lines */
+AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AudioRegionView* rv, list<AudioRange> const & r)
+       : Drag (editor, rv->get_canvas_group ())
+       , _ranges (r)
+       , _nothing_to_drag (false)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
 
-       /* get all lines in the automation view */
-       list<boost::shared_ptr<AutomationLine> > lines = _atav->lines ();
+       list<boost::shared_ptr<AutomationLine> > lines;
+       lines.push_back (rv->get_gain_line ());
+       y_origin = rv->get_time_axis_view().y_position();
+       setup (lines);
+}
 
-       /* find those that overlap the ranges being dragged */
-       list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
+/** @param lines AutomationLines to drag.
+ *  @param offset Offset from the session start to the points in the AutomationLines.
+ */
+void
+AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
+{
+       /* find the lines that overlap the ranges being dragged */
+       list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
        while (i != lines.end ()) {
-               list<boost::shared_ptr<AutomationLine> >::iterator j = i;
+               list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
                ++j;
 
-               pair<framepos_t, framepos_t> const r = (*i)->get_point_x_range ();
+               pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
 
                /* check this range against all the AudioRanges that we are using */
                list<AudioRange>::const_iterator k = _ranges.begin ();
                while (k != _ranges.end()) {
-                       if (k->coverage (r.first, r.second) != OverlapNone) {
+                       if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
                                break;
                        }
                        ++k;
@@ -4014,6 +4457,12 @@ AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* it
        /* Now ::lines contains the AutomationLines that somehow overlap our drag */
 }
 
+double
+AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
+{
+       return 1.0 - ((global_y - y_origin) / line->height());
+}
+
 void
 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
@@ -4022,6 +4471,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        /* Get line states before we start changing things */
        for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
                i->state = &i->line->get_state ();
+               i->original_fraction = y_fraction (i->line, _drags->current_pointer_y());
        }
 
        if (_ranges.empty()) {
@@ -4062,9 +4512,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                                double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
 
                                the_list->add (p, the_list->eval (p));
-                               j->line->add_always_in_view (p);
                                the_list->add (q, the_list->eval (q));
-                               j->line->add_always_in_view (q);
                        }
 
                        /* same thing for the end */
@@ -4090,9 +4538,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                                double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
 
                                the_list->add (p, the_list->eval (p));
-                               j->line->add_always_in_view (p);
                                the_list->add (q, the_list->eval (q));
-                               j->line->add_always_in_view (q);
                        }
                }
 
@@ -4131,7 +4577,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        }
 
        for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
-               i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state);
+               i->line->start_drag_multiple (i->points, y_fraction (i->line, _drags->current_pointer_y()), i->state);
        }
 }
 
@@ -4142,11 +4588,12 @@ AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
                return;
        }
 
-       for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
-               float const f = 1 - (_drags->current_pointer_y() / i->line->height());
-
+       for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
+               float const f = y_fraction (l->line, _drags->current_pointer_y());
                /* we are ignoring x position for this drag, so we can just pass in anything */
-               i->line->drag_motion (0, f, true, false);
+               uint32_t ignored;
+               l->line->drag_motion (0, f, true, false, ignored);
+               show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, f));
        }
 }
 
@@ -4159,8 +4606,7 @@ AutomationRangeDrag::finished (GdkEvent* event, bool)
 
        motion (event, false);
        for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
-               i->line->end_drag ();
-               i->line->clear_always_in_view ();
+               i->line->end_drag (false, 0);
        }
 
        _editor->session()->commit_reversible_command ();
@@ -4170,7 +4616,6 @@ void
 AutomationRangeDrag::aborted (bool)
 {
        for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
-               i->line->clear_always_in_view ();
                i->line->reset ();
        }
 }
@@ -4192,7 +4637,9 @@ PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionVie
        , _patch_change (i)
        , _cumulative_dx (0)
 {
-       DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
+       DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
+                                                  _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
+                                                  grab_frame()));
 }
 
 void
@@ -4203,8 +4650,8 @@ PatchChangeDrag::motion (GdkEvent* ev, bool)
        f = max (f, r->position ());
        f = min (f, r->last_frame ());
 
-       framecnt_t const dxf = f - grab_frame();
-       double const dxu = _editor->frame_to_unit (dxf);
+       framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
+       double const dxu = _editor->frame_to_unit (dxf); // permitted fx in units
        _patch_change->move (dxu - _cumulative_dx, 0);
        _cumulative_dx = dxu;
 }
@@ -4217,14 +4664,13 @@ PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
        }
 
        boost::shared_ptr<Region> r (_region_view->region ());
-
        framepos_t f = adjusted_current_frame (ev);
        f = max (f, r->position ());
        f = min (f, r->last_frame ());
 
        _region_view->move_patch_change (
                *_patch_change,
-               _region_view->region_frames_to_region_beats (f - r->position() - r->start())
+               _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
                );
 }
 
@@ -4241,3 +4687,298 @@ PatchChangeDrag::setup_pointer_frame_offset ()
        _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
 }
 
+MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
+       : RubberbandSelectDrag (e, rv->get_canvas_frame ())
+       , _region_view (rv)
+{
+
+}
+
+void
+MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
+{
+       framepos_t const p = _region_view->region()->position ();
+       double const y = _region_view->midi_view()->y_position ();
+
+       x1 = max ((framepos_t) 0, x1 - p);
+       x2 = max ((framepos_t) 0, x2 - p);
+       y1 = max (0.0, y1 - y);
+       y2 = max (0.0, y2 - y);
+       
+       _region_view->update_drag_selection (
+               _editor->frame_to_pixel (x1),
+               _editor->frame_to_pixel (x2),
+               y1,
+               y2,
+               Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
+               );
+}
+
+void
+MidiRubberbandSelectDrag::deselect_things ()
+{
+       /* XXX */
+}
+
+MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
+       : RubberbandSelectDrag (e, rv->get_canvas_frame ())
+       , _region_view (rv)
+{
+       _vertical_only = true;
+}
+
+void
+MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
+{
+       double const y = _region_view->midi_view()->y_position ();
+
+       y1 = max (0.0, y1 - y);
+       y2 = max (0.0, y2 - y);
+       
+       _region_view->update_vertical_drag_selection (
+               y1,
+               y2,
+               Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
+               );
+}
+
+void
+MidiVerticalSelectDrag::deselect_things ()
+{
+       /* XXX */
+}
+
+EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
+       : RubberbandSelectDrag (e, i)
+{
+
+}
+
+void
+EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
+{
+       if (drag_in_progress) {
+               /* We just want to select things at the end of the drag, not during it */
+               return;
+       }
+       
+       Selection::Operation op = ArdourKeyboard::selection_type (button_state);
+       
+       _editor->begin_reversible_command (_("rubberband selection"));
+       _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
+       _editor->commit_reversible_command ();
+}
+
+void
+EditorRubberbandSelectDrag::deselect_things ()
+{
+       if (!getenv("ARDOUR_SAE")) {
+               _editor->selection->clear_tracks();
+       }
+       _editor->selection->clear_regions();
+       _editor->selection->clear_points ();
+       _editor->selection->clear_lines ();
+}
+
+NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
+       : Drag (e, i)
+       , _region_view (rv)
+       , _drag_rect (0)
+{
+       
+}
+
+NoteCreateDrag::~NoteCreateDrag ()
+{
+       delete _drag_rect;
+}
+
+framecnt_t
+NoteCreateDrag::grid_frames (framepos_t t) const
+{
+       bool success;
+       Evoral::MusicalTime grid_beats = _editor->get_grid_type_as_beats (success, t);
+       if (!success) {
+               grid_beats = 1;
+       }
+
+       return _region_view->region_beats_to_region_frames (grid_beats);
+}
+
+void
+NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
+                                                
+       _drag_rect = new ArdourCanvas::SimpleRect (*_region_view->get_canvas_group ());
+
+       framepos_t pf = _drags->current_pointer_frame ();
+       framecnt_t const g = grid_frames (pf);
+
+       /* Hack so that we always snap to the note that we are over, instead of snapping
+          to the next one if we're more than halfway through the one we're over.
+       */
+       if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
+               pf -= g / 2;
+       }
+
+       _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
+
+       MidiStreamView* sv = _region_view->midi_stream_view ();
+       double const x = _editor->frame_to_pixel (_note[0]);
+       double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
+
+       _drag_rect->property_x1() = x;
+       _drag_rect->property_y1() = y;
+       _drag_rect->property_x2() = x;
+       _drag_rect->property_y2() = y + floor (_region_view->midi_stream_view()->note_height ());
+
+       _drag_rect->property_outline_what() = 0xff;
+       _drag_rect->property_outline_color_rgba() = 0xffffff99;
+       _drag_rect->property_fill_color_rgba()    = 0xffffff66;
+}
+
+void
+NoteCreateDrag::motion (GdkEvent* event, bool)
+{
+       _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
+       double const x = _editor->frame_to_pixel (_note[1]);
+       if (_note[1] > _note[0]) {
+               _drag_rect->property_x2() = x;
+       } else {
+               _drag_rect->property_x1() = x;
+       }
+}
+
+void
+NoteCreateDrag::finished (GdkEvent*, bool had_movement)
+{
+       if (!had_movement) {
+               return;
+       }
+       
+       framepos_t const start = min (_note[0], _note[1]);
+       framecnt_t length = (framecnt_t) fabs (_note[0] - _note[1]);
+
+       framecnt_t const g = grid_frames (start);
+       double const one_tick = 1 / Timecode::BBT_Time::ticks_per_beat;
+       
+       if (_editor->snap_mode() == SnapNormal && length < g) {
+               length = g - one_tick;
+       }
+
+       double const length_beats = max (one_tick, _region_view->region_frames_to_region_beats (length));
+
+       _region_view->create_note_at (start, _drag_rect->property_y1(), length_beats, false);
+}
+
+double
+NoteCreateDrag::y_to_region (double y) const
+{
+       double x = 0;
+       _region_view->get_canvas_group()->w2i (x, y);
+       return y;
+}
+
+void
+NoteCreateDrag::aborted (bool)
+{
+       
+}
+
+CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
+       : Drag (e, i)
+       , arv (rv)
+       , start (start_yn)
+{
+       std::cout << ("CrossfadeEdgeDrag is DEPRECATED.  See TrimDrag::preserve_fade_anchor") << endl;
+}
+
+void
+CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
+{
+       Drag::start_grab (event, cursor);
+}
+
+void
+CrossfadeEdgeDrag::motion (GdkEvent*, bool)
+{
+       double distance;
+       double new_length;
+       framecnt_t len;
+
+       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+
+       if (start) {
+               distance = _drags->current_pointer_x() - grab_x();
+               len = ar->fade_in()->back()->when;
+       } else {
+               distance = grab_x() - _drags->current_pointer_x();
+               len = ar->fade_out()->back()->when;
+       }
+
+       /* how long should it be ? */
+
+       new_length = len + _editor->unit_to_frame (distance);
+
+       /* now check with the region that this is legal */
+
+       new_length = ar->verify_xfade_bounds (new_length, start);
+
+       if (start) {
+               arv->redraw_start_xfade_to (ar, new_length);
+       } else {
+               arv->redraw_end_xfade_to (ar, new_length);
+       }
+}
+
+void
+CrossfadeEdgeDrag::finished (GdkEvent*, bool)
+{
+       double distance;
+       double new_length;
+       framecnt_t len;
+
+       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+
+       if (start) {
+               distance = _drags->current_pointer_x() - grab_x();
+               len = ar->fade_in()->back()->when;
+       } else {
+               distance = grab_x() - _drags->current_pointer_x();
+               len = ar->fade_out()->back()->when;
+       }
+
+       new_length = ar->verify_xfade_bounds (len + _editor->unit_to_frame (distance), start);
+       
+       _editor->begin_reversible_command ("xfade trim");
+       ar->playlist()->clear_owned_changes (); 
+
+       if (start) {
+               ar->set_fade_in_length (new_length);
+       } else {
+               ar->set_fade_out_length (new_length);
+       }
+
+       /* Adjusting the xfade may affect other regions in the playlist, so we need
+          to get undo Commands from the whole playlist rather than just the
+          region.
+       */
+
+       vector<Command*> cmds;
+       ar->playlist()->rdiff (cmds);
+       _editor->session()->add_commands (cmds);
+       _editor->commit_reversible_command ();
+
+}
+
+void
+CrossfadeEdgeDrag::aborted (bool)
+{
+       if (start) {
+               arv->redraw_start_xfade ();
+       } else {
+               arv->redraw_end_xfade ();
+       }
+}
+