Prevent region drags with the middle button in internal edit mode (#3869).
[ardour.git] / gtk2_ardour / editor_drag.cc
index fb3ef58802ee841b90be32cb0899302ea6f624af..00313d06720b41d00cba917179c48c96f185fa18 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"
@@ -91,6 +93,8 @@ DragManager::abort ()
 
        _drags.clear ();
 
+       _editor->set_follow_playhead (_old_follow_playhead, false);
+       
        _ending = false;
 }
 
@@ -112,6 +116,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 +147,8 @@ DragManager::end_grab (GdkEvent* e)
        _drags.clear ();
 
        _ending = false;
+
+       _editor->set_follow_playhead (_old_follow_playhead, false);
        
        return r;
 }
@@ -220,6 +230,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;
@@ -293,8 +304,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 +316,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,7 +349,7 @@ Drag::abort ()
                _item->ungrab (0);
        }
 
-       aborted ();
+       aborted (_move_threshold_passed);
 
        _editor->stop_canvas_autoscroll ();
        _editor->hide_verbose_canvas_cursor ();
@@ -895,7 +907,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 +1134,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 +1143,7 @@ RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & pl
 
 
 void
-RegionMoveDrag::aborted ()
+RegionMoveDrag::aborted (bool movement_occurred)
 {
        if (_copy) {
 
@@ -1142,12 +1154,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 +1176,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 +1196,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 +1232,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 +1244,7 @@ RegionInsertDrag::finished (GdkEvent *, bool)
 }
 
 void
-RegionInsertDrag::aborted ()
+RegionInsertDrag::aborted (bool)
 {
        delete _primary;
        _primary = 0;
@@ -1330,7 +1343,7 @@ RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RegionSpliceDrag::aborted ()
+RegionSpliceDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -1349,6 +1362,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);
@@ -1368,6 +1382,8 @@ RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
 {
        if (!movement_occurred) {
                add_region ();
+       } else {
+               _view->playlist()->thaw ();
        }
 
        if (_region) {
@@ -1391,8 +1407,12 @@ RegionCreateDrag::add_region ()
 }
 
 void
-RegionCreateDrag::aborted ()
+RegionCreateDrag::aborted (bool)
 {
+       if (_region) {
+               _view->playlist()->thaw ();
+       }
+
        /* XXX */
 }
 
@@ -1407,7 +1427,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 +1485,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 +1494,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 +1523,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");
 }
@@ -1533,6 +1552,7 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
        framepos_t const pf = adjusted_current_frame (event);
 
        if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
+               /* Move the contents of the region around without changing the region bounds */
                _operation = ContentsTrim;
                Drag::start_grab (event, _editor->cursors()->trimmer);
        } else {
@@ -1582,7 +1602,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) {
 
@@ -1601,13 +1621,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);
 
@@ -1691,16 +1710,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);
                                }
                        }
                }
@@ -1709,10 +1750,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 */
@@ -1723,12 +1761,13 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                if (_operation == StartTrim) {
                        i->view->trim_front_ending ();
                }
+               
                i->view->region()->resume_property_changes ();
        }
 }
 
 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
@@ -1737,7 +1776,7 @@ TrimDrag::aborted ()
 
        finished (0, true);
        
-       if (_have_transaction) {
+       if (movement_occurred) {
                _editor->undo ();
        }
 
@@ -1746,6 +1785,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)
@@ -1768,7 +1831,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())
@@ -1789,11 +1851,15 @@ MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        Drag::start_grab (event, cursor);
 
-       _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
-
        _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
 MeterMarkerDrag::motion (GdkEvent* event, bool)
 {
@@ -1813,7 +1879,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);
@@ -1840,7 +1906,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-MeterMarkerDrag::aborted ()
+MeterMarkerDrag::aborted (bool)
 {
        _marker->set_position (_marker->meter().frame ());
 }
@@ -1868,7 +1934,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())
@@ -1881,10 +1946,15 @@ 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);
 }
 
+void
+TempoMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
+}      
+
 void
 TempoMarkerDrag::motion (GdkEvent* event, bool)
 {
@@ -1902,7 +1972,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);
@@ -1929,7 +1999,7 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-TempoMarkerDrag::aborted ()
+TempoMarkerDrag::aborted (bool)
 {
        _marker->set_position (_marker->tempo().frame());
 }
@@ -1939,52 +2009,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 ();
-                       }
+       _editor->show_verbose_time_cursor (t, 10);
+       _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
@@ -1996,22 +2070,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
@@ -2025,25 +2088,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)
@@ -2060,12 +2121,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);
        
        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)
 {
@@ -2144,7 +2212,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);
@@ -2172,12 +2240,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);
        
        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)
 {
@@ -2260,7 +2335,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);
@@ -2284,13 +2359,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 ()
@@ -2310,8 +2378,6 @@ 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();
@@ -2377,6 +2443,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)
 {
@@ -2391,10 +2465,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;
        }
@@ -2488,7 +2558,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);
@@ -2586,12 +2656,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 */
 }
@@ -2599,11 +2667,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)
@@ -2714,7 +2778,7 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-ControlPointDrag::aborted ()
+ControlPointDrag::aborted (bool)
 {
        _point->line().reset ();
 }
@@ -2824,7 +2888,7 @@ LineDrag::finished (GdkEvent* event, bool)
 }
 
 void
-LineDrag::aborted ()
+LineDrag::aborted (bool)
 {
        _line->reset ();
 }
@@ -2842,7 +2906,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. */
@@ -2855,10 +2919,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());
 }
 
@@ -2880,21 +2944,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;
+
+       _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 = _line->property_x1();
+       _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 ();
 }
@@ -2981,19 +3058,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")) {
@@ -3008,7 +3082,7 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RubberbandSelectDrag::aborted ()
+RubberbandSelectDrag::aborted (bool)
 {
        _editor->rubberband_rect->hide ();
 }
@@ -3079,7 +3153,7 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 }
 
 void
-TimeFXDrag::aborted ()
+TimeFXDrag::aborted (bool)
 {
        _primary->get_time_axis_view().hide_timestretch ();
 }
@@ -3112,7 +3186,7 @@ ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
 }
 
 void
-ScrubDrag::aborted ()
+ScrubDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3130,9 +3204,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;
        }
@@ -3155,8 +3226,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:
@@ -3164,19 +3233,15 @@ 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);
+               _editor->show_verbose_time_cursor (_editor->selection->time[_editor->clicked_selection].start, 10);
        } else {
                _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
        }
@@ -3184,6 +3249,25 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
        _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)
 {
@@ -3370,7 +3454,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-SelectionDrag::aborted ()
+SelectionDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3576,7 +3660,7 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
 }
 
 void
-RangeMarkerBarDrag::aborted ()
+RangeMarkerBarDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -3657,14 +3741,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 ();
 }
@@ -3806,7 +3894,7 @@ NoteDrag::finished (GdkEvent* ev, bool moved)
 }
 
 void
-NoteDrag::aborted ()
+NoteDrag::aborted (bool)
 {
        /* XXX: TODO */
 }
@@ -4009,7 +4097,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 ();
@@ -4027,3 +4115,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();
+}
+