Use Stripable::Sorter in GUI consistently.
[ardour.git] / gtk2_ardour / editor_drag.cc
index 6a143eecd7501cc4e33b4ada96c559c08e439a0f..c9604b13dfb5320caab2603c8ed3020358902c1d 100644 (file)
@@ -40,6 +40,7 @@
 #include "ardour/profile.h"
 #include "ardour/region_factory.h"
 #include "ardour/session.h"
+#include "ardour/session_playlists.h"
 
 #include "canvas/canvas.h"
 #include "canvas/scroll_group.h"
@@ -238,6 +239,7 @@ Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
        , _grab_frame (0)
        , _last_pointer_frame (0)
        , _snap_delta (0)
+       , _snap_delta_music (0.0)
        , _constraint_pressed (false)
 {
 
@@ -355,6 +357,15 @@ Drag::snap_delta (guint state) const
 
        return 0;
 }
+double
+Drag::snap_delta_music (guint state) const
+{
+       if (ArdourKeyboard::indicates_snap_delta (state)) {
+               return _snap_delta_music;
+       }
+
+       return 0.0;
+}
 
 double
 Drag::current_pointer_x() const
@@ -373,11 +384,18 @@ Drag::current_pointer_y () const
 }
 
 void
-Drag::setup_snap_delta (framepos_t pos)
+Drag::setup_snap_delta (MusicFrame pos)
 {
-       MusicFrame snap (pos, 0);
+       TempoMap& map (_editor->session()->tempo_map());
+       MusicFrame snap (pos);
        _editor->snap_to (snap, ARDOUR::RoundNearest, false, true);
-       _snap_delta = snap.frame - pos;
+       _snap_delta = snap.frame - pos.frame;
+
+       _snap_delta_music = 0.0;
+
+       if (_snap_delta != 0) {
+               _snap_delta_music = map.exact_qn_at_frame (snap.frame, snap.division) - map.exact_qn_at_frame (pos.frame, pos.division);
+       }
 }
 
 bool
@@ -528,9 +546,11 @@ Drag::add_midi_region (MidiTimeAxisView* view, bool commit)
        return boost::shared_ptr<Region>();
 }
 
-struct PresentationInfoTimeAxisViewSorter {
-       bool operator() (TimeAxisView* a, TimeAxisView* b) {
-               return a->stripable()->presentation_info().order() < b->stripable()->presentation_info().order();
+struct TimeAxisViewStripableSorter {
+       bool operator() (TimeAxisView* tav_a, TimeAxisView* tav_b) {
+               boost::shared_ptr<ARDOUR::Stripable> const& a = tav_a->stripable ();
+               boost::shared_ptr<ARDOUR::Stripable> const& b = tav_b->stripable ();
+               return ARDOUR::Stripable::Sorter () (a, b);
        }
 };
 
@@ -546,7 +566,7 @@ RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<Re
        */
 
        TrackViewList track_views = _editor->track_views;
-       track_views.sort (PresentationInfoTimeAxisViewSorter ());
+       track_views.sort (TimeAxisViewStripableSorter ());
 
        for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
                _time_axis_views.push_back (*i);
@@ -618,7 +638,7 @@ void
 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
        Drag::start_grab (event, cursor);
-       setup_snap_delta (_last_position.frame);
+       setup_snap_delta (_last_position);
 
        show_verbose_cursor_time (_last_position.frame);
 
@@ -645,6 +665,11 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, MusicFrame* pending_r
        /* compute the amount of pointer motion in frames, and where
           the region would be if we moved it by that much.
        */
+       if (_x_constrained) {
+               *pending_region_position = _last_position;
+               return 0.0;
+       }
+
        *pending_region_position = adjusted_frame (_drags->current_pointer_frame (), event, false);
 
        framecnt_t sync_offset;
@@ -679,23 +704,19 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, MusicFrame* pending_r
        if ((pending_region_position->frame != _last_position.frame) && x_move_allowed) {
 
                /* x movement since last time (in pixels) */
-               dx = (static_cast<double> (pending_region_position->frame) - _last_position.frame) / _editor->samples_per_pixel;
+               dx = _editor->sample_to_pixel_unrounded (pending_region_position->frame - _last_position.frame);
 
                /* total x movement */
-               framecnt_t total_dx = pending_region_position->frame;
-               if (regions_came_from_canvas()) {
-                       total_dx = total_dx - grab_frame ();
-               }
+               framecnt_t total_dx = _editor->pixel_to_sample (_total_x_delta + dx);
 
-               /* check that no regions have gone off the start of the session */
                for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
-                       if ((i->view->region()->position() + total_dx) < 0) {
-                               dx = 0;
-                               *pending_region_position = _last_position;
+                       frameoffset_t const off = i->view->region()->position() + total_dx;
+                       if (off < 0) {
+                               dx = dx - _editor->sample_to_pixel_unrounded (off);
+                               *pending_region_position = MusicFrame (pending_region_position->frame - off, 0);
                                break;
                        }
                }
-
        }
 
        return dx;
@@ -932,8 +953,13 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
        }
 
        /* Work out the change in x */
+       TempoMap& tmap = _editor->session()->tempo_map();
        MusicFrame pending_region_position (0, 0);
        double const x_delta = compute_x_delta (event, &pending_region_position);
+
+       double const last_pos_qn = tmap.exact_qn_at_frame (_last_position.frame, _last_position.division);
+       double const qn_delta = tmap.exact_qn_at_frame (pending_region_position.frame, pending_region_position.division) - last_pos_qn;
+
        _last_position = pending_region_position;
 
        /* calculate hidden tracks in current y-axis delta */
@@ -1177,7 +1203,15 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                }
 
                /* Now move the region view */
