enter and exit node edit mode with dbl-click in any MIDI region
[ardour.git] / gtk2_ardour / editor_drag.cc
index 5a2abd61c687d5ce7cca7c43c86ab89204766cea..bfcfb29c031fc58fff04ed058da485e328a449e4 100644 (file)
 
 #include "gtkmm2ext/utils.h"
 
-#include "ardour/session.h"
+#include "ardour/audioregion.h"
 #include "ardour/dB.h"
-#include "ardour/region_factory.h"
+#include "ardour/midi_region.h"
 #include "ardour/operations.h"
+#include "ardour/region_factory.h"
+#include "ardour/session.h"
 
 #include "editor.h"
 #include "i18n.h"
@@ -66,14 +68,13 @@ using namespace ArdourCanvas;
 
 using Gtkmm2ext::Keyboard;
 
-double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
+double ControlPointDrag::_zero_gain_fraction = -1.0;
 
 DragManager::DragManager (Editor* e)
        : _editor (e)
        , _ending (false)
        , _current_pointer_frame (0)
 {
-
 }
 
 DragManager::~DragManager ()
@@ -410,20 +411,19 @@ RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<Re
 {
        _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
 
-       /* Make a list of non-hidden tracks to refer to during the drag */
+       /* Make a list of tracks to refer to during the drag; we include hidden tracks,
+          as some of the regions we are dragging may be on such tracks.
+       */
 
        TrackViewList track_views = _editor->track_views;
        track_views.sort (EditorOrderTimeAxisViewSorter ());
 
        for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
-               if (!(*i)->hidden()) {
-
-                       _time_axis_views.push_back (*i);
-
-                       TimeAxisView::Children children_list = (*i)->get_child_list ();
-                       for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
-                               _time_axis_views.push_back (j->get());
-                       }
+               _time_axis_views.push_back (*i);
+               
+               TimeAxisView::Children children_list = (*i)->get_child_list ();
+               for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
+                       _time_axis_views.push_back (j->get());
                }
        }
 
@@ -450,7 +450,9 @@ RegionDrag::region_going_away (RegionView* v)
        }
 }
 
-/** Given a non-hidden TimeAxisView, return the index of it into the _time_axis_views vector */
+/** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
+ *  or -1 if it is not found.
+ */
 int
 RegionDrag::find_time_axis_view (TimeAxisView* t) const
 {
@@ -483,7 +485,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;
 }
@@ -552,7 +554,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;
@@ -567,8 +569,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.
                        */
