Add 'controls' item to plugin insert context menu to always show Ardour generated...
[ardour.git] / gtk2_ardour / editor_drag.cc
index daebea1cfeeb4963cf0336c6fdc136f7a7604209..1784e59944ad0ab13ba5d4a6290115857dc37811 100644 (file)
@@ -32,6 +32,8 @@
 #include "ardour/session.h"
 #include "ardour/dB.h"
 #include "ardour/region_factory.h"
+#include "ardour/operations.h"
+
 #include "editor.h"
 #include "i18n.h"
 #include "keyboard.h"
@@ -52,6 +54,7 @@
 #include "debug.h"
 #include "editor_cursors.h"
 #include "mouse_cursors.h"
+#include "verbose_cursor.h"
 
 using namespace std;
 using namespace ARDOUR;
@@ -89,8 +92,12 @@ DragManager::abort ()
                delete *i;
        }
 
-       _drags.clear ();
+       if (!_drags.empty ()) {
+               _editor->set_follow_playhead (_old_follow_playhead, false);
+       }
 
+       _drags.clear ();
+       
        _ending = false;
 }
 
@@ -112,6 +119,10 @@ DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
 void
 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) {
@@ -139,6 +150,8 @@ DragManager::end_grab (GdkEvent* e)
        _drags.clear ();
 
        _ending = false;
+
+       _editor->set_follow_playhead (_old_follow_playhead, false);
        
        return r;
 }
@@ -220,6 +233,7 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
        }
 
        _raw_grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
+       setup_pointer_frame_offset ();
        _grab_frame = adjusted_frame (_raw_grab_frame, event);
        _last_pointer_frame = _grab_frame;
        _last_pointer_x = _grab_x;
@@ -262,7 +276,7 @@ Drag::end_grab (GdkEvent* event)
 
        finished (event, _move_threshold_passed);
 
-       _editor->hide_verbose_canvas_cursor();
+       _editor->verbose_cursor()->hide ();
 
        return _move_threshold_passed;
 }
@@ -293,8 +307,9 @@ bool
 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
 {
        /* check to see if we have moved in any way that matters since the last motion event */
-       if ( (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
-            (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
+       if (_move_threshold_passed &&
+           (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
+           (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
                return false;
        }
 
@@ -304,7 +319,7 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
 
        if (!from_autoscroll && !_move_threshold_passed) {
 
-               bool const xp = (::llabs (adjusted_current_frame (event) - _grab_frame) >= threshold.first);
+               bool const 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()));
@@ -337,12 +352,49 @@ Drag::abort ()
                _item->ungrab (0);
        }
 
-       aborted ();
+       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);
@@ -429,7 +481,7 @@ 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 ());
        _last_pointer_time_axis_view = find_time_axis_view (tv.first);
@@ -475,13 +527,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 */
@@ -539,7 +591,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        /* 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;
        }
 
@@ -681,7 +733,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        }
 
        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;
@@ -819,9 +871,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 */
@@ -895,7 +947,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(); ) {
@@ -1122,7 +1174,7 @@ RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & pl
        for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
                StatefulDiffCommand* c = new StatefulDiffCommand (*i);
                if (!c->empty()) {
-                       _editor->session()->add_command (new StatefulDiffCommand (*i));
+                       _editor->session()->add_command (c);
                } else {
                        delete c;
                }
@@ -1131,7 +1183,7 @@ RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & pl
 
 
 void
-RegionMoveDrag::aborted ()
+RegionMoveDrag::aborted (bool movement_occurred)
 {
        if (_copy) {
 
@@ -1142,12 +1194,12 @@ RegionMoveDrag::aborted ()
                _views.clear ();
 
        } else {
-               RegionMotionDrag::aborted ();
+               RegionMotionDrag::aborted (movement_occurred);
        }
 }
 
 void
-RegionMotionDrag::aborted ()
+RegionMotionDrag::aborted (bool)
 {
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                RegionView* rv = i->view;
@@ -1164,7 +1216,10 @@ RegionMotionDrag::aborted ()
 
        _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)
@@ -1181,10 +1236,8 @@ RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p,
 }
 
 void
-RegionMoveDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
+RegionMoveDrag::setup_pointer_frame_offset ()
 {
-       RegionMotionDrag::start_grab (event, c);
-
        _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
 }
 
@@ -1219,7 +1272,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));
@@ -1231,7 +1284,7 @@ RegionInsertDrag::finished (GdkEvent *, bool)
 }
 
 void