-               rv->move (x_delta, y_delta);
+               if (rv->region()->position_lock_style() == MusicTime) {
+                       double const last_qn = tmap.quarter_note_at_frame (rv->get_position());
+                       framepos_t const x_pos_music = tmap.frame_at_quarter_note (last_qn + qn_delta);
+
+                       rv->set_position (x_pos_music, 0);
+                       rv->move (0, y_delta);
+               } else {
+                       rv->move (x_delta, y_delta);
+               }
 
        } /* foreach region */
 
@@ -1411,7 +1445,7 @@ RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region,
                                output_chan =  _editor->session()->master_out()->n_inputs().n_audio();
                        }
                        audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
-                       tav =_editor->axis_view_from_stripable (audio_tracks.front());
+                       tav =_editor->time_axis_view_from_stripable (audio_tracks.front());
                } else {
                        ChanCount one_midi_port (DataType::MIDI, 1);
                        list<boost::shared_ptr<MidiTrack> > midi_tracks;
@@ -1420,7 +1454,7 @@ RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region,
                                                                          boost::shared_ptr<ARDOUR::PluginInfo>(),
                                                                          (ARDOUR::Plugin::PresetRecord*) 0,
                                                                          (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
-                       tav = _editor->axis_view_from_stripable (midi_tracks.front());
+                       tav = _editor->time_axis_view_from_stripable (midi_tracks.front());
                }
 
                if (tav) {
@@ -1560,7 +1594,7 @@ RegionMoveDrag::finished_no_copy (
        PlaylistSet frozen_playlists;
        set<RouteTimeAxisView*> views_to_update;
        RouteTimeAxisView* new_time_axis_view = 0;
-       framecnt_t const drag_delta = _primary->region()->position() - _last_position.frame;
+       framecnt_t const drag_delta = _primary->region()->position() - last_position.frame;
 
        typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
        PlaylistMapping playlist_mapping;
@@ -1632,7 +1666,7 @@ RegionMoveDrag::finished_no_copy (
 
                        /* insert into new playlist */
                        RegionView* new_view;
-                       if (rv == _primary) {
+                       if (rv == _primary && !_x_constrained) {
                                new_view = insert_region_into_playlist (
                                        RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, last_position, last_pos_qn,
                                        modified_playlists, true
@@ -2394,14 +2428,16 @@ RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisVi
 void
 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
 {
+
        if (first_move) {
                _editor->begin_reversible_command (_("create region"));
                _region = add_midi_region (_view, false);
                _view->playlist()->freeze ();
        } else {
+
                if (_region) {
                        framepos_t const f = adjusted_current_frame (event);
-                       if (f < grab_frame()) {
+                       if (f <= grab_frame()) {
                                _region->set_initial_position (f);
                        }
 
@@ -2411,9 +2447,10 @@ RegionCreateDrag::motion (GdkEvent* event, bool first_move)
                           a bit confusing as if a region starts 1 frame after a snap point, one cannot
                           place snapped notes at the start of the region.
                        */
-
-                       framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
-                       _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
+                       if (f != grab_frame()) {
+                               framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
+                               _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
+                       }
                }
        }
 }
@@ -2854,7 +2891,7 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
        framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
 
        framepos_t const pf = adjusted_current_frame (event);
-       setup_snap_delta (region_start);
+       setup_snap_delta (MusicFrame(region_start, 0));
 
        if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
                /* Move the contents of the region around without changing the region bounds */
@@ -2962,6 +2999,24 @@ TrimDrag::motion (GdkEvent* event, bool first_move)
                        if (insert_result.second) {
                                pl->freeze();
                        }
+
+                       MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (rv);
+                       /* a MRV start trim may change the source length. ensure we cover all playlists here */
+                       if (mrv && _operation == StartTrim) {
+                               vector<boost::shared_ptr<Playlist> > all_playlists;
+                               _editor->session()->playlists->get (all_playlists);
+                               for (vector<boost::shared_ptr<Playlist> >::iterator x = all_playlists.begin(); x != all_playlists.end(); ++x) {
+
+                                       if ((*x)->uses_source (rv->region()->source(0))) {
+                                               insert_result = _editor->motion_frozen_playlists.insert (*x);
+                                               if (insert_result.second) {
+                                                       (*x)->clear_owned_changes ();
+                                                       (*x)->freeze();
+                                               }
+
+                                       }
+                               }
+                       }
                }
        }
 
@@ -3110,29 +3165,22 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred)
                if (!_editor->selection->selected (_primary)) {
                        _primary->thaw_after_trim ();
                } else {
-
-                       set<boost::shared_ptr<Playlist> > diffed_playlists;
-
                        for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
                                i->view->thaw_after_trim ();
                                i->view->enable_display (true);
-
-                               /* Trimming one region may affect others on the playlist, so we need
-                                  to get undo Commands from the whole playlist rather than just the
-                                  region.  Use diffed_playlists to make sure we don't diff a given
-                                  playlist more than once.
-                               */
-                               boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
-                               if (diffed_playlists.find (p) == diffed_playlists.end()) {
-                                       vector<Command*> cmds;
-                                       p->rdiff (cmds);
-                                       _editor->session()->add_commands (cmds);
-                                       diffed_playlists.insert (p);
-                               }
                        }
                }
 
                for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
+                       /* Trimming one region may affect others on the playlist, so we need
+                          to get undo Commands from the whole playlist rather than just the
+                          region.  Use motion_frozen_playlists (a set) to make sure we don't
+                          diff a given playlist more than once.
+                       */
+
+                       vector<Command*> cmds;
+                       (*p)->rdiff (cmds);
+                       _editor->session()->add_commands (cmds);
                        (*p)->thaw ();
                }
 
@@ -3336,15 +3384,17 @@ MeterMarkerDrag::aborted (bool moved)
 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
        : Drag (e, i)
        , _copy (c)
-       , _grab_bpm (0.0)
-       , before_state (0)
+       , _grab_bpm (120.0, 4.0)
+       , _grab_qn (0.0)
+       , _before_state (0)
 {
        DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
 
        _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
        _real_section = &_marker->tempo();
        _movable = !_real_section->initial();
-       _grab_bpm = _real_section->note_types_per_minute();
+       _grab_bpm = Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), _real_section->end_note_types_per_minute());
+       _grab_qn = _real_section->pulse() * 4.0;
        assert (_marker);
 }
 
@@ -3371,6 +3421,7 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
        if (!_real_section->active()) {
                return;
        }
+       TempoMap& map (_editor->session()->tempo_map());
 
        if (first_move) {
 
@@ -3393,9 +3444,8 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
                swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
                _marker->hide();
 
-               TempoMap& map (_editor->session()->tempo_map());
                /* get current state */
-               before_state = &map.get_state();
+               _before_state = &map.get_state();
 
                if (!_copy) {
                        _editor->begin_reversible_command (_("move tempo mark"));
@@ -3403,15 +3453,14 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
                } else {
                        const Tempo tempo (_marker->tempo());
                        const framepos_t frame = adjusted_current_frame (event) + 1;
-                       const TempoSection::Type type = _real_section->type();
 
                        _editor->begin_reversible_command (_("copy tempo mark"));
 
                        if (_real_section->position_lock_style() == MusicTime) {
                                const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
-                               _real_section = map.add_tempo (tempo, map.exact_qn_at_frame (frame, divisions), 0, type, MusicTime);
+                               _real_section = map.add_tempo (tempo, map.exact_qn_at_frame (frame, divisions), 0, MusicTime);
                        } else {
-                               _real_section = map.add_tempo (tempo, 0.0, frame, type, AudioTime);
+                               _real_section = map.add_tempo (tempo, 0.0, frame, AudioTime);
                        }
 
                        if (!_real_section) {
@@ -3421,13 +3470,19 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
                }
 
        }
+       if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) {
+               double new_bpm = max (1.5, _grab_bpm.end_note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
+               stringstream strs;
+               _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), new_bpm));
+               strs << "end:" << fixed << setprecision(3) << new_bpm;
+               show_verbose_cursor_text (strs.str());
 
