new mouse mode (Draw (notes)); switch into internal mode when draw is selected; notab...
[ardour.git] / gtk2_ardour / editor_drag.cc
index 014b91bb61cd9f5f83b54d30ada5e96ea6561dad..7d088d0684459bda31c6d4c4a443008c8681054d 100644 (file)
 
 #include "gtkmm2ext/utils.h"
 
-#include "ardour/session.h"
+#include "ardour/audioregion.h"
 #include "ardour/dB.h"
+#include "ardour/midi_region.h"
+#include "ardour/operations.h"
 #include "ardour/region_factory.h"
+#include "ardour/session.h"
+
 #include "editor.h"
 #include "i18n.h"
 #include "keyboard.h"
@@ -52,6 +56,7 @@
 #include "debug.h"
 #include "editor_cursors.h"
 #include "mouse_cursors.h"
+#include "verbose_cursor.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -63,14 +68,13 @@ using namespace ArdourCanvas;
 
 using Gtkmm2ext::Keyboard;
 
-double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
+double ControlPointDrag::_zero_gain_fraction = -1.0;
 
 DragManager::DragManager (Editor* e)
        : _editor (e)
        , _ending (false)
        , _current_pointer_frame (0)
 {
-
 }
 
 DragManager::~DragManager ()
@@ -83,16 +87,18 @@ void
 DragManager::abort ()
 {
        _ending = true;
-       
+
        for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
                (*i)->abort ();
                delete *i;
        }
 
+       if (!_drags.empty ()) {
+               _editor->set_follow_playhead (_old_follow_playhead, false);
+       }
+
        _drags.clear ();
 
-       _editor->set_follow_playhead (_old_follow_playhead);
-       
        _ending = false;
 }
 
@@ -117,9 +123,9 @@ DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
        /* Prevent follow playhead during the drag to be nice to the user */
        _old_follow_playhead = _editor->follow_playhead ();
        _editor->set_follow_playhead (false);
-               
+
        _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
-       
+
        for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
                (*i)->start_grab (e, c);
        }
@@ -132,7 +138,7 @@ bool
 DragManager::end_grab (GdkEvent* e)
 {
        _ending = true;
-       
+
        bool r = false;
        for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
                bool const t = (*i)->end_grab (e);
@@ -147,7 +153,7 @@ DragManager::end_grab (GdkEvent* e)
        _ending = false;
 
        _editor->set_follow_playhead (_old_follow_playhead, false);
-       
+
        return r;
 }
 
@@ -157,13 +163,13 @@ DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
        bool r = false;
 
        _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
-       
+
        for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
                bool const t = (*i)->motion_handler (e, from_autoscroll);
                if (t) {
                        r = true;
                }
-               
+
        }
 
        return r;
@@ -180,7 +186,7 @@ DragManager::have_item (ArdourCanvas::Item* i) const
        return j != _drags.end ();
 }
 
-Drag::Drag (Editor* e, ArdourCanvas::Item* i) 
+Drag::Drag (Editor* e, ArdourCanvas::Item* i)
        : _editor (e)
        , _item (i)
        , _pointer_frame_offset (0)
@@ -271,7 +277,7 @@ Drag::end_grab (GdkEvent* event)
 
        finished (event, _move_threshold_passed);
 
-       _editor->hide_verbose_canvas_cursor();
+       _editor->verbose_cursor()->hide ();
 
        return _move_threshold_passed;
 }
@@ -314,7 +320,7 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
 
        if (!from_autoscroll && !_move_threshold_passed) {
 
-               bool const xp = (::llabs (_drags->current_pointer_frame () - _grab_frame) >= threshold.first);
+               bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
                bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);
 
                _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
@@ -332,7 +338,7 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
                        _last_pointer_x = _drags->current_pointer_x ();
                        _last_pointer_y = _drags->current_pointer_y ();
                        _last_pointer_frame = adjusted_current_frame (event);
-                       
+
                        return true;
                }
        }
@@ -350,9 +356,46 @@ Drag::abort ()
        aborted (_move_threshold_passed);
 
        _editor->stop_canvas_autoscroll ();
-       _editor->hide_verbose_canvas_cursor ();
+       _editor->verbose_cursor()->hide ();
+}
+
+void
+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
+               );
+
+       _editor->verbose_cursor()->show ();
+}
+
+void
+Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double xoffset)
+{
+       _editor->verbose_cursor()->show (xoffset);
+
+       _editor->verbose_cursor()->set_duration (
+               start, end,
+               _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
+               _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
+               );
+}
+
+void
+Drag::show_verbose_cursor_text (string const & text)
+{
+       _editor->verbose_cursor()->show ();
+
+       _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
+               );
 }
 
+
 struct EditorOrderTimeAxisViewSorter {
     bool operator() (TimeAxisView* a, TimeAxisView* b) {
            RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
@@ -368,30 +411,29 @@ RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<Re
 {
        _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
 
-       /* Make a list of non-hidden tracks to refer to during the drag */
+       /* Make a list of tracks to refer to during the drag; we include hidden tracks,
+          as some of the regions we are dragging may be on such tracks.
+       */
 
        TrackViewList track_views = _editor->track_views;
        track_views.sort (EditorOrderTimeAxisViewSorter ());
 
        for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
-               if (!(*i)->hidden()) {
-                       
-                       _time_axis_views.push_back (*i);
-
-                       TimeAxisView::Children children_list = (*i)->get_child_list ();
-                       for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
-                               _time_axis_views.push_back (j->get());
-                       }
+               _time_axis_views.push_back (*i);
+               
+               TimeAxisView::Children children_list = (*i)->get_child_list ();
+               for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
+                       _time_axis_views.push_back (j->get());
                }
        }
 
        /* the list of views can be empty at this point if this is a region list-insert drag
         */
-       
+
        for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
                _views.push_back (DraggingView (*i, this));
        }
-       
+
        RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
 }
 
@@ -408,7 +450,9 @@ RegionDrag::region_going_away (RegionView* v)
        }
 }
 
-/** Given a non-hidden TimeAxisView, return the index of it into the _time_axis_views vector */
+/** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
+ *  or -1 if it is not found.
+ */
 int
 RegionDrag::find_time_axis_view (TimeAxisView* t) const
 {
@@ -421,7 +465,7 @@ RegionDrag::find_time_axis_view (TimeAxisView* t) const
        if (i == N) {
                return -1;
        }
-       
+
        return i;
 }
 
@@ -439,9 +483,9 @@ RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
        Drag::start_grab (event, cursor);
 
-       _editor->show_verbose_time_cursor (_last_frame_position, 10);
+       show_verbose_cursor_time (_last_frame_position);
 
-       pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        _last_pointer_time_axis_view = find_time_axis_view (tv.first);
        _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
 }
@@ -457,19 +501,19 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r
        framepos_t sync_frame;
        framecnt_t sync_offset;
        int32_t sync_dir;
-       
+
        sync_offset = _primary->region()->sync_offset (sync_dir);
-       
+
        /* we don't handle a sync point that lies before zero.
         */
        if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
-               
+
                sync_frame = *pending_region_position + (sync_dir*sync_offset);
-               
+
                _editor->snap_to_with_modifier (sync_frame, event);
-               
+
                *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
-               
+
        } else {
                *pending_region_position = _last_frame_position;
        }
@@ -485,13 +529,13 @@ 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 */
+               /* x movement since last time (in pixels) */
                dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
 
                /* total x movement */
                framecnt_t total_dx = *pending_region_position;
                if (regions_came_from_canvas()) {
-                       total_dx = total_dx - grab_frame () + _pointer_frame_offset;
+                       total_dx = total_dx - grab_frame ();
                }
 
                /* check that no regions have gone off the start of the session */
@@ -510,7 +554,7 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r
 }
 
 bool
-RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) const
+RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const
 {
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                int const n = i->time_axis_view + delta_track;
@@ -524,9 +568,13 @@ RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) cons
                        /* not a track, or the wrong type */
                        return false;
                }
-               
-               int const l = i->layer + delta_layer;
-               if (delta_track == 0 && (l < 0 || l >= int (to->view()->layers()))) {
+
+               double const l = i->layer + delta_layer;
+
+               /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
+                  mode to allow the user to place a region below another on layer 0.
+               */
+               if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
                        /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
                           If it has, the layers will be munged later anyway, so it's ok.
                        */
@@ -544,12 +592,16 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        assert (!_views.empty ());
 
        /* Find the TimeAxisView that the pointer is now over */
-       pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+
+       if (first_move && tv.first->view()->layer_display() == Stacked) {
+               tv.first->view()->set_layer_display (Expanded);
+       }
 
        /* Bail early if we're not over a track */
        RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv.first);
        if (!rtv || !rtv->is_track()) {
-               _editor->hide_verbose_canvas_cursor ();
+               _editor->verbose_cursor()->hide ();
                return;
        }
 
@@ -557,7 +609,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
        /* Here's the current pointer position in terms of time axis view and layer */
        int const current_pointer_time_axis_view = find_time_axis_view (tv.first);
-       layer_t const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
+       double const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
 
        /* Work out the change in x */
        framepos_t pending_region_position;
@@ -565,7 +617,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
        /* Work out the change in y */
        int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
-       int delta_layer = current_pointer_layer - _last_pointer_layer;
+       double delta_layer = current_pointer_layer - _last_pointer_layer;
 
        if (!y_movement_allowed (delta_time_axis_view, delta_layer)) {
                /* this y movement is not allowed, so do no y movement this time */
@@ -592,86 +644,59 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
                if (first_move) {
 
-                       /* here we are calculating the y distance from the
-                          top of the first track view to the top of the region
-                          area of the track view that we're working on */
-                       
-                       /* this x value is just a dummy value so that we have something
-                          to pass to i2w () */
-                       
-                       double ix1 = 0;
-                       
-                       /* distance from the top of this track view to the region area
-                          of our track view is always 1 */
-                       
-                       double iy1 = 1;
-                       
-                       /* convert to world coordinates, ie distance from the top of
-                          the ruler section */
-                       
-                       rv->get_canvas_frame()->i2w (ix1, iy1);
-                       
-                       /* compensate for the ruler section and the vertical scrollbar position */
-                       iy1 += _editor->get_trackview_group_vertical_offset ();
-                       
-                       // hide any dependent views
-                       
                        rv->get_time_axis_view().hide_dependent_views (*rv);
-                       
-                       /*
-                         reparent to a non scrolling group so that we can keep the
-                         region selection above all time axis views.
-                         reparenting means we have to move the rv as the two
-                         parent groups have different coordinates.
+
+                       /* Absolutely no idea why this is necessary, but it is; without
+                          it, the region view disappears after the reparent.
                        */
+                       _editor->update_canvas_now ();
                        
-                       rv->get_canvas_group()->property_y() = iy1 - 1;
+                       /* 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->fake_set_opaque (true);
                }
 
-               /* Work out the change in y position of this region view */
-
-               double y_delta = 0;
-
                /* If we have moved tracks, we'll fudge the layer delta so that the
                   region gets moved back onto layer 0 on its new track; this avoids
                   confusion when dragging regions from non-zero layers onto different
                   tracks.
                */
-               int this_delta_layer = delta_layer;
+               double this_delta_layer = delta_layer;
                if (delta_time_axis_view != 0) {
                        this_delta_layer = - i->layer;
                }
 
-               /* Move this region to layer 0 on its old track */
-               StreamView* lv = _time_axis_views[i->time_axis_view]->view ();
-               if (lv->layer_display() == Stacked) {
-                       y_delta -= (lv->layers() - i->layer - 1) * lv->child_height ();
+               /* The TimeAxisView that this region is now on */
+               TimeAxisView* tv = _time_axis_views[i->time_axis_view + delta_time_axis_view];
+
+               /* Ensure it is moved from stacked -> expanded if appropriate */
+               if (tv->view()->layer_display() == Stacked) {
+                       tv->view()->set_layer_display (Expanded);
                }
 
-               /* Now move it to its right layer on the current track */
-               StreamView* cv = _time_axis_views[i->time_axis_view + delta_time_axis_view]->view ();
-               if (cv->layer_display() == Stacked) {
-                       y_delta += (cv->layers() - (i->layer + this_delta_layer) - 1) * cv->child_height ();
+               /* 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 ());
 
-               /* Move tracks */
-               if (delta_time_axis_view > 0) {
-                       for (int j = 0; j < delta_time_axis_view; ++j) {
-                               y_delta += _time_axis_views[i->time_axis_view + j]->current_height ();
-                       }
+               /* Update show/hidden status as the region view may have come from a hidden track,
+                  or have moved to one.
+               */
+               if (tv->hidden ()) {
+                       rv->get_canvas_group()->hide ();
                } else {
-                       /* start by subtracting the height of the track above where we are now */
-                       for (int j = 1; j <= -delta_time_axis_view; ++j) {
-                               y_delta -= _time_axis_views[i->time_axis_view - j]->current_height ();
-                       }
+                       rv->get_canvas_group()->show ();
                }
 
-               /* Set height */
-               rv->set_height (_time_axis_views[i->time_axis_view + delta_time_axis_view]->view()->child_height ());
-
                /* Update the DraggingView */
                i->time_axis_view += delta_time_axis_view;
                i->layer += this_delta_layer;
@@ -679,19 +704,40 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                if (_brushing) {
                        _editor->mouse_brush_insert_region (rv, pending_region_position);
                } else {
-                       rv->move (x_delta, y_delta);
+                       double x = 0;
+                       double y = 0;
+
+                       /* Get the y coordinate of the top of the track that this region is now on */
+                       tv->canvas_display()->i2w (x, y);
+                       y += _editor->get_trackview_group_vertical_offset();
+                       
+                       /* And adjust for the layer that it should be on */
+                       StreamView* cv = tv->view ();
+                       switch (cv->layer_display ()) {
+                       case Overlaid:
+                               break;
+                       case Stacked:
+                               y += (cv->layers() - i->layer - 1) * cv->child_height ();
+                               break;
+                       case Expanded:
+                               y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
+                               break;
+                       }
+
+                       /* Now move the region view */
+                       rv->move (x_delta, y - rv->get_canvas_group()->property_y());
                }
 
        } /* foreach region */
 
        _total_x_delta += x_delta;
-       
+
        if (first_move) {
                _editor->cursor_group->raise_to_top();
        }
 
        if (x_delta != 0 && !_brushing) {
-               _editor->show_verbose_time_cursor (_last_frame_position, 10);
+               show_verbose_cursor_time (_last_frame_position);
        }
 
        _last_pointer_time_axis_view += delta_time_axis_view;
@@ -708,20 +754,20 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move)
                list<DraggingView> new_regionviews;
 
                for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                       
+
                        RegionView* rv = i->view;
                        AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
                        MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
 
                        const boost::shared_ptr<const Region> original = rv->region();
                        boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
-                       region_copy->set_position (original->position(), this);
-                       
+                       region_copy->set_position (original->position());
+
                        RegionView* nrv;
                        if (arv) {
                                boost::shared_ptr<AudioRegion> audioregion_copy
                                = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
-                               
+
                                nrv = new AudioRegionView (*arv, audioregion_copy);
                        } else if (mrv) {
                                boost::shared_ptr<MidiRegion> midiregion_copy
@@ -730,29 +776,29 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move)
                        } else {
                                continue;
                        }
-                       
+
                        nrv->get_canvas_group()->show ();
                        new_regionviews.push_back (DraggingView (nrv, this));
-                       
+
                        /* swap _primary to the copy */
-                       
+
                        if (rv == _primary) {
                                _primary = nrv;
                        }
-                       
+
                        /* ..and deselect the one we copied */
-                       
+
                        rv->set_selected (false);
                }
-               
+
                if (!new_regionviews.empty()) {
-                       
+
                        /* reflect the fact that we are dragging the copies */
-                       
+
                        _views = new_regionviews;
-                       
+
                        swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
-                       
+
                        /*
                          sync the canvas to what we think is its current state
                          without it, the canvas seems to
@@ -768,8 +814,24 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move)
 }
 
 void
-RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
+RegionMotionDrag::finished (GdkEvent *, bool)
+{
+       for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
+               if (!(*i)->view()) {
+                       continue;
+               }
+
+               if ((*i)->view()->layer_display() == Expanded) {
+                       (*i)->view()->set_layer_display (Stacked);
+               }
+       }
+}
+
+void
+RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
 {
+       RegionMotionDrag::finished (ev, movement_occurred);
+       
        if (!movement_occurred) {
                /* just a click */
                return;
@@ -785,28 +847,36 @@ RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
 
        assert (!_views.empty ());
 
+       /* We might have hidden region views so that they weren't visible during the drag
+          (when they have been reparented).  Now everything can be shown again, as region
+          views are back in their track parent groups.
+       */
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->get_canvas_group()->show ();
+       }
+       
        bool const changed_position = (_last_frame_position != _primary->region()->position());
        bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
        framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
-
-       _editor->update_canvas_now ();
        
+       _editor->update_canvas_now ();
+
        if (_copy) {
-               
+
                finished_copy (
                        changed_position,
                        changed_tracks,
                        drag_delta
                        );
-               
+
        } else {
-               
+
                finished_no_copy (
                        changed_position,
                        changed_tracks,
                        drag_delta
                        );
-               
+
        }
 }
 