@@ -586,7 +592,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);
@@ -599,7 +609,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;
@@ -607,7 +617,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 */
@@ -634,86 +644,59 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
 
                if (first_move) {
 
-                       /* here we are calculating the y distance from the
-                          top of the first track view to the top of the region
-                          area of the track view that we're working on */
-
-                       /* this x value is just a dummy value so that we have something
-                          to pass to i2w () */
-
-                       double ix1 = 0;
-
-                       /* distance from the top of this track view to the region area
-                          of our track view is always 1 */
-
-                       double iy1 = 1;
-
-                       /* convert to world coordinates, ie distance from the top of
-                          the ruler section */
-
-                       rv->get_canvas_frame()->i2w (ix1, iy1);
-
-                       /* compensate for the ruler section and the vertical scrollbar position */
-                       iy1 += _editor->get_trackview_group_vertical_offset ();
-
-                       // hide any dependent views
-
                        rv->get_time_axis_view().hide_dependent_views (*rv);
 
-                       /*
-                         reparent to a non scrolling group so that we can keep the
-                         region selection above all time axis views.
-                         reparenting means we have to move the rv as the two
-                         parent groups have different coordinates.
+                       /* 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()->property_y() = iy1 - 1;
                        rv->get_canvas_group()->reparent (*(_editor->_region_motion_group));
-
+                       
                        rv->fake_set_opaque (true);
                }
 
-               /* Work out the change in y position of this region view */
-
-               double y_delta = 0;
-
                /* If we have moved tracks, we'll fudge the layer delta so that the
                   region gets moved back onto layer 0 on its new track; this avoids
                   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;
                }
 
-               /* Move this region to layer 0 on its old track */
-               StreamView* lv = _time_axis_views[i->time_axis_view]->view ();
-               if (lv->layer_display() == Stacked) {
-                       y_delta -= (lv->layers() - i->layer - 1) * lv->child_height ();
+               /* 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);
                }
 
-               /* Now move it to its right layer on the current track */
-               StreamView* cv = _time_axis_views[i->time_axis_view + delta_time_axis_view]->view ();
-               if (cv->layer_display() == Stacked) {
-                       y_delta += (cv->layers() - (i->layer + this_delta_layer) - 1) * cv->child_height ();
+               /* 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 ());
 
-               /* Move tracks */
-               if (delta_time_axis_view > 0) {
-                       for (int j = 0; j < delta_time_axis_view; ++j) {
-                               y_delta += _time_axis_views[i->time_axis_view + j]->current_height ();
-                       }
+               /* Update show/hidden status as the region view may have come from a hidden track,
+                  or have moved to one.
+               */
+               if (tv->hidden ()) {
+                       rv->get_canvas_group()->hide ();
                } else {
-                       /* start by subtracting the height of the track above where we are now */
-                       for (int j = 1; j <= -delta_time_axis_view; ++j) {
-                               y_delta -= _time_axis_views[i->time_axis_view - j]->current_height ();
-                       }
+                       rv->get_canvas_group()->show ();
                }
 
-               /* Set height */
-               rv->set_height (_time_axis_views[i->time_axis_view + delta_time_axis_view]->view()->child_height ());
-
                /* Update the DraggingView */
                i->time_axis_view += delta_time_axis_view;
                i->layer += this_delta_layer;
@@ -721,7 +704,28 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
                if (_brushing) {
                        _editor->mouse_brush_insert_region (rv, pending_region_position);
                } else {
-                       rv->move (x_delta, y_delta);
+                       double x = 0;
+                       double y = 0;
+
+                       /* Get the y coordinate of the top of the track that this region is now on */
+                       tv->canvas_display()->i2w (x, y);
+                       y += _editor->get_trackview_group_vertical_offset();
+                       
+                       /* And adjust for the layer that it should be on */
+                       StreamView* cv = tv->view ();
+                       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());
                }
 
        } /* foreach region */
@@ -810,10 +814,31 @@ 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 */
+               if (ev->type == GDK_2BUTTON_PRESS && ev->button.button == 1) {
+                       /* double click - internal edit mode */
+                       Glib::RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("toggle-internal-edit"));
+                       act->activate ();
+               }
                return;
        }
 
@@ -827,10 +852,18 @@ RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
 
        assert (!_views.empty ());
 
+       /* We might have hidden region views so that they weren't visible during the drag
+          (when they have been reparented).  Now everything can be shown again, as region
+          views are back in their track parent groups.
+       */
+       for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
+               i->view->get_canvas_group()->show ();
+       }
+       
        bool const changed_position = (_last_frame_position != _primary->region()->position());
        bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
        framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
-
+       
        _editor->update_canvas_now ();
 
        if (_copy) {
@@ -937,6 +970,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 */
@@ -955,13 +989,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) {
@@ -1015,9 +1051,8 @@ RegionMoveDrag::finished_no_copy (
 
                        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 */
@@ -1089,6 +1124,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.
@@ -1150,9 +1196,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 ();
@@ -1201,6 +1246,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 ());
@@ -1308,13 +1359,8 @@ 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);
-       layer_t layer = tvp.second;
-
-       if (tv && tv->layer_display() == Overlaid) {
-               layer = 0;
-       }
 
        /* The region motion is only processed if the pointer is over
           an audio track.
@@ -1547,7 +1593,10 @@ NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
 void
 NoteResizeDrag::aborted (bool)
 {
-       /* XXX: TODO */
+       MidiRegionSelection& ms (_editor->get_selection().midi_regions);
+       for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
+               (*r)->abort_resizing ();
+       }
 }
 
 RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
@@ -1860,7 +1909,6 @@ MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
          _copy (c)
 {
        DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
-
        _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
        assert (_marker);
 }
