Make smf_track_get_next_event gracefully handle empty tracks.
[ardour.git] / gtk2_ardour / editor_mouse.cc
index 32b84434b17da3bc18ef03a75861fd50ada04cf7..157bda3f79a8190c74213e9fa0d25f4d62fe190c 100644 (file)
@@ -1,4 +1,3 @@
-
 /*
     Copyright (C) 2000-2001 Paul Davis 
 
@@ -28,6 +27,7 @@
 
 #include <pbd/error.h>
 #include <gtkmm2ext/utils.h>
+#include <gtkmm2ext/tearoff.h>
 #include <pbd/memento_command.h>
 #include <pbd/basename.h>
 
@@ -48,6 +48,7 @@
 #include "keyboard.h"
 #include "editing.h"
 #include "rgb_macros.h"
+#include "control_point_dialog.h"
 
 #include <ardour/types.h>
 #include <ardour/profile.h>
@@ -348,11 +349,14 @@ Editor::set_mouse_mode (MouseMode m, bool force)
                break;
        }
 
-       if (mouse_mode == MouseNote)
-               midi_toolbar_frame.show();
-       else
-               midi_toolbar_frame.hide();
-
+       if (midi_tools_tearoff) {
+               if (mouse_mode == MouseNote) {
+                       midi_tools_tearoff->show();
+               } else {
+                       midi_tools_tearoff->hide();
+               }
+       }
+       
        ignore_mouse_mode_toggle = false;
        
        set_canvas_cursor ();
@@ -363,8 +367,15 @@ Editor::step_mouse_mode (bool next)
 {
        switch (current_mouse_mode()) {
        case MouseObject:
-               if (next) set_mouse_mode (MouseRange);
-               else set_mouse_mode (MouseTimeFX);
+               if (next) {
+                       if (Profile->get_sae()) {
+                               set_mouse_mode (MouseZoom);
+                       } else {
+                               set_mouse_mode (MouseRange);
+                       }
+               } else {
+                       set_mouse_mode (MouseTimeFX);
+               }
                break;
 
        case MouseRange:
@@ -373,8 +384,19 @@ Editor::step_mouse_mode (bool next)
                break;
 
        case MouseZoom:
-               if (next) set_mouse_mode (MouseGain);
-               else set_mouse_mode (MouseRange);
+               if (next) {
+                       if (Profile->get_sae()) {
+                               set_mouse_mode (MouseTimeFX);
+                       } else {
+                               set_mouse_mode (MouseGain);
+                       }
+               } else {
+                       if (Profile->get_sae()) {
+                               set_mouse_mode (MouseObject);
+                       } else {
+                               set_mouse_mode (MouseRange);
+                       }
+               }
                break;
        
        case MouseGain:
@@ -383,8 +405,15 @@ Editor::step_mouse_mode (bool next)
                break;
        
        case MouseTimeFX:
-               if (next) set_mouse_mode (MouseAudition);
-               else set_mouse_mode (MouseGain);
+               if (next) {
+                       set_mouse_mode (MouseAudition);
+               } else {
+                       if (Profile->get_sae()) {
+                               set_mouse_mode (MouseZoom);
+                       } else {
+                               set_mouse_mode (MouseGain);
+                       }
+               }
                break;
 
        case MouseAudition:
@@ -504,10 +533,11 @@ Editor::set_midi_edit_cursor (MidiEditMode m)
 void
 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
-       /* in object/audition/timefx mode, any button press sets
-          the selection if the object can be selected. this is a
-          bit of hack, because we want to avoid this if the
-          mouse operation is a region alignment.
+       /* in object/audition/timefx/gain-automation mode,
+          any button press sets the selection if the object
+          can be selected. this is a bit of hack, because
+          we want to avoid this if the mouse operation is a
+          region alignment.
 
           note: not dbl-click or triple-click
        */
@@ -515,6 +545,7 @@ Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType it
        if (((mouse_mode != MouseObject) &&
             (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)) {
@@ -533,7 +564,7 @@ Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType it
                        }
                }
        }
-           
+
        Selection::Operation op = Keyboard::selection_type (event->button.state);
        bool press = (event->type == GDK_BUTTON_PRESS);
 
@@ -598,7 +629,7 @@ bool
 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
 {
        Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
-       
+
        if (canvas_window) {
                Glib::RefPtr<const Gdk::Window> pointer_window;
                int x, y;
@@ -622,7 +653,7 @@ Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemTyp
        }
 
        button_selection (item, event, item_type);
-       
+
        if (drag_info.item == 0 &&
            (Keyboard::is_delete_event (&event->button) ||
             Keyboard::is_context_menu_event (&event->button) ||
@@ -676,27 +707,43 @@ Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemTyp
                                }
                                return true;
 
+                       case MarkerBarItem:
                        case TempoBarItem:
-                               return true;
-
                        case MeterBarItem:
+                               if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
+                                       start_cursor_grab_no_stop(&playhead_cursor->canvas_item, event);
+                               }
                                return true;
+                               break;
+
                                
                        case RangeMarkerBarItem:
-                               start_range_markerbar_op (item, event, CreateRangeMarker); 
+                               if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {                
+                                       start_cursor_grab_no_stop(&playhead_cursor->canvas_item, event);
+                               } else {
+                                       start_range_markerbar_op (item, event, CreateRangeMarker); 
+                               }       
                                return true;
                                break;
 
                        case CdMarkerBarItem:
-                               start_range_markerbar_op (item, event, CreateCDMarker); 
+                               if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
+                                       start_cursor_grab_no_stop(&playhead_cursor->canvas_item, event);
+                               } else {
+                                       start_range_markerbar_op (item, event, CreateCDMarker); 
+                               }
                                return true;
                                break;
 
                        case TransportMarkerBarItem:
-                               start_range_markerbar_op (item, event, CreateTransportMarker); 
+                               if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
+                                       start_cursor_grab_no_stop(&playhead_cursor->canvas_item, event);
+                               } else {
+                                       start_range_markerbar_op (item, event, CreateTransportMarker);
+                               }
                                return true;
                                break;
-
+                               
                        default:
                                break;
                        }