-       if (ArdourKeyboard::indicates_constraint (event->button.state)) {
+       } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
                /* use vertical movement to alter tempo .. should be log */
-               double new_bpm = max (1.5, _grab_bpm + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
+               double new_bpm = max (1.5, _grab_bpm.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
                stringstream strs;
-               _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type()));
-               strs << new_bpm;
+               _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type(), _real_section->end_note_types_per_minute()));
+               strs << "start:" << fixed << setprecision(3) << new_bpm;
                show_verbose_cursor_text (strs.str());
 
        } else if (_movable && !_real_section->locked_to_meter()) {
@@ -3442,8 +3497,6 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
                        pf = adjusted_current_frame (event);
                }
 
-               TempoMap& map (_editor->session()->tempo_map());
-
                /* snap to beat is 1, snap to bar is -1 (sorry) */
                const int sub_num = _editor->get_grid_music_divisions (event->button.state);
 
@@ -3470,7 +3523,7 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
        TempoMap& map (_editor->session()->tempo_map());
 
        XMLNode &after = map.get_state();
-       _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
+       _editor->session()->add_command (new MementoCommand<TempoMap>(map, _before_state, &after));
        _editor->commit_reversible_command ();
 
        // delete the dummy marker we used for visual representation while moving.
@@ -3484,7 +3537,7 @@ TempoMarkerDrag::aborted (bool moved)
        _marker->set_position (_marker->tempo().frame());
        if (moved) {
                TempoMap& map (_editor->session()->tempo_map());
-               map.set_state (*before_state, Stateful::current_state_version);
+               map.set_state (*_before_state, Stateful::current_state_version);
                // delete the dummy (hidden) marker we used for events while moving.
                delete _marker;
        }