@@ -829,9 +899,9 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed
        }
 
        if (_x_constrained) {
-               _editor->begin_reversible_command (_("fixed time region copy"));
+               _editor->begin_reversible_command (Operations::fixed_time_region_copy);
        } else {
-               _editor->begin_reversible_command (_("region copy"));
+               _editor->begin_reversible_command (Operations::region_copy);
        }
 
        /* insert the regions into their new playlists */
@@ -852,13 +922,13 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed
                RegionView* new_view = insert_region_into_playlist (
                        i->view->region(), dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]), i->layer, where, modified_playlists
                        );
-               
+
                if (new_view == 0) {
                        continue;
                }
 
                new_views.push_back (new_view);
-               
+
                /* we don't need the copied RegionView any more */
                views_to_delete.push_back (i->view);
        }
@@ -871,8 +941,8 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed
                delete *i;
        }
 
-       /* If we've created new regions either by copying or moving 
-          to a new track, we want to replace the old selection with the new ones 
+       /* If we've created new regions either by copying or moving
+          to a new track, we want to replace the old selection with the new ones
        */
 
        if (new_views.size() > 0) {
@@ -895,6 +965,7 @@ RegionMoveDrag::finished_no_copy (
        RegionSelection new_views;
        PlaylistSet modified_playlists;
        PlaylistSet frozen_playlists;
+       set<RouteTimeAxisView*> views_to_update;
 
        if (_brushing) {
                /* all changes were made during motion event handlers */
@@ -905,7 +976,7 @@ RegionMoveDrag::finished_no_copy (
        if (_x_constrained) {
                _editor->begin_reversible_command (_("fixed time region drag"));
        } else {
-               _editor->begin_reversible_command (_("region drag"));
+               _editor->begin_reversible_command (Operations::region_drag);
        }
 
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
@@ -913,13 +984,15 @@ RegionMoveDrag::finished_no_copy (
                RegionView* rv = i->view;
 
                RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
-               layer_t const dest_layer = i->layer;
+               double const dest_layer = i->layer;
 
                if (rv->region()->locked()) {
                        ++i;
                        continue;
                }
 
+               views_to_update.insert (dest_rtv);
+
                framepos_t where;
 
                if (changed_position && !_x_constrained) {
@@ -956,7 +1029,7 @@ RegionMoveDrag::finished_no_copy (
                        remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
 
                } else {
-                       
+
                        rv->region()->clear_changes ();
 
                        /*
@@ -972,12 +1045,11 @@ RegionMoveDrag::finished_no_copy (
                        /* just change the model */
 
                        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
-                       
-                       if (dest_rtv->view()->layer_display() == Stacked) {
-                               rv->region()->set_layer (dest_layer);
-                               rv->region()->set_pending_explicit_relayer (true);
+
+                       if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
+                               playlist->set_layer (rv->region(), dest_layer);
                        }
-                       
+
                        /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
 
                        pair<PlaylistSet::iterator, bool> r = frozen_playlists.insert (playlist);
@@ -989,19 +1061,19 @@ RegionMoveDrag::finished_no_copy (
                        /* this movement may result in a crossfade being modified, so we need to get undo
                           data from the playlist as well as the region.
                        */
-                       
+
                        r = modified_playlists.insert (playlist);
                        if (r.second) {
                                playlist->clear_changes ();
                        }
 
-                       rv->region()->set_position (where, (void*) this);
+                       rv->region()->set_position (where);
 
                        _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
                }
 
                if (changed_tracks) {
-                       
+
                        /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
                           was selected in all of them, then removing it from a playlist will have removed all
                           trace of it from _views (i.e. there were N regions selected, we removed 1,
@@ -1019,7 +1091,7 @@ RegionMoveDrag::finished_no_copy (
                           we can just iterate.
                        */
 
-                       
+
                        if (_views.empty()) {
                                break;
                        } else {
@@ -1031,8 +1103,8 @@ RegionMoveDrag::finished_no_copy (
                }
        }
 
-       /* If we've created new regions either by copying or moving 
-          to a new track, we want to replace the old selection with the new ones 
+       /* If we've created new regions either by copying or moving
+          to a new track, we want to replace the old selection with the new ones
        */
 
        if (new_views.size() > 0) {
@@ -1047,6 +1119,17 @@ RegionMoveDrag::finished_no_copy (
        add_stateful_diff_commands_for_playlists (modified_playlists);
 
        _editor->commit_reversible_command ();
+
+       /* We have futzed with the layering of canvas items on our streamviews.
+          If any region changed layer, this will have resulted in the stream
+          views being asked to set up their region views, and all will be well.
+          If not, we might now have badly-ordered region views.  Ask the StreamViews
+          involved to sort themselves out, just in case.
+       */
+
+       for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
+               (*i)->view()->playlist_layered ((*i)->track ());
+       }
 }
 
 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
@@ -1070,7 +1153,7 @@ RegionMoveDrag::remove_region_from_playlist (
 
        playlist->remove_region (region);
 }
-       
+
 
 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
  *  clearing the playlist's diff history first if necessary.
@@ -1079,7 +1162,7 @@ RegionMoveDrag::remove_region_from_playlist (
  *  @param dest_layer Destination layer.
  *  @param where Destination position.
  *  @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
- *  that clear_changes () is only called once per playlist. 
+ *  that clear_changes () is only called once per playlist.
  *  @return New RegionView, or 0 if no insert was performed.
  */
 RegionView *
@@ -1100,7 +1183,7 @@ RegionMoveDrag::insert_region_into_playlist (
        _new_region_view = 0;
        sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
 
-       /* clear history for the playlist we are about to insert to, provided we haven't already done so */     
+       /* clear history for the playlist we are about to insert to, provided we haven't already done so */
        pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
        if (r.second) {
                dest_playlist->clear_changes ();
@@ -1108,9 +1191,8 @@ RegionMoveDrag::insert_region_into_playlist (
 
        dest_playlist->add_region (region, where);
 
-       if (dest_rtv->view()->layer_display() == Stacked) {
-               region->set_layer (dest_layer);
-               region->set_pending_explicit_relayer (true);
+       if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
+               dest_playlist->set_layer (region, dest_layer);
        }
 
        c.disconnect ();
@@ -1159,6 +1241,12 @@ RegionMoveDrag::aborted (bool movement_occurred)
 void
 RegionMotionDrag::aborted (bool)
 {
+       for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
+               if ((*i)->view()->layer_display() == Expanded) {
+                       (*i)->view()->set_layer_display (Stacked);
+               }
+       }
+       
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                RegionView* rv = i->view;
                TimeAxisView* tv = &(rv->get_time_axis_view ());
@@ -1174,13 +1262,16 @@ RegionMotionDrag::aborted (bool)
 
        _editor->update_canvas_now ();
 }
-                                     
+
+/** @param b true to brush, otherwise false.
+ *  @param c true to make copies of the regions being moved, otherwise false.
+ */
 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
        : RegionMotionDrag (e, i, p, v, b),
          _copy (c)
 {
        DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
-       
+
        double speed = 1;
        RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
        if (rtv && rtv->is_track()) {
@@ -1200,7 +1291,7 @@ RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, Rout
        : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
-       
+
        assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
                (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
 
@@ -1227,7 +1318,7 @@ RegionInsertDrag::finished (GdkEvent *, bool)
 
        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
-       _editor->begin_reversible_command (_("insert region"));
+       _editor->begin_reversible_command (Operations::insert_region);
        playlist->clear_changes ();
        playlist->add_region (_primary->region (), _last_frame_position);
        _editor->session()->add_command (new StatefulDiffCommand (playlist));
@@ -1263,13 +1354,8 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
 {
        /* Which trackview is this ? */
 
-       pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
-       layer_t layer = tvp.second;
-
-       if (tv && tv->layer_display() == Overlaid) {
-               layer = 0;
-       }
 
        /* The region motion is only processed if the pointer is over
           an audio track.
@@ -1279,7 +1365,7 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
                /* To make sure we hide the verbose canvas cursor when the mouse is
                   not held over and audiotrack.
                */
-               _editor->hide_verbose_canvas_cursor ();
+               _editor->verbose_cursor()->hide ();
                return;
        }
 
@@ -1348,7 +1434,7 @@ RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisVi
          _view (dynamic_cast<MidiTimeAxisView*> (v))
 {
        DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
-       
+
        assert (_view);
 }
 
@@ -1357,16 +1443,23 @@ RegionCreateDrag::motion (GdkEvent* event, bool first_move)
 {
        if (first_move) {
                add_region();
+               _view->playlist()->freeze ();
        } else {
                if (_region) {
                        framepos_t const f = adjusted_current_frame (event);
                        if (f < grab_frame()) {
-                               _region->set_position (f, this);
+                               _region->set_position (f);
                        }
-                        
-                       /* again, don't use a zero-length region (see above) */
-                       framecnt_t const len = abs (f - grab_frame ());
-                       _region->set_length (len < 1 ? 1 : len, this);
+
+                       /* Don't use a zero-length region, and subtract 1 frame from the snapped length
+                          so that if this region is duplicated, its duplicate starts on
+                          a snap point rather than 1 frame after a snap point.  Otherwise things get
+                          a bit confusing as if a region starts 1 frame after a snap point, one cannot
+                          place snapped notes at the start of the region.
+                       */
+
+                       framecnt_t const len = abs (f - grab_frame () - 1);
+                       _region->set_length (len < 1 ? 1 : len);
                }
        }
 }
@@ -1376,6 +1469,8 @@ RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
 {
        if (!movement_occurred) {
                add_region ();
+       } else {
+               _view->playlist()->thaw ();
        }
 
        if (_region) {
@@ -1401,6 +1496,10 @@ RegionCreateDrag::add_region ()
 void
 RegionCreateDrag::aborted (bool)
 {
+       if (_region) {
+               _view->playlist()->thaw ();
+       }
+
        /* XXX */
 }
 
@@ -1489,7 +1588,10 @@ NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
 void
 NoteResizeDrag::aborted (bool)
 {
-       /* XXX: TODO */
+       MidiRegionSelection& ms (_editor->get_selection().midi_regions);
+       for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
+               (*r)->abort_resizing ();
+       }
 }
 
 RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
@@ -1558,16 +1660,16 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
 
        switch (_operation) {
        case StartTrim:
-               _editor->show_verbose_time_cursor (region_start, 10);
+               show_verbose_cursor_time (region_start);
                for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
                        i->view->trim_front_starting ();
                }
                break;
        case EndTrim:
-               _editor->show_verbose_time_cursor (region_end, 10);
+               show_verbose_cursor_time (region_end);
                break;
        case ContentsTrim:
-               _editor->show_verbose_time_cursor (pf, 10);
+               show_verbose_cursor_time (pf);
                break;
        }
 
@@ -1659,7 +1761,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                        }
 
                        framecnt_t frame_delta = 0;
-                       
+
                        bool left_direction = false;
                        if (last_pointer_frame() > adjusted_current_frame(event)) {
                                left_direction = true;
@@ -1680,13 +1782,13 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
 
        switch (_operation) {
        case StartTrim:
-               _editor->show_verbose_time_cursor ((framepos_t) (rv->region()->position() / speed), 10);
+               show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
                break;
        case EndTrim:
-               _editor->show_verbose_time_cursor ((framepos_t) (rv->region()->last_frame() / speed), 10);
+               show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed));
                break;
        case ContentsTrim:
-               _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+               show_verbose_cursor_time (adjusted_current_frame (event));
                break;
        }
 }
@@ -1707,7 +1809,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                                i->view->trim_front_ending ();
                        }
                }
-               
+
                if (!_editor->selection->selected (_primary)) {
                        _primary->thaw_after_trim ();
                } else {
@@ -1749,7 +1851,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                if (_operation == StartTrim) {
                        i->view->trim_front_ending ();
                }
-               
+
                i->view->region()->resume_property_changes ();
        }
 }
@@ -1763,7 +1865,7 @@ TrimDrag::aborted (bool movement_occurred)
        */
 
        finished (0, true);
-       
+
        if (movement_occurred) {
                _editor->undo ();
        }
@@ -1802,7 +1904,6 @@ MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
          _copy (c)
 {
        DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
-       
        _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
        assert (_marker);
 }
@@ -1810,36 +1911,8 @@ MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
-       if (_copy) {
-               // create a dummy marker for visual representation of moving the copy.
-               // The actual copying is not done before we reach the finish callback.
-               char name[64];
-               snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
-
-               MeterMarker* new_marker = new MeterMarker (
-                       *_editor,
-                       *_editor->meter_group,
-                       ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
-                       name,
-                       *new MeterSection (_marker->meter())
-                       );
-
-               _item = &new_marker->the_item ();
-               _marker = new_marker;
-
-       } else {
-
-               MetricSection& section (_marker->meter());
-
-               if (!section.movable()) {
-                       return;
-               }
-
-       }
-
        Drag::start_grab (event, cursor);
-
-       _editor->show_verbose_time_cursor (adjusted_current_frame(event), 10);
+       show_verbose_cursor_time (adjusted_current_frame(event));
 }
 
 void
@@ -1849,13 +1922,47 @@ MeterMarkerDrag::setup_pointer_frame_offset ()
 }
 
 void
-MeterMarkerDrag::motion (GdkEvent* event, bool)
+MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
 {
-       framepos_t const pf = adjusted_current_frame (event);
+       if (first_move) {
+
+               // create a dummy marker for visual representation of moving the
+               // section, because whether its a copy or not, we're going to 
+               // leave or lose the original marker (leave if its a copy; lose if its
+               // not, because we'll remove it from the map).
+               
+               MeterSection section (_marker->meter());
+
+               if (!section.movable()) {
+                       return;
+               }
+               
+               char name[64];
+               snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
+               
+               _marker = new MeterMarker (
+                       *_editor,
+                       *_editor->meter_group,
+                       ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
+                       name,
+                       *new MeterSection (_marker->meter())
+               );
+               
+               /* use the new marker for the grab */
+               swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
+
+               if (!_copy) {
+                       TempoMap& map (_editor->session()->tempo_map());
+                       /* get current state */
+                       before_state = &map.get_state();
+                       /* remove the section while we drag it */
+                       map.remove_meter (section, true);
+               }
+       }
 
+       framepos_t const pf = adjusted_current_frame (event);
        _marker->set_position (pf);
-       
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -1871,7 +1978,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        TempoMap& map (_editor->session()->tempo_map());
        map.bbt_time (last_pointer_frame(), when);
-
+       
        if (_copy == true) {
                _editor->begin_reversible_command (_("copy meter mark"));
                XMLNode &before = map.get_state();
@@ -1880,23 +1987,35 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
                _editor->commit_reversible_command ();
 
-               // delete the dummy marker we used for visual representation of copying.
-               // a new visual marker will show up automatically.
-               delete _marker;
        } else {
                _editor->begin_reversible_command (_("move meter mark"));
-               XMLNode &before = map.get_state();
-               map.move_meter (_marker->meter(), when);
+
+               /* we removed it before, so add it back now */
+               
+               map.add_meter (_marker->meter(), when);
                XMLNode &after = map.get_state();
-               _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
+               _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
                _editor->commit_reversible_command ();
        }
+
+       // delete the dummy marker we used for visual representation while moving.
+       // a new visual marker will show up automatically.
+       delete _marker;
 }
 
 void
-MeterMarkerDrag::aborted (bool)
+MeterMarkerDrag::aborted (bool moved)
 {
        _marker->set_position (_marker->meter().frame ());
+
+       if (moved) {
+               TempoMap& map (_editor->session()->tempo_map());
+               /* we removed it before, so add it back now */
+               map.add_meter (_marker->meter(), _marker->meter().frame());
+               // delete the dummy marker we used for visual representation while moving.
+               // a new visual marker will show up automatically.
+               delete _marker;
+       }
 }
 
 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
@@ -1904,7 +2023,7 @@ TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
          _copy (c)
 {
        DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
-       
+
        _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
        assert (_marker);
 }
@@ -1912,14 +2031,35 @@ TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
-       if (_copy) {
+       Drag::start_grab (event, cursor);
+       show_verbose_cursor_time (adjusted_current_frame (event));
+}
+
+void
+TempoMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
+}
+
+void
+TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
+{
+       if (first_move) {
 
+               // create a dummy marker for visual representation of moving the
+               // section, because whether its a copy or not, we're going to 
+               // leave or lose the original marker (leave if its a copy; lose if its
+               // not, because we'll remove it from the map).
+               
                // create a dummy marker for visual representation of moving the copy.
                // The actual copying is not done before we reach the finish callback.
+
                char name[64];
                snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
 
-               TempoMarker* new_marker = new TempoMarker (
+               TempoSection section (_marker->tempo());
+
+               _marker = new TempoMarker (
                        *_editor,
                        *_editor->tempo_group,
                        ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
@@ -1927,28 +2067,21 @@ TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                        *new TempoSection (_marker->tempo())
                        );
 
-               _item = &new_marker->the_item ();
-               _marker = new_marker;
+               /* use the new marker for the grab */
+               swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
 
+               if (!_copy) {
+                       TempoMap& map (_editor->session()->tempo_map());
+                       /* get current state */
+                       before_state = &map.get_state();
+                       /* remove the section while we drag it */
+                       map.remove_tempo (section, true);
+               }
        }
-
-       Drag::start_grab (event, cursor);
-
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
-}
-
-void
-TempoMarkerDrag::setup_pointer_frame_offset ()
-{
-       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
-}      
-
-void
-TempoMarkerDrag::motion (GdkEvent* event, bool)
-{
+
        framepos_t const pf = adjusted_current_frame (event);
        _marker->set_position (pf);
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -1960,10 +2093,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"));
@@ -1973,23 +2107,32 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
                _editor->commit_reversible_command ();
 
-               // delete the dummy marker we used for visual representation of copying.
-               // a new visual marker will show up automatically.
-               delete _marker;
        } else {
                _editor->begin_reversible_command (_("move tempo mark"));
-               XMLNode &before = map.get_state();
-               map.move_tempo (_marker->tempo(), when);
+               /* we removed it before, so add it back now */
+               map.add_tempo (_marker->tempo(), when);
                XMLNode &after = map.get_state();
-               _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
+               _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
                _editor->commit_reversible_command ();
        }
+
+       // delete the dummy marker we used for visual representation while moving.
+       // a new visual marker will show up automatically.
+       delete _marker;
 }
 
 void
-TempoMarkerDrag::aborted (bool)
+TempoMarkerDrag::aborted (bool moved)
 {
        _marker->set_position (_marker->tempo().frame());
+       if (moved) {
+               TempoMap& map (_editor->session()->tempo_map());
+               /* we removed it before, so add it back now */
+               map.add_tempo (_marker->tempo(), _marker->tempo().start());
+               // delete the dummy marker we used for visual representation while moving.
+               // a new visual marker will show up automatically.
+               delete _marker;
+       }
 }
 
 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
@@ -2007,7 +2150,7 @@ void
 CursorDrag::fake_locate (framepos_t t)
 {
        _editor->playhead_cursor->set_position (t);
-       
+
        Session* s = _editor->session ();
        if (s->timecode_transmission_suspended ()) {
                framepos_t const f = _editor->playhead_cursor->current_frame;
@@ -2015,7 +2158,7 @@ CursorDrag::fake_locate (framepos_t t)
                s->send_full_time_code (f);
        }
 
-       _editor->show_verbose_time_cursor (t, 10);
+       show_verbose_cursor_time (t);
        _editor->UpdateAllTransportClocks (t);
 }
 
@@ -2024,28 +2167,30 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
 {
        Drag::start_grab (event, c);
 
+       _grab_zoom = _editor->frames_per_unit;
+
        framepos_t where = _editor->event_frame (event, 0, 0);
        _editor->snap_to_with_modifier (where, event);
 
        _editor->_dragging_playhead = true;
-       
+
        Session* s = _editor->session ();
-       
+
        if (s) {
                if (_was_rolling && _stop) {
                        s->request_stop ();
                }
-               
+
                if (s->is_auditioning()) {
                        s->cancel_audition ();
                }
-               
+
                s->request_suspend_timecode_transmission ();
                while (!s->timecode_transmission_suspended ()) {
                        /* twiddle our thumbs */
                }
        }
-       
+
        fake_locate (where);
 }
 
@@ -2053,16 +2198,12 @@ void
 CursorDrag::motion (GdkEvent* event, bool)
 {
        framepos_t const adjusted_frame = adjusted_current_frame (event);
-
-       if (adjusted_frame == last_pointer_frame()) {
-               return;
-       }
-
-       fake_locate (adjusted_frame);
-       
+       if (adjusted_frame != last_pointer_frame()) {
+               fake_locate (adjusted_frame);
 #ifdef GTKOSX
-       _editor->update_canvas_now ();
+               _editor->update_canvas_now ();
 #endif
+       }
 }
 
 void
@@ -2091,7 +2232,7 @@ CursorDrag::aborted (bool)
                _editor->session()->request_resume_timecode_transmission ();
                _editor->_dragging_playhead = false;
        }
-       
+
        _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
 }
 
@@ -2109,8 +2250,8 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
        boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
 
-       _editor->show_verbose_duration_cursor (r->position(), r->position() + r->fade_in()->back()->when, 10);
-       
+       show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
+
        arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
 }
 
@@ -2151,7 +2292,7 @@ FadeInDrag::motion (GdkEvent* event, bool)
                tmp->show_fade_line((framecnt_t) fade_length);
        }
 
-       _editor->show_verbose_duration_cursor (region->position(), region->position() + fade_length, 10);
+       show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
 }
 
 void
@@ -2228,8 +2369,8 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
        boost::shared_ptr<AudioRegion> r = arv->audio_region ();
 
-       _editor->show_verbose_duration_cursor (r->last_frame() - r->fade_out()->back()->when, r->last_frame(), 10);
-       
+       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);
 }
 