@@ -1868,35 +1916,7 @@ MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
-       if (_copy) {
-               // create a dummy marker for visual representation of moving the copy.
-               // The actual copying is not done before we reach the finish callback.
-               char name[64];
-               snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
-
-               MeterMarker* new_marker = new MeterMarker (
-                       *_editor,
-                       *_editor->meter_group,
-                       ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
-                       name,
-                       *new MeterSection (_marker->meter())
-                       );
-
-               _item = &new_marker->the_item ();
-               _marker = new_marker;
-
-       } else {
-
-               MetricSection& section (_marker->meter());
-
-               if (!section.movable()) {
-                       return;
-               }
-
-       }
-
        Drag::start_grab (event, cursor);
-
        show_verbose_cursor_time (adjusted_current_frame(event));
 }
 
@@ -1907,12 +1927,46 @@ MeterMarkerDrag::setup_pointer_frame_offset ()
 }
 
 void
-MeterMarkerDrag::motion (GdkEvent* event, bool)
+MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
 {
-       framepos_t const pf = adjusted_current_frame (event);
+       if (first_move) {
 
-       _marker->set_position (pf);
+               // create a dummy marker for visual representation of moving the
+               // section, because whether its a copy or not, we're going to 
+               // leave or lose the original marker (leave if its a copy; lose if its
+               // not, because we'll remove it from the map).
+               
+               MeterSection section (_marker->meter());
+
+               if (!section.movable()) {
+                       return;
+               }
+               
+               char name[64];
+               snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
+               
+               _marker = new MeterMarker (
+                       *_editor,
+                       *_editor->meter_group,
+                       ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
+                       name,
+                       *new MeterSection (_marker->meter())
+               );
+               
+               /* use the new marker for the grab */
+               swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
+
+               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, true);
+               }
+       }
 
+       framepos_t const pf = adjusted_current_frame (event);
+       _marker->set_position (pf);
        show_verbose_cursor_time (pf);
 }
 
@@ -1929,7 +1983,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
 
        TempoMap& map (_editor->session()->tempo_map());
        map.bbt_time (last_pointer_frame(), when);
-
+       
        if (_copy == true) {
                _editor->begin_reversible_command (_("copy meter mark"));
                XMLNode &before = map.get_state();
@@ -1938,23 +1992,35 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
                _editor->commit_reversible_command ();
 
-               // delete the dummy marker we used for visual representation of copying.
-               // a new visual marker will show up automatically.
-               delete _marker;
        } else {
                _editor->begin_reversible_command (_("move meter mark"));
-               XMLNode &before = map.get_state();
-               map.move_meter (_marker->meter(), when);
+
+               /* 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 ();
        }
+
+       // delete the dummy marker we used for visual representation while moving.
+       // a new visual marker will show up automatically.
+       delete _marker;
 }
 
 void
-MeterMarkerDrag::aborted (bool)
+MeterMarkerDrag::aborted (bool moved)
 {
        _marker->set_position (_marker->meter().frame ());
+
+       if (moved) {
+               TempoMap& map (_editor->session()->tempo_map());
+               /* we removed it before, so add it back now */
+               map.add_meter (_marker->meter(), _marker->meter().frame());
+               // delete the dummy marker we used for visual representation while moving.
+               // a new visual marker will show up automatically.
+               delete _marker;
+       }
 }
 
 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