@@ -3494,7 +3547,7 @@ BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
        , _grab_qn (0.0)
        , _tempo (0)
-       , before_state (0)
+       , _before_state (0)
 {
        DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
 
@@ -3506,18 +3559,28 @@ BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        Drag::start_grab (event, cursor);
        TempoMap& map (_editor->session()->tempo_map());
        _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
+       _editor->tempo_curve_selected (_tempo, true);
+
        ostringstream sstr;
+       if (_tempo->clamped()) {
+               TempoSection* prev = map.previous_tempo_section (_tempo);
+               if (prev) {
+                       _editor->tempo_curve_selected (prev, true);
+                       sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
+               }
+       }
 
-       sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).note_types_per_minute() << "\n";
-       sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
+       sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
        show_verbose_cursor_text (sstr.str());
-       finished (event, false);
 }
 
 void
 BBTRulerDrag::setup_pointer_frame_offset ()
 {
        TempoMap& map (_editor->session()->tempo_map());
+       /* get current state */
+       _before_state = &map.get_state();
+
        const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame()));
        const uint32_t divisions = _editor->get_grid_beat_divisions (0);
        double beat = 0.0;
@@ -3544,8 +3607,6 @@ BBTRulerDrag::motion (GdkEvent* event, bool first_move)
        TempoMap& map (_editor->session()->tempo_map());
 
        if (first_move) {
-               /* get current state */
-               before_state = &map.get_state();
                _editor->begin_reversible_command (_("stretch tempo"));
        }
 
@@ -3561,9 +3622,16 @@ BBTRulerDrag::motion (GdkEvent* event, bool first_move)
                /* adjust previous tempo to match pointer frame */
                _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.frame_at_quarter_note (_grab_qn), pf);
        }
+
        ostringstream sstr;
-       sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute() << "\n";
-       sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
+       if (_tempo->clamped()) {
+               TempoSection* prev = map.previous_tempo_section (_tempo);
+               if (prev) {
+                       _editor->tempo_curve_selected (prev, true);
+                       sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
+               }
+       }
+       sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
        show_verbose_cursor_text (sstr.str());
 }
 
@@ -3577,18 +3645,255 @@ BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
        TempoMap& map (_editor->session()->tempo_map());
 
        XMLNode &after = map.get_state();
-       _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
+       _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
        _editor->commit_reversible_command ();
+       _editor->tempo_curve_selected (_tempo, false);
+
+       if (_tempo->clamped()) {
+               TempoSection* prev_tempo = map.previous_tempo_section (_tempo);
+               if (prev_tempo) {
+                       _editor->tempo_curve_selected (prev_tempo, false);
+               }
+       }
 }
 
 void
 BBTRulerDrag::aborted (bool moved)
 {
        if (moved) {
-               _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
+               _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
        }
 }
 
+TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+       , _grab_qn (0.0)
+       , _grab_tempo (0.0)
+       , _tempo (0)
+       , _next_tempo (0)
+       , _drag_valid (true)
+       , _before_state (0)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n");
+
+}
+
+void
+TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
+       TempoMap& map (_editor->session()->tempo_map());
+       /* get current state */
+       _before_state = &map.get_state();
+       _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
+
+       _next_tempo = map.next_tempo_section (_tempo);
+       if (_next_tempo) {
+               if (!map.next_tempo_section (_next_tempo)) {
+                       _drag_valid = false;
+                       finished (event, false);
+
+                       return;
+               }
+               _editor->tempo_curve_selected (_tempo, true);
+               _editor->tempo_curve_selected (_next_tempo, true);
+
+               ostringstream sstr;
+               sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
+               sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
+               sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
+               show_verbose_cursor_text (sstr.str());
+       } else {
+               _drag_valid = false;
+       }
+
+       _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type());
+}
+
+void
+TempoTwistDrag::setup_pointer_frame_offset ()
+{
+       TempoMap& map (_editor->session()->tempo_map());
+       const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame()));
+       const uint32_t divisions = _editor->get_grid_beat_divisions (0);
+       double beat = 0.0;
+
+       if (divisions > 0) {
+               beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions);
+       } else {
+               /* while it makes some sense for the user to determine the division to 'grab',
+                  grabbing a bar often leads to confusing results wrt the actual tempo section being altered
+                  and the result over steep tempo curves. Use sixteenths.
+               */
+               beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4);
+       }
+
+       _grab_qn = map.quarter_note_at_beat (beat);
+
+       _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn);
+
+}
+
+void
+TempoTwistDrag::motion (GdkEvent* event, bool first_move)
+{
+
+       if (!_next_tempo || !_drag_valid) {
+               return;
+       }
+
+       TempoMap& map (_editor->session()->tempo_map());
+
+       if (first_move) {
+               _editor->begin_reversible_command (_("twist tempo"));
+       }
+
+       framepos_t pf;
+
+       if (_editor->snap_musical()) {
+               pf = adjusted_current_frame (event, false);
+       } else {
+               pf = adjusted_current_frame (event);
+       }
+
+       /* adjust this and the next tempi to match pointer frame */
+       double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
+       _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.frame_at_quarter_note (_grab_qn), pf);
+
+       ostringstream sstr;
+       sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
+       sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
+       sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
+       show_verbose_cursor_text (sstr.str());
+}
+
+void
+TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred)
+{
+       TempoMap& map (_editor->session()->tempo_map());
+
+       if (!movement_occurred || !_drag_valid) {
+               return;
+       }
+
+       _editor->tempo_curve_selected (_tempo, false);
+       _editor->tempo_curve_selected (_next_tempo, false);
+
+       XMLNode &after = map.get_state();
+       _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
+       _editor->commit_reversible_command ();
+}
+
+void
+TempoTwistDrag::aborted (bool moved)
+{
+       if (moved) {
+               _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
+       }
+}
+
+TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i)
+       : Drag (e, i)
+       , _grab_qn (0.0)
+       , _tempo (0)
+       , _before_state (0)
+{
+       DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n");
+       TempoMarker* marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
+       _tempo = &marker->tempo();
+       _grab_qn = _tempo->pulse() * 4.0;
+}
+
+void
+TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
+       TempoMap& tmap (_editor->session()->tempo_map());
+
+       /* get current state */
+       _before_state = &tmap.get_state();
+
+
+       ostringstream sstr;
+
+       TempoSection* prev = 0;
+       if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
+               _editor->tempo_curve_selected (tmap.previous_tempo_section (_tempo), true);
+               sstr << "end: " << fixed << setprecision(3) << tmap.tempo_section_at_frame (_tempo->frame() - 1).end_note_types_per_minute() << "\n";
+       }
+
+       if (_tempo->clamped()) {
+               _editor->tempo_curve_selected (_tempo, true);
+               sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
+       }
+
+       show_verbose_cursor_text (sstr.str());
+}
+
+void
+TempoEndDrag::setup_pointer_frame_offset ()
+{
+       TempoMap& map (_editor->session()->tempo_map());
+
+       _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn);
+
+}
+
+void
+TempoEndDrag::motion (GdkEvent* event, bool first_move)
+{
+       TempoMap& map (_editor->session()->tempo_map());
+
+       if (first_move) {
+               _editor->begin_reversible_command (_("stretch end tempo"));
+       }
+
+
+
+       framepos_t const pf = adjusted_current_frame (event, false);
+       map.gui_stretch_tempo_end (&map.tempo_section_at_frame (_tempo->frame() - 1), map.frame_at_quarter_note (_grab_qn), pf);
+
+       ostringstream sstr;
+       sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_frame (_tempo->frame() - 1).end_note_types_per_minute() << "\n";
+
+       if (_tempo->clamped()) {
+               sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
+       }
+
+       show_verbose_cursor_text (sstr.str());
+}
+
+void
+TempoEndDrag::finished (GdkEvent* event, bool movement_occurred)
+{
+       if (!movement_occurred) {
+               return;
+       }
+
+       TempoMap& tmap (_editor->session()->tempo_map());
+
+       XMLNode &after = tmap.get_state();
+       _editor->session()->add_command(new MementoCommand<TempoMap>(tmap, _before_state, &after));
+       _editor->commit_reversible_command ();
+
+       TempoSection* prev = 0;
+       if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
+               _editor->tempo_curve_selected (prev, false);
+       }
+
+       if (_tempo->clamped()) {
+               _editor->tempo_curve_selected (_tempo, false);
+
+       }
+}
+
+void
+TempoEndDrag::aborted (bool moved)
+{
+       if (moved) {
+               _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
+       }
+}
 
 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
        : Drag (e, &c.track_canvas_item(), false)