-RegionInsertDrag::aborted ()
+RegionInsertDrag::aborted (bool)
 {
        delete _primary;
        _primary = 0;
@@ -1271,7 +1324,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;
        }
 
@@ -1330,7 +1383,7 @@ RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RegionSpliceDrag::aborted ()
+RegionSpliceDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -1349,6 +1402,7 @@ 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);
@@ -1356,8 +1410,14 @@ RegionCreateDrag::motion (GdkEvent* event, bool first_move)
                                _region->set_position (f, this);
                        }
                         
-                       /* again, don't use a zero-length region (see above) */
-                       framecnt_t const len = abs (f - grab_frame ());
+                       /* 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, this);
                }
        }
@@ -1368,6 +1428,8 @@ RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
 {
        if (!movement_occurred) {
                add_region ();
+       } else {
+               _view->playlist()->thaw ();
        }
 
        if (_region) {
@@ -1391,8 +1453,12 @@ RegionCreateDrag::add_region ()
 }
 
 void
-RegionCreateDrag::aborted ()
+RegionCreateDrag::aborted (bool)
 {
+       if (_region) {
+               _view->playlist()->thaw ();
+       }
+
        /* XXX */
 }
 
@@ -1407,7 +1473,7 @@ void
 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
 {
        Gdk::Cursor* cursor;
-       ArdourCanvas::CanvasNote* cnote = dynamic_cast<ArdourCanvas::CanvasNote*>(_item);
+       ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item);
        float x_fraction = cnote->mouse_x_fraction ();
 
        if (x_fraction > 0.0 && x_fraction < 0.25) {
@@ -1465,7 +1531,7 @@ NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
 {
        MidiRegionSelection& ms (_editor->get_selection().midi_regions);
        for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
-               (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNote*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
+               (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
        }
 }
 
@@ -1474,12 +1540,12 @@ 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::CanvasNote*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
+               (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
        }
 }
 
 void
-NoteResizeDrag::aborted ()
+NoteResizeDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -1503,14 +1569,13 @@ RegionGainDrag::finished (GdkEvent *, bool)
 }
 
 void
-RegionGainDrag::aborted ()
+RegionGainDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
 
 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
        : RegionDrag (e, i, p, v)
-       , _have_transaction (false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
 }
@@ -1551,16 +1616,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;
        }
 
@@ -1583,7 +1648,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                speed = tv->track()->speed();
        }
 
-       framecnt_t const dt = adjusted_current_frame (event) - grab_frame ();
+       framecnt_t const dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
 
        if (first_move) {
 
@@ -1602,13 +1667,12 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                }
 
                _editor->begin_reversible_command (trim_type);
-               _have_transaction = true;
 
                for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                        RegionView* rv = i->view;
                        rv->fake_set_opaque (false);
                        rv->enable_display (false);
-                       rv->region()->clear_changes ();
+                       rv->region()->playlist()->clear_owned_changes ();
 
                        AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
 
@@ -1674,13 +1738,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;
        }
 }
@@ -1692,16 +1756,38 @@ 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 ();
+                       }
+               }
+               
                if (!_editor->selection->selected (_primary)) {
                        _primary->thaw_after_trim ();
                } else {
 
+                       set<boost::shared_ptr<Playlist> > diffed_playlists;
+
                        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                               i->view->thaw_after_trim ();
+                                i->view->thaw_after_trim ();
                                i->view->enable_display (true);
                                i->view->fake_set_opaque (true);
-                               if (_have_transaction) {
-                                       _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
+
+                               /* Trimming one region may affect others on the playlist, so we need
+                                  to get undo Commands from the whole playlist rather than just the
+                                  region.  Use diffed_playlists to make sure we don't diff a given
+                                  playlist more than once.
+                               */
+                               boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
+                               if (diffed_playlists.find (p) == diffed_playlists.end()) {
+                                       vector<Command*> cmds;
+                                       p->rdiff (cmds);
+                                       _editor->session()->add_commands (cmds);
+                                       diffed_playlists.insert (p);
                                }
                        }
                }
@@ -1710,10 +1796,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                }
 
                _editor->motion_frozen_playlists.clear ();