@@ -2239,7 +2380,7 @@ FadeOutDrag::setup_pointer_frame_offset ()
        AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
        boost::shared_ptr<AudioRegion> r = arv->audio_region ();
        _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
-}      
+}
 
 void
 FadeOutDrag::motion (GdkEvent* event, bool)
@@ -2272,7 +2413,7 @@ FadeOutDrag::motion (GdkEvent* event, bool)
                tmp->show_fade_line(region->length() - fade_length);
        }
 
-       _editor->show_verbose_duration_cursor (region->last_frame() - fade_length, region->last_frame(), 10);
+       show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
 }
 
 void
@@ -2341,7 +2482,7 @@ MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
 {
        DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
-       
+
        _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
        assert (_marker);
 
@@ -2372,9 +2513,9 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        // _line->raise_to_top();
 
        if (is_start) {
-               _editor->show_verbose_time_cursor (location->start(), 10);
+               show_verbose_cursor_time (location->start());
        } else {
-               _editor->show_verbose_time_cursor (location->end(), 10);
+               show_verbose_cursor_time (location->end());
        }
 
        Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
@@ -2453,10 +2594,6 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
        framepos_t next = newframe;
 
-       if (newframe == last_pointer_frame()) {
-               return;
-       }
-
        if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
                move_both = true;
        }