@@ -3633,7 +3938,7 @@ void
 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
 {
        Drag::start_grab (event, c);
-       setup_snap_delta (_editor->playhead_cursor->current_frame());
+       setup_snap_delta (MusicFrame (_editor->playhead_cursor->current_frame(), 0));
 
        _grab_zoom = _editor->samples_per_pixel;
 
@@ -3641,6 +3946,7 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
 
        _editor->snap_to_with_modifier (where, event);
        _editor->_dragging_playhead = true;
+       _editor->_control_scroll_target = where.frame;
 
        Session* s = _editor->session ();
 
@@ -3658,7 +3964,7 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
                }
 
 
-               if (AudioEngine::instance()->connected()) {
+               if (AudioEngine::instance()->running()) {
 
                        /* do this only if we're the engine is connected
                         * because otherwise this request will never be
@@ -3668,7 +3974,7 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
                         */
 
                        s->request_suspend_timecode_transmission ();
-                       while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
+                       while (AudioEngine::instance()->running() && !s->timecode_transmission_suspended ()) {
                                /* twiddle our thumbs */
                        }
                }
@@ -3736,7 +4042,7 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
        boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
-       setup_snap_delta (r->position());
+       setup_snap_delta (MusicFrame (r->position(), 0));
 
        show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
 }
@@ -3862,7 +4168,7 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
        boost::shared_ptr<AudioRegion> r = arv->audio_region ();
-       setup_snap_delta (r->last_frame());
+       setup_snap_delta (MusicFrame (r->last_frame(), 0));
 
        show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
 }
@@ -4023,7 +4329,7 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        } else {
                show_verbose_cursor_time (location->end());
        }
-       setup_snap_delta (is_start ? location->start() : location->end());
+       setup_snap_delta (MusicFrame (is_start ? location->start() : location->end(), 0));
 
        Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
 
@@ -4410,7 +4716,7 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
        _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
        _fixed_grab_y = _point->get_y();
 
-       setup_snap_delta (_editor->pixel_to_sample (_fixed_grab_x));
+       setup_snap_delta (MusicFrame (_editor->pixel_to_sample (_fixed_grab_x), 0));
 
        float const fraction = 1 - (_point->get_y() / _point->line().height());
        show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
@@ -4908,10 +5214,10 @@ TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        _editor->get_selection().add (_primary);
 
-       framepos_t where = _primary->region()->position();
+       MusicFrame where (_primary->region()->position(), 0);
        setup_snap_delta (where);
 
-       show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
+       show_verbose_cursor_duration (where.frame, adjusted_current_frame (event), 0);
 }
 
 void
@@ -5200,11 +5506,17 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                        //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
                        TrackViewList tracks_to_add;
                        TrackViewList tracks_to_remove;
+                       vector<RouteGroup*> selected_route_groups;
 
                        if (!first_move) {
                                for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
                                        if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
                                                tracks_to_remove.push_back (*i);
+                                       } else {
+                                               RouteGroup* rg = (*i)->route_group();
+                                               if (rg && rg->is_active() && rg->is_select()) {
+                                                       selected_route_groups.push_back (rg);
+                                               }
                                        }
                                }
                        }