@@ -1970,14 +2036,35 @@ TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
 void
 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
 {
-       if (_copy) {
+       Drag::start_grab (event, cursor);
+       show_verbose_cursor_time (adjusted_current_frame (event));
+}
 
+void
+TempoMarkerDrag::setup_pointer_frame_offset ()
+{
+       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
+}
+
+void
+TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
+{
+       if (first_move) {
+
+               // create a dummy marker for visual representation of moving the
+               // section, because whether its a copy or not, we're going to 
+               // leave or lose the original marker (leave if its a copy; lose if its
+               // not, because we'll remove it from the map).
+               
                // create a dummy marker for visual representation of moving the copy.
                // The actual copying is not done before we reach the finish callback.
+
                char name[64];
                snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
 
-               TempoMarker* new_marker = new TempoMarker (
+               TempoSection section (_marker->tempo());
+
+               _marker = new TempoMarker (
                        *_editor,
                        *_editor->tempo_group,
                        ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
@@ -1985,25 +2072,18 @@ TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                        *new TempoSection (_marker->tempo())
                        );
 
-               _item = &new_marker->the_item ();
-               _marker = new_marker;
+               /* use the new marker for the grab */
+               swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
 
+               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, true);
+               }
        }
 
-       Drag::start_grab (event, cursor);
-
-       show_verbose_cursor_time (adjusted_current_frame (event));
-}
-
-void
-TempoMarkerDrag::setup_pointer_frame_offset ()
-{
-       _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
-}
-
-void
-TempoMarkerDrag::motion (GdkEvent* event, bool)
-{
        framepos_t const pf = adjusted_current_frame (event);
        _marker->set_position (pf);
        show_verbose_cursor_time (pf);
@@ -2018,10 +2098,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"));
@@ -2031,23 +2112,32 @@ TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
                _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
                _editor->commit_reversible_command ();
 
-               // delete the dummy marker we used for visual representation of copying.
-               // a new visual marker will show up automatically.
-               delete _marker;
        } else {
                _editor->begin_reversible_command (_("move tempo mark"));
-               XMLNode &before = map.get_state();
-               map.move_tempo (_marker->tempo(), when);
+               /* 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 ();
        }
+
+       // delete the dummy marker we used for visual representation while moving.
+       // a new visual marker will show up automatically.
+       delete _marker;
 }
 
 void
-TempoMarkerDrag::aborted (bool)
+TempoMarkerDrag::aborted (bool moved)
 {
        _marker->set_position (_marker->tempo().frame());
+       if (moved) {
+               TempoMap& map (_editor->session()->tempo_map());
+               /* we removed it before, so add it back now */
+               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;
+       }
 }
 
 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
@@ -2112,39 +2202,6 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
 void
 CursorDrag::motion (GdkEvent* event, bool)
 {
-       if (_drags->current_pointer_y() != last_pointer_y()) {
-
-               /* zoom when we move the pointer up and down */
-
-               /* y range to operate over (pixels) */
-               double const y_range = 512;
-               /* we will multiply the grab zoom by a factor between scale_range and scale_range^-1 */
-               double const scale_range = 4;
-               /* dead zone around the grab point in which to do no zooming (pixels) */
-               double const dead_zone = 128;
-
-               /* current dy */
-               double dy = _drags->current_pointer_y() - grab_y();
-
-               if (dy < -dead_zone || dy > dead_zone) {
-                       /* we are outside the dead zone; remove it from our calculation */
-                       if (dy < 0) {
-                               dy += dead_zone;
-                       } else {
-                               dy -= dead_zone;
-                       }
-
-                       /* get a number from -1 to 1 as dy ranges from -y_range to y_range */
-                       double udy = max (min (dy / y_range, 1.0), -1.0);
-
-                       /* and zoom, using playhead focus temporarily */
-                       Editing::ZoomFocus const zf = _editor->get_zoom_focus ();
-                       _editor->set_zoom_focus (Editing::ZoomFocusPlayhead);
-                       _editor->temporal_zoom (_grab_zoom * pow (scale_range, -udy));
-                       _editor->set_zoom_focus (zf);
-               }
-       }
-
        framepos_t const adjusted_frame = adjusted_current_frame (event);
        if (adjusted_frame != last_pointer_frame()) {
                fake_locate (adjusted_frame);
@@ -2742,7 +2799,7 @@ MarkerDrag::aborted (bool)
 }
 
 void
-MarkerDrag::update_item (Location* location)
+MarkerDrag::update_item (Location*)
 {
         /* noop */
 }
@@ -2752,6 +2809,10 @@ ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
          _cumulative_x_drag (0),
          _cumulative_y_drag (0)
 {
+       if (_zero_gain_fraction < 0.0) {
+               _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
+       }
+
        DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
 
        _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
@@ -3114,7 +3175,37 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool)
                _editor->rubberband_rect->raise_to_top();
 
                show_verbose_cursor_time (pf);
+
+               do_select_things (event, true);
+       }
+}
+
+void
+RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
+{
+       framepos_t x1;
+       framepos_t x2;
+       
+       if (grab_frame() < last_pointer_frame()) {
+               x1 = grab_frame ();
+               x2 = last_pointer_frame ();
+       } else {
+               x2 = grab_frame ();
+               x1 = last_pointer_frame ();
+       }
+
+       double y1;
+       double y2;
+       
+       if (_drags->current_pointer_y() < grab_y()) {
+               y1 = _drags->current_pointer_y();
+               y2 = grab_y();
+       } else {
+               y2 = _drags->current_pointer_y();
+               y1 = grab_y();
        }
+
+       select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
 }
 
 void
