Merge branch 'master' into cairocanvas
[ardour.git] / gtk2_ardour / editor_drag.cc
index 2d6c98b37325254f80566184f7a0445e67e877ba..e069d9f6c575b643d332e89baaca9436b4baadec 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"
 #include "editor_drag.h"
 #include "audio_time_axis.h"
 #include "midi_time_axis.h"
-#include "canvas-note.h"
 #include "selection.h"
 #include "midi_selection.h"
 #include "automation_time_axis.h"
 #include "debug.h"
 #include "editor_cursors.h"
 #include "mouse_cursors.h"
+#include "note_base.h"
+#include "patch_change.h"
 #include "verbose_cursor.h"
 
 using namespace std;
@@ -199,25 +202,21 @@ Drag::Drag (Editor* e, ArdourCanvas::Item* i)
 }
 
 void
-Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time)
+Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
 {
-       _item->ungrab (0);
+       _item->ungrab ();
        _item = new_item;
 
        if (cursor == 0) {
-               cursor = _editor->which_grabber_cursor ();
+               _item->grab ();
+       } else {
+               _item->grab ();
        }
-
-       _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 +239,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 ();
+                            
+       } else {
+               /* CAIROCANVAS need a variant here that passes *cursor */
+               _item->grab ();
+
+       }
 
        if (_editor->session() && _editor->session()->transport_rolling()) {
                _was_rolling = true;
@@ -273,7 +277,7 @@ Drag::end_grab (GdkEvent* event)
 {
        _editor->stop_canvas_autoscroll ();
 
-       _item->ungrab (event ? event->button.time : 0);
+       _item->ungrab ();
 
        finished (event, _move_threshold_passed);
 
@@ -330,7 +334,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);
@@ -350,7 +356,7 @@ void
 Drag::abort ()
 {
        if (_item) {
-               _item->ungrab (0);
+               _item->ungrab ();
        }
 
        aborted (_move_threshold_passed);
@@ -365,7 +371,7 @@ Drag::show_verbose_cursor_time (framepos_t frame)
        _editor->verbose_cursor()->set_time (
                frame,
                _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
-               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
+               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value()
                );
 
        _editor->verbose_cursor()->show ();
@@ -379,7 +385,7 @@ Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double xof
        _editor->verbose_cursor()->set_duration (
                start, end,
                _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
-               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
+               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value()
                );
 }
 
@@ -391,17 +397,33 @@ Drag::show_verbose_cursor_text (string const & text)
        _editor->verbose_cursor()->set (
                text,
                _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
-               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
+               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value()
                );
 }
 
+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);
     }
 };
 
@@ -434,7 +456,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
@@ -488,10 +510,6 @@ RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        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;
-
-       if (tv.first->view()->layer_display() == Stacked) {
-               tv.first->view()->set_layer_display (Expanded);
-       }
 }
 
 double
@@ -534,7 +552,7 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r
        if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
 
                /* x movement since last time (in pixels) */
-               dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
+               dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_pixel;
 
                /* total x movement */
                framecnt_t total_dx = *pending_region_position;
@@ -578,7 +596,6 @@ RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const
                /* 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.
@@ -599,6 +616,10 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        /* Find the TimeAxisView that the pointer is now over */
        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);
        if (!rtv || !rtv->is_track()) {
@@ -639,28 +660,28 @@ 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) {
 
-                       rv->get_time_axis_view().hide_dependent_views (*rv);
-                                                                                   
+                       rv->drag_start (); 
+
+                       /* 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()->reparent (*(_editor->_region_motion_group));
+
+                       rv->get_canvas_group()->reparent (_editor->_region_motion_group);
                        
                        rv->fake_set_opaque (true);
-                       
-                       if (!rv->get_time_axis_view().hidden()) {
-                               /* the track that this region view is on is hidden, so hide the region too */
-                               rv->get_canvas_group()->hide ();
-                       }
                }
 
                /* If we have moved tracks, we'll fudge the layer delta so that the
@@ -680,15 +701,15 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                if (tv->view()->layer_display() == Stacked) {
                        tv->view()->set_layer_display (Expanded);
                }
-                       
+               
                /* We're only allowed to go -ve in layer on Expanded views */
                if (tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
                        this_delta_layer = - i->layer;
                }
-
+               
                /* Set height */
                rv->set_height (tv->view()->child_height ());
