drastic rethink of the relationship between remote control ID and route order keys...
[ardour.git] / gtk2_ardour / editor_drag.cc
index 020428a76bc85c43864070e1e9a970c488c58509..8ae618b37f32ab17cf7075cc5238a18b0a7850fb 100644 (file)
@@ -205,19 +205,15 @@ Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t tim
        _item = new_item;
 
        if (cursor == 0) {
-               cursor = _editor->which_grabber_cursor ();
+               _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, time);
+       } else {
+               _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
        }
-
-       _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
 }
 
 void
 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
 {
-       if (cursor == 0) {
-               cursor = _editor->which_grabber_cursor ();
-       }
-
        // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
 
        if (Keyboard::is_button2_event (&event->button)) {
@@ -240,9 +236,14 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
        _last_pointer_x = _grab_x;
        _last_pointer_y = _grab_y;
 
-       _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
-                    *cursor,
-                    event->button.time);
+       if (cursor == 0) {
+               _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
+                            event->button.time);
+       } else {
+               _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
+                            *cursor,
+                            event->button.time);
+       }
 
        if (_editor->session() && _editor->session()->transport_rolling()) {
                _was_rolling = true;
@@ -330,7 +331,9 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
 
                if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
                        if (!from_autoscroll) {
-                               _editor->maybe_autoscroll (true, allow_vertical_autoscroll ());
+                               bool const moving_left = _drags->current_pointer_x() < _last_pointer_x;
+                               bool const moving_up = _drags->current_pointer_y() < _last_pointer_y;
+                               _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), moving_left, moving_up);
                        }
 
                        motion (event, _move_threshold_passed != old_move_threshold_passed);
@@ -395,13 +398,29 @@ Drag::show_verbose_cursor_text (string const & text)
                );
 }
 
+boost::shared_ptr<Region>
+Drag::add_midi_region (MidiTimeAxisView* view)
+{
+       if (_editor->session()) {
+               const TempoMap& map (_editor->session()->tempo_map());
+               framecnt_t pos = grab_frame();
+               const Meter& m = map.meter_at (pos);
+               /* not that the frame rate used here can be affected by pull up/down which
+                  might be wrong.
+               */
+               framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
+               return view->add_region (grab_frame(), len, true);
+       }
+
+       return boost::shared_ptr<Region>();
+}
 
 struct EditorOrderTimeAxisViewSorter {
     bool operator() (TimeAxisView* a, TimeAxisView* b) {
            RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
            RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
            assert (ra && rb);
-           return ra->route()->order_key (N_ ("editor")) < rb->route()->order_key (N_ ("editor"));
+           return ra->route()->order_key (EditorSort) < rb->route()->order_key (EditorSort);
     }
 };
 
@@ -434,7 +453,7 @@ RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<Re
                _views.push_back (DraggingView (*i, this));
        }
 
-       RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
+       RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
 }
 
 void
@@ -485,7 +504,7 @@ RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        show_verbose_cursor_time (_last_frame_position);
 
-       pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        _last_pointer_time_axis_view = find_time_axis_view (tv.first);
        _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
 }
@@ -554,7 +573,7 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r
 }
 
 bool
-RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) const
+RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const
 {
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                int const n = i->time_axis_view + delta_track;
@@ -569,8 +588,12 @@ RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) cons
                        return false;
                }
 
-               int const l = i->layer + delta_layer;
-               if (delta_track == 0 && (l < 0 || l >= int (to->view()->layers()))) {
+               double const l = i->layer + delta_layer;
+
+               /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
+                  mode to allow the user to place a region below another on layer 0.
+               */
+               if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
                        /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
                           If it has, the layers will be munged later anyway, so it's ok.
                        */
@@ -588,7 +611,11 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        assert (!_views.empty ());
 
        /* Find the TimeAxisView that the pointer is now over */
-       pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+
+       if (first_move && tv.first->view()->layer_display() == Stacked) {
+               tv.first->view()->set_layer_display (Expanded);
+       }
 
        /* Bail early if we're not over a track */
        RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv.first);