-
-               if (_have_transaction) {
-                       _editor->commit_reversible_command();
-               }
+               _editor->commit_reversible_command();
 
        } else {
                /* no mouse movement */
@@ -1730,7 +1813,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-TrimDrag::aborted ()
+TrimDrag::aborted (bool movement_occurred)
 {
        /* Our motion method is changing model state, so use the Undo system
           to cancel.  Perhaps not ideal, as this will leave an Undo point
@@ -1739,7 +1822,7 @@ TrimDrag::aborted ()
 
        finished (0, true);
        
-       if (_have_transaction) {
+       if (movement_occurred) {
                _editor->undo ();
        }
 
@@ -1748,6 +1831,30 @@ TrimDrag::aborted ()
        }
 }
 
+void
+TrimDrag::setup_pointer_frame_offset ()
+{
+       list<DraggingView>::iterator i = _views.begin ();
+       while (i != _views.end() && i->view != _primary) {
+               ++i;
+       }
+
+       if (i == _views.end()) {
+               return;
+       }
+
+       switch (_operation) {
+       case StartTrim:
+               _pointer_frame_offset = raw_grab_frame() - i->initial_position;
+               break;
+       case EndTrim:
+               _pointer_frame_offset = raw_grab_frame() - i->initial_end;
+               break;
+       case ContentsTrim:
+               break;
+       }
+}
+
 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
        : Drag (e, i),
          _copy (c)
@@ -1770,7 +1877,6 @@ MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                MeterMarker* new_marker = new MeterMarker (
                        *_editor,
                        *_editor->meter_group,
-                       *_editor->cursor_group,
                        ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
                        name,
                        *new MeterSection (_marker->meter())
@@ -1791,9 +1897,13 @@ MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        Drag::start_grab (event, cursor);
 
-       _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
+       show_verbose_cursor_time (adjusted_current_frame(event));
+}
 
-       _editor->show_verbose_time_cursor (adjusted_current_frame(event), 10);
+void
+MeterMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
 }
 
 void
@@ -1803,7 +1913,7 @@ MeterMarkerDrag::motion (GdkEvent* event, bool)
 
        _marker->set_position (pf);
        
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -1815,7 +1925,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
-       BBT_Time when;
+       Timecode::BBT_Time when;
 
        TempoMap& map (_editor->session()->tempo_map());
        map.bbt_time (last_pointer_frame(), when);
@@ -1842,7 +1952,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-MeterMarkerDrag::aborted ()
+MeterMarkerDrag::aborted (bool)
 {
        _marker->set_position (_marker->meter().frame ());
 }
@@ -1870,7 +1980,6 @@ TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                TempoMarker* new_marker = new TempoMarker (
                        *_editor,
                        *_editor->tempo_group,
-                       *_editor->cursor_group,
                        ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
                        name,
                        *new TempoSection (_marker->tempo())
@@ -1883,16 +1992,21 @@ TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        Drag::start_grab (event, cursor);
 
-       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
+void
+TempoMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
+}      
+
 void
 TempoMarkerDrag::motion (GdkEvent* event, bool)
 {
        framepos_t const pf = adjusted_current_frame (event);
        _marker->set_position (pf);
-       _editor->show_verbose_time_cursor (pf, 10);
+       show_verbose_cursor_time (pf);
 }
 
 void
@@ -1904,7 +2018,7 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
-       BBT_Time when;
+       Timecode::BBT_Time when;
 
        TempoMap& map (_editor->session()->tempo_map());
        map.bbt_time (last_pointer_frame(), when);
@@ -1931,7 +2045,7 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-TempoMarkerDrag::aborted ()
+TempoMarkerDrag::aborted (bool)
 {
        _marker->set_position (_marker->tempo().frame());
 }
@@ -1941,52 +2055,56 @@ CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
          _stop (s)
 {
        DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
-       
-       _cursor = reinterpret_cast<EditorCursor*> (_item->get_data ("cursor"));
-       assert (_cursor);
 }
 
+/** Do all the things we do when dragging the playhead to make it look as though
+ *  we have located, without actually doing the locate (because that would cause
+ *  the diskstream buffers to be refilled, which is too slow).
+ */
 void
-CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
+CursorDrag::fake_locate (framepos_t t)
 {
-       Drag::start_grab (event, c);
-
-       if (!_stop) {
-
-               framepos_t where = _editor->event_frame (event, 0, 0);
-
-               _editor->snap_to_with_modifier (where, event);
-               _editor->playhead_cursor->set_position (where);
-
+       _editor->playhead_cursor->set_position (t);
+       
+       Session* s = _editor->session ();
+       if (s->timecode_transmission_suspended ()) {
+               framepos_t const f = _editor->playhead_cursor->current_frame;
+               s->send_mmc_locate (f);
+               s->send_full_time_code (f);
        }
 
-       if (_cursor == _editor->playhead_cursor) {
-               _editor->_dragging_playhead = true;
-
-               Session* s = _editor->session ();
-
-               if (s) {
-                       if (_was_rolling && _stop) {
-                               s->request_stop ();
-                       }
+       show_verbose_cursor_time (t);
+       _editor->UpdateAllTransportClocks (t);
+}
 
-                       if (s->is_auditioning()) {
-                               s->cancel_audition ();
-                       }
+void
+CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
+{
+       Drag::start_grab (event, c);
 
-                       s->request_suspend_timecode_transmission ();
+       framepos_t where = _editor->event_frame (event, 0, 0);
+       _editor->snap_to_with_modifier (where, event);
 
-                       if (s->timecode_transmission_suspended ()) {
-                               framepos_t const f = _editor->playhead_cursor->current_frame;
-                               s->send_mmc_locate (f);
-                               s->send_full_time_code (f);
-                       }
+       _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 */
                }
        }
-
-       _pointer_frame_offset = raw_grab_frame() - _cursor->current_frame;
-
-       _editor->show_verbose_time_cursor (_cursor->current_frame, 10);
+       
+       fake_locate (where);
 }
 
 void
@@ -1998,22 +2116,11 @@ CursorDrag::motion (GdkEvent* event, bool)
                return;
        }
 
-       _cursor->set_position (adjusted_frame);
-
-       _editor->show_verbose_time_cursor (_cursor->current_frame, 10);
-
-       Session* s = _editor->session ();
-       if (s && _item == &_editor->playhead_cursor->canvas_item && s->timecode_transmission_suspended ()) {
-               framepos_t const f = _editor->playhead_cursor->current_frame;
-               s->send_mmc_locate (f);
-               s->send_full_time_code (f);
-       }
+       fake_locate (adjusted_frame);
        
-
 #ifdef GTKOSX
        _editor->update_canvas_now ();
 #endif
-       _editor->UpdateAllTransportClocks (_cursor->current_frame);
 }
 
 void
@@ -2027,25 +2134,23 @@ CursorDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
-       if (_item == &_editor->playhead_cursor->canvas_item) {
-               Session* s = _editor->session ();
-               if (s) {
-                       s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
-                       _editor->_pending_locate_request = true;
-                       s->request_resume_timecode_transmission ();
-               }
+       Session* s = _editor->session ();
+       if (s) {
+               s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
+               _editor->_pending_locate_request = true;
+               s->request_resume_timecode_transmission ();
        }
 }
 
 void
-CursorDrag::aborted ()
+CursorDrag::aborted (bool)
 {
        if (_editor->_dragging_playhead) {
                _editor->session()->request_resume_timecode_transmission ();
                _editor->_dragging_playhead = false;
        }
        
-       _cursor->set_position (adjusted_frame (grab_frame (), 0, false));
+       _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
 }
 
 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
@@ -2062,12 +2167,19 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
        boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
 
-       _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
-       _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);
 }
 