@@ -5212,12 +5524,35 @@ SelectionDrag::motion (GdkEvent* event, bool first_move)
                        for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
                                if (!_editor->selection->tracks.contains (*i)) {
                                        tracks_to_add.push_back (*i);
+                                       RouteGroup* rg = (*i)->route_group();
+
+                                       if (rg && rg->is_active() && rg->is_select()) {
+                                               selected_route_groups.push_back (rg);
+                                       }
                                }
                        }
 
                        _editor->selection->add (tracks_to_add);
 
                        if (!tracks_to_remove.empty()) {
+
+                               /* check all these to-be-removed tracks against the
+                                * possibility that they are selected by being
+                                * in the same group as an approved track.
+                                */
+
+                               for (TrackViewList::iterator i = tracks_to_remove.begin(); i != tracks_to_remove.end(); ) {
+                                       RouteGroup* rg = (*i)->route_group();
+
+                                       if (rg && find (selected_route_groups.begin(), selected_route_groups.end(), rg) != selected_route_groups.end()) {
+                                               i = tracks_to_remove.erase (i);
+                                       } else {
+                                               ++i;
+                                       }
+                               }
+
+                               /* remove whatever is left */
+
                                _editor->selection->remove (tracks_to_remove);
                        }
                }
@@ -5619,6 +5954,7 @@ NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
        : Drag (e, i)
        , _cumulative_dx (0)
        , _cumulative_dy (0)
+       , _earliest (0.0)
        , _was_selected (false)
        , _copy (false)
 {
@@ -5630,18 +5966,25 @@ NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
        _note_height = _region->midi_stream_view()->note_height ();
 }
 
+void
+NoteDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame()
+               - _editor->session()->tempo_map().frame_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
+}
+
 void
 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
 {
        Drag::start_grab (event);
 
-       if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
+       if (ArdourKeyboard::indicates_copy (event->button.state)) {
                _copy = true;
        } else {
                _copy = false;
        }
 
-       setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
+       setup_snap_delta (MusicFrame (_region->source_beats_to_absolute_frames (_primary->note()->time ()), 0));
 
        if (!(_was_selected = _primary->selected())) {
 
@@ -5665,55 +6008,38 @@ NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
        }
 }
 