@@ -601,7 +628,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
        /* Here's the current pointer position in terms of time axis view and layer */
        int const current_pointer_time_axis_view = find_time_axis_view (tv.first);
-       layer_t const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
+       double const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
 
        /* Work out the change in x */
        framepos_t pending_region_position;
@@ -609,7 +636,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
        /* Work out the change in y */
        int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
-       int delta_layer = current_pointer_layer - _last_pointer_layer;
+       double delta_layer = current_pointer_layer - _last_pointer_layer;
 
        if (!y_movement_allowed (delta_time_axis_view, delta_layer)) {
                /* this y movement is not allowed, so do no y movement this time */
@@ -636,22 +663,22 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
                if (first_move) {
 
-                       rv->get_time_axis_view().hide_dependent_views (*rv);
-                                                                                   
+                       rv->drag_start (); 
+
+                       /* Absolutely no idea why this is necessary, but it is; without
+                          it, the region view disappears after the reparent.
+                       */
+                       _editor->update_canvas_now ();
+                       
                        /* Reparent to a non scrolling group so that we can keep the
                           region selection above all time axis views.
                           Reparenting means that we will have to move the region view
                           later, as the two parent groups have different coordinates.
                        */
-                       
+
                        rv->get_canvas_group()->reparent (*(_editor->_region_motion_group));
                        
                        rv->fake_set_opaque (true);
-                       
-                       if (!rv->get_time_axis_view().hidden()) {
-                               /* the track that this region view is on is hidden, so hide the region too */
-                               rv->get_canvas_group()->hide ();
-                       }
                }
 
                /* If we have moved tracks, we'll fudge the layer delta so that the
@@ -659,17 +686,27 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                   confusion when dragging regions from non-zero layers onto different
                   tracks.
                */
-               int this_delta_layer = delta_layer;
+               double this_delta_layer = delta_layer;
                if (delta_time_axis_view != 0) {
                        this_delta_layer = - i->layer;
                }
 
                /* The TimeAxisView that this region is now on */
                TimeAxisView* tv = _time_axis_views[i->time_axis_view + delta_time_axis_view];
-                       
+
+               /* Ensure it is moved from stacked -> expanded if appropriate */
+               if (tv->view()->layer_display() == Stacked) {
+                       tv->view()->set_layer_display (Expanded);
+               }
+               
+               /* We're only allowed to go -ve in layer on Expanded views */
+               if (tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
+                       this_delta_layer = - i->layer;
+               }
+               
                /* Set height */
                rv->set_height (tv->view()->child_height ());
-
+               
                /* Update show/hidden status as the region view may have come from a hidden track,
                   or have moved to one.
                */
@@ -695,10 +732,17 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                        
                        /* And adjust for the layer that it should be on */
                        StreamView* cv = tv->view ();
-                       if (cv->layer_display() == Stacked) {
+                       switch (cv->layer_display ()) {
+                       case Overlaid:
+                               break;
+                       case Stacked:
                                y += (cv->layers() - i->layer - 1) * cv->child_height ();
+                               break;
+                       case Expanded:
+                               y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
+                               break;
                        }
-                       
+
                        /* Now move the region view */
                        rv->move (x_delta, y - rv->get_canvas_group()->property_y());
                }
@@ -789,8 +833,24 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move)
 }
 
 void
-RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
+RegionMotionDrag::finished (GdkEvent *, bool)
+{
+       for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
+               if (!(*i)->view()) {
+                       continue;
+               }
+
+               if ((*i)->view()->layer_display() == Expanded) {
+                       (*i)->view()->set_layer_display (Stacked);
+               }
+       }
+}
+
+void
+RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
 {
+       RegionMotionDrag::finished (ev, movement_occurred);
+       
        if (!movement_occurred) {
                /* just a click */
                return;
@@ -837,6 +897,10 @@ RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
                        );
 
        }
+
+       if (_editor->session() && Config->get_always_play_range()) {
+               _editor->session()->request_locate (_editor->get_selection().regions.start());
+       }
 }
 
 void