-
+               
                /* Update show/hidden status as the region view may have come from a hidden track,
                   or have moved to one.
                */
@@ -709,7 +730,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                        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);
+                       tv->canvas_display()->item_to_canvas (x, y);
                        y += _editor->get_trackview_group_vertical_offset();
                        
                        /* And adjust for the layer that it should be on */
@@ -726,17 +747,13 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                        }
 
                        /* Now move the region view */
-                       rv->move (x_delta, y - rv->get_canvas_group()->property_y());
+                       rv->move (x_delta, y - rv->get_canvas_group()->position().y);
                }
 
        } /* foreach region */
 
        _total_x_delta += x_delta;
 
-       if (first_move) {
-               _editor->cursor_group->raise_to_top();
-       }
-
        if (x_delta != 0 && !_brushing) {
                show_verbose_cursor_time (_last_frame_position);
        }
@@ -818,16 +835,21 @@ void
 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;
@@ -874,6 +896,8 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
                        );
 
        }
+
+       _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
 }
 
 void
@@ -903,7 +927,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;
                }
 
@@ -961,9 +985,6 @@ RegionMoveDrag::finished_no_copy (
        RegionSelection new_views;
        PlaylistSet modified_playlists;
        PlaylistSet frozen_playlists;
-
-       list<pair<boost::shared_ptr<Region>, double> > pending_explicit_relayers;
-       Playlist::RegionList pending_implicit_relayers;
        set<RouteTimeAxisView*> views_to_update;
 
        if (_brushing) {
@@ -985,7 +1006,7 @@ RegionMoveDrag::finished_no_copy (
                RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
                double const dest_layer = i->layer;
 
-               if (rv->region()->locked()) {
+               if (rv->region()->locked() || rv->region()->video_locked()) {
                        ++i;
                        continue;
                }
@@ -1037,20 +1058,16 @@ RegionMoveDrag::finished_no_copy (
                           No need to do anything for copies as they are fake regions which will be deleted.
                        */
 
-                       rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
-                       rv->get_canvas_group()->property_y() = i->initial_y;
-                       rv->get_time_axis_view().reveal_dependent_views (*rv);
+                       rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
+                       rv->get_canvas_group()->set_y_position (i->initial_y);
+                       rv->drag_end ();
 
                        /* just change the model */
 
                        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
-                       bool const explicit_relayer = dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded;
-                       
-                       if (explicit_relayer) {
-                               pending_explicit_relayers.push_back (make_pair (rv->region (), dest_layer));
-                       } else {
-                               pending_implicit_relayers.push_back (rv->region ());
+                       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 */
@@ -1059,7 +1076,6 @@ RegionMoveDrag::finished_no_copy (
 
                        if (r.second) {
                                playlist->freeze ();
-                               playlist->suspend_relayer ();
                        }
 
                        /* this movement may result in a crossfade being modified, so we need to get undo
@@ -1115,24 +1131,10 @@ RegionMoveDrag::finished_no_copy (
                _editor->selection->set (new_views);
        }
 
-       /* We can't use the normal mechanism for relayering, as some regions may require an explicit relayer
-          rather than an implicit one.  So we thaw before resuming relayering, then do the relayers
-          that we require.
-       */
-
-       for (PlaylistSet::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
+       for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
                (*p)->thaw();
-               (*p)->resume_relayer ();
-       }
-
-       for (list<pair<boost::shared_ptr<Region>, double> >::iterator i = pending_explicit_relayers.begin(); i != pending_explicit_relayers.end(); ++i) {
-               i->first->playlist()->relayer (i->first, i->second);
        }
 
-       for (Playlist::RegionList::iterator i = pending_implicit_relayers.begin(); i != pending_implicit_relayers.end(); ++i) {
-               (*i)->playlist()->relayer (*i);
-       }
-       
        /* write commands for the accumulated diffs for all our modified playlists */
        add_stateful_diff_commands_for_playlists (modified_playlists);
 
@@ -1140,11 +1142,11 @@ RegionMoveDrag::finished_no_copy (
 
        /* 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.
+          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 ());
        }
@@ -1210,7 +1212,7 @@ RegionMoveDrag::insert_region_into_playlist (
        dest_playlist->add_region (region, where);
 
        if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
-               dest_playlist->relayer (region, dest_layer);
+               dest_playlist->set_layer (region, dest_layer);
        }
 
        c.disconnect ();
@@ -1264,15 +1266,15 @@ RegionMotionDrag::aborted (bool)
                        (*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 ());
                RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
                assert (rtv);
-               rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
-               rv->get_canvas_group()->property_y() = 0;
-               rv->get_time_axis_view().reveal_dependent_views (*rv);
+               rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
+               rv->get_canvas_group()->set_y_position (0);
+               rv->drag_end ();
                rv->fake_set_opaque (false);
                rv->move (-_total_x_delta, 0);
                rv->set_height (rtv->view()->child_height ());
@@ -1331,8 +1333,8 @@ RegionInsertDrag::finished (GdkEvent *, bool)
 
        RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[_views.front().time_axis_view]);
 
-       _primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
-       _primary->get_canvas_group()->property_y() = 0;
+       _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
+       _primary->get_canvas_group()->set_y_position (0);
 
        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
@@ -1460,7 +1462,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) {
@@ -1476,7 +1478,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);
                }
        }
@@ -1486,29 +1488,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
@@ -1532,7 +1515,8 @@ void
 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
 {
        Gdk::Cursor* cursor;
-       ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item);
+       NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
+       assert (cnote);
        float x_fraction = cnote->mouse_x_fraction ();
 
        if (x_fraction > 0.0 && x_fraction < 0.25) {
@@ -1546,7 +1530,7 @@ NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
        region = &cnote->region_view();
 
        double const region_start = region->get_position_pixels();
-       double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;
+       double const middle_point = region_start + cnote->x0() + (cnote->x1() - cnote->x0()) / 2.0L;
 
        if (grab_x() <= middle_point) {
                cursor = _editor->cursors()->left_side_trim;
@@ -1556,7 +1540,7 @@ NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
                at_front = false;
        }
 
-       _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, *cursor, event->motion.time);
+       _item->grab ();
 
        if (event->motion.state & Keyboard::PrimaryModifier) {
                relative = false;
@@ -1590,7 +1574,9 @@ NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
 {
        MidiRegionSelection& ms (_editor->get_selection().midi_regions);
        for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
-               (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
+               NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
+               assert (nb);
+               (*r)->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative);
        }
 }
 
@@ -1599,7 +1585,9 @@ NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
 {
        MidiRegionSelection& ms (_editor->get_selection().midi_regions);
        for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
-               (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
+               NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
+               assert (nb);
+               (*r)->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative);
        }
 }
 
@@ -1612,34 +1600,172 @@ NoteResizeDrag::aborted (bool)
        }
 }
 
-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
-RegionGainDrag::motion (GdkEvent* /*event*/, bool)
+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
+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(dt);
+
+       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
@@ -1740,6 +1866,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();
@@ -1760,13 +1887,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;
 
@@ -1818,16 +1973,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 {
@@ -1853,6 +2053,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 ();
                }
@@ -1950,7 +2151,7 @@ MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
                // not, because we'll remove it from the map).
                
                MeterSection section (_marker->meter());
-               
+
                if (!section.movable()) {
                        return;
                }
@@ -1971,8 +2172,10 @@ MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
 
                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);
+                       map.remove_meter (section, true);
                }
        }
 
@@ -2005,13 +2208,12 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        } else {
                _editor->begin_reversible_command (_("move meter mark"));
-               XMLNode &before = map.get_state();
 
                /* 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 ();
        }
 
@@ -2089,8 +2291,10 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
 
                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);
+                       map.remove_tempo (section, true);
                }
        }
 
@@ -2108,10 +2312,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"));
@@ -2123,11 +2328,10 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        } else {
                _editor->begin_reversible_command (_("move tempo mark"));
-               XMLNode &before = map.get_state();
                /* 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 ();
        }
 
@@ -2143,7 +2347,7 @@ TempoMarkerDrag::aborted (bool moved)
        if (moved) {
                TempoMap& map (_editor->session()->tempo_map());
                /* we removed it before, so add it back now */
-               map.add_tempo (_marker->tempo(), _marker->tempo().frame());
+               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;
@@ -2168,7 +2372,7 @@ CursorDrag::fake_locate (framepos_t t)
 
        Session* s = _editor->session ();
        if (s->timecode_transmission_suspended ()) {
-               framepos_t const f = _editor->playhead_cursor->current_frame;
+               framepos_t const f = _editor->playhead_cursor->current_frame ();
                s->send_mmc_locate (f);
                s->send_full_time_code (f);
        }
@@ -2182,7 +2386,7 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
 {
        Drag::start_grab (event, c);
 
-       _grab_zoom = _editor->frames_per_unit;
+       _grab_zoom = _editor->frames_per_pixel;
 
        framepos_t where = _editor->event_frame (event, 0, 0);
        _editor->snap_to_with_modifier (where, event);
@@ -2200,9 +2404,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 */
+                       }
                }
        }
 
@@ -2234,7 +2449,7 @@ CursorDrag::finished (GdkEvent* event, bool movement_occurred)
 
        Session* s = _editor->session ();
        if (s) {
-               s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
+               s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
                _editor->_pending_locate_request = true;
                s->request_resume_timecode_transmission ();
        }
@@ -2266,8 +2481,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
@@ -2303,8 +2516,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);
@@ -2346,7 +2558,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));
@@ -2365,8 +2576,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);
        }
 }
 
