Modify region trim cursor if a region can only be trimmed in one direction.
[ardour.git] / gtk2_ardour / editor_mouse.cc
index 4f85f2084663dc68505e3b54ce5e6145c1a0fcce..653fdc320497e915592b8e39656f7a26396dc47e 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2000-2001 Paul Davis 
+    Copyright (C) 2000-2001 Paul Davis
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
 #include <gtkmm2ext/tearoff.h>
 #include "pbd/memento_command.h"
 #include "pbd/basename.h"
+#include "pbd/stateful_diff_command.h"
 
 #include "ardour_ui.h"
 #include "actions.h"
+#include "canvas-note.h"
 #include "editor.h"
 #include "time_axis_view.h"
 #include "audio_time_axis.h"
@@ -52,6 +54,8 @@
 #include "rgb_macros.h"
 #include "control_point_dialog.h"
 #include "editor_drag.h"
+#include "automation_region_view.h"
+#include "edit_note_dialog.h"
 
 #include "ardour/types.h"
 #include "ardour/profile.h"
@@ -67,6 +71,7 @@
 #include "ardour/utils.h"
 #include "ardour/region_factory.h"
 #include "ardour/source_factory.h"
+#include "ardour/session.h"
 
 #include <bitset>
 
 using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
-using namespace sigc;
 using namespace Gtk;
 using namespace Editing;
+using Gtkmm2ext::Keyboard;
 
 bool
-Editor::mouse_frame (nframes64_t& where, bool& in_track_canvas) const
+Editor::mouse_frame (framepos_t& where, bool& in_track_canvas) const
 {
        int x, y;
        double wx, wy;
@@ -91,7 +96,7 @@ Editor::mouse_frame (nframes64_t& where, bool& in_track_canvas) const
        if (!canvas_window) {
                return false;
        }
-       
+
        pointer_window = canvas_window->get_pointer (x, y, mask);
 
        if (pointer_window == track_canvas->get_bin_window()) {
@@ -108,12 +113,12 @@ Editor::mouse_frame (nframes64_t& where, bool& in_track_canvas) const
        event.type = GDK_BUTTON_RELEASE;
        event.button.x = wx;
        event.button.y = wy;
-       
+
        where = event_frame (&event, 0, 0);
        return true;
 }
 
-nframes64_t
+framepos_t
 Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
 {
        double cx, cy;
@@ -133,13 +138,11 @@ Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
        case GDK_BUTTON_PRESS:
        case GDK_2BUTTON_PRESS:
        case GDK_3BUTTON_PRESS:
-
                *pcx = event->button.x;
                *pcy = event->button.y;
                _trackview_group->w2i(*pcx, *pcy);
                break;
        case GDK_MOTION_NOTIFY:
-       
                *pcx = event->motion.x;
                *pcy = event->motion.y;
                _trackview_group->w2i(*pcx, *pcy);
@@ -161,7 +164,7 @@ Editor::event_frame (GdkEvent const * event, double* pcx, double* pcy) const
           position is negative (as can be the case with motion events in particular),
           the frame location is always positive.
        */
-       
+
        return pixel_to_frame (*pcx);
 }
 
@@ -172,14 +175,14 @@ Editor::which_grabber_cursor ()
 
        if (_internal_editing) {
                switch (mouse_mode) {
-               case MouseObject:
+               case MouseRange:
                        c = midi_pencil_cursor;
                        break;
-                       
-               case MouseRange:
-                       c = midi_select_cursor;
+
+               case MouseObject:
+                       c = grabber_note_cursor;
                        break;
-                       
+
                case MouseTimeFX:
                        c = midi_resize_cursor;
                        break;
@@ -195,6 +198,10 @@ Editor::which_grabber_cursor ()
                        c = grabber_edit_point_cursor;
                        break;
                default:
+                        boost::shared_ptr<Movable> m = _movable.lock();
+                        if (m && m->locked()) {
+                                c = speaker_cursor;
+                        } 
                        break;
                }
        }
@@ -202,20 +209,43 @@ Editor::which_grabber_cursor ()
        return c;
 }
 
+void
+Editor::set_current_trimmable (boost::shared_ptr<Trimmable> t)
+{
+        boost::shared_ptr<Trimmable> st = _trimmable.lock();
+
+        if (!st || st == t) {
+                _trimmable = t;
+                set_canvas_cursor ();
+        } 
+        
+}
+
+void
+Editor::set_current_movable (boost::shared_ptr<Movable> m)
+{
+        boost::shared_ptr<Movable> sm = _movable.lock();
+
+        if (!sm || sm != m) {
+                _movable = m;
+                set_canvas_cursor ();
+        }
+}
+
 void
 Editor::set_canvas_cursor ()
 {
        if (_internal_editing) {
 
                switch (mouse_mode) {
-               case MouseObject:
+               case MouseRange:
                        current_canvas_cursor = midi_pencil_cursor;
                        break;
-                       
-               case MouseRange:
-                       current_canvas_cursor = midi_select_cursor;
+
+               case MouseObject:
+                       current_canvas_cursor = which_grabber_cursor();
                        break;
-                       
+
                case MouseTimeFX:
                        current_canvas_cursor = midi_resize_cursor;
                        break;
@@ -230,38 +260,61 @@ Editor::set_canvas_cursor ()
                case MouseRange:
                        current_canvas_cursor = selector_cursor;
                        break;
-                       
+
                case MouseObject:
                        current_canvas_cursor = which_grabber_cursor();
                        break;
-                       
+
                case MouseGain:
                        current_canvas_cursor = cross_hair_cursor;
                        break;
-                       
+
                case MouseZoom:
-                       current_canvas_cursor = zoom_cursor;
+                       if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
+                               current_canvas_cursor = zoom_out_cursor;
+                       } else {
+                               current_canvas_cursor = zoom_in_cursor;
+                       }
                        break;
-                       
+
                case MouseTimeFX:
                        current_canvas_cursor = time_fx_cursor; // just use playhead
                        break;
-                       
+
                case MouseAudition:
                        current_canvas_cursor = speaker_cursor;
                        break;
                }
        }
 
-       if (is_drawable()) {
-               track_canvas->get_window()->set_cursor(*current_canvas_cursor);
+       switch (_join_object_range_state) {
+       case JOIN_OBJECT_RANGE_NONE:
+               break;
+       case JOIN_OBJECT_RANGE_OBJECT:
+               current_canvas_cursor = which_grabber_cursor ();
+               break;
+       case JOIN_OBJECT_RANGE_RANGE:
+               current_canvas_cursor = selector_cursor;
+               break;
+       }
+
+       /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */
+       if (join_object_range_button.get_active() && last_item_entered) {
+               if (last_item_entered->property_parent() && (*last_item_entered->property_parent()).get_data (X_("timeselection"))) {
+                       pair<TimeAxisView*, int> tvp = trackview_by_y_position (_last_motion_y + vertical_adjustment.get_value() - canvas_timebars_vsize);
+                       if (dynamic_cast<AutomationTimeAxisView*> (tvp.first)) {
+                               current_canvas_cursor = up_down_cursor;
+                       }
+               }
        }
+
+        set_canvas_cursor (current_canvas_cursor, true);
 }
 
 void
 Editor::set_mouse_mode (MouseMode m, bool force)
 {
-       if (_drag) {
+       if (_drags->active ()) {
                return;
        }
 
@@ -313,32 +366,32 @@ Editor::mouse_mode_toggled (MouseMode m)
        mouse_mode = m;
 
        instant_save ();
-
-       if (mouse_mode != MouseRange) {
-
-               /* in all modes except range, hide the range selection,
-                  show the object (region) selection.
-               */
-
-               for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
-                       (*i)->set_should_show_selection (true);
-               }
-               for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
-                       (*i)->hide_selection ();
-               }
-
-       } else {
-
-               /* 
-                  in range mode,show the range selection.
-               */
-
-               for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
-                       if ((*i)->get_selected()) {
-                               (*i)->show_selection (selection->time);
-                       }
-               }
-       }
+        
+        if (!internal_editing()) {
+                if (mouse_mode != MouseRange && _join_object_range_state == JOIN_OBJECT_RANGE_NONE) {
+                        
+                        /* in all modes except range and joined object/range, hide the range selection,
+                           show the object (region) selection.
+                        */
+                        
+                        for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
+                                (*i)->set_should_show_selection (true);
+                        }
+                        for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+                                (*i)->hide_selection ();
+                        }
+                        
+                } else {
+                        
+                        /*
+                          in range or object/range mode, show the range selection.
+                        */
+                        
+                        for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
+                                (*i)->show_selection (selection->time);
+                        }
+                }
+        }
 
        set_canvas_cursor ();
 }
@@ -379,12 +432,12 @@ Editor::step_mouse_mode (bool next)
                        }
                }
                break;
-       
+
        case MouseGain:
                if (next) set_mouse_mode (MouseTimeFX);
                else set_mouse_mode (MouseZoom);
                break;
-       
+
        case MouseTimeFX:
                if (next) {
                        set_mouse_mode (MouseAudition);
@@ -414,51 +467,62 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
           region alignment.
 
           note: not dbl-click or triple-click
+
+          Also note that there is no region selection in internal edit mode, otherwise
+          for operations operating on the selection (e.g. cut) it is not obvious whether
+          to cut notes or regions.
        */
 
        if (((mouse_mode != MouseObject) &&
+            (_join_object_range_state != JOIN_OBJECT_RANGE_OBJECT) &&
             (mouse_mode != MouseAudition || item_type != RegionItem) &&
             (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
             (mouse_mode != MouseGain) &&
             (mouse_mode != MouseRange)) ||
+           ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3) ||
+           internal_editing()) {
 
-           ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
-               
                return;
        }
 
        if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
 
                if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
-                       
+
                        /* almost no selection action on modified button-2 or button-3 events */
-               
+
                        if (item_type != RegionItem && event->button.button != 2) {
                                return;
                        }
                }
        }
 
-       Selection::Operation op = Keyboard::selection_type (event->button.state);
+       Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
        bool press = (event->type == GDK_BUTTON_PRESS);
 
        // begin_reversible_command (_("select on click"));
-       
+
        switch (item_type) {
        case RegionItem:
-               if (mouse_mode != MouseRange) {
+               if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
                        set_selected_regionview_from_click (press, op, true);
                } else if (event->type == GDK_BUTTON_PRESS) {
-                       set_selected_track_as_side_effect ();
+                       selection->clear_tracks ();
+                       set_selected_track_as_side_effect (op, true);
+               }
+               if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
+                       clicked_selection = select_range_around_region (selection->regions.front());
                }
                break;
-               
+
        case RegionViewNameHighlight:
        case RegionViewName:
-               if (mouse_mode != MouseRange) {
+        case LeftFrameHandle:
+        case RightFrameHandle:
+               if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
                        set_selected_regionview_from_click (press, op, true);
                } else if (event->type == GDK_BUTTON_PRESS) {
-                       set_selected_track_as_side_effect ();
+                       set_selected_track_as_side_effect (op);
                }
                break;
 
@@ -467,33 +531,32 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
        case FadeInItem:
        case FadeOutHandleItem:
        case FadeOutItem:
-               if (mouse_mode != MouseRange) {
+               if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
                        set_selected_regionview_from_click (press, op, true);
                } else if (event->type == GDK_BUTTON_PRESS) {
-                       set_selected_track_as_side_effect ();
+                       set_selected_track_as_side_effect (op);
                }
                break;
 
        case ControlPointItem:
-               set_selected_track_as_side_effect ();
-               if (mouse_mode != MouseRange) {
+               set_selected_track_as_side_effect (op, true);
+               if (mouse_mode != MouseRange || _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
                        set_selected_control_point_from_click (op, false);
                }
                break;
-               
+
        case StreamItem:
-               /* for context click or range selection, select track */
+               /* for context click, select track */
                if (event->button.button == 3) {
-                       set_selected_track_as_side_effect ();
-               } else if (event->type == GDK_BUTTON_PRESS && mouse_mode == MouseRange) {
-                       set_selected_track_as_side_effect ();
+                       selection->clear_tracks ();
+                       set_selected_track_as_side_effect (op, true);
                }
                break;
-                   
+
        case AutomationTrackItem:
-               set_selected_track_as_side_effect (true);
+               set_selected_track_as_side_effect (op, true);
                break;
-               
+
        default:
                break;
        }
@@ -502,352 +565,469 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
 bool
 Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
-       if (_drag) {
-               _drag->item()->ungrab (event->button.time);
-       }
-       
        /* single mouse clicks on any of these item types operate
           independent of mouse mode, mostly because they are
           not on the main track canvas or because we want
           them to be modeless.
        */
-       
+
        switch (item_type) {
        case PlayheadCursorItem:
-               assert (_drag == 0);
-               _drag = new CursorDrag (this, item, true);
-               _drag->start_grab (event);
+               _drags->set (new CursorDrag (this, item, true), event);
                return true;
-               
+
        case MarkerItem:
                if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
                        hide_marker (item, event);
                } else {
-                       assert (_drag == 0);
-                       _drag = new MarkerDrag (this, item);
-                       _drag->start_grab (event);
+                       _drags->set (new MarkerDrag (this, item), event);
                }
                return true;
-               
+
        case TempoMarkerItem:
-               assert (_drag == 0);
-               _drag = new TempoMarkerDrag (
-                       this,
-                       item,
-                       Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
+               _drags->set (
+                       new TempoMarkerDrag (
+                               this,
+                               item,
+                               Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
+                               ),
+                       event
                        );
-               _drag->start_grab (event);
                return true;
-               
+
        case MeterMarkerItem:
-               assert (_drag == 0);
-               _drag = new MeterMarkerDrag (
-                       this,
-                       item, 
-                       Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
+               _drags->set (
+                       new MeterMarkerDrag (
+                               this,
+                               item,
+                               Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)
+                               ),
+                       event
                        );
-               _drag->start_grab (event);
                return true;
-               
+
        case MarkerBarItem:
        case TempoBarItem:
        case MeterBarItem:
                if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
-                       assert (_drag == 0);
-                       _drag = new CursorDrag (this, &playhead_cursor->canvas_item, false);
-                       _drag->start_grab (event);
+                       _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
                }
                return true;
                break;
 
-                               
+
        case RangeMarkerBarItem:
-               assert (_drag == 0);
                if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
-                       _drag = new CursorDrag (this, &playhead_cursor->canvas_item, false);
+                       _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
                } else {
-                       _drag = new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker); 
-               }       
-               _drag->start_grab (event);
+                       _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateRangeMarker), event);
+               }
                return true;
                break;
-               
+
        case CdMarkerBarItem:
-               assert (_drag == 0);
                if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
-                       _drag = new CursorDrag (this, &playhead_cursor->canvas_item, false);
+                       _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
                } else {
-                       _drag = new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker);
+                       _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateCDMarker), event);
                }
-               _drag->start_grab (event);
                return true;
                break;
-               
+
        case TransportMarkerBarItem:
-               assert (_drag == 0);
                if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
-                       _drag = new CursorDrag (this, &playhead_cursor->canvas_item, false);
+                       _drags->set (new CursorDrag (this, &playhead_cursor->canvas_item, false), event);
                } else {
-                       _drag = new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker);
+                       _drags->set (new RangeMarkerBarDrag (this, item, RangeMarkerBarDrag::CreateTransportMarker), event);
                }
-               _drag->start_grab (event);
                return true;
                break;
-               
+
        default:
                break;
        }
-       
-       if (internal_editing()) {
-               assert (_drag == 0);
-               _drag = new RegionCreateDrag (this, item, clicked_axisview);
-               _drag->start_grab (event);
-       } else {
-               switch (mouse_mode) {
-               case MouseRange:
-                       switch (item_type) {
-                       case StartSelectionTrimItem:
-                               assert (_drag == 0);
-                               _drag = new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim);
-                               _drag->start_grab (event);
-                               break;
-                               
-                       case EndSelectionTrimItem:
-                               assert (_drag == 0);
-                               _drag = new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim);
-                               _drag->start_grab (event);
-                               break;
-                               
-                       case SelectionItem:
-                               if (Keyboard::modifier_state_contains 
-                                   (event->button.state, Keyboard::ModifierMask(Keyboard::SecondaryModifier))) {
-                                       // contains and not equals because I can't use alt as a modifier alone.
-                                       start_selection_grab (item, event);
-                               } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
-                                       /* grab selection for moving */
-                                       assert (_drag == 0);
-                                       _drag = new SelectionDrag (this, item, SelectionDrag::SelectionMove);
-                                       _drag->start_grab (event);
-                               } else {
-                                       /* this was debated, but decided the more common action was to
-                                          make a new selection */
-                                       assert (_drag == 0);
-                                       _drag = new SelectionDrag (this, item, SelectionDrag::CreateSelection);
-                                       _drag->start_grab (event);
+
+       if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
+               /* special case: allow trim of range selections in joined object mode;
+                  in theory eff should equal MouseRange in this case, but it doesn't
+                  because entering the range selection canvas item results in entered_regionview
+                  being set to 0, so update_join_object_range_location acts as if we aren't
+                  over a region.
+               */
+               if (item_type == StartSelectionTrimItem) {
+                       _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
+               } else if (item_type == EndSelectionTrimItem) {
+                       _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
+               }
+       }
+
+       Editing::MouseMode eff = effective_mouse_mode ();
+
+       /* special case: allow drag of region fade in/out in object mode with join object/range enabled */
+       if (item_type == FadeInHandleItem || item_type == FadeOutHandleItem) {
+               eff = MouseObject;
+       }
+
+       switch (eff) {
+       case MouseRange:
+               switch (item_type) {
+               case StartSelectionTrimItem:
+                       _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionStartTrim), event);
+                       break;
+
+               case EndSelectionTrimItem:
+                       _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionEndTrim), event);
+                       break;
+
+               case SelectionItem:
+                       if (Keyboard::modifier_state_contains
+                           (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier))) {
+                               // contains and not equals because I can't use alt as a modifier alone.
+                               start_selection_grab (item, event);
+                       } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
+                               /* grab selection for moving */
+                               _drags->set (new SelectionDrag (this, item, SelectionDrag::SelectionMove), event);
+                       } else {
+                               double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
+                               pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
+                               if (tvp.first) {
+                                       AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
+                                       if (join_object_range_button.get_active() && atv) {
+                                               /* smart "join" mode: drag automation */
+                                               _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, up_down_cursor);
+                                       } else {
+                                               /* this was debated, but decided the more common action was to
+                                                  make a new selection */
+                                               _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
+                                       }
                                }
-                               break;
-                               
-                       default:
-                               assert (_drag == 0);
-                               _drag = new SelectionDrag (this, item, SelectionDrag::CreateSelection);
-                               _drag->start_grab (event);
                        }
-                       return true;
                        break;
-                       
-               case MouseObject:
-                       if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
-                           event->type == GDK_BUTTON_PRESS) {
-                               
-                               assert (_drag == 0);
-                               _drag = new RubberbandSelectDrag (this, item);
-                               _drag->start_grab (event);
-                               
-                       } else if (event->type == GDK_BUTTON_PRESS) {
-                               
-                               switch (item_type) {
-                               case FadeInHandleItem:
-                               {
-                                       assert (_drag == 0);
-                                       RegionSelection s = get_equivalent_regions (selection->regions, RouteGroup::Edit);
-                                       _drag = new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s);
-                                       _drag->start_grab (event);
+
+                case NoteItem:
+                        if (internal_editing()) {
+                                /* trim notes if we're in internal edit mode and near the ends of the note */
+                                ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
+                                cerr << "NoteItem button press, cursor = " << current_canvas_cursor << endl;
+                                if (cn->mouse_near_ends()) {
+                                        _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
+                                } else {
+                                        _drags->set (new NoteDrag (this, item), event);
+                                }
+                        }
+                       return true;
+
+                case StreamItem:
+                        if (internal_editing()) {
+                               if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
+                                       _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
                                        return true;
                                }
-                               
-                               case FadeOutHandleItem:
-                               {
-                                       assert (_drag == 0);
-                                       RegionSelection s = get_equivalent_regions (selection->regions, RouteGroup::Edit);
-                                       _drag = new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s);
-                                       _drag->start_grab (event);
+                        } else {
+                               _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
+                               return true;
+                       }
+                        break;
+
+                case RegionViewNameHighlight:
+                case LeftFrameHandle:
+                case RightFrameHandle:
+                        if (!clicked_regionview->region()->locked()) {
+                                RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
+                                _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
+                                return true;
+                        }
+                        break;
+
+               default:
+                        if (!internal_editing()) {
+                                _drags->set (new SelectionDrag (this, item, SelectionDrag::CreateSelection), event);
+                        }
+               }
+               return true;
+               break;
+
+       case MouseObject:
+               switch (item_type) {
+               case NoteItem:
+                       if (internal_editing()) {
+                                ArdourCanvas::CanvasNote* cn = dynamic_cast<ArdourCanvas::CanvasNote*> (item);
+                                if (cn->mouse_near_ends()) {
+                                        _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
+                                } else {
+                                        _drags->set (new NoteDrag (this, item), event);
+                                }
+                               return true;
+                       }
+                       break;
+                       
+               default:
+                       break;
+               }
+
+               if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
+                   event->type == GDK_BUTTON_PRESS) {
+
+                       _drags->set (new RubberbandSelectDrag (this, item), event);
+
+               } else if (event->type == GDK_BUTTON_PRESS) {
+
+                       switch (item_type) {
+                       case FadeInHandleItem:
+                       {
+                               RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
+                               _drags->set (new FadeInDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, fade_in_cursor);
+                               return true;
+                       }
+
+                       case FadeOutHandleItem:
+                       {
+                               RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
+                               _drags->set (new FadeOutDrag (this, item, reinterpret_cast<RegionView*> (item->get_data("regionview")), s), event, fade_out_cursor);
+                               return true;
+                       }
+
+                       case FeatureLineItem:
+                       {                       
+                               if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
+                                       remove_transient(item);
                                        return true;
                                }
                                
-                               case RegionItem:
-                                       if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
-                                               start_region_copy_grab (item, event, clicked_regionview);
-                                       } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
-                                               start_region_brush_grab (item, event, clicked_regionview);
-                                       } else {
-                                               start_region_grab (item, event, clicked_regionview);
-                                       }
+                               _drags->set (new FeatureLineDrag (this, item), event);
+                               return true;
+                               break;
+                       }
+
+                       case RegionItem:
+                               if (dynamic_cast<AutomationRegionView*> (clicked_regionview)) {
+                                       /* click on an automation region view; do nothing here and let the ARV's signal handler
+                                          sort it out.
+                                       */
                                        break;
-                                       
-                               case RegionViewNameHighlight:
-                               {
-                                       assert (_drag == 0);
-                                       RegionSelection s = get_equivalent_regions (selection->regions, RouteGroup::Edit);
-                                       _drag = new TrimDrag (this, item, clicked_regionview, s.by_layer());
-                                       _drag->start_grab (event);
-                                       return true;
+                               }
+
+                               if (internal_editing ()) {
+                                       /* no region drags in internal edit mode */
                                        break;
                                }
                                
-                               case RegionViewName:
-                               {
-                                       /* rename happens on edit clicks */
-                                       assert (_drag == 0);
-                                       RegionSelection s = get_equivalent_regions (selection->regions, RouteGroup::Edit);
-                                       _drag = new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer());
-                                       _drag->start_grab (event);
-                                       return true;
-                                       break;
+                               /* click on a normal region view */
+                               if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
+                                       add_region_copy_drag (item, event, clicked_regionview);
+                               } 
+                               else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
+                                       add_region_brush_drag (item, event, clicked_regionview);
+                               } else {
+                                       add_region_drag (item, event, clicked_regionview);
                                }
                                