+void
+FadeInDrag::setup_pointer_frame_offset ()
+{
+       AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
+       boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
+       _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
+}
+
 void
 FadeInDrag::motion (GdkEvent* event, bool)
 {
@@ -2097,7 +2209,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
@@ -2146,7 +2258,7 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-FadeInDrag::aborted ()
+FadeInDrag::aborted (bool)
 {
        for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
                AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
@@ -2174,12 +2286,19 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        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());
-       _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);
 }
 
+void
+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)
 {
@@ -2211,7 +2330,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
@@ -2262,7 +2381,7 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-FadeOutDrag::aborted ()
+FadeOutDrag::aborted (bool)
 {
        for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
                AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
@@ -2286,13 +2405,6 @@ MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
 
        _points.push_back (Gnome::Art::Point (0, 0));
        _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
-
-       _line = new ArdourCanvas::Line (*_editor->timebar_group);
-       _line->property_width_pixels() = 1;
-       _line->property_points () = _points;
-       _line->hide ();
-
-       _line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MarkerDragLine.get();
 }
 
 MarkerDrag::~MarkerDrag ()
@@ -2312,17 +2424,15 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        Location *location = _editor->find_location_from_marker (_marker, is_start);
        _editor->_dragging_edit_point = true;
 
-       _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
-
        update_item (location);
 
        // _drag_line->show();
        // _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);
@@ -2379,6 +2489,14 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        }
 }
 