@@ -924,6 +988,7 @@ RegionMoveDrag::finished_no_copy (
        RegionSelection new_views;
        PlaylistSet modified_playlists;
        PlaylistSet frozen_playlists;
+       set<RouteTimeAxisView*> views_to_update;
 
        if (_brushing) {
                /* all changes were made during motion event handlers */
@@ -942,13 +1007,15 @@ RegionMoveDrag::finished_no_copy (
                RegionView* rv = i->view;
 
                RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
-               layer_t const dest_layer = i->layer;
+               double const dest_layer = i->layer;
 
                if (rv->region()->locked()) {
                        ++i;
                        continue;
                }
 
+               views_to_update.insert (dest_rtv);
+
                framepos_t where;
 
                if (changed_position && !_x_constrained) {
@@ -996,15 +1063,14 @@ RegionMoveDrag::finished_no_copy (
 
                        rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
                        rv->get_canvas_group()->property_y() = i->initial_y;
-                       rv->get_time_axis_view().reveal_dependent_views (*rv);
+                       rv->drag_end ();
 
                        /* just change the model */
 
                        boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
 
-                       if (dest_rtv->view()->layer_display() == Stacked) {
-                               rv->region()->set_layer (dest_layer);
-                               rv->region()->set_pending_explicit_relayer (true);
+                       if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
+                               playlist->set_layer (rv->region(), dest_layer);
                        }
 
                        /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
@@ -1076,6 +1142,17 @@ RegionMoveDrag::finished_no_copy (
        add_stateful_diff_commands_for_playlists (modified_playlists);
 
        _editor->commit_reversible_command ();
+
+       /* We have futzed with the layering of canvas items on our streamviews.
+          If any region changed layer, this will have resulted in the stream
+          views being asked to set up their region views, and all will be well.
+          If not, we might now have badly-ordered region views.  Ask the StreamViews
+          involved to sort themselves out, just in case.
+       */
+
+       for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
+               (*i)->view()->playlist_layered ((*i)->track ());
+       }
 }
 
 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
@@ -1137,9 +1214,8 @@ RegionMoveDrag::insert_region_into_playlist (
 
        dest_playlist->add_region (region, where);
 
-       if (dest_rtv->view()->layer_display() == Stacked) {
-               region->set_layer (dest_layer);
-               region->set_pending_explicit_relayer (true);
+       if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
+               dest_playlist->set_layer (region, dest_layer);
        }
 
        c.disconnect ();
@@ -1188,6 +1264,12 @@ RegionMoveDrag::aborted (bool movement_occurred)
 void
 RegionMotionDrag::aborted (bool)
 {
+       for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
+               if ((*i)->view()->layer_display() == Expanded) {
+                       (*i)->view()->set_layer_display (Stacked);
+               }
+       }
+       
        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                RegionView* rv = i->view;
                TimeAxisView* tv = &(rv->get_time_axis_view ());
@@ -1195,7 +1277,7 @@ RegionMotionDrag::aborted (bool)
                assert (rtv);
                rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
                rv->get_canvas_group()->property_y() = 0;
-               rv->get_time_axis_view().reveal_dependent_views (*rv);
+               rv->drag_end ();
                rv->fake_set_opaque (false);
                rv->move (-_total_x_delta, 0);
                rv->set_height (rtv->view()->child_height ());
@@ -1295,7 +1377,7 @@ RegionSpliceDrag::motion (GdkEvent* event, bool)
 {
        /* Which trackview is this ? */
 
-       pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
+       pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
        RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
 
        /* The region motion is only processed if the pointer is over
@@ -1383,7 +1465,7 @@ void
 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
 {
        if (first_move) {
-               add_region();
+               _region = add_midi_region (_view);
                _view->playlist()->freeze ();
        } else {
                if (_region) {
@@ -1409,29 +1491,10 @@ void
 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
 {
        if (!movement_occurred) {
-               add_region ();
+               add_midi_region (_view);
        } else {
                _view->playlist()->thaw ();
        }
-
-       if (_region) {
-               _editor->commit_reversible_command ();
-       }
-}
-
-void
-RegionCreateDrag::add_region ()
-{
-       if (_editor->session()) {
-               const TempoMap& map (_editor->session()->tempo_map());
-               framecnt_t pos = grab_frame();
-               const Meter& m = map.meter_at (pos);
-               /* not that the frame rate used here can be affected by pull up/down which
-                  might be wrong.
-               */
-               framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
-               _region = _view->add_region (grab_frame(), len, false);
-       }
 }
 
 void
@@ -1535,30 +1598,6 @@ NoteResizeDrag::aborted (bool)
        }
 }
 
-RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
-       : Drag (e, i)
-{
-       DEBUG_TRACE (DEBUG::Drags, "New RegionGainDrag\n");
-}
-
-void
-RegionGainDrag::motion (GdkEvent* /*event*/, bool)
-{
-
-}
-
-void
-RegionGainDrag::finished (GdkEvent *, bool)
-{
-
-}
-
-void
-RegionGainDrag::aborted (bool)
-{
-       /* XXX: TODO */
-}
-
 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
        : RegionDrag (e, i, p, v)
 {
@@ -1663,6 +1702,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
 
                        if (arv) {
                                arv->temporarily_hide_envelope ();
+                               arv->drag_start ();
                        }
 
                        boost::shared_ptr<Playlist> pl = rv->region()->playlist();
@@ -1783,6 +1823,10 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->motion_frozen_playlists.clear ();
                _editor->commit_reversible_command();
 
+               for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
+                       i->view->drag_end ();
+               }
+
        } else {
                /* no mouse movement */
                _editor->point_trim (event, adjusted_current_frame (event));
@@ -1873,7 +1917,7 @@ MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
                // not, because we'll remove it from the map).
                
                MeterSection section (_marker->meter());
-               
+
                if (!section.movable()) {
                        return;
                }
@@ -1894,8 +1938,10 @@ MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
 
                if (!_copy) {
                        TempoMap& map (_editor->session()->tempo_map());
+                       /* get current state */
+                       before_state = &map.get_state();
                        /* remove the section while we drag it */
-                       map.remove_meter (section);
+                       map.remove_meter (section, true);
                }
        }
 
@@ -1928,13 +1974,12 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        } else {
                _editor->begin_reversible_command (_("move meter mark"));
-               XMLNode &before = map.get_state();
 
                /* we removed it before, so add it back now */
                
                map.add_meter (_marker->meter(), when);
                XMLNode &after = map.get_state();
-               _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
+               _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
                _editor->commit_reversible_command ();
        }
 