-                               case ControlPointItem:
-                                       assert (_drag == 0);
-                                       _drag = new ControlPointDrag (this, item);
-                                       _drag->start_grab (event);
-                                       return true;
-                                       break;
-                                       
-                               case AutomationLineItem:
-                                       assert (_drag == 0);
-                                       _drag = new LineDrag (this, item);
-                                       _drag->start_grab (event);
-                                       return true;
-                                       break;
+                               if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT && !selection->regions.empty()) {
+                                       _drags->add (new SelectionDrag (this, clicked_axisview->get_selection_rect (clicked_selection)->rect, SelectionDrag::SelectionMove));
+                               }
 
-                               case StreamItem:
-                               case AutomationTrackItem:
-                                       assert (_drag == 0);
-                                       _drag = new RubberbandSelectDrag (this, item);
-                                       _drag->start_grab (event);
-                                       break;
-                                       
-#ifdef WITH_CMT
-                               case ImageFrameHandleStartItem:
-                                       imageframe_start_handle_op(item, event) ;
-                                       return(true) ;
-                                       break ;
-                               case ImageFrameHandleEndItem:
-                                       imageframe_end_handle_op(item, event) ;
-                                       return(true) ;
-                                       break ;
-                               case MarkerViewHandleStartItem:
-                                       markerview_item_start_handle_op(item, event) ;
-                                       return(true) ;
-                                       break ;
-                               case MarkerViewHandleEndItem:
-                                       markerview_item_end_handle_op(item, event) ;
-                                       return(true) ;
-                                       break ;
-                               case MarkerViewItem:
-                                       start_markerview_grab(item, event) ;
-                                       break ;
-                               case ImageFrameItem:
-                                       start_imageframe_grab(item, event) ;
-                                       break ;
-#endif
-                                       
-                               case MarkerBarItem:
-                                       
-                                       break;
-                                       
-                               default:
-                                       break;
+                               _drags->start_grab (event);
+                               break;
+
+                       case RegionViewNameHighlight:
+                       case LeftFrameHandle:
+                        case RightFrameHandle:
+                               if (!internal_editing () && !clicked_regionview->region()->locked()) {
+                                       RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
+                                       _drags->set (new TrimDrag (this, item, clicked_regionview, s.by_layer()), event);
+                                       return true;
                                }
-                       }
-                       return true;
-                       break;
-                       
-               case MouseGain:
-                       switch (item_type) {
-                       case RegionItem:
-                               /* start a grab so that if we finish after moving
-                                  we can tell what happened.
-                               */
-                               assert (_drag == 0);
-                               _drag = new RegionGainDrag (this, item);
-                               _drag->start_grab (event, current_canvas_cursor);
                                break;
-                               
-                       case GainLineItem:
-                               assert (_drag == 0);
-                               _drag = new LineDrag (this, item);
-                               _drag->start_grab (event);
-                               return true;
-                               
-                       case ControlPointItem:
-                               assert (_drag == 0);
-                               _drag = new ControlPointDrag (this, item);
-                               _drag->start_grab (event);
+
+                       case RegionViewName:
+                       {
+                               /* rename happens on edit clicks */
+                               RegionSelection s = get_equivalent_regions (selection->regions, Properties::edit.property_id);
+                               _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, s.by_layer()), event);
                                return true;
                                break;
-                               
-                       default:
-                               break;
                        }