@@ -3123,36 +3214,12 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
        if (movement_occurred) {
 
                motion (event, false);
+               do_select_things (event, false);
 
-               double y1,y2;
-               if (_drags->current_pointer_y() < grab_y()) {
-                       y1 = _drags->current_pointer_y();
-                       y2 = grab_y();
-               } else {
-                       y2 = _drags->current_pointer_y();
-                       y1 = grab_y();
-               }
-
-
-               Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
-
-               _editor->begin_reversible_command (_("rubberband selection"));
-
-               if (grab_frame() < last_pointer_frame()) {
-                       _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
-               } else {
-                       _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
-               }
+       } else {
 
-               _editor->commit_reversible_command ();
+               deselect_things ();
 
-       } else {
-               if (!getenv("ARDOUR_SAE")) {
-                       _editor->selection->clear_tracks();
-               }
-               _editor->selection->clear_regions();
-               _editor->selection->clear_points ();
-               _editor->selection->clear_lines ();
        }
 
        _editor->rubberband_rect->hide();
@@ -3878,43 +3945,25 @@ NoteDrag::total_dx () const
        frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
 
        /* primary note time */
-       frameoffset_t const n = _region->beats_to_frames (_primary->note()->time ());
+       frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
 
-       /* new time of the primary note relative to the region position */
+       /* new time of the primary note in session frames */
        frameoffset_t st = n + dx;
 
+       framepos_t const rp = _region->region()->position ();
+
        /* prevent the note being dragged earlier than the region's position */
-       if (st < 0) {
-               st = 0;
-       }
+       st = max (st, rp);
 
        /* snap and return corresponding delta */
-       return _region->snap_frame_to_frame (st) - n;
+       return _region->snap_frame_to_frame (st - rp) + rp - n;
 }
 
-/** @return Current total drag y change in notes */
+/** @return Current total drag y change in note number */
 int8_t
 NoteDrag::total_dy () const
 {
-       /* this is `backwards' to make increasing note number go in the right direction */
-       double const dy = _drags->current_pointer_y() - grab_y();
-
-       /* dy in notes */
-       int8_t ndy = 0;
-
-       if (abs (dy) >= _note_height) {
-               if (dy > 0) {
-                       ndy = (int8_t) ceil (dy / _note_height / 2.0);
-               } else {
-                       ndy = (int8_t) floor (dy / _note_height / 2.0);
-               }
-       }
-
-       /* more positive value = higher pitch and higher y-axis position on track,
-          which is the inverse of the X-centric geometric universe
-       */
-
-       return -ndy;
+       return ((int8_t) (grab_y() / _note_height)) - ((int8_t) (_drags->current_pointer_y() / _note_height));
 }
 
 void
@@ -3922,11 +3971,11 @@ NoteDrag::motion (GdkEvent *, bool)
 {
        /* Total change in x and y since the start of the drag */
        frameoffset_t const dx = total_dx ();
-       int8_t const dy = -total_dy ();
+       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 = _editor->frame_to_unit (dx) - _cumulative_dx;
-       double const tdy = dy * _note_height - _cumulative_dy;
+       double const tdy = -dy * _note_height - _cumulative_dy;
 
        if (tdx || tdy) {
                _cumulative_dx += tdx;
@@ -3936,9 +3985,17 @@ NoteDrag::motion (GdkEvent *, bool)
 
                _region->move_selection (tdx, tdy, note_delta);
 
+               /* the new note value may be the same as the old one, but we
+                * don't know what that means because the selection may have
+                * involved more than one note and we might be doing something
+                * odd with them. so show the note value anyway, always.
+                */
+
                char buf[12];
-               snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (_primary->note()->note() + note_delta).c_str(),
-                         (int) floor (_primary->note()->note() + note_delta));
+               uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
+               
+               snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
+                         (int) floor (new_note));
 
                show_verbose_cursor_text (buf);
        }
@@ -4234,7 +4291,7 @@ PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
 
        _region_view->move_patch_change (
                *_patch_change,
-               _region_view->frames_to_beats (f - r->position() - r->start())
+               _region_view->region_frames_to_region_beats (f - r->position() - r->start())
                );
 }
 