@@ -2012,8 +2057,10 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
 
                if (!_copy) {
                        TempoMap& map (_editor->session()->tempo_map());
+                       /* get current state */
+                       before_state = &map.get_state();
                        /* remove the section while we drag it */
-                       map.remove_tempo (section);
+                       map.remove_tempo (section, true);
                }
        }
 
@@ -2031,10 +2078,11 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        motion (event, false);
 
+       TempoMap& map (_editor->session()->tempo_map());
+       framepos_t beat_time = map.round_to_beat (last_pointer_frame(), 0);
        Timecode::BBT_Time when;
 
-       TempoMap& map (_editor->session()->tempo_map());
-       map.bbt_time (last_pointer_frame(), when);
+       map.bbt_time (beat_time, when);
 
        if (_copy == true) {
                _editor->begin_reversible_command (_("copy tempo mark"));
@@ -2046,11 +2094,10 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        } else {
                _editor->begin_reversible_command (_("move tempo mark"));
-               XMLNode &before = map.get_state();
                /* we removed it before, so add it back now */
                map.add_tempo (_marker->tempo(), when);
                XMLNode &after = map.get_state();
-               _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
+               _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
                _editor->commit_reversible_command ();
        }
 
@@ -2066,7 +2113,7 @@ TempoMarkerDrag::aborted (bool moved)
        if (moved) {
                TempoMap& map (_editor->session()->tempo_map());
                /* we removed it before, so add it back now */
-               map.add_tempo (_marker->tempo(), _marker->tempo().frame());
+               map.add_tempo (_marker->tempo(), _marker->tempo().start());
                // delete the dummy marker we used for visual representation while moving.
                // a new visual marker will show up automatically.
                delete _marker;
@@ -2771,6 +2818,10 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
                                        event->button.x + 10, event->button.y + 10);
 
        _editor->verbose_cursor()->show ();