@@ -2385,8 +2595,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
@@ -2424,8 +2632,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());
@@ -2469,7 +2676,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));
@@ -2488,8 +2694,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);
        }
 }
 
@@ -2501,17 +2706,24 @@ MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
        _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
        assert (_marker);
 
-       _points.push_back (Gnome::Art::Point (0, 0));
-       _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
+       _points.push_back (ArdourCanvas::Duple (0, 0));
+       _points.push_back (ArdourCanvas::Duple (0, physical_screen_height (_editor->get_window())));
 }
 
 MarkerDrag::~MarkerDrag ()
 {
-       for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
-               delete *i;
+       for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
+               delete i->location;
        }
 }
 
+MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, Marker* m)
+{
+       location = new Location (*l);
+       markers.push_back (m);
+       move_both = false;
+}
+
 void
 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
@@ -2537,7 +2749,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)) {
@@ -2579,11 +2791,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;
+                       }
+               }
+                       
        }
 }
 
@@ -2601,33 +2839,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;
                        }
@@ -2637,7 +2873,7 @@ MarkerDrag::motion (GdkEvent* event, bool)
                        } else {
 
 
-                               switch (marker->type()) {
+                               switch (_marker->type()) {
                                case Marker::SessionStart:
                                case Marker::RangeStart:
                                case Marker::LoopStart:
@@ -2656,27 +2892,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;
                }
 
@@ -2691,13 +2925,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()) {
@@ -2710,7 +2944,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()) {
@@ -2724,12 +2958,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());
@@ -2760,6 +3002,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;
@@ -2774,7 +3020,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();
@@ -2790,9 +3036,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());
                        }
                }
        }