@@ -2582,7 +2719,7 @@ MarkerDrag::motion (GdkEvent* event, bool)
 
        assert (!_copied_locations.empty());
 
-       _editor->show_verbose_time_cursor (newframe, 10);
+       show_verbose_cursor_time (newframe);
 
 #ifdef GTKOSX
        _editor->update_canvas_now ();
@@ -2657,7 +2794,7 @@ MarkerDrag::aborted (bool)
 }
 
 void
-MarkerDrag::update_item (Location* location)
+MarkerDrag::update_item (Location*)
 {
         /* noop */
 }
@@ -2667,8 +2804,12 @@ ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
          _cumulative_x_drag (0),
          _cumulative_y_drag (0)
 {
+       if (_zero_gain_fraction < 0.0) {
+               _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
+       }
+
        DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
-       
+
        _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
        assert (_point);
 }
@@ -2688,10 +2829,10 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
 
-       _editor->set_verbose_canvas_cursor (_point->line().get_verbose_cursor_string (fraction),
-                                           event->button.x + 10, event->button.y + 10);
+       _editor->verbose_cursor()->set (_point->line().get_verbose_cursor_string (fraction),
+                                       event->button.x + 10, event->button.y + 10);
 
-       _editor->show_verbose_canvas_cursor ();
+       _editor->verbose_cursor()->show ();
 }
 
 void