-                       return true;
-                       break;
-                       
-                       switch (item_type) {
+
                        case ControlPointItem:
-                               assert (_drag == 0);
-                               _drag = new ControlPointDrag (this, item);
-                               _drag->start_grab (event);
+                               _drags->set (new ControlPointDrag (this, item), event);
+                               return true;
                                break;
-                               
+
                        case AutomationLineItem:
-                               assert (_drag == 0);
-                               _drag = new LineDrag (this, item);
-                               _drag->start_grab (event);
+                               _drags->set (new LineDrag (this, item), event);
+                               return true;
                                break;
-                               
-                       case RegionItem:
-                               // XXX need automation mode to identify which
-                               // line to use
-                               // start_line_grab_from_regionview (item, event);
+
+                       case StreamItem:
+                               if (internal_editing()) {
+                                       if (dynamic_cast<MidiTimeAxisView*> (clicked_axisview)) {
+                                               _drags->set (new RegionCreateDrag (this, item, clicked_axisview), event);
+                                       }
+                                       return true;
+                               } else {
+                                       _drags->set (new RubberbandSelectDrag (this, item), event);
+                               }
                                break;
                                
+                       case AutomationTrackItem:
+                               /* rubberband drag to select automation points */
+                               _drags->set (new RubberbandSelectDrag (this, item), event);
+                               break;
+
+                       case SelectionItem:
+                       {
+                               if (join_object_range_button.get_active()) {
+                                       /* we're in "smart" joined mode, and we've clicked on a Selection */
+                                       double const y = event->button.y + vertical_adjustment.get_value() - canvas_timebars_vsize;
+                                       pair<TimeAxisView*, int> tvp = trackview_by_y_position (y);
+                                       if (tvp.first) {
+                                               /* if we're over an automation track, start a drag of its data */
+                                               AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*> (tvp.first);
+                                               if (atv) {
+                                                       _drags->set (new AutomationRangeDrag (this, atv->base_item(), selection->time), event, up_down_cursor);
+                                               }
+
+                                               /* if we're over a track and a region, and in the `object' part of a region,
+                                                  put a selection around the region and drag both
+                                               */
+                                               RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
+                                               if (rtv && _join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
+                                                       boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (rtv->route ());
+                                                       if (t) {
+                                                               boost::shared_ptr<Playlist> pl = t->playlist ();
+                                                               if (pl) {
+                                                                       
+                                                                       boost::shared_ptr<Region> r = pl->top_region_at (event_frame (event));
+                                                                       if (r) {
+                                                                               RegionView* rv = rtv->view()->find_view (r);
+                                                                               clicked_selection = select_range_around_region (rv);
+                                                                               _drags->add (new SelectionDrag (this, item, SelectionDrag::SelectionMove));
+                                                                               list<RegionView*> rvs;
+                                                                               rvs.push_back (rv);
+                                                                               _drags->add (new RegionMoveDrag (this, item, rv, rvs, false, false));
+                                                                               _drags->start_grab (event);
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                               break;
+                       }
+
+#ifdef WITH_CMT
+                       case ImageFrameHandleStartItem:
+                               imageframe_start_handle_op(item, event) ;
+                               return(true) ;
+                               break ;
+                       case ImageFrameHandleEndItem:
+                               imageframe_end_handle_op(item, event) ;
+                               return(true) ;
+                               break ;
+                       case MarkerViewHandleStartItem:
+                               markerview_item_start_handle_op(item, event) ;
+                               return(true) ;
+                               break ;
+                       case MarkerViewHandleEndItem:
+                               markerview_item_end_handle_op(item, event) ;
+                               return(true) ;
+                               break ;
+                       case MarkerViewItem:
+                               start_markerview_grab(item, event) ;
+                               break ;
+                       case ImageFrameItem:
+                               start_imageframe_grab(item, event) ;
+                               break ;
+#endif
+
+                       case MarkerBarItem:
+
+                               break;
+
                        default:
                                break;
                        }
-                       return true;
+               }
+               return true;
+               break;
+
+       case MouseGain:
+               switch (item_type) {
+               case RegionItem:
+                       /* start a grab so that if we finish after moving
+                          we can tell what happened.
+                       */
+                       _drags->set (new RegionGainDrag (this, item), event, current_canvas_cursor);
                        break;
-                       
-               case MouseZoom:
-                       if (event->type == GDK_BUTTON_PRESS) {
-                               assert (_drag == 0);
-                               _drag = new MouseZoomDrag (this, item);
-                               _drag->start_grab (event);
-                       }
-                       
+
+               case GainLineItem:
+                       _drags->set (new LineDrag (this, item), event);
+                       return true;
+
+               case ControlPointItem:
+                       _drags->set (new ControlPointDrag (this, item), event);
                        return true;
                        break;
-                       
-               case MouseTimeFX:
-                       if (item_type == RegionItem) {
-                               assert (_drag == 0);
-                               _drag = new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer());
-                               _drag->start_grab (event);
-                       }
+
+               default:
                        break;
-                       
-               case MouseAudition:
-                       _drag = new ScrubDrag (this, item);
-                       _drag->start_grab (event);
-                       scrub_reversals = 0;
-                       scrub_reverse_distance = 0;
-                       last_scrub_x = event->button.x;
-                       scrubbing_direction = 0;
-                       track_canvas->get_window()->set_cursor (*transparent_cursor);
+               }
+               return true;
+               break;
+
+               switch (item_type) {
+               case ControlPointItem:
+                       _drags->set (new ControlPointDrag (this, item), event);
                        break;
-                       
+
+               case AutomationLineItem:
+                       _drags->set (new LineDrag (this, item), event);
+                       break;
+
+               case RegionItem:
+                       // XXX need automation mode to identify which
+                       // line to use
+                       // start_line_grab_from_regionview (item, event);
+                       break;
+
                default:
                        break;
                }
+               return true;
+               break;
+
+       case MouseZoom:
+               if (event->type == GDK_BUTTON_PRESS) {
+                       _drags->set (new MouseZoomDrag (this, item), event);
+               }
+
+               return true;
+               break;
+
+       case MouseTimeFX:
+               if (internal_editing() && item_type == NoteItem) {
+                       /* drag notes if we're in internal edit mode */
+                       _drags->set (new NoteResizeDrag (this, item), event, current_canvas_cursor);
+                       return true;
+               } else if ((!internal_editing() || dynamic_cast<AudioRegionView*> (clicked_regionview)) && clicked_regionview) {
+                       /* do time-FX if we're not in internal edit mode, or we are but we clicked on an audio region */
+                       _drags->set (new TimeFXDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
+                       return true;
+               }
+               break;
+
+       case MouseAudition:
+               _drags->set (new ScrubDrag (this, item), event);
+               scrub_reversals = 0;
+               scrub_reverse_distance = 0;
+               last_scrub_x = event->button.x;
+               scrubbing_direction = 0;
+               set_canvas_cursor (transparent_cursor);
+               return true;
+               break;
+
+       default:
+               break;
        }
 
        return false;
@@ -856,55 +1036,55 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 bool
 Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
-       switch (mouse_mode) {
+       Editing::MouseMode const eff = effective_mouse_mode ();
+       switch (eff) {
        case MouseObject:
                switch (item_type) {
                case RegionItem:
                        if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
-                               start_region_copy_grab (item, event, clicked_regionview);
+                               add_region_copy_drag (item, event, clicked_regionview);
                        } else {
-                               start_region_grab (item, event, clicked_regionview);
+                               add_region_drag (item, event, clicked_regionview);
                        }
+                       _drags->start_grab (event);
                        return true;
                        break;
                case ControlPointItem:
-                       assert (_drag == 0);
-                       _drag = new ControlPointDrag (this, item);
-                       _drag->start_grab (event);
+                       _drags->set (new ControlPointDrag (this, item), event);
                        return true;
                        break;
-                       
+
                default:
                        break;
                }
-                       
+
                switch (item_type) {
                case RegionViewNameHighlight:
-                       assert (_drag == 0);
-                       _drag = new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer());
-                       _drag->start_grab (event);
+                case LeftFrameHandle:
+                case RightFrameHandle:
+                       if (!internal_editing ()) {
+                               _drags->set (new TrimDrag (this, item, clicked_regionview, selection->regions.by_layer()), event);
+                       }
                        return true;
                        break;
-                       
+
                case RegionViewName:
-                       assert (_drag == 0);
-                       _drag = new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer());
-                       _drag->start_grab (event);
+                       _drags->set (new TrimDrag (this, clicked_regionview->get_name_highlight(), clicked_regionview, selection->regions.by_layer()), event);
                        return true;
                        break;
-                       
+
                default:
                        break;
                }
-               
+
                break;
 
        case MouseRange:
                /* relax till release */
                return true;
                break;
-               
-               
+
+
        case MouseZoom:
                if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
                        temporal_zoom_session();
@@ -913,7 +1093,7 @@ Editor::button_press_handler_2 (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                }
                return true;
                break;
-               
+
        default:
                break;
        }
@@ -927,7 +1107,7 @@ Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemTyp
        if (event->type != GDK_BUTTON_PRESS) {
                return false;
        }
-       
+
        Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
 
        if (canvas_window) {
@@ -937,28 +1117,27 @@ Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemTyp
                Gdk::ModifierType mask;
 
                pointer_window = canvas_window->get_pointer (x, y, mask);
-               
+
                if (pointer_window == track_canvas->get_bin_window()) {
                        track_canvas->window_to_world (x, y, wx, wy);
-                       allow_vertical_scroll = true;
-               } else {
-                       allow_vertical_scroll = false;
                }
        }
 
+        pre_press_cursor = current_canvas_cursor;
+
        track_canvas->grab_focus();
 
-       if (session && session->actively_recording()) {
+       if (_session && _session->actively_recording()) {
                return true;
        }
 
        button_selection (item, event, item_type);
 
-       if (_drag == 0 &&
+       if (!_drags->active () &&
            (Keyboard::is_delete_event (&event->button) ||
             Keyboard::is_context_menu_event (&event->button) ||
             Keyboard::is_edit_event (&event->button))) {
-               
+
                /* handled by button release */
                return true;
        }
@@ -986,23 +1165,25 @@ Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemTyp
 bool
 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
-       nframes64_t where = event_frame (event, 0, 0);
+       framepos_t where = event_frame (event, 0, 0);
        AutomationTimeAxisView* atv = 0;
-       
+
+        if (pre_press_cursor) {
+                set_canvas_cursor (pre_press_cursor);
+                pre_press_cursor = 0;
+        }
+
        /* no action if we're recording */
-                                               
-       if (session && session->actively_recording()) {
+
+       if (_session && _session->actively_recording()) {
                return true;
        }
 
-       /* first, see if we're finishing a drag ... */
+       /* see if we're finishing a drag */
 
        bool were_dragging = false;
-       if (_drag) {
-               bool const r = _drag->end_grab (event);
-               delete _drag;
-               _drag = 0;
-               cerr << "DRAG DONE, r = " << r << endl;
+       if (_drags->active ()) {
+               bool const r = _drags->end_grab (event);
                if (r) {
                        /* grab dragged, so do nothing else */
                        return true;
@@ -1010,25 +1191,25 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 
                were_dragging = true;
        }
-       
-       button_selection (item, event, item_type);
+
+        update_region_layering_order_editor ();
 
        /* edit events get handled here */
 
-       if (_drag == 0 && Keyboard::is_edit_event (&event->button)) {
+       if (!_drags->active () && Keyboard::is_edit_event (&event->button)) {
                switch (item_type) {
                case RegionItem:
-                       edit_region ();
+                       show_region_properties ();
                        break;
 
                case TempoMarkerItem:
                        edit_tempo_marker (item);
                        break;
-                       
+
                case MeterMarkerItem:
                        edit_meter_marker (item);
                        break;
-                       
+
                case RegionViewName:
                        if (clicked_regionview->name_active()) {
                                return mouse_rename_region (item, event);
@@ -1039,6 +1220,10 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                        edit_control_point (item);
                        break;
 
+               case NoteItem:
+                       edit_note (item);
+                       break;
+
                default:
                        break;
                }
@@ -1049,7 +1234,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 
        if (Keyboard::is_context_menu_event (&event->button)) {
 
-               if (_drag == 0) {
+               if (!_drags->active ()) {
 
                        /* no matter which button pops up the context menu, tell the menu
                           widget to use button 1 to drive menu selection.
@@ -1062,27 +1247,29 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                        case FadeOutHandleItem:
                                popup_fade_context_menu (1, event->button.time, item, item_type);
                                break;
-                       
+
                        case StreamItem:
-                               popup_track_context_menu (1, event->button.time, item_type, false, where);
+                               popup_track_context_menu (1, event->button.time, item_type, false);
                                break;
-                               
+
                        case RegionItem:
                        case RegionViewNameHighlight:
+                       case LeftFrameHandle:
+                       case RightFrameHandle:
                        case RegionViewName:
-                               popup_track_context_menu (1, event->button.time, item_type, false, where);
+                               popup_track_context_menu (1, event->button.time, item_type, false);
                                break;
-                               
+
                        case SelectionItem:
-                               popup_track_context_menu (1, event->button.time, item_type, true, where);
+                               popup_track_context_menu (1, event->button.time, item_type, true);
                                break;
 
                        case AutomationTrackItem:
-                               popup_track_context_menu (1, event->button.time, item_type, false, where);
+                               popup_track_context_menu (1, event->button.time, item_type, false);
                                break;
 
-                       case MarkerBarItem: 
-                       case RangeMarkerBarItem: 
+                       case MarkerBarItem:
+                       case RangeMarkerBarItem:
                        case TransportMarkerBarItem:
                        case CdMarkerBarItem:
                        case TempoBarItem:
@@ -1095,15 +1282,15 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                break;
 
                        case TempoMarkerItem:
-                               tm_marker_context_menu (&event->button, item);
+                               tempo_or_meter_marker_context_menu (&event->button, item);
                                break;
-                               
+
                        case MeterMarkerItem:
-                               tm_marker_context_menu (&event->button, item);
+                               tempo_or_meter_marker_context_menu (&event->button, item);
                                break;
-                       
+
                        case CrossfadeViewItem:
-                               popup_track_context_menu (1, event->button.time, item_type, false, where);
+                               popup_track_context_menu (1, event->button.time, item_type, false);
                                break;
 
 #ifdef WITH_CMT
@@ -1120,7 +1307,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
                                break ;
 #endif
-                               
+
                        default:
                                break;
                        }
@@ -1131,13 +1318,15 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 
        /* delete events get handled here */
 
-       if (_drag == 0 && Keyboard::is_delete_event (&event->button)) {
+       Editing::MouseMode const eff = effective_mouse_mode ();
+
+       if (!_drags->active () && Keyboard::is_delete_event (&event->button)) {
 
                switch (item_type) {
                case TempoMarkerItem:
                        remove_tempo_marker (item);
                        break;
-                       
+
                case MeterMarkerItem:
                        remove_meter_marker (item);
                        break;
@@ -1147,19 +1336,23 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                        break;
 
                case RegionItem:
-                       if (mouse_mode == MouseObject) {
+                       if (eff == MouseObject) {
                                remove_clicked_region ();
                        }
                        break;
-                       
+
                case ControlPointItem:
-                       if (mouse_mode == MouseGain) {
+                       if (eff == MouseGain) {
                                remove_gain_control_point (item, event);
                        } else {
                                remove_control_point (item, event);
                        }
                        break;
 
+               case NoteItem:
+                       remove_midi_note (item, event);
+                       break;
+
                default:
                        break;
                }
@@ -1200,54 +1393,50 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                mouse_add_new_tempo_event (where);
                        }
                        return true;
-                       
+
                case MeterBarItem:
                        if (!_dragging_playhead) {
                                mouse_add_new_meter_event (pixel_to_frame (event->button.x));
-                       } 
+                       }
                        return true;
                        break;
 
                default:
                        break;
                }
-               
-               switch (mouse_mode) {
+
+               switch (eff) {
                case MouseObject:
                        switch (item_type) {
                        case AutomationTrackItem:
                                atv = dynamic_cast<AutomationTimeAxisView*>(clicked_axisview);
                                if (atv) {
                                        atv->add_automation_event (item, event, where, event->button.y);
-                               } 
+                               }
                                return true;
-                               
                                break;
-                               
+
                        default:
                                break;
                        }
                        break;
 
                case MouseGain:
-                       // Gain only makes sense for audio regions
-
-                       if (!dynamic_cast<AudioRegionView*>(clicked_regionview)) {
-                               break;
-                       }
-
                        switch (item_type) {
                        case RegionItem:
+                       {
                                /* check that we didn't drag before releasing, since
                                   its really annoying to create new control
                                   points when doing this.
                                */
-                               if (were_dragging) {
-                                       dynamic_cast<AudioRegionView*>(clicked_regionview)->add_gain_point_event (item, event);
+                               AudioRegionView* arv = dynamic_cast<AudioRegionView*> (clicked_regionview);
+                               if (were_dragging && arv) {
+                                       arv->add_gain_point_event (item, event);
                                }
                                return true;
                                break;
-                               
+                       }
+
                        case AutomationTrackItem:
                                dynamic_cast<AutomationTimeAxisView*>(clicked_axisview)->
                                        add_automation_event (item, event, where, event->button.y);
@@ -1257,9 +1446,9 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                break;
                        }
                        break;
-                       
+
                case MouseAudition:
-                       track_canvas->get_window()->set_cursor (*current_canvas_cursor);
+                        set_canvas_cursor (current_canvas_cursor);
                        if (scrubbing_direction == 0) {
                                /* no drag, just a click */
                                switch (item_type) {
@@ -1271,10 +1460,10 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                }
                        } else {
                                /* make sure we stop */
-                               session->request_transport_speed (0.0);
-                       }
+                               _session->request_transport_speed (0.0);
+                       }
                        break;
-                       
+
                default:
                        break;
 
@@ -1285,8 +1474,8 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 
 
        case 2:
-               switch (mouse_mode) {
-                       
+               switch (eff) {
+
                case MouseObject:
                        switch (item_type) {
                        case RegionItem:
@@ -1298,29 +1487,29 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                        // Button2 click is unused
                                }
                                return true;
-                               
+
                                break;
-                               
+
                        default:
                                break;
                        }
                        break;
-                       
+
                case MouseRange:
-                       
+
                        // x_style_paste (where, 1.0);
                        return true;
                        break;
-                       
+
                default:
                        break;
                }
 
                break;
-       
+
        case 3:
                break;
-               
+
        default:
                break;
        }
@@ -1328,16 +1517,14 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 }
 
 bool
-Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType item_type)
+Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
        ControlPoint* cp;
        Marker * marker;
        double fraction;
-       
-       if (last_item_entered != item) {
-               last_item_entered = item;
-               last_item_entered_n = 0;
-       }
+        bool ret = true;
+
+       last_item_entered = item;
 
        switch (item_type) {
        case ControlPointItem:
@@ -1348,21 +1535,18 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                        double at_x, at_y;
                        at_x = cp->get_x();
                        at_y = cp->get_y ();
-                       cp->item()->i2w (at_x, at_y);
+                       cp->i2w (at_x, at_y);
                        at_x += 10.0;
                        at_y += 10.0;
 
                        fraction = 1.0 - (cp->get_y() / cp->line().height());
 
-                       if (is_drawable() && dynamic_cast<ScrubDrag*> (_drag) == 0) {
-                               track_canvas->get_window()->set_cursor (*fader_cursor);
+                       if (is_drawable() && !_drags->active ()) {
+                               set_canvas_cursor (fader_cursor);
                        }
 
-                       last_item_entered_n++;
                        set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
-                       if (last_item_entered_n < 10) {
-                               show_verbose_canvas_cursor ();
-                       }
+                       show_verbose_canvas_cursor ();
                }
                break;
 
@@ -1372,11 +1556,11 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                        if (line)
                                line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
                        if (is_drawable()) {
-                               track_canvas->get_window()->set_cursor (*fader_cursor);
+                               set_canvas_cursor (fader_cursor);
                        }
                }
                break;
-                       
+
        case AutomationLineItem:
                if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
                        {
@@ -1385,17 +1569,25 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                                        line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
                        }
                        if (is_drawable()) {
-                               track_canvas->get_window()->set_cursor (*fader_cursor);
+                               set_canvas_cursor (fader_cursor);
                        }
                }
                break;
-               
+
        case RegionViewNameHighlight:
-               if (is_drawable() && mouse_mode == MouseObject) {
-                       track_canvas->get_window()->set_cursor (*trimmer_cursor);
+               if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
+                       set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
+                       _over_region_trim_target = true;
                }
                break;
 
+       case LeftFrameHandle:
+       case RightFrameHandle:
+               if (is_drawable() && mouse_mode == MouseObject && !internal_editing() && entered_regionview) {
+                       set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
+               }
+                break;
+
        case StartSelectionTrimItem:
        case EndSelectionTrimItem:
 
@@ -1407,7 +1599,7 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
 #endif
 
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*trimmer_cursor);
+                       set_canvas_cursor (trimmer_cursor);
                }
                break;
 
@@ -1415,24 +1607,25 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                if (is_drawable()) {
                        switch (_edit_point) {
                        case EditAtMouse:
-                               track_canvas->get_window()->set_cursor (*grabber_edit_point_cursor);
+                               set_canvas_cursor (grabber_edit_point_cursor);
                                break;
                        default:
-                               track_canvas->get_window()->set_cursor (*grabber_cursor);
+                               set_canvas_cursor (grabber_cursor);
                                break;
                        }
                }
                break;
 
        case RegionViewName:
-               
+
                /* when the name is not an active item, the entire name highlight is for trimming */
 
                if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
                        if (mouse_mode == MouseObject && is_drawable()) {
-                               track_canvas->get_window()->set_cursor (*trimmer_cursor);
+                               set_canvas_cursor_for_region_view (event->crossing.x, entered_regionview);
+                               _over_region_trim_target = true;
                        }
-               } 
+               }
                break;
 
 
@@ -1444,14 +1637,14 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                                cursor = selector_cursor;
                                break;
                        case MouseZoom:
-                               cursor = zoom_cursor;
+                               cursor = zoom_in_cursor;
                                break;
                        default:
                                cursor = cross_hair_cursor;
                                break;
                        }
 
-                       track_canvas->get_window()->set_cursor (*cursor);
+                       set_canvas_cursor (cursor);
 
                        AutomationTimeAxisView* atv;
                        if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
@@ -1468,7 +1661,7 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
        case MeterBarItem:
        case TempoBarItem:
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*timebar_cursor);
+                       set_canvas_cursor (timebar_cursor);
                }
                break;
 
@@ -1482,20 +1675,41 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
        case MeterMarkerItem:
        case TempoMarkerItem:
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*timebar_cursor);
+                       set_canvas_cursor (timebar_cursor);
                }
                break;
+
        case FadeInHandleItem:
+               if (mouse_mode == MouseObject && !internal_editing()) {
+                       ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
+                       if (rect) {
+                               rect->property_fill_color_rgba() = 0xBBBBBBAA;
+                       }
+                       set_canvas_cursor (fade_in_cursor);
+               }
+                break;
+
        case FadeOutHandleItem:
-               if (mouse_mode == MouseObject) {
+               if (mouse_mode == MouseObject && !internal_editing()) {
                        ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
                        if (rect) {
-                               rect->property_fill_color_rgba() = 0;
-                               rect->property_outline_pixels() = 1;
+                               rect->property_fill_color_rgba() = 0xBBBBBBAA;
                        }
+                       set_canvas_cursor (fade_out_cursor);
                }
                break;
-
+       case FeatureLineItem:
+               {
+                       ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
+                       line->property_color_rgba() = 0xFF0000FF;
+               }
+               break;
+       case SelectionItem:
+               if (join_object_range_button.get_active()) {
+                       set_canvas_cursor ();
+               }
+               break;
+               
        default:
                break;
        }
@@ -1520,11 +1734,11 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                break;
        }
 
-       return false;
+       return ret;
 }
 
 bool
-Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType item_type)
+Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
        AutomationLine* al;
        ControlPoint* cp;
@@ -1532,24 +1746,27 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
        Location *loc;
        RegionView* rv;
        bool is_start;
+        bool ret = true;
 
        switch (item_type) {
        case ControlPointItem:
                cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
                if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
-                       if (cp->line().npoints() > 1 && !cp->selected()) {
+                       if (cp->line().npoints() > 1 && !cp->get_selected()) {
                                cp->set_visible (false);
                        }
                }
-               
+
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*current_canvas_cursor);
+                        set_canvas_cursor (current_canvas_cursor);
                }
 
                hide_verbose_canvas_cursor ();
                break;
-               
+
        case RegionViewNameHighlight:
+       case LeftFrameHandle:
+       case RightFrameHandle:
        case StartSelectionTrimItem:
        case EndSelectionTrimItem:
        case PlayheadCursorItem:
@@ -1561,8 +1778,10 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
        case MarkerViewHandleEndItem:
 #endif
 
+               _over_region_trim_target = false;
+               
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*current_canvas_cursor);
+                        set_canvas_cursor (current_canvas_cursor);
                }
                break;
 
@@ -1575,15 +1794,17 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                                line->property_fill_color_rgba() = al->get_line_color();
                }
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*current_canvas_cursor);
+                        set_canvas_cursor (current_canvas_cursor);
                }
                break;
 
        case RegionViewName:
                /* see enter_handler() for notes */
+               _over_region_trim_target = false;
+               
                if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
                        if (is_drawable() && mouse_mode == MouseObject) {
-                               track_canvas->get_window()->set_cursor (*current_canvas_cursor);
+                                set_canvas_cursor (current_canvas_cursor);
                        }
                }
                break;
@@ -1595,10 +1816,10 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
        case TempoBarItem:
        case MarkerBarItem:
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*current_canvas_cursor);
+                        set_canvas_cursor (current_canvas_cursor);
                }
                break;
-               
+
        case MarkerItem:
                if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
                        break;
@@ -1610,9 +1831,9 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                // fall through
        case MeterMarkerItem:
        case TempoMarkerItem:
-               
+
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*timebar_cursor);
+                       set_canvas_cursor (timebar_cursor);
                }
 
                break;