+
+       if (!_point->can_slide ()) {
+               _x_constrained = true;
+       }
 }
 
 void
@@ -2824,7 +2875,7 @@ ControlPointDrag::motion (GdkEvent* event, bool)
 
        bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
 
-       _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
+       _point->line().drag_motion (_editor->frame_to_unit_unrounded (cx_frames), fraction, false, push);
 
        _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
 }
@@ -2935,14 +2986,7 @@ LineDrag::motion (GdkEvent* event, bool)
        cy = min ((double) _line->height(), cy);
 
        double const fraction = 1.0 - (cy / _line->height());
-
-       bool push;
-
-       if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
-               push = false;
-       } else {
-               push = true;
-       }
+       bool const push = !Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
 
        /* we are ignoring x position for this drag, so we can just pass in anything */
        _line->drag_motion (0, fraction, true, push);
@@ -3049,6 +3093,7 @@ FeatureLineDrag::aborted (bool)
 
 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
+       , _vertical_only (false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
 }
@@ -3100,8 +3145,14 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
                double x2 = _editor->frame_to_pixel (end);
 
                _editor->rubberband_rect->property_x1() = x1;
+               if (_vertical_only) {
+                       /* fixed 10 pixel width */
+                       _editor->rubberband_rect->property_x2() = x1 + 10;
+               } else {
+                       _editor->rubberband_rect->property_x2() = x2;
+               } 
+
                _editor->rubberband_rect->property_y1() = y1;
-               _editor->rubberband_rect->property_x2() = x2;
                _editor->rubberband_rect->property_y2() = y2;
 
                _editor->rubberband_rect->show();
@@ -3151,7 +3202,23 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
 
        } else {
 
-               deselect_things ();
+               /* just a click */
+
+               bool do_deselect = true;
+               MidiTimeAxisView* mtv;
+
+               if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
+                       /* MIDI track */
+                       if (_editor->selection->empty()) {
+                               /* nothing selected */
+                               add_midi_region (mtv);
+                               do_deselect = false;
+                       }
+               } 
+
+               if (do_deselect) {
+                       deselect_things ();
+               }
 
        }
 
@@ -3182,11 +3249,16 @@ void
 TimeFXDrag::motion (GdkEvent* event, bool)
 {
        RegionView* rv = _primary;
+       StreamView* cv = rv->get_time_axis_view().view ();
+
+       pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
+       int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
+       int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
 
        framepos_t const pf = adjusted_current_frame (event);
 
        if (pf > rv->region()->position()) {
-               rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
+               rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
        }
 
        show_verbose_cursor_time (pf);
@@ -3217,13 +3289,16 @@ TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
        }
 #endif
 
-       // XXX how do timeFX on multiple regions ?
-
-       RegionSelection rs;
-       rs.add (_primary);
+       if (!_editor->get_selection().regions.empty()) {
+               /* primary will already be included in the selection, and edit
+                  group shared editing will propagate selection across
+                  equivalent regions, so just use the current region
+                  selection.
+               */
 
-       if (_editor->time_stretch (rs, percentage) == -1) {
-               error << _("An error occurred while executing time stretch operation") << endmsg;
+               if (_editor->time_stretch (_editor->get_selection().regions, percentage) == -1) {
+                       error << _("An error occurred while executing time stretch operation") << endmsg;
+               }
        }
 }
 
@@ -3272,6 +3347,7 @@ SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
        , _copy (false)
        , _original_pointer_time_axis (-1)
        , _last_pointer_time_axis (-1)
+       , _time_selection_at_start (!_editor->get_selection().time.empty())
 {
        DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
 }
@@ -3503,16 +3579,42 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
                }
 
                /* XXX what if its a music time selection? */