@@ -2734,7 +2875,7 @@ ControlPointDrag::motion (GdkEvent* event, bool)
        cy = min ((double) _point->line().height(), cy);
 
        framepos_t cx_frames = _editor->unit_to_frame (cx);
-       
+
        if (!_x_constrained) {
                _editor->snap_to_with_modifier (cx_frames, event);
        }
@@ -2747,7 +2888,7 @@ ControlPointDrag::motion (GdkEvent* event, bool)
 
        _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
 
-       _editor->set_verbose_canvas_cursor_text (_point->line().get_verbose_cursor_string (fraction));
+       _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
 }
 
 void
@@ -2764,7 +2905,7 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
        } else {
                motion (event, false);
        }
-       
+
        _point->line().end_drag ();
        _editor->session()->commit_reversible_command ();
 }
@@ -2816,7 +2957,7 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        uint32_t before;
        uint32_t after;
-       
+
        if (!_line->control_points_adjacent (frame_within_region, before, after)) {
                /* no adjacent points */
                return;
@@ -2833,10 +2974,10 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        _line->start_drag_line (before, after, fraction);
 
-       _editor->set_verbose_canvas_cursor (_line->get_verbose_cursor_string (fraction),
-                                           event->button.x + 10, event->button.y + 10);
+       _editor->verbose_cursor()->set (_line->get_verbose_cursor_string (fraction),
+                                       event->button.x + 10, event->button.y + 10);
 
-       _editor->show_verbose_canvas_cursor ();
+       _editor->verbose_cursor()->show ();
 }
 
 void
@@ -2868,7 +3009,7 @@ LineDrag::motion (GdkEvent* event, bool)
        /* we are ignoring x position for this drag, so we can just pass in anything */
        _line->drag_motion (0, fraction, true, push);
 
-       _editor->set_verbose_canvas_cursor_text (_line->get_verbose_cursor_string (fraction));
+       _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
 }
 
 void
@@ -2897,8 +3038,8 @@ void
 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 {
        Drag::start_grab (event);
-       
-       _line = reinterpret_cast<SimpleLine*> (_item);
+
+       _line = reinterpret_cast<Line*> (_item);
        assert (_line);
 
        /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
@@ -2910,11 +3051,11 @@ FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
 
        /* store grab start in parent frame */
        _region_view_grab_x = cx;
-       
-       _before = _line->property_x1();
-       
+
+       _before = *(float*) _item->get_data ("position");
+
        _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
-               
+
        _max_x = _editor->frame_to_pixel(_arv->get_duration());
 }
 
@@ -2922,31 +3063,44 @@ void
 FeatureLineDrag::motion (GdkEvent*, bool)
 {
        double dx = _drags->current_pointer_x() - last_pointer_x();
-       
+
        double cx = _region_view_grab_x + _cumulative_x_drag + dx;
-       
+
        _cumulative_x_drag += dx;
-               
+
        /* Clamp the min and max extent of the drag to keep it within the region view bounds */
-       
+
        if (cx > _max_x){
                cx = _max_x;
        }
        else if(cx < 0){
                cx = 0;
        }
-       
-       _line->property_x1() = cx; 
-       _line->property_x2() = cx;
 
-       _before = _line->property_x1();
+       ArdourCanvas::Points points;
+
+       double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
+
+       _line->get_bounds(x1, y2, x2, y2);
+
+       points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
+       points.push_back(Gnome::Art::Point(cx, y2 - y1));
+
+       _line->property_points() = points;
+
+       float *pos = new float;
+       *pos = cx;
+
+       _line->set_data ("position", pos);
+
+       _before = cx;
 }
 
 void
 FeatureLineDrag::finished (GdkEvent*, bool)
 {
        _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
-       _arv->update_transient(_before, _line->property_x1());
+       _arv->update_transient(_before, _before);
 }
 
 void
@@ -2965,7 +3119,7 @@ void
 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        Drag::start_grab (event);
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
@@ -3015,49 +3169,52 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
                _editor->rubberband_rect->show();
                _editor->rubberband_rect->raise_to_top();
 
-               _editor->show_verbose_time_cursor (pf, 10);
+               show_verbose_cursor_time (pf);
+
+               do_select_things (event, true);
        }
 }
 
 void
-RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
+RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
 {
-       if (movement_occurred) {
-
-               motion (event, false);
+       framepos_t x1;
+       framepos_t x2;
+       
+       if (grab_frame() < last_pointer_frame()) {
+               x1 = grab_frame ();
+               x2 = last_pointer_frame ();
+       } else {
+               x2 = grab_frame ();
+               x1 = last_pointer_frame ();
+       }
 
-               double y1,y2;
-               if (_drags->current_pointer_y() < grab_y()) {
-                       y1 = _drags->current_pointer_y();
-                       y2 = grab_y();
-               } else {
-                       y2 = _drags->current_pointer_y();
-                       y1 = grab_y();
-               }
+       double y1;
+       double y2;
+       
+       if (_drags->current_pointer_y() < grab_y()) {
+               y1 = _drags->current_pointer_y();
+               y2 = grab_y();
+       } else {
+               y2 = _drags->current_pointer_y();
+               y1 = grab_y();
+       }
 
+       select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
+}
 
-               Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
-               bool committed;
+void
+RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
+{
+       if (movement_occurred) {
 
-               _editor->begin_reversible_command (_("rubberband selection"));
+               motion (event, false);
+               do_select_things (event, false);
 
-               if (grab_frame() < last_pointer_frame()) {
-                       committed = _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
-               } else {
-                       committed = _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
-               }
+       } else {
 
-               if (!committed) {
-                       _editor->commit_reversible_command ();
-               }
+               deselect_things ();
 
-       } else {
-               if (!getenv("ARDOUR_SAE")) {
-                       _editor->selection->clear_tracks();
-               }
-               _editor->selection->clear_regions();
-               _editor->selection->clear_points ();
-               _editor->selection->clear_lines ();
        }
 
        _editor->rubberband_rect->hide();
@@ -3080,7 +3237,7 @@ TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
        Drag::start_grab (event, cursor);
 
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
@@ -3094,7 +3251,7 @@ TimeFXDrag::motion (GdkEvent* event, bool)
                rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
        }
 
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -3122,8 +3279,6 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 #endif
 
-       _editor->begin_reversible_command (_("timestretch"));
-
        // XXX how do timeFX on multiple regions ?
 
        RegionSelection rs;
@@ -3223,9 +3378,9 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
        }
 
        if (_operation == SelectionMove) {
-               _editor->show_verbose_time_cursor (_editor->selection->time[_editor->clicked_selection].start, 10);
+               show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
        } else {
-               _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+               show_verbose_cursor_time (adjusted_current_frame (event));
        }
 
        _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
@@ -3261,7 +3416,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        if (pending_time_axis.first == 0) {
                return;
        }
-       
+
        framepos_t const pending_position = adjusted_current_frame (event);
 
        /* only alter selection if things have changed */
@@ -3306,7 +3461,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                                        //_editor->selection->set (_editor->clicked_axisview);
                                        _editor->set_selected_track_as_side_effect (Selection::Set);
                                }
-                               
+
                                _editor->clicked_selection = _editor->selection->set (start, end);
                        }
                }
@@ -3321,7 +3476,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                /* deselect any tracks that this drag no longer includes, being careful to only deselect
                   tracks that we selected in the first place.
                */
-               
+
                int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
                int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
 
@@ -3330,7 +3485,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
 
                        list<TimeAxisView*>::iterator tmp = i;
                        ++tmp;
-                       
+
                        if ((*i)->order() < min_order || (*i)->order() > max_order) {
                                _editor->selection->remove (*i);
                                _added_time_axes.remove (*i);
@@ -3391,9 +3546,9 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
        }
 
        if (_operation == SelectionMove) {
-               _editor->show_verbose_time_cursor(start, 10);
+               show_verbose_cursor_time(start);
        } else {
-               _editor->show_verbose_time_cursor(pending_position, 10);
+               show_verbose_cursor_time(pending_position);
        }
 }
 
@@ -3425,7 +3580,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
                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);
                }
@@ -3447,8 +3602,8 @@ RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operat
          _copy (false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
-       
-       _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0, 
+
+       _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
                                                   physical_screen_height (_editor->get_window()));
        _drag_rect->hide ();
 
@@ -3485,7 +3640,7 @@ RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 
        Drag::start_grab (event, cursor);
 
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
@@ -3516,7 +3671,7 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
        if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
                framepos_t grab = grab_frame ();
                _editor->snap_to (grab);
-               
+
                if (pf < grab_frame()) {
                        start = pf;
                        end = grab;
@@ -3557,7 +3712,7 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
                update_item (_editor->temp_location);
        }
 
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 
 }
 
@@ -3590,7 +3745,7 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
                        newloc = new Location (
                                *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
                                );
-                       
+
                        _editor->session()->locations()->add (newloc, true);
                        XMLNode &after = _editor->session()->locations()->get_state();
                        _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
@@ -3674,8 +3829,8 @@ MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
                Drag::start_grab (event, _editor->cursors()->zoom_in);
                _zoom_out = false;
        }
-               
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
@@ -3707,7 +3862,7 @@ MouseZoomDrag::motion (GdkEvent* event, bool first_move)
 
                _editor->reposition_zoom_rect(start, end);
 
-               _editor->show_verbose_time_cursor (pf, 10);
+               show_verbose_cursor_time (pf);
        }
 }
 
@@ -3723,7 +3878,11 @@ MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
                        _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
                }
        } else {
-               _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
+               if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
+                       _editor->tav_zoom_step (_zoom_out);
+               } else {
+                       _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
+               }
        }
 
        _editor->zoom_rect->hide();
@@ -3781,50 +3940,37 @@ NoteDrag::total_dx () const
        frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
 
        /* primary note time */
-       frameoffset_t const n = _region->beats_to_frames (_primary->note()->time ());
-       
-       /* new time of the primary note relative to the region position */
-       frameoffset_t const st = n + dx;
+       frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
+
+       /* new time of the primary note in session frames */
+       frameoffset_t st = n + dx;
+
+       framepos_t const rp = _region->region()->position ();
+
+       /* prevent the note being dragged earlier than the region's position */
+       st = max (st, rp);
 
        /* snap and return corresponding delta */
-       return _region->snap_frame_to_frame (st) - n;
+       return _region->snap_frame_to_frame (st - rp) + rp - n;
 }
 
-/** @return Current total drag y change in notes */
+/** @return Current total drag y change in note number */
 int8_t
 NoteDrag::total_dy () const
 {
-       /* this is `backwards' to make increasing note number go in the right direction */
-       double const dy = _drags->current_pointer_y() - grab_y();
-
-       /* dy in notes */
-       int8_t ndy = 0;
-
-       if (abs (dy) >= _note_height) {
-               if (dy > 0) {
-                       ndy = (int8_t) ceil (dy / _note_height / 2.0);
-               } else {
-                       ndy = (int8_t) floor (dy / _note_height / 2.0);
-               }
-       }
-
-       /* more positive value = higher pitch and higher y-axis position on track,
-          which is the inverse of the X-centric geometric universe 
-       */
-
-       return -ndy; 
-}      
+       return ((int8_t) (grab_y() / _note_height)) - ((int8_t) (_drags->current_pointer_y() / _note_height));
+}
 
 void
 NoteDrag::motion (GdkEvent *, bool)
 {
        /* Total change in x and y since the start of the drag */
        frameoffset_t const dx = total_dx ();
-       int8_t const dy = -total_dy ();
+       int8_t const dy = total_dy ();
 
        /* Now work out what we have to do to the note canvas items to set this new drag delta */
        double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx;
-       double const tdy = dy * _note_height - _cumulative_dy;
+       double const tdy = -dy * _note_height - _cumulative_dy;
 
        if (tdx || tdy) {
                _cumulative_dx += tdx;
@@ -3834,11 +3980,19 @@ NoteDrag::motion (GdkEvent *, bool)
 
                _region->move_selection (tdx, tdy, note_delta);
 
+               /* the new note value may be the same as the old one, but we
+                * don't know what that means because the selection may have
+                * involved more than one note and we might be doing something
+                * odd with them. so show the note value anyway, always.
+                */
+
                char buf[12];
-               snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (_primary->note()->note() + note_delta).c_str(),
-                         (int) floor (_primary->note()->note() + note_delta));
-                
-               _editor->show_verbose_canvas_cursor_with (buf);
+               uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
+               
+               snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
+                         (int) floor (new_note));
+
+               show_verbose_cursor_text (buf);
        }
 }
 