@@ -826,7 +873,13 @@ Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemTyp
                case MouseGain:
                        switch (item_type) {
                        case RegionItem:
-                               // start_line_grab_from_regionview (item, event);
+                               /* start a grab so that if we finish after moving
+                                  we can tell what happened.
+                               */
+                               drag_info.item = item;
+                               drag_info.motion_callback = &Editor::region_gain_motion_callback;
+                               drag_info.finished_callback = 0;
+                               start_grab (event, current_canvas_cursor);
                                break;
 
                        case GainLineItem:
@@ -978,7 +1031,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 {
        nframes64_t where = event_frame (event, 0, 0);
        AutomationTimeAxisView* atv = 0;
-
+       
        /* no action if we're recording */
                                                
        if (session && session->actively_recording()) {
@@ -997,7 +1050,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
        button_selection (item, event, item_type);
 
        /* edit events get handled here */
-       
+
        if (drag_info.item == 0 && Keyboard::is_edit_event (&event->button)) {
                switch (item_type) {
                case RegionItem:
@@ -1018,6 +1071,10 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                        }
                        break;
 
+               case ControlPointItem:
+                       edit_control_point (item);
+                       break;
+
                default:
                        break;
                }
@@ -1159,36 +1216,44 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                        return true;
 
                case MarkerBarItem:
-                       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-                               snap_to (where, 0, true);
+                       if (!_dragging_playhead) {
+                               if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
+                                       snap_to (where, 0, true);
+                               }
+                               mouse_add_new_marker (where);
                        }
-                       mouse_add_new_marker (where);
                        return true;
 
                case CdMarkerBarItem:
-                       // if we get here then a dragged range wasn't done
-                       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-                               snap_to (where, 0, true);
+                       if (!_dragging_playhead) {
+                               // if we get here then a dragged range wasn't done
+                               if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
+                                       snap_to (where, 0, true);
+                               }
+                               mouse_add_new_marker (where, true);
                        }
-                       mouse_add_new_marker (where, true);
                        return true;
 
                case TempoBarItem:
-                       if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
-                               snap_to (where);
+                       if (!_dragging_playhead) {
+                               if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
+                                       snap_to (where);
+                               }
+                               mouse_add_new_tempo_event (where);
                        }
-                       mouse_add_new_tempo_event (where);
                        return true;
                        
                case MeterBarItem:
-                       mouse_add_new_meter_event (pixel_to_frame (event->button.x));
+                       if (!_dragging_playhead) {
+                               mouse_add_new_meter_event (pixel_to_frame (event->button.x));
+                       } 
                        return true;
                        break;
 
                default:
                        break;
                }
-
+               
                switch (mouse_mode) {
                case MouseObject:
                        switch (item_type) {
@@ -1215,7 +1280,13 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
 
                        switch (item_type) {
                        case RegionItem:
-                               dynamic_cast<AudioRegionView*>(clicked_regionview)->add_gain_point_event (item, event);
+                               /* check that we didn't drag before releasing, since
+                                  its really annoying to create new control
+                                  points when doing this.
+                               */
+                               if (drag_info.first_move) { 
+                                       dynamic_cast<AudioRegionView*>(clicked_regionview)->add_gain_point_event (item, event);
+                               }
                                return true;
                                break;
                                
@@ -1321,8 +1392,8 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_
                        at_x = cp->get_x();
                        at_y = cp->get_y ();
                        cp->item()->i2w (at_x, at_y);
-                       at_x += 20.0;
-                       at_y += 20.0;
+                       at_x += 10.0;
+                       at_y += 10.0;
 
                        fraction = 1.0 - (cp->get_y() / cp->line().height());
 
@@ -1567,7 +1638,7 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_
        case TempoBarItem:
        case MarkerBarItem:
                if (is_drawable()) {
-                       track_canvas->get_window()->set_cursor (*timebar_cursor);
+                       track_canvas->get_window()->set_cursor (*current_canvas_cursor);
                }
                break;
                
@@ -1779,6 +1850,9 @@ Editor::motion_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item
        case PlayheadCursorItem:
        case MarkerItem:
        case ControlPointItem:
+       case MarkerBarItem:
+       case TempoBarItem:
+       case MeterBarItem:
        case RangeMarkerBarItem:
        case TransportMarkerBarItem:
        case CdMarkerBarItem:
@@ -1805,7 +1879,9 @@ Editor::motion_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item
                  if (!from_autoscroll) {
                          maybe_autoscroll_horizontally (&event->motion);
                  }
-                 (this->*(drag_info.motion_callback)) (item, event);
+                 if (drag_info.motion_callback) {
+                         (this->*(drag_info.motion_callback)) (item, event);
+                 }
                  goto handled;
          }
          goto not_handled;
@@ -1815,6 +1891,15 @@ Editor::motion_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item
        }
 
        switch (mouse_mode) {
+       case MouseGain:
+               if (item_type == RegionItem) {
+                       if (drag_info.item && drag_info.motion_callback) {
+                               (this->*(drag_info.motion_callback)) (item, event);
+                       }
+                       goto handled;
+               }
+               break;
+
        case MouseObject:
        case MouseRange:
        case MouseZoom:
@@ -1825,7 +1910,9 @@ Editor::motion_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item
                        if (!from_autoscroll) {
                                maybe_autoscroll (&event->motion);
                        }
-                       (this->*(drag_info.motion_callback)) (item, event);
+                       if (drag_info.motion_callback) {
+                               (this->*(drag_info.motion_callback)) (item, event);
+                       }
                        goto handled;
                }
                goto not_handled;
@@ -1879,6 +1966,7 @@ Editor::finalize_drag ()
        drag_info.last_pointer_frame = 0;
        drag_info.current_pointer_frame = 0;
        drag_info.brushing = false;
+       range_marker_drag_rect->hide();
        drag_info.clear_copied_locations ();
 }
 
@@ -1897,7 +1985,7 @@ Editor::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
 
        // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
 