+void
+MarkerDrag::setup_pointer_frame_offset ()
+{
+       bool is_start;
+       Location *location = _editor->find_location_from_marker (_marker, is_start);
+       _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
+}
+
 void
 MarkerDrag::motion (GdkEvent* event, bool)
 {
@@ -2393,10 +2511,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;
        }
@@ -2490,7 +2604,7 @@ MarkerDrag::motion (GdkEvent* event, bool)
                                        copy_location->set_end (new_end);
                                } else  if (new_start < copy_location->end()) {
                                        copy_location->set_start (new_start);
-                               } else {
+                               } else if (newframe > 0) {
                                        _editor->snap_to (next, 1, true);
                                        copy_location->set_end (next);
                                        copy_location->set_start (newframe);
@@ -2522,7 +2636,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 ();
@@ -2588,12 +2702,10 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        XMLNode &after = _editor->session()->locations()->get_state();
        _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
        _editor->commit_reversible_command ();
-
-       _line->hide();
 }
 
 void
-MarkerDrag::aborted ()
+MarkerDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -2601,11 +2713,7 @@ MarkerDrag::aborted ()
 void
 MarkerDrag::update_item (Location* location)
 {
-       double const x1 = _editor->frame_to_pixel (location->start());
-
-       _points.front().set_x(x1);
-       _points.back().set_x(x1);
-       _line->property_points() = _points;
+        /* noop */
 }
 
 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
@@ -2634,10 +2742,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
@@ -2693,7 +2801,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
@@ -2716,7 +2824,7 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-ControlPointDrag::aborted ()
+ControlPointDrag::aborted (bool)
 {
        _point->line().reset ();
 }
@@ -2779,10 +2887,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
@@ -2814,7 +2922,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
@@ -2826,7 +2934,7 @@ LineDrag::finished (GdkEvent* event, bool)
 }
 
 void
-LineDrag::aborted ()
+LineDrag::aborted (bool)
 {
        _line->reset ();
 }
@@ -2844,7 +2952,7 @@ 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. */
@@ -2857,10 +2965,10 @@ 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());
 }
 
@@ -2882,21 +2990,34 @@ FeatureLineDrag::motion (GdkEvent*, bool)
                cx = 0;
        }
        
-       _line->property_x1() = cx; 
-       _line->property_x2() = cx;
+       ArdourCanvas::Points points;
+       
+       double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
 
-       _before = _line->property_x1();
+       _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
-FeatureLineDrag::aborted ()
+FeatureLineDrag::aborted (bool)
 {
        //_line->reset ();
 }
@@ -2911,7 +3032,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
@@ -2961,7 +3082,7 @@ 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);
        }
 }
 
@@ -2983,19 +3104,16 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
 
 
                Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
-               bool committed;
 
                _editor->begin_reversible_command (_("rubberband selection"));
 
                if (grab_frame() < last_pointer_frame()) {
-                       committed = _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
+                       _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);
+                       _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
                }
 
-               if (!committed) {
-                       _editor->commit_reversible_command ();
-               }
+               _editor->commit_reversible_command ();
 
        } else {
                if (!getenv("ARDOUR_SAE")) {
@@ -3010,7 +3128,7 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RubberbandSelectDrag::aborted ()
+RubberbandSelectDrag::aborted (bool)
 {
        _editor->rubberband_rect->hide ();
 }
@@ -3026,7 +3144,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
@@ -3040,7 +3158,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
@@ -3068,8 +3186,6 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 #endif
 
-       _editor->begin_reversible_command (_("timestretch"));
-
        // XXX how do timeFX on multiple regions ?
 
        RegionSelection rs;
@@ -3081,7 +3197,7 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 }
 
 void
-TimeFXDrag::aborted ()
+TimeFXDrag::aborted (bool)
 {
        _primary->get_time_axis_view().hide_timestretch ();
 }
@@ -3114,7 +3230,7 @@ ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 }
 
 void