@@ -2848,6 +3094,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
@@ -2899,9 +3151,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));
 }
@@ -2921,7 +3171,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 ();
 }
 
@@ -2966,9 +3216,9 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
        double cx = event->button.x;
        double cy = event->button.y;
 
-       _line->parent_group().w2i (cx, cy);
+       _line->parent_group().canvas_to_item (cx, cy);
 
-       framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit);
+       framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_pixel);
 
        uint32_t before;
        uint32_t after;
@@ -3012,17 +3262,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));
 }
@@ -3031,7 +3274,7 @@ void
 LineDrag::finished (GdkEvent* event, bool)
 {
        motion (event, false);
-       _line->end_drag ();
+       _line->end_drag (false, 0);
        _editor->session()->commit_reversible_command ();
 }
 
@@ -3062,7 +3305,7 @@ FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
        double cx = event->button.x;
        double cy = event->button.y;
 
-       _item->property_parent().get_value()->w2i(cx, cy);
+       _item->parent()->canvas_to_item (cx, cy);
 
        /* store grab start in parent frame */
        _region_view_grab_x = cx;
@@ -3092,16 +3335,9 @@ FeatureLineDrag::motion (GdkEvent*, bool)
                cx = 0;
        }
 
-       ArdourCanvas::Points points;
-
-       double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
-
-       _line->get_bounds(x1, y2, x2, y2);
-
-       points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
-       points.push_back(Gnome::Art::Point(cx, y2 - y1));
-
-       _line->property_points() = points;
+       boost::optional<Rect> bbox = _line->bounding_box ();
+       assert (bbox);
+       _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
 
        float *pos = new float;
        *pos = cx;