-       if (event->button.button == 2) {
+       if (Keyboard::is_button2_event (&event->button)) {
                if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
                        drag_info.y_constrained = true;
                        drag_info.x_constrained = false;
@@ -1993,6 +2081,14 @@ Editor::end_grab (ArdourCanvas::Item* item, GdkEvent* event)
        return did_drag;
 }
 
+void
+Editor::region_gain_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
+{
+       if (drag_info.first_move && drag_info.move_threshold_passed) {
+               drag_info.first_move = false;
+       }
+}
+
 void
 Editor::start_fade_in_grab (ArdourCanvas::Item* item, GdkEvent* event)
 {
@@ -2252,6 +2348,39 @@ Editor::start_cursor_grab (ArdourCanvas::Item* item, GdkEvent* event)
        show_verbose_time_cursor (cursor->current_frame, 10);
 }
 
+void
+Editor::start_cursor_grab_no_stop (ArdourCanvas::Item* item, GdkEvent* event)
+{
+       drag_info.item = item;
+       drag_info.motion_callback = &Editor::cursor_drag_motion_callback;
+       drag_info.finished_callback = &Editor::cursor_drag_finished_ensure_locate_callback;
+
+       start_grab (event);
+
+       if ((drag_info.data = (item->get_data ("cursor"))) == 0) {
+               fatal << _("programming error: cursor canvas item has no cursor data pointer!") << endmsg;
+               /*NOTREACHED*/
+       }
+
+       Cursor* cursor = (Cursor *) drag_info.data;
+       nframes64_t where = event_frame (event, 0, 0);
+
+       snap_to(where);
+       playhead_cursor->set_position (where);
+
+       if (cursor == playhead_cursor) {
+               _dragging_playhead = true;
+
+               if (session && session->is_auditioning()) {
+                       session->cancel_audition ();
+               }
+       }
+
+       drag_info.pointer_frame_offset = drag_info.grab_frame - cursor->current_frame;  
+       
+       show_verbose_time_cursor (cursor->current_frame, 10);
+}
+
 void
 Editor::cursor_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
 {
@@ -2274,11 +2403,14 @@ Editor::cursor_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
        if (adjusted_frame == drag_info.last_pointer_frame) return;
 
        cursor->set_position (adjusted_frame);
-       
-       UpdateAllTransportClocks (cursor->current_frame);
 
        show_verbose_time_cursor (cursor->current_frame, 10);
 
+#ifdef GTKOSX
+       track_canvas->update_now ();
+#endif
+       UpdateAllTransportClocks (cursor->current_frame);
+
        drag_info.last_pointer_frame = adjusted_frame;
        drag_info.first_move = false;
 }
@@ -2286,12 +2418,28 @@ Editor::cursor_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
 void
 Editor::cursor_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
 {
-       if (drag_info.first_move) return;
+       _dragging_playhead = false;
+
+       if (drag_info.first_move) {
+               return;
+       }
        
        cursor_drag_motion_callback (item, event);
+       
+       if (item == &playhead_cursor->canvas_item) {
+               if (session) {
+                       session->request_locate (playhead_cursor->current_frame, drag_info.was_rolling);
+               }
+       } 
+}
 
+void
+Editor::cursor_drag_finished_ensure_locate_callback (ArdourCanvas::Item* item, GdkEvent* event)
+{
        _dragging_playhead = false;
        
+       cursor_drag_motion_callback (item, event);
+       
        if (item == &playhead_cursor->canvas_item) {
                if (session) {
                        session->request_locate (playhead_cursor->current_frame, drag_info.was_rolling);
@@ -2309,8 +2457,7 @@ Editor::update_marker_drag_item (Location *location)
                marker_drag_line_points.front().set_x(x1);
                marker_drag_line_points.back().set_x(x1);
                marker_drag_line->property_points() = marker_drag_line_points;
-       }
-       else {
+       } else {
                range_marker_drag_rect->property_x1() = x1;
                range_marker_drag_rect->property_x2() = x2;
        }
@@ -2417,14 +2564,14 @@ Editor::start_marker_grab (ArdourCanvas::Item* item, GdkEvent* event)
 void
 Editor::marker_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
 {
-       nframes64_t f_delta;    
+       nframes64_t f_delta = 0;
        nframes64_t newframe;
        bool is_start;
        bool move_both = false;
        Marker* dragged_marker = (Marker*) drag_info.data;
        Marker* marker;
        Location  *real_location;
-       Location  *copy_location;
+       Location *copy_location = 0;
 
        if (drag_info.pointer_frame_offset <= drag_info.current_pointer_frame) {
                newframe = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
@@ -2575,6 +2722,7 @@ Editor::marker_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
 #ifdef GTKOSX
        track_canvas->update_now ();
 #endif
+       edit_point_clock.set (copy_location->start());
 }
 
 void
@@ -2966,7 +3114,7 @@ Editor::start_control_point_grab (ArdourCanvas::Item* item, GdkEvent* event)
 
        float fraction = 1.0 - (control_point->get_y() / control_point->line().height());
        set_verbose_canvas_cursor (control_point->line().get_verbose_cursor_string (fraction), 
-                                  drag_info.current_pointer_x + 20, drag_info.current_pointer_y + 20);
+                                  drag_info.current_pointer_x + 10, drag_info.current_pointer_y + 10);
 
        show_verbose_canvas_cursor ();
 }
@@ -3058,6 +3206,28 @@ Editor::control_point_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent
        cp->line().end_drag (cp);
 }
 
+void
+Editor::edit_control_point (ArdourCanvas::Item* item)
+{
+       ControlPoint* p = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"));
+
+       if (p == 0) {
+               fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
+               /*NOTREACHED*/
+       }
+
+       ControlPointDialog d (p);
+       d.set_position (Gtk::WIN_POS_MOUSE);
+       ensure_float (d);
+
+       if (d.run () != RESPONSE_ACCEPT) {
+               return;
+       }
+
+       p->line().modify_point_y (*p, d.get_y_fraction ());
+}
+
+
 void
 Editor::start_line_grab_from_regionview (ArdourCanvas::Item* item, GdkEvent* event)
 {
@@ -3092,12 +3262,14 @@ Editor::start_line_grab (AutomationLine* line, GdkEvent* event)
        nframes64_t frame_within_region;
 
        /* need to get x coordinate in terms of parent (TimeAxisItemView)
-          origin.
+          origin, and ditto for y.
        */
 
        cx = event->button.x;
        cy = event->button.y;
+
        line->parent_group().w2i (cx, cy);
+
        frame_within_region = (nframes64_t) floor (cx * frames_per_unit);
 
        if (!line->control_points_adjacent (frame_within_region, current_line_drag_info.before, 
@@ -3113,12 +3285,17 @@ Editor::start_line_grab (AutomationLine* line, GdkEvent* event)
 
        start_grab (event, fader_cursor);
 
+       /* store grab start in parent frame */
+
+       drag_info.grab_x = cx;
+       drag_info.grab_y = cy;
+
        double fraction = 1.0 - (cy / line->height());
 
        line->start_drag (0, drag_info.grab_frame, fraction);
        
        set_verbose_canvas_cursor (line->get_verbose_cursor_string (fraction),
-                                  drag_info.current_pointer_x + 20, drag_info.current_pointer_y + 20);
+                                  drag_info.current_pointer_x + 10, drag_info.current_pointer_y + 10);
        show_verbose_canvas_cursor ();
 }
 
@@ -3128,19 +3305,18 @@ Editor::line_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
        AutomationLine* line = reinterpret_cast<AutomationLine *> (drag_info.data);
 
        double dy = drag_info.current_pointer_y - drag_info.last_pointer_y;
-
+       
        if (event->button.state & Keyboard::SecondaryModifier) {
                dy *= 0.1;
        }
 
-       double cx = drag_info.current_pointer_x;
        double cy = drag_info.grab_y + drag_info.cumulative_y_drag + dy;
 
        // calculate zero crossing point. back off by .01 to stay on the
        // positive side of zero
-       double _unused = 0;
        double zero_gain_y = (1.0 - ZERO_GAIN_FRACTION) * line->height() - .01;
-       line->parent_group().i2w(_unused, zero_gain_y);
+
+       // line->parent_group().i2w(_unused, zero_gain_y);
 
        // make sure we hit zero when passing through
        if ((cy < zero_gain_y and (cy - dy) > zero_gain_y)
@@ -3150,11 +3326,10 @@ Editor::line_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
 
        drag_info.cumulative_y_drag = cy - drag_info.grab_y;
 
-       line->parent_group().w2i (cx, cy);
-
        cy = max (0.0, cy);
        cy = min ((double) line->height(), cy);
 
+
        double fraction = 1.0 - (cy / line->height());
 
        bool push;
@@ -3184,7 +3359,7 @@ Editor::start_region_grab (ArdourCanvas::Item* item, GdkEvent* event)
        if (selection->regions.empty() || clicked_regionview == 0) {
                return;
        }
-
+       _region_motion_group->raise_to_top ();
        drag_info.copy = false;
        drag_info.item = item;
        drag_info.data = clicked_regionview;
@@ -3210,22 +3385,14 @@ Editor::start_region_grab (ArdourCanvas::Item* item, GdkEvent* event)
        drag_info.last_frame_position = (nframes64_t) (clicked_regionview->region()->position() / speed);
        drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
        drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
+       drag_info.source_layer = clicked_regionview->region()->layer();
        drag_info.dest_trackview = drag_info.source_trackview;
+       drag_info.dest_layer = drag_info.source_layer;
        // we want a move threshold
        drag_info.want_move_threshold = true;
-       
        show_verbose_time_cursor (drag_info.last_frame_position, 10);
 
        begin_reversible_command (_("move region(s)"));
-       /* 
-          the group containing moved regions may have been 
-          offset during autoscroll. reset its y offset
-          (we should really handle this in the same way 
-          we do with the x axis, but a simple way of achieving that 
-          eludes me right now). 
-       */
-
-       _region_motion_group->property_y() = 0;
 
        /* sync the canvas to what we think is its current state */
        track_canvas->update_now();
@@ -3239,6 +3406,7 @@ Editor::start_create_region_grab (ArdourCanvas::Item* item, GdkEvent* event)
        drag_info.data = clicked_axisview;
        drag_info.source_trackview = clicked_axisview;
        drag_info.dest_trackview = drag_info.source_trackview;
+       drag_info.dest_layer = drag_info.source_layer;
        drag_info.motion_callback = &Editor::create_region_drag_motion_callback;
        drag_info.finished_callback = &Editor::create_region_drag_finished_callback;
 
@@ -3251,7 +3419,7 @@ Editor::start_region_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
        if (selection->regions.empty() || clicked_regionview == 0) {
                return;
        }
-
+       _region_motion_group->raise_to_top ();
        drag_info.copy = true;
        drag_info.item = item;
        drag_info.data = clicked_regionview;    
@@ -3268,6 +3436,7 @@ Editor::start_region_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
        
        drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
        drag_info.dest_trackview = drag_info.source_trackview;
+       drag_info.dest_layer = drag_info.source_layer;
        drag_info.last_frame_position = (nframes64_t) (clicked_regionview->region()->position() / speed);
        drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
        // we want a move threshold
@@ -3275,8 +3444,6 @@ Editor::start_region_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
        drag_info.motion_callback = &Editor::region_drag_motion_callback;
        drag_info.finished_callback = &Editor::region_drag_finished_callback;
        show_verbose_time_cursor (drag_info.last_frame_position, 10);
-
-       _region_motion_group->property_y() = 0;
 }
 
 void
@@ -3306,6 +3473,7 @@ Editor::start_region_brush_grab (ArdourCanvas::Item* item, GdkEvent* event)
        drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
        drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
        drag_info.dest_trackview = drag_info.source_trackview;
+       drag_info.dest_layer = drag_info.source_layer;
        // we want a move threshold
        drag_info.want_move_threshold = true;
        drag_info.brushing = true;
@@ -3329,20 +3497,23 @@ Editor::possibly_copy_regions_during_grab (GdkEvent* event)
                        RegionView* nrv;
 
                        rv = (*i);
-
                        AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
                        MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
+                       
+                       const boost::shared_ptr<const Region> original = rv->region();
+                       boost::shared_ptr<Region> region_copy = RegionFactory::create (original);
 
                        if (arv) {
-                               nrv = new AudioRegionView (*arv);
+                               boost::shared_ptr<AudioRegion> audioregion_copy
+                                       = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
+                               nrv = new AudioRegionView (*arv, audioregion_copy);
                        } else if (mrv) {
-                               nrv = new MidiRegionView (*mrv);
-                       } else {
-                               continue;
-                       }
-
-                       const boost::shared_ptr<const Region> original = rv->region();
-                       boost::shared_ptr<Region> region_copy = RegionFactory::create (original);
+                               boost::shared_ptr<MidiRegion> midiregion_copy
+                                       = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
+                               nrv = new MidiRegionView (*mrv, midiregion_copy);
+                       } else {
+                               continue;
+                       }
 
                        nrv->get_canvas_group()->show ();
                        new_regionviews.push_back (nrv);
@@ -3379,12 +3550,13 @@ Editor::possibly_copy_regions_during_grab (GdkEvent* event)
 }
 
 bool