@@ -3883,7 +4037,7 @@ AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* it
        , _nothing_to_drag (false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
-       
+
        _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
        assert (_atav);
 
@@ -3941,13 +4095,13 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                                i->points.push_back (i->line->nth (j));
                        }
                }
-               
+
        } else {
 
                for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
 
                        framecnt_t const half = (i->start + i->end) / 2;
-                       
+
                        /* find the line that this audio range starts in */
                        list<Line>::iterator j = _lines.begin();
                        while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
@@ -3956,7 +4110,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
                        if (j != _lines.end()) {
                                boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
-                               
+
                                /* j is the line that this audio range starts in; fade into it;
                                   64 samples length plucked out of thin air.
                                */
@@ -3976,7 +4130,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                        }
 
                        /* same thing for the end */
-                       
+
                        j = _lines.begin();
                        while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
                                ++j;
@@ -3984,11 +4138,11 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
                        if (j != _lines.end()) {
                                boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
-                               
+
                                /* j is the line that this audio range starts in; fade out of it;
                                   64 samples length plucked out of thin air.
                                */
-                               
+
                                framepos_t b = i->end - 64;
                                if (b < half) {
                                        b = half;
@@ -3996,7 +4150,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
                                double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
                                double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
-                               
+
                                the_list->add (p, the_list->eval (p));
                                j->line->add_always_in_view (p);
                                the_list->add (q, the_list->eval (q));
@@ -4064,7 +4218,7 @@ AutomationRangeDrag::finished (GdkEvent* event, bool)
        if (_nothing_to_drag) {
                return;
        }
-       
+
        motion (event, false);
        for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
                i->line->end_drag ();
@@ -4110,7 +4264,7 @@ PatchChangeDrag::motion (GdkEvent* ev, bool)
        boost::shared_ptr<Region> r = _region_view->region ();
        f = max (f, r->position ());
        f = min (f, r->last_frame ());
-       
+
        framecnt_t const dxf = f - grab_frame();
        double const dxu = _editor->frame_to_unit (dxf);
        _patch_change->move (dxu - _cumulative_dx, 0);
@@ -4125,14 +4279,14 @@ 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->frames_to_beats (f - r->position() - r->start())
+               _region_view->region_frames_to_region_beats (f - r->position() - r->start())
                );
 }
 
@@ -4146,6 +4300,176 @@ void
 PatchChangeDrag::setup_pointer_frame_offset ()
 {
        boost::shared_ptr<Region> region = _region_view->region ();
-       _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_patch_change->patch()->time()) - region->position() + region->start();
+       _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
+}
+
+MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
+       : RubberbandSelectDrag (e, rv->get_canvas_frame ())
+       , _region_view (rv)
+{
+
+}
+
+void
+MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
+{
+       framepos_t const p = _region_view->region()->position ();
+       double const y = _region_view->midi_view()->y_position ();
+
+       x1 = max ((framepos_t) 0, x1 - p);
+       x2 = max ((framepos_t) 0, x2 - p);
+       y1 = max (0.0, y1 - y);
+       y2 = max (0.0, y2 - y);
+       
+       _region_view->update_drag_selection (
+               _editor->frame_to_pixel (x1),
+               _editor->frame_to_pixel (x2),
+               y1,
+               y2,
+               Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
+               );
+}
+
+void
+MidiRubberbandSelectDrag::deselect_things ()
+{
+       /* XXX */
+}
+
+EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
+       : RubberbandSelectDrag (e, i)
+{
+
+}
+
+void
+EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
+{
+       if (drag_in_progress) {
+               /* We just want to select things at the end of the drag, not during it */
+               return;
+       }
+       
+       Selection::Operation op = ArdourKeyboard::selection_type (button_state);
+       
+       _editor->begin_reversible_command (_("rubberband selection"));
+       _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
+       _editor->commit_reversible_command ();
+}
+
+void
+EditorRubberbandSelectDrag::deselect_things ()
+{
+       if (!getenv("ARDOUR_SAE")) {
+               _editor->selection->clear_tracks();
+       }
+       _editor->selection->clear_regions();
+       _editor->selection->clear_points ();
+       _editor->selection->clear_lines ();
+}
+
+NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
+       : Drag (e, i)
+       , _region_view (rv)
+       , _drag_rect (0)
+{
+       
+}
+
+NoteCreateDrag::~NoteCreateDrag ()
+{
+       delete _drag_rect;
+}
+
+framecnt_t
+NoteCreateDrag::grid_frames (framepos_t t) const
+{
+       bool success;
+       Evoral::MusicalTime grid_beats = _editor->get_grid_type_as_beats (success, t);
+       if (!success) {
+               grid_beats = 1;
+       }
+
+       return _region_view->region_beats_to_region_frames (grid_beats);
+}
+
+void
+NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
+                                                
+       _drag_rect = new ArdourCanvas::SimpleRect (*_region_view->get_canvas_group ());
+
+       framepos_t pf = _drags->current_pointer_frame ();
+       framecnt_t const g = grid_frames (pf);
+
+       /* Hack so that we always snap to the note that we are over, instead of snapping
+          to the next one if we're more than halfway through the one we're over.
+       */
+       if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
+               pf -= g / 2;
+       }
+
+       _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
+
+       MidiStreamView* sv = _region_view->midi_stream_view ();
+       double const x = _editor->frame_to_pixel (_note[0]);
+       double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
+
+       _drag_rect->property_x1() = x;
+       _drag_rect->property_y1() = y;
+       _drag_rect->property_x2() = x;
+       _drag_rect->property_y2() = y + floor (_region_view->midi_stream_view()->note_height ());
+
+       _drag_rect->property_outline_what() = 0xff;
+       _drag_rect->property_outline_color_rgba() = 0xffffff99;
+       _drag_rect->property_fill_color_rgba()    = 0xffffff66;
+}
+
+void
+NoteCreateDrag::motion (GdkEvent* event, bool)
+{
+       _note[1] = adjusted_current_frame (event) - _region_view->region()->position ();
+       double const x = _editor->frame_to_pixel (_note[1]);
+       if (_note[1] > _note[0]) {
+               _drag_rect->property_x2() = x;
+       } else {
+               _drag_rect->property_x1() = x;
+       }
+}
+
+void
+NoteCreateDrag::finished (GdkEvent* event, 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 const g = grid_frames (start);
+       double const one_tick = 1 / Timecode::BBT_Time::ticks_per_beat;
+       
+       if (_editor->snap_mode() == SnapNormal && length < g) {
+               length = g - one_tick;
+       }
+
+       double const length_beats = max (one_tick, _region_view->region_frames_to_region_beats (length));
+
+       _region_view->create_note_at (start, _drag_rect->property_y1(), length_beats, false);
 }
 
+double
+NoteCreateDrag::y_to_region (double y) const
+{
+       double x = 0;
+       _region_view->get_canvas_group()->w2i (x, y);
+       return y;
+}
+
+void
+NoteCreateDrag::aborted (bool)
+{
+       
+}