@@ -1627,21 +1848,28 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* /*event*/, ItemType i
                                rect->property_outline_pixels() = 0;
                        }
                }
+                set_canvas_cursor (current_canvas_cursor);
                break;
 
        case AutomationTrackItem:
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*current_canvas_cursor);
+                        set_canvas_cursor (current_canvas_cursor);
                        clear_entered_track = true;
-                       Glib::signal_idle().connect (mem_fun(*this, &Editor::left_automation_track));
+                       Glib::signal_idle().connect (sigc::mem_fun(*this, &Editor::left_automation_track));
                }
                break;
-               
+       case FeatureLineItem:
+               {
+                       ArdourCanvas::SimpleLine *line = dynamic_cast<ArdourCanvas::SimpleLine *> (item);
+                       line->property_color_rgba() = (guint) ARDOUR_UI::config()->canvasvar_ZeroLine.get();;
+               }
+               break;
+
        default:
                break;
        }
 
-       return false;
+       return ret;
 }
 
 gint
@@ -1655,101 +1883,103 @@ Editor::left_automation_track ()
 }
 
 void
-Editor::scrub ()
+Editor::scrub (framepos_t frame, double current_x)
 {
        double delta;
-       
+
        if (scrubbing_direction == 0) {
                /* first move */
-               session->request_locate (_drag->current_pointer_frame(), false);
-               session->request_transport_speed (0.1);
+               _session->request_locate (frame, false);
+               _session->request_transport_speed (0.1);
                scrubbing_direction = 1;
-               
+
        } else {
-               
-               if (last_scrub_x > _drag->current_pointer_x()) {
-                       
+
+               if (last_scrub_x > current_x) {
+
                        /* pointer moved to the left */
-                       
+
                        if (scrubbing_direction > 0) {
-                               
+
                                /* we reversed direction to go backwards */
-                               
+
                                scrub_reversals++;
-                               scrub_reverse_distance += (int) (last_scrub_x - _drag->current_pointer_x());
-                               
+                               scrub_reverse_distance += (int) (last_scrub_x - current_x);
+
                        } else {
-                               
+
                                /* still moving to the left (backwards) */
-                               
+
                                scrub_reversals = 0;
                                scrub_reverse_distance = 0;
-                               
-                               delta = 0.01 * (last_scrub_x - _drag->current_pointer_x());
-                               session->request_transport_speed (session->transport_speed() - delta);
+
+                               delta = 0.01 * (last_scrub_x - current_x);
+                               _session->request_transport_speed (_session->transport_speed() - delta);
                        }
-                       
+
                } else {
                        /* pointer moved to the right */
-                       
+
                        if (scrubbing_direction < 0) {
                                /* we reversed direction to go forward */
-                               
+
                                scrub_reversals++;
-                               scrub_reverse_distance += (int) (_drag->current_pointer_x() - last_scrub_x);
-                               
+                               scrub_reverse_distance += (int) (current_x - last_scrub_x);
+
                        } else {
                                /* still moving to the right */
-                               
+
                                scrub_reversals = 0;
                                scrub_reverse_distance = 0;
-                               
-                               delta = 0.01 * (_drag->current_pointer_x() - last_scrub_x);
-                               session->request_transport_speed (session->transport_speed() + delta);
+
+                               delta = 0.01 * (current_x - last_scrub_x);
+                               _session->request_transport_speed (_session->transport_speed() + delta);
                        }
                }
-               
+
                /* if there have been more than 2 opposite motion moves detected, or one that moves
                   back more than 10 pixels, reverse direction
                */
-               
+
                if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
-                       
+
                        if (scrubbing_direction > 0) {
                                /* was forwards, go backwards */
-                               session->request_transport_speed (-0.1);
+                               _session->request_transport_speed (-0.1);
                                scrubbing_direction = -1;
                        } else {
                                /* was backwards, go forwards */
-                               session->request_transport_speed (0.1);
+                               _session->request_transport_speed (0.1);
                                scrubbing_direction = 1;
                        }
-                       
+
                        scrub_reverse_distance = 0;
                        scrub_reversals = 0;
                }
        }
-       
-       last_scrub_x = _drag->current_pointer_x();
+
+       last_scrub_x = current_x;
 }
 
 bool
 Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from_autoscroll)
 {
+       _last_motion_y = event->motion.y;
+       
        if (event->motion.is_hint) {
                gint x, y;
-               
+
                /* We call this so that MOTION_NOTIFY events continue to be
                   delivered to the canvas. We need to do this because we set
                   Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
                   the density of the events, at the expense of a round-trip
                   to the server. Given that this will mostly occur on cases
                   where DISPLAY = :0.0, and given the cost of what the motion
-                  event might do, its a good tradeoff.  
+                  event might do, its a good tradeoff.
                */
 
                track_canvas->get_pointer (x, y);
-       } 
+       }
 
        if (current_stepping_trackview) {
                /* don't keep the persistent stepped trackview if the mouse moves */
@@ -1757,16 +1987,26 @@ Editor::motion_handler (ArdourCanvas::Item* /*item*/, GdkEvent* event, bool from
                step_timeout.disconnect ();
        }
 
-       if (session && session->actively_recording()) {
+       if (_session && _session->actively_recording()) {
                /* Sorry. no dragging stuff around while we record */
                return true;
        }
 
-       bool handled = false;
-       if (_drag) {
-               handled = _drag->motion_handler (event, from_autoscroll);
+       JoinObjectRangeState const old = _join_object_range_state;
+       update_join_object_range_location (event->motion.x, event->motion.y);
+       if (_join_object_range_state != old) {
+               set_canvas_cursor ();
+       }
+
+       if (_over_region_trim_target) {
+               set_canvas_cursor_for_region_view (event->motion.x, entered_regionview);
        }
 
+       bool handled = false;
+       if (_drags->active ()) {
+               handled = _drags->motion_handler (event, from_autoscroll);
+       }
+       
        if (!handled) {
                return false;
        }
@@ -1787,7 +2027,7 @@ Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* /*event*/)
 
        // We shouldn't remove the first or last gain point
        if (control_point->line().is_last_point(*control_point) ||
-               control_point->line().is_first_point(*control_point)) { 
+               control_point->line().is_first_point(*control_point)) {
                return;
        }
 
@@ -1828,23 +2068,36 @@ Editor::edit_control_point (ArdourCanvas::Item* item)
        p->line().modify_point_y (*p, d.get_y_fraction ());
 }
 
+void
+Editor::edit_note (ArdourCanvas::Item* item)
+{
+       ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
+       assert (e);
+
+       EditNoteDialog d (&e->region_view(), e);
+       d.set_position (Gtk::WIN_POS_MOUSE);
+       ensure_float (d);
+
+       d.run ();
+}
+       
 
 void
 Editor::visible_order_range (int* low, int* high) const
 {
        *low = TimeAxisView::max_order ();
        *high = 0;
-       
+
        for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
 
                RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
-               
+
                if (!rtv->hidden()) {
-                       
+
                        if (*high < rtv->order()) {
                                *high = rtv->order ();
                        }
-                       
+
                        if (*low > rtv->order()) {
                                *low = rtv->order ();
                        }
@@ -1858,46 +2111,46 @@ Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
        /* Either add to or set the set the region selection, unless
           this is an alignment click (control used)
        */
-       
+
        if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
                TimeAxisView* tv = &rv.get_time_axis_view();
                RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tv);
                double speed = 1.0;
                if (rtv && rtv->is_track()) {
-                       speed = rtv->get_diskstream()->speed();
+                       speed = rtv->track()->speed();
                }
 
-               nframes64_t where = get_preferred_edit_position();
+               framepos_t where = get_preferred_edit_position();
 
                if (where >= 0) {
 
                        if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
-                               
-                               align_region (rv.region(), SyncPoint, (nframes64_t) (where * speed));
-                               
+
+                               align_region (rv.region(), SyncPoint, (framepos_t) (where * speed));
+
                        } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
-                               
-                               align_region (rv.region(), End, (nframes64_t) (where * speed));
-                               
+
+                               align_region (rv.region(), End, (framepos_t) (where * speed));
+
                        } else {
-                               
-                               align_region (rv.region(), Start, (nframes64_t) (where * speed));
+
+                               align_region (rv.region(), Start, (framepos_t) (where * speed));
                        }
                }
        }
 }
 
 void
