drastic rethink of the relationship between remote control ID and route order keys...
[ardour.git] / gtk2_ardour / editor_drag.cc
index 5201d3346df98d3bae2661067ea0a1b6b9f79439..8ae618b37f32ab17cf7075cc5238a18b0a7850fb 100644 (file)
@@ -331,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);
@@ -396,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);
     }
 };
 
@@ -435,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
@@ -645,7 +663,7 @@ 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.
@@ -680,15 +698,15 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                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.
                */
@@ -879,6 +897,10 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
                        );
 
        }
+
+       if (_editor->session() && Config->get_always_play_range()) {
+               _editor->session()->request_locate (_editor->get_selection().regions.start());
+       }
 }
 
 void
@@ -1041,7 +1063,7 @@ 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 */
 
@@ -1255,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 ());
@@ -1443,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) {
@@ -1469,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
@@ -1595,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)
 {
@@ -1723,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();
@@ -1843,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));
@@ -2834,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
@@ -2887,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));
 }
@@ -2998,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);
@@ -3221,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 ();
+               }
 
        }
 
@@ -3252,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);
@@ -3287,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;
+               }
        }
 }
 
@@ -3342,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");
 }
@@ -3573,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)) {
@@ -3593,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 ();
@@ -3881,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)) {
@@ -3966,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
@@ -4008,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) {
@@ -4039,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");
 
-       /* find those that overlap the ranges being dragged */
-       list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
+       list<boost::shared_ptr<AutomationLine> > lines;
+       lines.push_back (rv->get_gain_line ());
+       setup (lines);
+}
+
+/** @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;
@@ -4132,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 */
@@ -4160,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);
                        }
                }
 
@@ -4230,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 ();
@@ -4240,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 ();
        }
 }
@@ -4262,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
@@ -4273,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;
 }
@@ -4287,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()))
                );
 }
 
@@ -4319,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 ();
@@ -4352,7 +4412,7 @@ MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
 }
 
 void
-MidiVerticalSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
+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 ();
 
@@ -4465,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;
@@ -4475,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;
@@ -4509,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 ();
+       }
+}
+