@@ -4248,6 +4305,176 @@ void
 PatchChangeDrag::setup_pointer_frame_offset ()
 {
        boost::shared_ptr<Region> region = _region_view->region ();
-       _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_patch_change->patch()->time()) - region->position() + region->start();
+       _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
+}
+
+MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
+       : RubberbandSelectDrag (e, rv->get_canvas_frame ())
+       , _region_view (rv)
+{
+
+}
+
+void
+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 ();
+
+       x1 = max ((framepos_t) 0, x1 - p);
+       x2 = max ((framepos_t) 0, x2 - p);
+       y1 = max (0.0, y1 - y);
+       y2 = max (0.0, y2 - y);
+       
+       _region_view->update_drag_selection (
+               _editor->frame_to_pixel (x1),
+               _editor->frame_to_pixel (x2),
+               y1,
+               y2,
+               Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
+               );
+}
+
+void
+MidiRubberbandSelectDrag::deselect_things ()
+{
+       /* XXX */
+}
+
+EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
+       : RubberbandSelectDrag (e, i)
+{
+
+}
+
+void
+EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
+{
+       if (drag_in_progress) {
+               /* We just want to select things at the end of the drag, not during it */
+               return;
+       }
+       
+       Selection::Operation op = ArdourKeyboard::selection_type (button_state);
+       
+       _editor->begin_reversible_command (_("rubberband selection"));
+       _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
+       _editor->commit_reversible_command ();
+}
+
+void
+EditorRubberbandSelectDrag::deselect_things ()
+{
+       if (!getenv("ARDOUR_SAE")) {
+               _editor->selection->clear_tracks();
+       }
+       _editor->selection->clear_regions();
+       _editor->selection->clear_points ();
+       _editor->selection->clear_lines ();
+}
+
+NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
+       : Drag (e, i)
+       , _region_view (rv)
+       , _drag_rect (0)
+{
+       
+}
+
+NoteCreateDrag::~NoteCreateDrag ()
+{
+       delete _drag_rect;
+}
+
+framecnt_t
+NoteCreateDrag::grid_frames (framepos_t t) const
+{
+       bool success;
+       Evoral::MusicalTime grid_beats = _editor->get_grid_type_as_beats (success, t);
+       if (!success) {
+               grid_beats = 1;
+       }
+
+       return _region_view->region_beats_to_region_frames (grid_beats);
 }
 
+void
+NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
+{
+       Drag::start_grab (event, cursor);
+                                                
+       _drag_rect = new ArdourCanvas::SimpleRect (*_region_view->get_canvas_group ());
+
+       framepos_t pf = _drags->current_pointer_frame ();
+       framecnt_t const g = grid_frames (pf);
+
+       /* Hack so that we always snap to the note that we are over, instead of snapping
+          to the next one if we're more than halfway through the one we're over.
+       */
+       if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
+               pf -= g / 2;
+       }
+
+       _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
+
+       MidiStreamView* sv = _region_view->midi_stream_view ();
+       double const x = _editor->frame_to_pixel (_note[0]);
+       double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
+
+       _drag_rect->property_x1() = x;
+       _drag_rect->property_y1() = y;
+       _drag_rect->property_x2() = x;
+       _drag_rect->property_y2() = y + floor (_region_view->midi_stream_view()->note_height ());
+
+       _drag_rect->property_outline_what() = 0xff;
+       _drag_rect->property_outline_color_rgba() = 0xffffff99;
+       _drag_rect->property_fill_color_rgba()    = 0xffffff66;
+}
+
+void
+NoteCreateDrag::motion (GdkEvent* event, bool)
+{
+       _note[1] = 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;
+       } else {
+               _drag_rect->property_x1() = x;
+       }
+}
+
+void
+NoteCreateDrag::finished (GdkEvent* event, bool had_movement)
+{
+       if (!had_movement) {
+               return;
+       }
+       
+       framepos_t const start = min (_note[0], _note[1]);
+       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 - one_tick;
+       }
+
+       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
+NoteCreateDrag::y_to_region (double y) const
+{
+       double x = 0;
+       _region_view->get_canvas_group()->w2i (x, y);
+       return y;
+}
+
+void
+NoteCreateDrag::aborted (bool)
+{
+       
+}