-Editor::show_verbose_time_cursor (nframes64_t frame, double offset, double xpos, double ypos) 
+Editor::show_verbose_time_cursor (framepos_t frame, double offset, double xpos, double ypos)
 {
        char buf[128];
-       SMPTE::Time smpte;
+       Timecode::Time timecode;
        BBT_Time bbt;
        int hours, mins;
-       nframes64_t frame_rate;
+       framepos_t frame_rate;
        float secs;
 
-       if (session == 0) {
+       if (_session == 0) {
                return;
        }
 
@@ -1911,24 +2164,24 @@ Editor::show_verbose_time_cursor (nframes64_t frame, double offset, double xpos,
 
        switch (m) {
        case AudioClock::BBT:
-               session->bbt_time (frame, bbt);
+               _session->bbt_time (frame, bbt);
                snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
                break;
-               
-       case AudioClock::SMPTE:
-               session->smpte_time (frame, smpte);
-               snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
+
+       case AudioClock::Timecode:
+               _session->timecode_time (frame, timecode);
+               snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
                break;
 
        case AudioClock::MinSec:
                /* XXX this is copied from show_verbose_duration_cursor() */
-               frame_rate = session->frame_rate();
+               frame_rate = _session->frame_rate();
                hours = frame / (frame_rate * 3600);
                frame = frame % (frame_rate * 3600);
                mins = frame / (frame_rate * 60);
                frame = frame % (frame_rate * 60);
                secs = (float) frame / (float) frame_rate;
-               snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
+               snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
                break;
 
        default:
@@ -1938,26 +2191,25 @@ Editor::show_verbose_time_cursor (nframes64_t frame, double offset, double xpos,
 
        if (xpos >= 0 && ypos >=0) {
                set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
-       }
-       else {
-               set_verbose_canvas_cursor (buf, _drag->current_pointer_x() + offset - horizontal_adjustment.get_value(), _drag->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
+       } else {
+               set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset - horizontal_position(), _drags->current_pointer_y() + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
        }
        show_verbose_canvas_cursor ();
 }
 
 void
-Editor::show_verbose_duration_cursor (nframes64_t start, nframes64_t end, double offset, double xpos, double ypos) 
+Editor::show_verbose_duration_cursor (framepos_t start, framepos_t end, double offset, double xpos, double ypos)
 {
        char buf[128];
-       SMPTE::Time smpte;
+       Timecode::Time timecode;
        BBT_Time sbbt;
        BBT_Time ebbt;
        int hours, mins;
-       nframes64_t distance, frame_rate;
+       framepos_t distance, frame_rate;
        float secs;
-       Meter meter_at_start(session->tempo_map().meter_at(start));
+       Meter meter_at_start(_session->tempo_map().meter_at(start));
 
-       if (session == 0) {
+       if (_session == 0) {
                return;
        }
 
@@ -1971,8 +2223,8 @@ Editor::show_verbose_duration_cursor (nframes64_t start, nframes64_t end, double
 
        switch (m) {
        case AudioClock::BBT:
-               session->bbt_time (start, sbbt);
-               session->bbt_time (end, ebbt);
+               _session->bbt_time (start, sbbt);
+               _session->bbt_time (end, ebbt);
 
                /* subtract */
                /* XXX this computation won't work well if the
@@ -1992,25 +2244,25 @@ Editor::show_verbose_duration_cursor (nframes64_t start, nframes64_t end, double
                        ebbt.beats--;
                        ebbt.ticks = int(Meter::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
                }
-               
+
                snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
                break;
-               
-       case AudioClock::SMPTE:
-               session->smpte_duration (end - start, smpte);
-               snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
+
+       case AudioClock::Timecode:
+               _session->timecode_duration (end - start, timecode);
+               snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
                break;
 
        case AudioClock::MinSec:
                /* XXX this stuff should be elsewhere.. */
                distance = end - start;
-               frame_rate = session->frame_rate();
+               frame_rate = _session->frame_rate();
                hours = distance / (frame_rate * 3600);
                distance = distance % (frame_rate * 3600);
                mins = distance / (frame_rate * 60);
                distance = distance % (frame_rate * 60);
                secs = (float) distance / (float) frame_rate;
-               snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
+               snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%07.4f", hours, mins, secs);
                break;
 
        default:
@@ -2022,7 +2274,7 @@ Editor::show_verbose_duration_cursor (nframes64_t start, nframes64_t end, double
                set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
        }
        else {
-               set_verbose_canvas_cursor (buf, _drag->current_pointer_x() + offset, _drag->current_pointer_y() + offset);
+               set_verbose_canvas_cursor (buf, _drags->current_pointer_x() + offset, _drags->current_pointer_y() + offset);
        }
 
        show_verbose_canvas_cursor ();
@@ -2050,178 +2302,18 @@ Editor::cancel_selection ()
 
        selection->clear ();
        clicked_selection = 0;
-}      
-
-
-void
-Editor::single_contents_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool swap_direction, bool obey_snap)
-{
-       boost::shared_ptr<Region> region (rv.region());
-
-       if (region->locked()) {
-               return;
-       }
-
-       nframes64_t new_bound;
-
-       double speed = 1.0;
-       TimeAxisView* tvp = clicked_axisview;
-       RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
-
-       if (tv && tv->is_track()) {
-               speed = tv->get_diskstream()->speed();
-       }
-       
-       if (left_direction) {
-               if (swap_direction) {
-                       new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
-               } else {
-                       new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
-               }
-       } else {
-               if (swap_direction) {
-                       new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
-               } else {
-                       new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
-               }
-       }
-
-       if (obey_snap) {
-               snap_to (new_bound);
-       }
-       region->trim_start ((nframes64_t) (new_bound * speed), this);   
-       rv.region_changed (StartChanged);
-}
-
-void
-Editor::single_start_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap, bool no_overlap)
-{
-       boost::shared_ptr<Region> region (rv.region()); 
-
-       if (region->locked()) {
-               return;
-       }
-
-       nframes64_t new_bound;
-
-       double speed = 1.0;
-       TimeAxisView* tvp = clicked_axisview;
-       RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
-
-       if (tv && tv->is_track()) {
-               speed = tv->get_diskstream()->speed();
-       }
-       
-       if (left_direction) {
-               new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
-       } else {
-               new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
-       }
-
-       if (obey_snap) {
-               snap_to (new_bound, (left_direction ? 0 : 1));  
-       }
-       
-       nframes64_t pre_trim_first_frame = region->first_frame();
-
-       region->trim_front ((nframes64_t) (new_bound * speed), this);
-  
-       if (no_overlap) {
-               //Get the next region on the left of this region and shrink/expand it.
-               boost::shared_ptr<Playlist> playlist (region->playlist());
-               boost::shared_ptr<Region> region_left = playlist->find_next_region (pre_trim_first_frame, End, 0);
-               
-               bool regions_touching = false;
-
-               if (region_left != 0 && (pre_trim_first_frame == region_left->last_frame() + 1)){
-                   regions_touching = true;
-               }
-
-               //Only trim region on the left if the first frame has gone beyond the left region's last frame.
-               if (region_left != 0 && 
-                       (region_left->last_frame() > region->first_frame() || regions_touching)) 
-               {
-                       region_left->trim_end(region->first_frame(), this);
-               }
-       }
-
-       
-
-       rv.region_changed (Change (LengthChanged|PositionChanged|StartChanged));
-}
-
-void
-Editor::single_end_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap, bool no_overlap)
-{
-       boost::shared_ptr<Region> region (rv.region());
-
-       if (region->locked()) {
-               return;
-       }
-
-       nframes64_t new_bound;
-
-       double speed = 1.0;
-       TimeAxisView* tvp = clicked_axisview;
-       RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
-
-       if (tv && tv->is_track()) {
-               speed = tv->get_diskstream()->speed();
-       }
-
-       if (left_direction) {
-               new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) - frame_delta;
-       } else {
-               new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) + frame_delta;
-       }
-
-       if (obey_snap) {
-               snap_to (new_bound);
-       }
-
-       nframes64_t pre_trim_last_frame = region->last_frame();
-
-       region->trim_end ((nframes64_t) (new_bound * speed), this);
-
-       if (no_overlap) {
-               //Get the next region on the right of this region and shrink/expand it.
-               boost::shared_ptr<Playlist> playlist (region->playlist());
-               boost::shared_ptr<Region> region_right = playlist->find_next_region (pre_trim_last_frame, Start, 1);
-
-               bool regions_touching = false;
-
-               if (region_right != 0 && (pre_trim_last_frame == region_right->first_frame() - 1)){
-                   regions_touching = true;
-               }
-
-               //Only trim region on the right if the last frame has gone beyond the right region's first frame.
-               if (region_right != 0 &&
-                       (region_right->first_frame() < region->last_frame() || regions_touching)) 
-               {
-                       region_right->trim_front(region->last_frame() + 1, this);
-               }
-               
-               rv.region_changed (Change (LengthChanged|PositionChanged|StartChanged));
-       }
-       else {
-               rv.region_changed (LengthChanged);
-       }
 }
 
 
 void
-Editor::point_trim (GdkEvent* event)
+Editor::point_trim (GdkEvent* event, framepos_t new_bound)
 {
        RegionView* rv = clicked_regionview;
 
-       nframes64_t new_bound = _drag->current_pointer_frame();
-
-       snap_to_with_modifier (new_bound, event);
-
        /* Choose action dependant on which button was pressed */
        switch (event->button.button) {
        case 1:
-               begin_reversible_command (_("Start point trim"));
+               begin_reversible_command (_("start point trim"));
 
                if (selection->selected (rv)) {
                        for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
@@ -2232,82 +2324,54 @@ Editor::point_trim (GdkEvent* event)
                                }
 
                                if (!(*i)->region()->locked()) {
-                                       boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
-                                       XMLNode &before = pl->get_state();
-
+                                       (*i)->region()->clear_changes ();
                                        (*i)->region()->trim_front (new_bound, this);
-
-                                       XMLNode &after = pl->get_state();
-                                       session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
+                                       _session->add_command(new StatefulDiffCommand ((*i)->region()));
                                }
                        }
 
                } else {
                        if (!rv->region()->locked()) {
-                               boost::shared_ptr<Playlist> pl = rv->region()->playlist();
-                               XMLNode &before = pl->get_state();
+                               rv->region()->clear_changes ();
                                rv->region()->trim_front (new_bound, this);
-                               XMLNode &after = pl->get_state();
-                               session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
+                               _session->add_command(new StatefulDiffCommand (rv->region()));
                        }
                }
 
                commit_reversible_command();
-       
+
                break;
        case 2:
                begin_reversible_command (_("End point trim"));
 
                if (selection->selected (rv)) {
-                       
+
                        for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
                        {
                                if (!(*i)->region()->locked()) {
-                                       boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
-                                       XMLNode &before = pl->get_state();
+                                       (*i)->region()->clear_changes();
                                        (*i)->region()->trim_end (new_bound, this);
-                                       XMLNode &after = pl->get_state();
-                                       session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
+                                       _session->add_command(new StatefulDiffCommand ((*i)->region()));
                                }
                        }
 
                } else {
 
                        if (!rv->region()->locked()) {
-                               boost::shared_ptr<Playlist> pl = rv->region()->playlist();
-                               XMLNode &before = pl->get_state();
+                               rv->region()->clear_changes ();
                                rv->region()->trim_end (new_bound, this);
-                               XMLNode &after = pl->get_state();
-                               session->add_command (new MementoCommand<Playlist>(*pl.get(), &before, &after));
+                               _session->add_command (new StatefulDiffCommand (rv->region()));
                        }
                }
 
                commit_reversible_command();
-       
+
                break;
        default:
                break;
        }
 }
 
-void
-Editor::thaw_region_after_trim (RegionView& rv)
-{
-       boost::shared_ptr<Region> region (rv.region());
-
-       if (region->locked()) {
-               return;
-       }
-
-       region->thaw (_("trimmed region"));
-
-       AudioRegionView* arv = dynamic_cast<AudioRegionView*>(&rv);
-       
-       if (arv) {
-               arv->unhide_envelope ();
-       }
-}
-
 void
 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
 {
@@ -2319,13 +2383,13 @@ Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* /*event*/)
                /*NOTREACHED*/
        }
 
-       Location* location = find_location_from_marker (marker, is_start);      
+       Location* location = find_location_from_marker (marker, is_start);
        location->set_hidden (true, this);
 }
 
 
 void