-ScrubDrag::aborted ()
+ScrubDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3132,9 +3248,6 @@ SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
 void
 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
 {
-       framepos_t start = 0;
-       framepos_t end = 0;
-
        if (_editor->session() == 0) {
                return;
        }
@@ -3157,8 +3270,6 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
                        _editor->clicked_axisview->order_selection_trims (_item, true);
                }
                Drag::start_grab (event, _editor->cursors()->left_side_trim);
-               start = _editor->selection->time[_editor->clicked_selection].start;
-               _pointer_frame_offset = raw_grab_frame() - start;
                break;
 
        case SelectionEndTrim:
@@ -3166,26 +3277,41 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
                        _editor->clicked_axisview->order_selection_trims (_item, false);
                }
                Drag::start_grab (event, _editor->cursors()->right_side_trim);
-               end = _editor->selection->time[_editor->clicked_selection].end;
-               _pointer_frame_offset = raw_grab_frame() - end;
                break;
 
        case SelectionMove:
-               start = _editor->selection->time[_editor->clicked_selection].start;
                Drag::start_grab (event, cursor);
-               _pointer_frame_offset = raw_grab_frame() - start;
                break;
        }
 
        if (_operation == SelectionMove) {
-               _editor->show_verbose_time_cursor (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 ();
 }
 
+void
+SelectionDrag::setup_pointer_frame_offset ()
+{
+       switch (_operation) {
+       case CreateSelection:
+               _pointer_frame_offset = 0;
+               break;
+
+       case SelectionStartTrim:
+       case SelectionMove:
+               _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
+               break;
+
+       case SelectionEndTrim:
+               _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
+               break;
+       }
+}
+
 void
 SelectionDrag::motion (GdkEvent* event, bool first_move)
 {
@@ -3327,9 +3453,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);
        }
 }
 
@@ -3372,7 +3498,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-SelectionDrag::aborted ()
+SelectionDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3421,7 +3547,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
@@ -3493,7 +3619,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);
 
 }
 
@@ -3578,7 +3704,7 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RangeMarkerBarDrag::aborted ()
+RangeMarkerBarDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3611,7 +3737,7 @@ MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
                _zoom_out = false;
        }
                
-       _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
+       show_verbose_cursor_time (adjusted_current_frame (event));
 }
 
 void
@@ -3643,7 +3769,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);
        }
 }
 
@@ -3659,14 +3785,18 @@ 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();
 }
 
 void
-MouseZoomDrag::aborted ()
+MouseZoomDrag::aborted (bool)
 {
        _editor->zoom_rect->hide ();
 }
@@ -3720,7 +3850,12 @@ NoteDrag::total_dx () const
        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 st = n + dx;
+
+       /* prevent the note being dragged earlier than the region's position */
+       if (st < 0) {
+               st = 0;
+       }
 
        /* snap and return corresponding delta */
        return _region->snap_frame_to_frame (st) - n;
@@ -3774,7 +3909,7 @@ NoteDrag::motion (GdkEvent *, bool)
                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);
+               show_verbose_cursor_text (buf);
        }
 }
 
@@ -3808,7 +3943,7 @@ NoteDrag::finished (GdkEvent* ev, bool moved)
 }
 
 void
-NoteDrag::aborted ()
+NoteDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -4011,7 +4146,7 @@ AutomationRangeDrag::finished (GdkEvent* event, bool)
 }
 
 void
-AutomationRangeDrag::aborted ()
+AutomationRangeDrag::aborted (bool)
 {
        for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
                i->line->clear_always_in_view ();
@@ -4029,3 +4164,59 @@ DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
        initial_position = v->region()->position ();
        initial_end = v->region()->position () + v->region()->length ();
 }
+
+PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
+       : Drag (e, i)
+       , _region_view (r)
+       , _patch_change (i)
+       , _cumulative_dx (0)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
+}
+
+void
+PatchChangeDrag::motion (GdkEvent* ev, bool)
+{
+       framepos_t f = adjusted_current_frame (ev);
+       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);
+       _cumulative_dx = dxu;
+}
+
+void
+PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
+{
+       if (!movement_occurred) {
+               return;
+       }
+
+       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())
+               );
+}
+
+void
+PatchChangeDrag::aborted (bool)
+{
+       _patch_change->move (-_cumulative_dx, 0);
+}
+
+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();
+}
+