-               if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
-                       s->request_play_range (&_editor->selection->time, true);
+               if (s) {
+                       if ((s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
+                               s->request_play_range (&_editor->selection->time, true);
+                       } else {
+                               if (Config->get_always_play_range()) {
+                                       if (_editor->doing_range_stuff()) {
+                                               s->request_locate (_editor->get_selection().time.start());
+                                       } 
+                               }
+                       }
                }
 
-
        } else {
-               /* just a click, no pointer movement.*/
+               /* just a click, no pointer movement.
+                */
 
                if (Keyboard::no_modifier_keys_pressed (&event->button)) {
-                       _editor->selection->clear_time();
+                       if (!_time_selection_at_start) {
+                               if (_editor->clicked_regionview) {
+                                       if (_editor->get_selection().selected (_editor->clicked_regionview)) {
+                                               /* range select the entire current
+                                                  region selection
+                                               */
+                                               _editor->select_range (_editor->get_selection().regions.start(), 
+                                                                      _editor->get_selection().regions.end_frame());
+                                       } else {
+                                               /* range select this (unselected)
+                                                * region
+                                                */
+                                               _editor->select_range (_editor->clicked_regionview->region()->position(), 
+                                                                      _editor->clicked_regionview->region()->last_frame());
+                                       }
+                               }
+                       } else {
+                               _editor->selection->clear_time();
+                       }
                }
 
                if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
@@ -3523,6 +3625,11 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
                        s->request_stop (false, false);
                }
 
+               if (Config->get_always_play_range()) {
+                       if (_editor->doing_range_stuff()) {
+                               s->request_locate (_editor->get_selection().time.start());
+                       } 
+               }
        }
 
        _editor->stop_canvas_autoscroll ();
@@ -3811,9 +3918,9 @@ MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
                motion (event, false);
 
                if (grab_frame() < last_pointer_frame()) {
-                       _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
+                       _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame());
                } else {
-                       _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
+                       _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame());
                }
        } else {
                if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
@@ -3896,7 +4003,15 @@ NoteDrag::total_dx () const
 int8_t
 NoteDrag::total_dy () const
 {
-       return ((int8_t) (grab_y() / _note_height)) - ((int8_t) (_drags->current_pointer_y() / _note_height));
+       MidiStreamView* msv = _region->midi_stream_view ();
+       double const y = _region->midi_view()->y_position ();
+       /* new current note */
+       uint8_t n = msv->y_to_note (_drags->current_pointer_y () - y);
+       /* clamp */
+       n = max (msv->lowest_note(), n);
+       n = min (msv->highest_note(), n);
+       /* and work out delta */
+       return n - msv->y_to_note (grab_y() - y);
 }
 
 void
@@ -3938,8 +4053,11 @@ void
 NoteDrag::finished (GdkEvent* ev, bool moved)
 {
        if (!moved) {
-               if (_editor->current_mouse_mode() == Editing::MouseObject) {
-
+               /* no motion - select note */
+               
+               if (_editor->current_mouse_mode() == Editing::MouseObject ||
+                   _editor->current_mouse_mode() == Editing::MouseDraw) {
+                       
                        if (_was_selected) {
                                bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
                                if (add) {
@@ -3969,31 +4087,48 @@ NoteDrag::aborted (bool)
        /* XXX: TODO */
 }
 
-AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* item, list<AudioRange> const & r)
-       : Drag (editor, item)
+/** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
+AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
+       : Drag (editor, atv->base_item ())
        , _ranges (r)
        , _nothing_to_drag (false)
 {
        DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
 
-       _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
-       assert (_atav);
+       setup (atv->lines ());
+}
 
-       /* get all lines in the automation view */
-       list<boost::shared_ptr<AutomationLine> > lines = _atav->lines ();
+/** Make an AutomationRangeDrag for region gain lines */
+AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AudioRegionView* rv, list<AudioRange> const & r)
+       : Drag (editor, rv->get_canvas_group ())
+       , _ranges (r)
+       , _nothing_to_drag (false)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
+
+       list<boost::shared_ptr<AutomationLine> > lines;
+       lines.push_back (rv->get_gain_line ());
+       setup (lines);
+}
 
-       /* find those that overlap the ranges being dragged */
-       list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
+/** @param lines AutomationLines to drag.
+ *  @param offset Offset from the session start to the points in the AutomationLines.
+ */
+void
+AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
+{
+       /* find the lines that overlap the ranges being dragged */
+       list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
        while (i != lines.end ()) {
-               list<boost::shared_ptr<AutomationLine> >::iterator j = i;
+               list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
                ++j;
 
-               pair<framepos_t, framepos_t> const r = (*i)->get_point_x_range ();
+               pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
 
                /* check this range against all the AudioRanges that we are using */
                list<AudioRange>::const_iterator k = _ranges.begin ();
                while (k != _ranges.end()) {
-                       if (k->coverage (r.first, r.second) != OverlapNone) {
+                       if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
                                break;
                        }
                        ++k;
@@ -4062,9 +4197,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                                double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
 
                                the_list->add (p, the_list->eval (p));
-                               j->line->add_always_in_view (p);
                                the_list->add (q, the_list->eval (q));
-                               j->line->add_always_in_view (q);
                        }
 
                        /* same thing for the end */
@@ -4090,9 +4223,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                                double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
 
                                the_list->add (p, the_list->eval (p));
-                               j->line->add_always_in_view (p);
                                the_list->add (q, the_list->eval (q));
-                               j->line->add_always_in_view (q);
                        }
                }
 
@@ -4160,7 +4291,6 @@ AutomationRangeDrag::finished (GdkEvent* event, bool)
        motion (event, false);
        for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
                i->line->end_drag ();
-               i->line->clear_always_in_view ();
        }
 
        _editor->session()->commit_reversible_command ();
@@ -4170,7 +4300,6 @@ void
 AutomationRangeDrag::aborted (bool)
 {
        for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
-               i->line->clear_always_in_view ();
                i->line->reset ();
        }
 }
@@ -4192,7 +4321,9 @@ PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionVie
        , _patch_change (i)
        , _cumulative_dx (0)
 {
-       DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
+       DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
+                                                  _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
+                                                  grab_frame()));
 }
 
 void