-Editor::reposition_zoom_rect (nframes64_t start, nframes64_t end)
+Editor::reposition_zoom_rect (framepos_t start, framepos_t end)
 {
        double x1 = frame_to_pixel (start);
        double x2 = frame_to_pixel (end);
@@ -2364,18 +2428,18 @@ Editor::mouse_rename_region (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/)
 
 
 void
-Editor::mouse_brush_insert_region (RegionView* rv, nframes64_t pos)
+Editor::mouse_brush_insert_region (RegionView* rv, framepos_t pos)
 {
        /* no brushing without a useful snap setting */
 
-       switch (snap_mode) {
+       switch (_snap_mode) {
        case SnapMagnetic:
                return; /* can't work because it allows region to be placed anywhere */
        default:
                break; /* OK */
        }
 
-       switch (snap_type) {
+       switch (_snap_type) {
        case SnapToMark:
                return;
 
@@ -2384,7 +2448,7 @@ Editor::mouse_brush_insert_region (RegionView* rv, nframes64_t pos)
        }
 
        /* don't brush a copy over the original */
-       
+
        if (pos == rv->region()->position()) {
                return;
        }
@@ -2396,16 +2460,16 @@ Editor::mouse_brush_insert_region (RegionView* rv, nframes64_t pos)
        }
 
        boost::shared_ptr<Playlist> playlist = rtv->playlist();
-       double speed = rtv->get_diskstream()->speed();
-       
-       XMLNode &before = playlist->get_state();
-       playlist->add_region (RegionFactory::create (rv->region()), (nframes64_t) (pos * speed));
-       XMLNode &after = playlist->get_state();
-       session->add_command(new MementoCommand<Playlist>(*playlist.get(), &before, &after));
-       
-       // playlist is frozen, so we have to update manually
-       
-       playlist->Modified(); /* EMIT SIGNAL */
+       double speed = rtv->track()->speed();
+
+        playlist->clear_changes ();
+       boost::shared_ptr<Region> new_region (RegionFactory::create (rv->region()));
+        playlist->add_region (new_region, (framepos_t) (pos * speed));
+       _session->add_command (new StatefulDiffCommand (playlist));
+
+       // playlist is frozen, so we have to update manually XXX this is disgusting
+
+       playlist->RegionAdded (new_region); /* EMIT SIGNAL */
 }
 
 gint
@@ -2419,56 +2483,58 @@ Editor::track_height_step_timeout ()
 }
 
 void
-Editor::start_region_grab (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
+Editor::add_region_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
 {
        assert (region_view);
 
+        if (!region_view->region()->playlist()) {
+                return;
+        }
+
        _region_motion_group->raise_to_top ();
-       
-       assert (_drag == 0);
-       
+
        if (Config->get_edit_mode() == Splice) {
-               _drag = new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer());
+               _drags->add (new RegionSpliceDrag (this, item, region_view, selection->regions.by_layer()));
        } else {
-               RegionSelection s = get_equivalent_regions (selection->regions, RouteGroup::Edit);
-               _drag = new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false);
+               RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
+               _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, false));
        }
-       
-       _drag->start_grab (event);
-
-       begin_reversible_command (_("move region(s)"));
 
        /* sync the canvas to what we think is its current state */
        update_canvas_now();
 }
 
 void
-Editor::start_region_copy_grab (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
+Editor::add_region_copy_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
 {
        assert (region_view);
-       assert (_drag == 0);
-       
+
+        if (!region_view->region()->playlist()) {
+                return;
+        }
+
        _region_motion_group->raise_to_top ();
 
-       RegionSelection s = get_equivalent_regions (selection->regions, RouteGroup::Edit);
-       _drag = new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true);
-       _drag->start_grab(event);
+       RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
+       _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), false, true));
 }
 
 void
-Editor::start_region_brush_grab (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
+Editor::add_region_brush_drag (ArdourCanvas::Item* item, GdkEvent* event, RegionView* region_view)
 {
        assert (region_view);
-       assert (_drag == 0);
-       
+
+        if (!region_view->region()->playlist()) {
+                return;
+        }
+        
        if (Config->get_edit_mode() == Splice) {
                return;
        }
 
-       RegionSelection s = get_equivalent_regions (selection->regions, RouteGroup::Edit);
-       _drag = new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false);
-       _drag->start_grab (event);
-       
+       RegionSelection s = get_equivalent_regions (selection->regions, ARDOUR::Properties::edit.property_id);
+       _drags->add (new RegionMoveDrag (this, item, region_view, s.by_layer(), true, false));
+
        begin_reversible_command (_("Drag region brush"));
 }
 
@@ -2493,7 +2559,7 @@ Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
        }
 
        /* XXX fix me one day to use all new regions */
-       
+
        boost::shared_ptr<Region> region (new_regions.front());
 
        /* add it to the current stream/playlist.
@@ -2502,11 +2568,11 @@ Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
           catch the signal it sends when it creates the regionview to
           set the regionview we want to then drag.
        */
-       
+
        latest_regionviews.clear();
-       sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view));
-       
-       /* A selection grab currently creates two undo/redo operations, one for 
+       sigc::connection c = clicked_routeview->view()->RegionViewAdded.connect (sigc::mem_fun(*this, &Editor::collect_new_region_view));
+
+       /* A selection grab currently creates two undo/redo operations, one for
           creating the new region and another for moving it.
        */
 
@@ -2514,35 +2580,34 @@ Editor::start_selection_grab (ArdourCanvas::Item* /*item*/, GdkEvent* event)
 
        boost::shared_ptr<Playlist> playlist = clicked_axisview->playlist();
 
-       XMLNode *before = &(playlist->get_state());
+        playlist->clear_changes ();
        clicked_routeview->playlist()->add_region (region, selection->time[clicked_selection].start);
-       XMLNode *after = &(playlist->get_state());
-       session->add_command(new MementoCommand<Playlist>(*playlist, before, after));
+       _session->add_command(new StatefulDiffCommand (playlist));
 
        commit_reversible_command ();
-       
+
        c.disconnect ();
-       
+
        if (latest_regionviews.empty()) {
                /* something went wrong */
                return;
        }
 
        /* we need to deselect all other regionviews, and select this one
-          i'm ignoring undo stuff, because the region creation will take care of it 
+          i'm ignoring undo stuff, because the region creation will take care of it
        */
        selection->set (latest_regionviews);
-       
-       assert (_drag == 0);
-       _drag = new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false);
-       _drag->start_grab (event);
+
+       _drags->set (new RegionMoveDrag (this, latest_regionviews.front()->get_canvas_group(), latest_regionviews.front(), latest_regionviews, false, false), event);
 }
 
 void
-Editor::break_drag ()
+Editor::escape ()
 {
-       if (_drag) {
-               _drag->break_drag ();
+       if (_drags->active ()) {
+               _drags->abort ();
+       } else {
+               selection->clear ();
        }
 }
 
@@ -2552,12 +2617,117 @@ Editor::set_internal_edit (bool yn)
        _internal_editing = yn;
 
        if (yn) {
-               mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_select")))));
-               mouse_move_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
+               mouse_select_button.set_image (*(manage (new Image (::get_icon("midi_tool_pencil")))));
+               mouse_select_button.get_image ()->show ();
+                ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
+                set_canvas_cursor ();
+
+               /* deselect everything to avoid confusion when e.g. we can't now cut a previously selected
+                  region because cut means "cut note" rather than "cut region".
+               */
+               selection->clear ();
+
        } else {
-               mouse_select_button.set_image (*(manage (new Image (::get_xpm("tool_range.xpm")))));
-               mouse_move_button.set_image (*(manage (new Image (::get_icon("tool_object")))));
+
+               mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
+               mouse_select_button.get_image ()->show ();
+                ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Select/Move Ranges"));
+                mouse_mode_toggled (mouse_mode); // sets cursor
        }
+}
 
-       set_canvas_cursor ();
+/** Update _join_object_range_state which indicate whether we are over the top or bottom half of a region view,
+ *  used by the `join object/range' tool mode.
+ */
+void
+Editor::update_join_object_range_location (double x, double y)
+{
+       /* XXX: actually, this decides based on whether the mouse is in the top or bottom half of a RouteTimeAxisView;
+          entered_{track,regionview} is not always setup (e.g. if the mouse is over a TimeSelection), and to get a Region
+          that we're over requires searching the playlist.
+       */
+          
+       if (join_object_range_button.get_active() == false || (mouse_mode != MouseRange && mouse_mode != MouseObject)) {
+               _join_object_range_state = JOIN_OBJECT_RANGE_NONE;
+               return;
+       }
+       
+       if (mouse_mode == MouseObject) {
+               _join_object_range_state = JOIN_OBJECT_RANGE_OBJECT;
+       } else if (mouse_mode == MouseRange) {
+               _join_object_range_state = JOIN_OBJECT_RANGE_RANGE;
+       }
+
+       /* XXX: maybe we should make entered_track work in all cases, rather than resorting to this */
+       pair<TimeAxisView*, int> tvp = trackview_by_y_position (y + vertical_adjustment.get_value() - canvas_timebars_vsize);
+       
+       if (tvp.first) {
+
+               RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
+               if (rtv) {
+
+                       double cx = 0;
+                       double cy = y;
+                       rtv->canvas_display()->w2i (cx, cy);
+
+                       double const c = cy / rtv->view()->child_height();
+                       double d;
+                       double const f = modf (c, &d);
+
+                       _join_object_range_state = f < 0.5 ? JOIN_OBJECT_RANGE_RANGE : JOIN_OBJECT_RANGE_OBJECT;
+               }
+       }
+}
+
+Editing::MouseMode
+Editor::effective_mouse_mode () const
+{
+       if (_join_object_range_state == JOIN_OBJECT_RANGE_OBJECT) {
+               return MouseObject;
+       } else if (_join_object_range_state == JOIN_OBJECT_RANGE_RANGE) {
+               return MouseRange;
+       }
+
+       return mouse_mode;
+}
+
+void
+Editor::remove_midi_note (ArdourCanvas::Item* item, GdkEvent *)
+{
+       ArdourCanvas::CanvasNoteEvent* e = dynamic_cast<ArdourCanvas::CanvasNoteEvent*> (item);
+       assert (e);
+
+       e->region_view().delete_note (e->note ());
+}
+
+void
+Editor::set_canvas_cursor_for_region_view (double x, RegionView* rv)
+{
+       ArdourCanvas::Group* g = rv->get_canvas_group ();
+       ArdourCanvas::Group* p = g->get_parent_group ();
+
+       /* Compute x in region view parent coordinates */
+       double dy = 0;
+       p->w2i (x, dy);
+
+       double x1, x2, y1, y2;
+       g->get_bounds (x1, y1, x2, y2);
+
+       /* Halfway across the region */
+       double const h = (x1 + x2) / 2;
+
+       Trimmable::CanTrim ct = rv->region()->can_trim ();
+       if (x <= h) {
+               if (ct & Trimmable::FrontTrimEarlier) {
+                       set_canvas_cursor (left_side_trim_cursor);
+               } else {
+                       set_canvas_cursor (left_side_trim_right_only_cursor);
+               }
+       } else {
+               if (ct & Trimmable::EndTrimLater) {
+                       set_canvas_cursor (right_side_trim_cursor);
+               } else {
+                       set_canvas_cursor (right_side_trim_left_only_cursor);
+               }
+       }
 }