-Editor::check_region_drag_possible (RouteTimeAxisView** tv)
+Editor::check_region_drag_possible (RouteTimeAxisView** tv, layer_t* layer)
 {
        /* Which trackview is this ? */
 
-       TimeAxisView* tvp = trackview_by_y_position (drag_info.current_pointer_y);
-       (*tv) = dynamic_cast<RouteTimeAxisView*>(tvp);
+       std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (drag_info.current_pointer_y);
+       (*tv) = dynamic_cast<RouteTimeAxisView*> (tvp.first);
+       (*layer) = tvp.second;
 
        /* The region motion is only processed if the pointer is over
           an audio track.
@@ -3397,7 +3569,7 @@ Editor::check_region_drag_possible (RouteTimeAxisView** tv)
                hide_verbose_canvas_cursor ();
                return false;
        }
-       
+
        return true;
 }
 
@@ -3411,8 +3583,9 @@ void
 Editor::region_drag_splice_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
 {
        RouteTimeAxisView* tv;
+       layer_t layer;
        
-       if (!check_region_drag_possible (&tv)) {
+       if (!check_region_drag_possible (&tv, &layer)) {
                return;
        }
 
@@ -3473,188 +3646,278 @@ Editor::region_drag_splice_finished_callback (ArdourCanvas::Item* item, GdkEvent
 {
 }
 
+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 ();
+                       }
+               }
+       }
+}
+
+/** @param new_order New track order.
+ *  @param old_order Old track order.
+ *  @param visible_y_low Lowest visible order.
+ *  @param visible_y_high Highest visible order.
+ *  @param tracks Bitset of tracks indexed by order; 0 means a audio/MIDI track, 1 means something else.
+ *  @param heigh_list Heights of tracks indexed by order.
+ *  @return true if y movement should not happen, otherwise false.
+ */
+bool
+Editor::y_movement_disallowed (
+       int new_order, int old_order, int y_span, int visible_y_low, int visible_y_high,
+       bitset<512> const & tracks, vector<int32_t> const & height_list
+       ) const
+{
+       if (new_order != old_order) {
+
+               /* this isn't the pointer track */      
+
+               if (y_span > 0) {
+
+                       /* moving up the canvas */
+                       if ( (new_order - y_span) >= visible_y_low) {
+
+                               int32_t n = 0;
+
+                               /* work out where we'll end up with this y span, taking hidden TimeAxisViews into account */
+                               int32_t visible_tracks = 0;
+                               while (visible_tracks < y_span ) {
+                                       visible_tracks++;
+                                       while (height_list[new_order - (visible_tracks - n)] == 0) {
+                                               /* passing through a hidden track */
+                                               n--;
+                                       }                 
+                               }
+                
+                               if (tracks[new_order - (y_span - n)] != 0x00) {
+                                       /* moving to a non-track; disallow */
+                                       return true;
+                               }
+                               
+
+                       } else {
+                               /* moving beyond the lowest visible track; disallow */
+                               return true;
+                       }                 
+                 
+               } else if (y_span < 0) {
+
+                       /* moving down the canvas */
+                       if ((new_order - y_span) <= visible_y_high) {
+
+                               int32_t visible_tracks = 0;
+                               int32_t n = 0;
+                               while (visible_tracks > y_span ) {
+                                       visible_tracks--;
+                     
+                                       while (height_list[new_order - (visible_tracks - n)] == 0) {
+                                               /* passing through a hidden track */
+                                               n++;
+                                       }                
+                               }
+                                               
+                               if (tracks[new_order - (y_span - n)] != 0x00) {
+                                       /* moving to a non-track; disallow */
+                                       return true;
+                               }
+
+                               
+                       } else {
+
+                               /* moving beyond the highest visible track; disallow */
+                               return true;
+                       }
+               }               
+               
+       } else {
+               
+               /* this is the pointer's track */
+               
+               if ((new_order - y_span) > visible_y_high) {
+                       /* we will overflow */
+                       return true;
+               } else if ((new_order - y_span) < visible_y_low) {
+                       /* we will overflow */
+                       return true;
+               }
+       }
+
+       return false;
+}
+
 void
 Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
 {
        double x_delta;
        double y_delta = 0;
-       RegionView* rv = reinterpret_cast<RegionView*> (drag_info.data); 
        nframes64_t pending_region_position = 0;
-       int32_t pointer_y_span = 0, canvas_pointer_y_span = 0, original_pointer_order;
-       int32_t visible_y_high = 0, visible_y_low = 512;  //high meaning higher numbered.. not the height on the screen
+       int32_t pointer_order_span = 0, canvas_pointer_order_span = 0;
+       int32_t pointer_layer_span = 0;
+       
        bool clamp_y_axis = false;
-       vector<int32_t>  height_list(512) ;
        vector<int32_t>::iterator j;
-       RouteTimeAxisView* tv;
 
        possibly_copy_regions_during_grab (event);
 
-       if (!check_region_drag_possible (&tv)) {
+       /* *pointer* variables reflect things about the pointer; as we may be moving
+          multiple regions, much detail must be computed per-region */
+
+       /* current_pointer_view will become the TimeAxisView that we're currently pointing at, and
+          current_pointer_layer the current layer on that TimeAxisView */
+       RouteTimeAxisView* current_pointer_view;
+       layer_t current_pointer_layer;
+       if (!check_region_drag_possible (&current_pointer_view, &current_pointer_layer)) {
                return;
        }
 
-       original_pointer_order = drag_info.dest_trackview->order;
-       
+       /* TimeAxisView that we were pointing at last time we entered this method */
+       TimeAxisView const * const last_pointer_view = drag_info.dest_trackview;
+       /* the order of the track that we were pointing at last time we entered this method */
+       int32_t const last_pointer_order = last_pointer_view->order ();
+       /* the layer that we were pointing at last time we entered this method */
+       layer_t const last_pointer_layer = drag_info.dest_layer;
+
        /************************************************************
-            Y-Delta Computation
+            Y DELTA COMPUTATION
        ************************************************************/   
 
+       /* Height of TimeAxisViews, indexed by order */
+       /* XXX: hard-coded limit of TimeAxisViews */
+       vector<int32_t> height_list (512);
+       
        if (drag_info.brushing) {
                clamp_y_axis = true;
-               pointer_y_span = 0;
+               pointer_order_span = 0;
                goto y_axis_done;
        }
 
-       if ((pointer_y_span = (drag_info.dest_trackview->order - tv->order)) != 0) {
+       /* the change in track order between this callback and the last */
+       pointer_order_span = last_pointer_view->order() - current_pointer_view->order();
+       /* the change in layer between this callback and the last;
+          only meaningful if pointer_order_span == 0 (ie we've not moved tracks) */
+       pointer_layer_span = last_pointer_layer - current_pointer_layer;
+
+       if (pointer_order_span != 0) {
+
+               int32_t children = 0;
+               /* XXX: hard-coded limit of tracks */
+               bitset <512> tracks (0x00);
 
-               int32_t children = 0, numtracks = 0;
-               // XXX hard coding track limit, oh my, so very very bad
-               bitset <1024> tracks (0x00);
+               int visible_y_high;
+               int visible_y_low;
+               visible_order_range (&visible_y_low, &visible_y_high);
+               
                /* get a bitmask representing the visible tracks */
 
                for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
-                       TimeAxisView *tracklist_timeview;
-                       tracklist_timeview = (*i);
-                       RouteTimeAxisView* rtv2 = dynamic_cast<RouteTimeAxisView*>(tracklist_timeview);
+                       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
                        TimeAxisView::Children children_list;
              
-                       /* zeroes are audio tracks. ones are other types. */
+                       /* zeroes are audio/MIDI tracks. ones are other types. */
              
-                       if (!rtv2->hidden()) {
+                       if (!rtv->hidden()) {
                                
-                               if (visible_y_high < rtv2->order) {
-                                       visible_y_high = rtv2->order;
-                               }
-                               if (visible_y_low > rtv2->order) {
-                                       visible_y_low = rtv2->order;
-                               }
-               
-                               if (!rtv2->is_track()) {                                  
-                                       tracks = tracks |= (0x01 << rtv2->order);
+                               if (!rtv->is_track()) {
+                                       /* not an audio nor MIDI track */
+                                       tracks = tracks |= (0x01 << rtv->order());
                                }
        
-                               height_list[rtv2->order] = (*i)->current_height();
+                               height_list[rtv->order()] = (*i)->current_height();
                                children = 1;
 
-                               if ((children_list = rtv2->get_child_list()).size() > 0) {
+                               if ((children_list = rtv->get_child_list()).size() > 0) {
                                        for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) { 
-                                               tracks = tracks |= (0x01 << (rtv2->order + children));
-                                               height_list[rtv2->order + children] =  (*j)->current_height();
-                                               numtracks++;
+                                               tracks = tracks |= (0x01 << (rtv->order() + children));
+                                               height_list[rtv->order() + children] = (*j)->current_height();
                                                children++;     
                                        }
                                }
-                               numtracks++;        
                        }
                }
-               /* find the actual span according to the canvas */
-
-               canvas_pointer_y_span = pointer_y_span;
-               if (drag_info.dest_trackview->order >= tv->order) {
-                       int32_t y;
-                       for (y = tv->order; y < drag_info.dest_trackview->order; y++) {
-                               if (height_list[y] == 0 ) {
-                                       canvas_pointer_y_span--;
+               
+               /* find the actual pointer span, in terms of the number of visible tracks;
+                  to do this, we reduce |pointer_order_span| by the number of hidden tracks
+                  over the span */
+
+               canvas_pointer_order_span = pointer_order_span;
+               if (last_pointer_view->order() >= current_pointer_view->order()) {
+                       for (int32_t y = current_pointer_view->order(); y < last_pointer_view->order(); y++) {
+                               if (height_list[y] == 0) {
+                                       canvas_pointer_order_span--;
                                }
                        }
                } else {
-                       int32_t y;
-                       for (y = drag_info.dest_trackview->order;y <= tv->order; y++) {
-                               if (    height_list[y] == 0 ) {
-                                       canvas_pointer_y_span++;
+                       for (int32_t y = last_pointer_view->order(); y <= current_pointer_view->order(); y++) {
+                               if (height_list[y] == 0) {
+                                       canvas_pointer_order_span++;
                                }
                        }
                }
 
                for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
-                       RegionView* rv2 = (*i);
-                       double ix1, ix2, iy1, iy2;
-                       int32_t n = 0;
+                       
+                       RegionView* rv = (*i);
 
-                       if (rv2->region()->locked()) {
+                       if (rv->region()->locked()) {
                                continue;
                        }
 
-                       rv2->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
-                       rv2->get_canvas_frame()->i2w (ix1, iy1);
+                       double ix1, ix2, iy1, iy2;
+                       rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
+                       rv->get_canvas_frame()->i2w (ix1, iy1);
                        iy1 += vertical_adjustment.get_value() - canvas_timebars_vsize;
 
-                       TimeAxisView* tvp2 = trackview_by_y_position (iy1);
-                       RouteTimeAxisView* rtv2 = dynamic_cast<RouteTimeAxisView*>(tvp2);
-
-                       if (rtv2->order != original_pointer_order) {    
-                               /* this isn't the pointer track */      
+                       /* get the new trackview for this particular region */
+                       std::pair<TimeAxisView*, int> const tvp = trackview_by_y_position (iy1);
+                       assert (tvp.first);
+                       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
 
-                               if (canvas_pointer_y_span > 0) {
-
-                                       /* moving up the canvas */
-                                       if ((rtv2->order - canvas_pointer_y_span) >= visible_y_low) {
-       
-                                               int32_t visible_tracks = 0;
-                                               while (visible_tracks < canvas_pointer_y_span ) {
-                                                       visible_tracks++;
-                 
-                                                       while (height_list[rtv2->order - (visible_tracks - n)] == 0) {
-                                                               /* we're passing through a hidden track */
-                                                               n--;
-                                                       }                 
-                                               }
-                
-                                               if (tracks[rtv2->order - (canvas_pointer_y_span - n)] != 0x00) {                  
-                                                       clamp_y_axis = true;
-                                               }
-                   
-                                       } else {
-                                               clamp_y_axis = true;
-                                       }                 
-                 
-                               } else if (canvas_pointer_y_span < 0) {
+                       /* I know this method has a slightly excessive argument list, but I think
+                          it's nice to separate the code out all the same, since it has such a
+                          simple result, and it makes it clear that there are no other
+                          side-effects.
+                       */
 
-                                       /*moving down the canvas*/
+                       /* XXX: not sure that we should be passing canvas_pointer_order_span in here,
+                          as surely this is a per-region thing... */
+                       
+                       clamp_y_axis = y_movement_disallowed (
+                               rtv->order(), last_pointer_order, canvas_pointer_order_span, visible_y_low, visible_y_high,
+                               tracks, height_list
+                               );
 
-                                       if ((rtv2->order - (canvas_pointer_y_span - n)) <= visible_y_high) { // we will overflow
-                   
-                   
-                                               int32_t visible_tracks = 0;
-                   
-                                               while (visible_tracks > canvas_pointer_y_span ) {
-                                                       visible_tracks--;
-                     
-                                                       while (height_list[rtv2->order - (visible_tracks - n)] == 0) {             
-                                                               n++;
-                                                       }                
-                                               }
-                                               if (  tracks[rtv2->order - ( canvas_pointer_y_span - n)] != 0x00) {
-                                                       clamp_y_axis = true;
-                           
-                                               }
-                                       } else {
-                         
-                                               clamp_y_axis = true;
-                                       }
-                               }               
-                 
-                       } else {
-                     
-                               /* this is the pointer's track */
-                               if ((rtv2->order - pointer_y_span) > visible_y_high) { // we will overflow 
-                                       clamp_y_axis = true;
-                               } else if ((rtv2->order - pointer_y_span) < visible_y_low) { // we will underflow
-                                       clamp_y_axis = true;
-                               }
-                       }             
                        if (clamp_y_axis) {
                                break;
                        }
                }
 
-       } else  if (drag_info.dest_trackview == tv) {
-               clamp_y_axis = true;
-       }         
+       } else if (drag_info.dest_trackview == current_pointer_view) {
+
+               if (current_pointer_layer == last_pointer_layer) {
+                       /* No movement; clamp */
+                       clamp_y_axis = true;
+               } 
+       }
 
   y_axis_done:
        if (!clamp_y_axis) {
-               drag_info.dest_trackview = tv;        
+               drag_info.dest_trackview = current_pointer_view;
+               drag_info.dest_layer = current_pointer_layer;
        }
          
        /************************************************************
@@ -3674,7 +3937,7 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
 
                        pending_region_position = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
 
-                       sync_offset = rv->region()->sync_offset (sync_dir);
+                       sync_offset = clicked_regionview->region()->sync_offset (sync_dir);
 
                        /* we don't handle a sync point that lies before zero.
                         */
@@ -3688,7 +3951,7 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
                                        snap_to (sync_frame);   
                                }
            
-                               pending_region_position = rv->region()->adjust_to_sync (sync_frame);
+                               pending_region_position = clicked_regionview->region()->adjust_to_sync (sync_frame);
 
                        } else {
                                pending_region_position = drag_info.last_frame_position;
@@ -3698,12 +3961,10 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
                        pending_region_position = 0;
                }
          
-               if (pending_region_position > max_frames - rv->region()->length()) {
+               if (pending_region_position > max_frames - clicked_regionview->region()->length()) {
                        pending_region_position = drag_info.last_frame_position;
                }
 
-               // printf ("3: pending_region_position= %lu    %lu\n", pending_region_position, drag_info.last_frame_position );
-
                bool x_move_allowed;
                
                if (Config->get_edit_mode() == Lock) {
@@ -3729,17 +3990,15 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
                                x_delta = -((double) (drag_info.last_frame_position - pending_region_position) / frames_per_unit);
                                for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
 
-                                       RegionView* rv2 = (*i);
+                                       RegionView* rv = (*i);
 
                                        // If any regionview is at zero, we need to know so we can stop further leftward motion.
        
                                        double ix1, ix2, iy1, iy2;
-                                       rv2->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
-                                       rv2->get_canvas_frame()->i2w (ix1, iy1);
-                       
+                                       rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
+                                       rv->get_canvas_frame()->i2w (ix1, iy1);
+
                                        if (-x_delta > ix1 + horizontal_adjustment.get_value()) {
-                                               //      do_move = false;
-                                               cerr << "illegal move" << endl;
                                                x_delta = 0;
                                                pending_region_position = drag_info.last_frame_position;
                                                break;
@@ -3764,9 +4023,9 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
            PREPARE TO MOVE
        ************************************************************/
 
-       if (x_delta == 0 && (pointer_y_span == 0)) {
+       if (x_delta == 0 && pointer_order_span == 0 && pointer_layer_span == 0) {
                /* haven't reached next snap point, and we're not switching
-                  trackviews. nothing to do.
+                  trackviews nor layers. nothing to do.
                */
                return;
        }
@@ -3787,23 +4046,35 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
                const list<RegionView*>& layered_regions = selection->regions.by_layer();
                
                for (list<RegionView*>::const_iterator i = layered_regions.begin(); i != layered_regions.end(); ++i) {
-           
+
                        RegionView* rv = (*i);
-                       double ix1, ix2, iy1, iy2;
-                       int32_t temp_pointer_y_span = pointer_y_span;
 
                        if (rv->region()->locked()) {
                                continue;
                        }
 
-                       /* get item BBox, which will be relative to parent. so we have
-                          to query on a child, then convert to world coordinates using
-                          the parent.
-                       */
+                       /* here we are calculating the y distance from the
+                          top of the first track view to the top of the region
+                          area of the track view that we're working on */
 
-                       rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
+                       /* this x value is just a dummy value so that we have something
+                          to pass to i2w () */
+
+                       double ix1 = 0;
+
+                       /* distance from the top of this track view to the region area
+                          of our track view is always 1 */
+                       
+                       double iy1 = 1;
+
+                       /* convert to world coordinates, ie distance from the top of
+                          the ruler section */
+                       
                        rv->get_canvas_frame()->i2w (ix1, iy1);
 
+                       /* compensate for the ruler section and the vertical scrollbar position */
+                       iy1 += get_trackview_group_vertical_offset ();
+
                        if (drag_info.first_move) {
 
                                // hide any dependent views 
@@ -3817,97 +4088,116 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
                                   parent groups have different coordinates.
                                */
 
-                               rv->get_canvas_group()->property_y() =  iy1 - 1;
+                               rv->get_canvas_group()->property_y() = iy1 - 1;
                                rv->get_canvas_group()->reparent(*_region_motion_group);
 
                                rv->fake_set_opaque (true);
                        }
-                       /* for evaluation of the track position of iy1, we have to adjust 
-                          to allow for the vertical scrolling adjustment and the height of the timebars.
-                       */
-                       
-                       cerr << "adjust y from " << iy1 << " using "
-                            << vertical_adjustment.get_value() << " - "
-                            << canvas_timebars_vsize
-                            << endl;
 
-                       iy1 += vertical_adjustment.get_value() - canvas_timebars_vsize;
+                       /* current view for this particular region */
+                       std::pair<TimeAxisView*, int> pos = trackview_by_y_position (iy1);
+                       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (pos.first);
 
-                       TimeAxisView* tvp2 = trackview_by_y_position (iy1);
-                       RouteTimeAxisView* canvas_rtv = dynamic_cast<RouteTimeAxisView*>(tvp2);
-                       RouteTimeAxisView* temp_rtv;
+                       if (pointer_order_span != 0 && !clamp_y_axis) {
 
-                       if ((pointer_y_span != 0) && !clamp_y_axis) {
-                               y_delta = 0;
+                               /* INTER-TRACK MOVEMENT */
+
+                               /* move through the height list to the track that the region is currently on */
+                               vector<int32_t>::iterator j = height_list.begin ();
                                int32_t x = 0;
-                               for (j = height_list.begin(); j!= height_list.end(); j++) {     
-                                       if (x == canvas_rtv->order) {
-                                               /* we found the track the region is on */
-                                               if (x != original_pointer_order) {
-                                                       /*this isn't from the same track we're dragging from */
-                                                       temp_pointer_y_span = canvas_pointer_y_span;
-                                               }                 
-                                               while (temp_pointer_y_span > 0) {
-                                                       /* we're moving up canvas-wise,
-                                                          so  we need to find the next track height
-                                                       */
-                                                       if (j != height_list.begin()) {           
-                                                               j--;
+                               while (j != height_list.end () && x != rtv->order ()) {
+                                       ++x;
+                                       ++j;
+                               }
+
+                               y_delta = 0;
+                               int32_t temp_pointer_order_span = canvas_pointer_order_span;
+
+                               if (j != height_list.end ()) {
+
+                                       /* Account for layers in the original and
+                                          destination tracks.  If we're moving around in layers we assume
+                                          that only one track is involved, so it's ok to use *pointer*
+                                          variables here. */
+
+                                       StreamView* lv = last_pointer_view->view ();
+                                       assert (lv);
+
+                                       /* move to the top of the last trackview */
+                                       if (lv->layer_display () == Stacked) {
+                                               y_delta -= (lv->layers() - last_pointer_layer - 1) * lv->child_height ();
+                                       }
+                                       
+                                       StreamView* cv = current_pointer_view->view ();
+                                       assert (cv);
+
+                                       /* move to the right layer on the current trackview */
+                                       if (cv->layer_display () == Stacked) {
+                                               y_delta += (cv->layers() - current_pointer_layer - 1) * cv->child_height ();
+                                       }
+
+                                       /* And for being on a non-topmost layer on the new
+                                          track */
+
+                                       while (temp_pointer_order_span > 0) {
+                                               /* we're moving up canvas-wise,
+                                                  so we need to find the next track height
+                                               */
+                                               if (j != height_list.begin()) {           
+                                                       j--;
+                                               }
+
+                                               if (x != last_pointer_order) {
+                                                       if ((*j) == 0) {
+                                                               ++temp_pointer_order_span;
                                                        }
-                                                       if (x != original_pointer_order) { 
-                                                               /* we're not from the dragged track, so ignore hidden tracks. */              
-                                                               if ((*j) == 0) {
-                                                                       temp_pointer_y_span++;
-                                                               }
-                                                       }          
-                                                       y_delta -= (*j);        
-                                                       temp_pointer_y_span--;  
                                                }
 
-                                               while (temp_pointer_y_span < 0) {                 
-                                                       y_delta += (*j);
-                                                       if (x != original_pointer_order) { 
-                                                               if ((*j) == 0) {
-                                                                       temp_pointer_y_span--;
-                                                               }
-                                                       }          
-                   
-                                                       if (j != height_list.end()) {                 
-                                                               j++;
+                                               y_delta -= (*j);
+                                               temp_pointer_order_span--;
+                                       }
+
+                                       while (temp_pointer_order_span < 0) {
+
+                                               y_delta += (*j);
+
+                                               if (x != last_pointer_order) {
+                                                       if ((*j) == 0) {
+                                                               --temp_pointer_order_span;
                                                        }
-                                                       temp_pointer_y_span++;
                                                }
-                                               /* find out where we'll be when we move and set height accordingly */
-                 
-                                               tvp2 = trackview_by_y_position (iy1 + y_delta);
-                                               temp_rtv = dynamic_cast<RouteTimeAxisView*>(tvp2);
-                                               rv->set_height (temp_rtv->current_height());
+                                               
+                                               if (j != height_list.end()) {                 
+                                                       j++;
+                                               }
 
-                                               /*   if you un-comment the following, the region colours will follow the track colours whilst dragging,
-                                                    personally, i think this can confuse things, but never mind.
-                                               */
-                                 
-                                               //const GdkColor& col (temp_rtv->view->get_region_color());
-                                               //rv->set_color (const_cast<GdkColor&>(col));
-                                               break;          
+                                               temp_pointer_order_span++;
                                        }
-                                       x++;
+
+                                       
+                                       /* find out where we'll be when we move and set height accordingly */
+
+                                       std::pair<TimeAxisView*, int> const pos = trackview_by_y_position (iy1 + y_delta);
+                                       RouteTimeAxisView const * temp_rtv = dynamic_cast<RouteTimeAxisView*> (pos.first);
+                                       rv->set_height (temp_rtv->view()->child_height());
+                               
+                                       /* if you un-comment the following, the region colours will follow
+                                          the track colours whilst dragging; personally
+                                          i think this can confuse things, but never mind.
+                                       */
+                                       
+                                       //const GdkColor& col (temp_rtv->view->get_region_color());
+                                       //rv->set_color (const_cast<GdkColor&>(col));
                                }
                        }
 
-                       /* prevent the regionview from being moved to before 
-                          the zero position on the canvas.
-                       */
-                       /* clamp */
-               
-                       if (x_delta < 0) {
-                               if (-x_delta > ix1) {
-                                       x_delta = -ix1;
-                               }
-                       } else if ((x_delta > 0) && (rv->region()->last_frame() > max_frames - x_delta)) {
-                               x_delta = max_frames - rv->region()->last_frame();
+                       if (pointer_order_span == 0 && pointer_layer_span != 0 && !clamp_y_axis) {
+
+                               /* INTER-LAYER MOVEMENT in the same track */
+                               y_delta = rtv->view()->child_height () * pointer_layer_span;
                        }
 
+
                        if (drag_info.brushing) {
                                mouse_brush_insert_region (rv, pending_region_position);
                        } else {
@@ -3942,6 +4232,10 @@ Editor::region_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event
        PlaylistSet frozen_playlists;
        list <sigc::connection> modified_playlist_connections;
        pair<PlaylistSet::iterator,bool> insert_result, frozen_insert_result;
+       nframes64_t drag_delta;
+       bool changed_tracks, changed_position;
+       std::pair<TimeAxisView*, int> tvp;
+       std::map<RegionView*, RouteTimeAxisView*> final;
 
        /* first_move is set to false if the regionview has been moved in the 
           motion handler. 
@@ -3954,13 +4248,6 @@ Editor::region_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event
 
        nocommit = false;
 
-       /* XXX is this true??? i can''t tell the difference.
-          The regionview has been moved at some stage during the grab so we need
-          to account for any mouse movement between this event and the last one. 
-       */      
-
-       //region_drag_motion_callback (item, event);
-
        if (Config->get_edit_mode() == Splice && !pre_drag_region_selection.empty()) {
                selection->set (pre_drag_region_selection);
                pre_drag_region_selection.clear ();
@@ -4003,19 +4290,31 @@ Editor::region_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event
        }
 
        begin_reversible_command (op_string);
+       changed_position = (drag_info.last_frame_position != (nframes64_t) (clicked_regionview->region()->position()));
+       tvp = trackview_by_y_position (drag_info.current_pointer_y);
+       changed_tracks = (tvp.first != &clicked_regionview->get_time_axis_view());
+
+       drag_delta = clicked_regionview->region()->position() - drag_info.last_frame_position;
+
+       track_canvas->update_now ();
+
+       /* make a list of where each region ended up */
+       for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
 
-       for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ) {
-                       
-               RegionView* rv = (*i);              
                double ix1, ix2, iy1, iy2;
-               rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
-               rv->get_canvas_frame()->i2w (ix1, iy1);
+               (*i)->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
+               (*i)->get_canvas_frame()->i2w (ix1, iy1);
                iy1 += vertical_adjustment.get_value() - canvas_timebars_vsize;
 
-               TimeAxisView* dest_tv = trackview_by_y_position (iy1);
-               RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*>(dest_tv);
-               double speed;
-               bool changed_tracks, changed_position;
+               std::pair<TimeAxisView*, int> tv = trackview_by_y_position (iy1);
+               final[*i] = dynamic_cast<RouteTimeAxisView*> (tv.first);
+       }
+
+       for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ) {
+
+               RegionView* rv = (*i);
+               RouteTimeAxisView* dest_rtv = final[*i];
+
                nframes64_t where;
 
                if (rv->region()->locked()) {
@@ -4023,27 +4322,14 @@ Editor::region_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event
                        continue;
                }
 
-               /* adjust for track speed */
-
-               speed = 1.0;
-               
-               if (dest_rtv && dest_rtv->get_diskstream()) {
-                       speed = dest_rtv->get_diskstream()->speed();
-               }
-               
-               changed_position = (drag_info.last_frame_position != (nframes64_t) (rv->region()->position()/speed));
-               changed_tracks = (dest_tv != &rv->get_time_axis_view());
-
                if (changed_position && !drag_info.x_constrained) {
-                       _master_group->w2i(ix1, iy1);
-                       where = (nframes64_t) (unit_to_frame (ix1) * speed);
+                       where = rv->region()->position() - drag_delta;
                } else {
                        where = rv->region()->position();
                }
                        
                boost::shared_ptr<Region> new_region;
 
-
                if (drag_info.copy) {
                        /* we already made a copy */
                        new_region = rv->region();
@@ -5573,7 +5859,9 @@ Editor::end_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
                }
                
        } else {
-               selection->clear_tracks();
+               if (!getenv("ARDOUR_SAE")) {
+                       selection->clear_tracks();
+               }
                selection->clear_regions();
                selection->clear_points ();
                selection->clear_lines ();