@@ -4203,8 +4334,8 @@ PatchChangeDrag::motion (GdkEvent* ev, bool)
        f = max (f, r->position ());
        f = min (f, r->last_frame ());
 
-       framecnt_t const dxf = f - grab_frame();
-       double const dxu = _editor->frame_to_unit (dxf);
+       framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
+       double const dxu = _editor->frame_to_unit (dxf); // permitted fx in units
        _patch_change->move (dxu - _cumulative_dx, 0);
        _cumulative_dx = dxu;
 }
@@ -4217,14 +4348,13 @@ PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
        }
 
        boost::shared_ptr<Region> r (_region_view->region ());
-
        framepos_t f = adjusted_current_frame (ev);
        f = max (f, r->position ());
        f = min (f, r->last_frame ());
 
        _region_view->move_patch_change (
                *_patch_change,
-               _region_view->region_frames_to_region_beats (f - r->position() - r->start())
+               _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
                );
 }
 
@@ -4249,7 +4379,7 @@ MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* r
 }
 
 void
-MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
+MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
 {
        framepos_t const p = _region_view->region()->position ();
        double const y = _region_view->midi_view()->y_position ();
@@ -4274,6 +4404,34 @@ MidiRubberbandSelectDrag::deselect_things ()
        /* XXX */
 }
 
+MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
+       : RubberbandSelectDrag (e, rv->get_canvas_frame ())
+       , _region_view (rv)
+{
+       _vertical_only = true;
+}
+
+void
+MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
+{
+       double const y = _region_view->midi_view()->y_position ();
+
+       y1 = max (0.0, y1 - y);
+       y2 = max (0.0, y2 - y);
+       
+       _region_view->update_vertical_drag_selection (
+               y1,
+               y2,
+               Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
+               );
+}
+
+void
+MidiVerticalSelectDrag::deselect_things ()
+{
+       /* XXX */
+}
+
 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
        : RubberbandSelectDrag (e, i)
 {
@@ -4367,7 +4525,7 @@ NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 void
 NoteCreateDrag::motion (GdkEvent* event, bool)
 {
-       _note[1] = adjusted_current_frame (event) - _region_view->region()->position ();
+       _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
        double const x = _editor->frame_to_pixel (_note[1]);
        if (_note[1] > _note[0]) {
                _drag_rect->property_x2() = x;
@@ -4377,7 +4535,7 @@ NoteCreateDrag::motion (GdkEvent* event, bool)
 }
 
 void
-NoteCreateDrag::finished (GdkEvent* event, bool had_movement)
+NoteCreateDrag::finished (GdkEvent*, bool had_movement)
 {
        if (!had_movement) {
                return;
@@ -4387,11 +4545,15 @@ NoteCreateDrag::finished (GdkEvent* event, bool had_movement)
        framecnt_t length = abs (_note[0] - _note[1]);
 
        framecnt_t const g = grid_frames (start);
+       double const one_tick = 1 / Timecode::BBT_Time::ticks_per_beat;
+       
        if (_editor->snap_mode() == SnapNormal && length < g) {
-               length = g;
+               length = g - one_tick;
        }
 
-       _region_view->create_note_at (start, _drag_rect->property_y1(), _region_view->region_frames_to_region_beats (length), true, false);
+       double const length_beats = max (one_tick, _region_view->region_frames_to_region_beats (length));
+
+       _region_view->create_note_at (start, _drag_rect->property_y1(), length_beats, false);
 }
 
 double
@@ -4407,3 +4569,99 @@ NoteCreateDrag::aborted (bool)
 {
        
 }
+
+CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
+       : Drag (e, i)
+       , arv (rv)
+       , start (start_yn)
+{
+}
+
+void
+CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
+{
+       Drag::start_grab (event, cursor);
+}
+
+void
+CrossfadeEdgeDrag::motion (GdkEvent*, bool)
+{
+       double distance;
+       double new_length;
+       framecnt_t len;
+
+       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+
+       if (start) {
+               distance = _drags->current_pointer_x() - grab_x();
+               len = ar->fade_in()->back()->when;
+       } else {
+               distance = grab_x() - _drags->current_pointer_x();
+               len = ar->fade_out()->back()->when;
+       }
+
+       /* how long should it be ? */
+
+       new_length = len + _editor->unit_to_frame (distance);
+
+       /* now check with the region that this is legal */
+
+       new_length = ar->verify_xfade_bounds (new_length, start);
+
+       if (start) {
+               arv->redraw_start_xfade_to (ar, new_length);
+       } else {
+               arv->redraw_end_xfade_to (ar, new_length);
+       }
+}
+
+void
+CrossfadeEdgeDrag::finished (GdkEvent*, bool)
+{
+       double distance;
+       double new_length;
+       framecnt_t len;
+
+       boost::shared_ptr<AudioRegion> ar (arv->audio_region());
+
+       if (start) {
+               distance = _drags->current_pointer_x() - grab_x();
+               len = ar->fade_in()->back()->when;
+       } else {
+               distance = grab_x() - _drags->current_pointer_x();
+               len = ar->fade_out()->back()->when;
+       }
+
+       new_length = ar->verify_xfade_bounds (len + _editor->unit_to_frame (distance), start);
+       
+       _editor->begin_reversible_command ("xfade trim");
+       ar->playlist()->clear_owned_changes (); 
+
+       if (start) {
+               ar->set_fade_in_length (new_length);
+       } else {
+               ar->set_fade_out_length (new_length);
+       }
+
+       /* Adjusting the xfade may affect other regions in the playlist, so we need
+          to get undo Commands from the whole playlist rather than just the
+          region.
+       */
+
+       vector<Command*> cmds;
+       ar->playlist()->rdiff (cmds);
+       _editor->session()->add_commands (cmds);
+       _editor->commit_reversible_command ();
+
+}
+
+void
+CrossfadeEdgeDrag::aborted (bool)
+{
+       if (start) {
+               arv->redraw_start_xfade ();
+       } else {
+               arv->redraw_end_xfade ();
+       }
+}
+