-/** @return Current total drag x change in frames */
-frameoffset_t
-NoteDrag::total_dx (const guint state) const
+/** @return Current total drag x change in quarter notes */
+double
+NoteDrag::total_dx (GdkEvent * event) const
 {
        if (_x_constrained) {
                return 0;
        }
+
        TempoMap& map (_editor->session()->tempo_map());
 
        /* dx in frames */
        frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
 
        /* primary note time */
-       double const quarter_note_start = _region->region()->quarter_note() - _region->midi_region()->start_beats();
-       frameoffset_t const n = map.frame_at_quarter_note (quarter_note_start + _primary->note()->time().to_double());
-
-       /* new time of the primary note in session frames */
-       frameoffset_t st = n + dx + snap_delta (state);
-
-       framepos_t const rp = _region->region()->position ();
+       frameoffset_t const n = map.frame_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
 
-       /* prevent the note being dragged earlier than the region's position */
-       st = max (st, rp);
+       /* primary note time in quarter notes */
+       double const n_qn = _region->session_relative_qn (_primary->note()->time().to_double());
 
-       /* possibly snap and return corresponding delta */
+       /* new time of the primary note in session frames */
+       frameoffset_t st = n + dx + snap_delta (event->button.state);
 
-       bool snap = true;
+       /* possibly snap and return corresponding delta in quarter notes */
+       MusicFrame snap (st, 0);
+       _editor->snap_to_with_modifier (snap, event);
+       double ret = map.exact_qn_at_frame (snap.frame, snap.division) - n_qn - snap_delta_music (event->button.state);
 
-       if (ArdourKeyboard::indicates_snap (state)) {
-               if (_editor->snap_mode () != SnapOff) {
-                       snap = false;
-               }
-       } else {
-               if (_editor->snap_mode () == SnapOff) {
-                       snap = false;
-                       /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
-                       if (ArdourKeyboard::indicates_snap_delta (state)) {
-                               snap = true;
-                       }
-               }
+       /* prevent the earliest note being dragged earlier than the region's start position */
+       if (_earliest + ret < _region->midi_region()->start_beats()) {
+               ret -= (_earliest + ret) - _region->midi_region()->start_beats();
        }
 
-       frameoffset_t ret;
-       if (snap) {
-               bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
-               ret =  _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
-       } else {
-               ret = st - n - snap_delta (state);
-       }
        return ret;
 }
 
@@ -5739,30 +6065,33 @@ NoteDrag::total_dy () const
 void
 NoteDrag::motion (GdkEvent * event, bool first_move)
 {
-       if (_copy && first_move) {
-               /* make copies of all the selected notes */
-               _primary = _region->copy_selection ();
+       if (first_move) {
+               _earliest = _region->earliest_in_selection().to_double();
+               if (_copy) {
+                       /* make copies of all the selected notes */
+                       _primary = _region->copy_selection (_primary);
+               }
        }
 
        /* Total change in x and y since the start of the drag */
-       frameoffset_t const dx = total_dx (event->button.state);
+       double const dx_qn = total_dx (event);
        int8_t const dy = total_dy ();
 
        /* Now work out what we have to do to the note canvas items to set this new drag delta */
-       double const tdx = _x_constrained ? 0 : _editor->sample_to_pixel (dx) - _cumulative_dx;
+       double const tdx = _x_constrained ? 0 : dx_qn - _cumulative_dx;
        double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
 
        if (tdx || tdy) {
-               _cumulative_dx += tdx;
+               _cumulative_dx = dx_qn;
                _cumulative_dy += tdy;
 
                int8_t note_delta = total_dy();
 
                if (tdx || tdy) {
                        if (_copy) {
-                               _region->move_copies (tdx, tdy, note_delta);
+                               _region->move_copies (dx_qn, tdy, note_delta);
                        } else {
-                               _region->move_selection (tdx, tdy, note_delta);
+                               _region->move_selection (dx_qn, tdy, note_delta);
                        }
 
                        /* the new note value may be the same as the old one, but we
@@ -5823,7 +6152,7 @@ NoteDrag::finished (GdkEvent* ev, bool moved)
                        }
                }
        } else {
-               _region->note_dropped (_primary, total_dx (ev->button.state), total_dy(), _copy);
+               _region->note_dropped (_primary, total_dx (ev), total_dy(), _copy);
        }
 }
 
@@ -6363,7 +6692,7 @@ NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
        framepos_t const start = min (_note[0], _note[1]);
        framepos_t const start_sess_rel = start + _region_view->region()->position();
        framecnt_t length = max (_editor->pixel_to_sample (1.0), (framecnt_t) fabs ((double)(_note[0] - _note[1])));
-       framecnt_t const g = grid_frames (start);
+       framecnt_t const g = grid_frames (start_sess_rel);
 
        if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
                length = g;
@@ -6373,7 +6702,10 @@ NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
        const double qn_length = map.quarter_notes_between_frames (start_sess_rel, start_sess_rel + length);
        Evoral::Beats qn_length_beats = max (Evoral::Beats::ticks(1), Evoral::Beats (qn_length));
 
+       _editor->begin_reversible_command (_("Create Note"));
+       _region_view->clear_editor_note_selection();
        _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
+       _editor->commit_reversible_command ();
 }
 
 double
@@ -6394,7 +6726,7 @@ HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView*
        : Drag (e, i)
        , _region_view (rv)
        , _last_pos (0)
-       , _last_y (0.0)
+       , _y (0.0)
 {
 }
 
@@ -6409,6 +6741,8 @@ HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 
        TempoMap& map (_editor->session()->tempo_map());
 
+       _y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
+
        const framepos_t pf = _drags->current_pointer_frame ();
        const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
 
@@ -6421,14 +6755,13 @@ HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
        }
 
        const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
-       const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
-
        Evoral::Beats length = _region_view->get_grid_beats (pf);
 
-       _region_view->create_note_at (start, y, length, event->button.state, false);
+       _editor->begin_reversible_command (_("Create Hit"));
+       _region_view->clear_editor_note_selection();
+       _region_view->create_note_at (start, _y, length, event->button.state, false);
 
        _last_pos = start;
-       _last_y = y;
 }
 
 void
@@ -6445,28 +6778,28 @@ HitCreateDrag::motion (GdkEvent* event, bool)
 
        const double eqaf = map.exact_qn_at_frame (pf, divisions);
        const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position ();
-       const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
 
-       if (_last_pos == start && y == _last_y) {
+       if (_last_pos == start) {
                return;
        }
 
        Evoral::Beats length = _region_view->get_grid_beats (pf);
 
        boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
+
        if (eqaf >= mr->quarter_note() + mr->length_beats()) {
                return;
        }
 
-       _region_view->create_note_at (start, y, length, event->button.state, false);
+       _region_view->create_note_at (start, _y, length, event->button.state, false);
 
        _last_pos = start;
-       _last_y = y;
 }
 
 void
 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
 {
+       _editor->commit_reversible_command ();
 
 }