@@ -3126,6 +3362,7 @@ FeatureLineDrag::aborted (bool)
 
 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
+       , _vertical_only (false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
 }
@@ -3176,10 +3413,16 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
                double x1 = _editor->frame_to_pixel (start);
                double x2 = _editor->frame_to_pixel (end);
 
-               _editor->rubberband_rect->property_x1() = x1;
-               _editor->rubberband_rect->property_y1() = y1;
-               _editor->rubberband_rect->property_x2() = x2;
-               _editor->rubberband_rect->property_y2() = y2;
+               _editor->rubberband_rect->set_x0 (x1);
+               if (_vertical_only) {
+                       /* fixed 10 pixel width */
+                       _editor->rubberband_rect->set_x1 (x1 + 10);
+               } else {
+                       _editor->rubberband_rect->set_x1 (x2);
+               } 
+
+               _editor->rubberband_rect->set_y0 (y1);
+               _editor->rubberband_rect->set_y1 (y2);
 
                _editor->rubberband_rect->show();
                _editor->rubberband_rect->raise_to_top();
@@ -3228,7 +3471,29 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
 
        } else {
 
-               deselect_things ();
+               /* just a click */
+
+               bool do_deselect = true;
+               MidiTimeAxisView* mtv;
+
+               if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
+                       /* MIDI track */
+                       if (_editor->selection->empty()) {
+                               /* nothing selected */
+                               add_midi_region (mtv);
+                               do_deselect = false;
+                       }
+               } 
+
+               /* do not deselect if Primary or Tertiary (toggle-select or
+                * extend-select are pressed.
+                */
+
+               if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) && 
+                   !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) && 
+                   do_deselect) {
+                       deselect_things ();
+               }
 
        }
 
@@ -3259,11 +3524,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);
@@ -3294,13 +3564,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;
+               }
        }
 }
 
@@ -3346,11 +3619,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
@@ -3364,10 +3644,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);
@@ -3390,6 +3670,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) {
@@ -3417,6 +3701,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;
        }
 }
 
@@ -3425,7 +3712,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) {
@@ -3446,10 +3734,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 {
@@ -3463,12 +3756,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 */
 
@@ -3536,28 +3829,39 @@ 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) {
+       if (event->button.x >= _editor->horizontal_position() + _editor->_visible_canvas_width) {
                _editor->start_canvas_autoscroll (1, 0);
        }
 
        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) {
@@ -3580,22 +3884,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);
                }
@@ -3603,6 +3928,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
        }
 
        _editor->stop_canvas_autoscroll ();
+       _editor->clicked_selection = 0;
 }
 
 void
@@ -3618,12 +3944,13 @@ RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operat
 {
        DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
 
-       _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
-                                                  physical_screen_height (_editor->get_window()));
+       _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group, 
+                                                 ArdourCanvas::Rect (0.0, 0.0, 0.0,
+                                                                     physical_screen_height (_editor->get_window())));
        _drag_rect->hide ();
 
-       _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
-       _drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
+       _drag_rect->set_fill_color (ARDOUR_UI::config()->canvasvar_RangeDragRect.get());
+       _drag_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_RangeDragRect.get());
 }
 
 void
@@ -3663,7 +3990,7 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
 {
        framepos_t start = 0;
        framepos_t end = 0;
-       ArdourCanvas::SimpleRect *crect;
+       ArdourCanvas::Rectangle *crect;
 
        switch (_operation) {
        case CreateRangeMarker:
@@ -3676,7 +4003,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;
        }
@@ -3712,7 +4039,7 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
                }
        }
 
-       if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
+       if (event->button.x >= _editor->horizontal_position() + _editor->_visible_canvas_width) {
                _editor->start_canvas_autoscroll (1, 0);
        }
 
@@ -3721,8 +4048,8 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
 
                double x1 = _editor->frame_to_pixel (start);
                double x2 = _editor->frame_to_pixel (end);
-               crect->property_x1() = x1;
-               crect->property_x2() = x2;
+               crect->set_x0 (x1);
+               crect->set_x1 (x2);
 
                update_item (_editor->temp_location);
        }
@@ -3823,8 +4150,8 @@ RangeMarkerBarDrag::update_item (Location* location)
        double const x1 = _editor->frame_to_pixel (location->start());
        double const x2 = _editor->frame_to_pixel (location->end());
 
-       _drag_rect->property_x1() = x1;
-       _drag_rect->property_x2() = x2;
+       _drag_rect->set_x0 (x1);
+       _drag_rect->set_x1 (x2);
 }
 
 MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i)
@@ -3888,9 +4215,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)) {
@@ -3916,7 +4243,8 @@ NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
 {
        DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
 
-       _primary = dynamic_cast<CanvasNoteEvent*> (_item);
+       _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
+       assert (_primary);
        _region = &_primary->region_view ();
        _note_height = _region->midi_stream_view()->note_height ();
 }
@@ -3973,7 +4301,15 @@ NoteDrag::total_dx () const
 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
@@ -4015,8 +4351,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) {
@@ -4046,31 +4385,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;
@@ -4091,6 +4448,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)
 {
@@ -4099,6 +4462,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()) {
@@ -4139,9 +4503,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 */
@@ -4167,9 +4529,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);
                        }
                }
 
@@ -4208,7 +4568,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);
        }
 }
 
@@ -4219,11 +4579,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));
        }
 }
 
@@ -4236,8 +4597,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 ();
@@ -4247,7 +4607,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 ();
        }
 }
@@ -4257,19 +4616,21 @@ DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
 {
        time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
        layer = v->region()->layer ();
-       initial_y = v->get_canvas_group()->property_y ();
+       initial_y = v->get_canvas_group()->position().y;
        initial_playlist = v->region()->playlist ();
        initial_position = v->region()->position ();
        initial_end = v->region()->position () + v->region()->length ();
 }
 
-PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
-       : Drag (e, i)
+PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
+       : Drag (e, i->canvas_item ())
        , _region_view (r)
        , _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
@@ -4280,9 +4641,9 @@ 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);
-       _patch_change->move (dxu - _cumulative_dx, 0);
+       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 (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
        _cumulative_dx = dxu;
 }
 
@@ -4294,21 +4655,20 @@ 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()))
                );
 }
 
 void
 PatchChangeDrag::aborted (bool)
 {
-       _patch_change->move (-_cumulative_dx, 0);
+       _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
 }
 
 void
@@ -4326,7 +4686,7 @@ MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* r
 }
 
 void
-MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
+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 ();
@@ -4351,6 +4711,34 @@ 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)
 {
@@ -4413,7 +4801,7 @@ NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
        Drag::start_grab (event, cursor);
                                                 
-       _drag_rect = new ArdourCanvas::SimpleRect (*_region_view->get_canvas_group ());
+       _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
 
        framepos_t pf = _drags->current_pointer_frame ();
        framecnt_t const g = grid_frames (pf);
@@ -4431,51 +4819,51 @@ NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        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;
+       _drag_rect->set (ArdourCanvas::Rect (x, y, x, y + floor (_region_view->midi_stream_view()->note_height ())));
+       _drag_rect->set_outline_what (0xff);
+       _drag_rect->set_outline_color (0xffffff99);
+       _drag_rect->set_fill_color (0xffffff66);
 }
 
 void
 NoteCreateDrag::motion (GdkEvent* event, bool)
 {
-       _note[1] = adjusted_current_frame (event) - _region_view->region()->position ();
+       _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;
+               _drag_rect->set_x1 (x);
        } else {
-               _drag_rect->property_x1() = x;
+               _drag_rect->set_x0 (x);
        }
 }
 
 void
-NoteCreateDrag::finished (GdkEvent* event, bool had_movement)
+NoteCreateDrag::finished (GdkEvent*, bool had_movement)
 {
        if (!had_movement) {
                return;
        }
        
        framepos_t const start = min (_note[0], _note[1]);
-       framecnt_t length = abs (_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;
+               length = g - one_tick;
        }
 
-       _region_view->create_note_at (start, _drag_rect->property_y1(), _region_view->region_frames_to_region_beats (length), true, false);
+       double const length_beats = max (one_tick, _region_view->region_frames_to_region_beats (length));
+
+       _region_view->create_note_at (start, _drag_rect->y0(), length_beats, false);
 }
 
 double
 NoteCreateDrag::y_to_region (double y) const
 {
        double x = 0;
-       _region_view->get_canvas_group()->w2i (x, y);
+       _region_view->get_canvas_group()->canvas_to_item (x, y);
        return y;
 }
 
@@ -4484,3 +4872,100 @@ 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 ();
+       }
+}
+