merge from 2.0-ongoing by hand, minus key binding editor
[ardour.git] / gtk2_ardour / editor_mouse.cc
index fe17b31abf8ef28da650ef758e76f4934e0de4e4..da95d50d95949e8088db8361d67a6d1850879f80 100644 (file)
@@ -39,6 +39,7 @@
 #include "streamview.h"
 #include "region_gain_line.h"
 #include "automation_time_axis.h"
+#include "control_point.h"
 #include "prompter.h"
 #include "utils.h"
 #include "selection.h"
@@ -159,6 +160,12 @@ Editor::mouse_mode_toggled (MouseMode m)
                        set_mouse_mode (m);
                }
                break;
+       
+       case MouseNote:
+               if (mouse_note_button.get_active()) {
+                       set_mouse_mode (m);
+               }
+               break;
 
        default:
                break;
@@ -206,7 +213,7 @@ Editor::set_mouse_mode (MouseMode m, bool force)
                }
        }
 
-       /* XXX the hack of unsetting all other buttongs should go 
+       /* XXX the hack of unsetting all other buttons should go 
           away once GTK2 allows us to use regular radio buttons drawn like
           normal buttons, rather than my silly GroupedButton hack.
        */
@@ -243,12 +250,22 @@ Editor::set_mouse_mode (MouseMode m, bool force)
                mouse_audition_button.set_active (true);
                current_canvas_cursor = speaker_cursor;
                break;
+       
+       case MouseNote:
+               mouse_note_button.set_active (true);
+               set_midi_edit_cursor (current_midi_edit_mode());
+               break;
        }
 
+       if (mouse_mode == MouseNote)
+               midi_toolbar_frame.show();
+       else
+               midi_toolbar_frame.hide();
+
        ignore_mouse_mode_toggle = false;
 
        if (is_drawable()) {
-               track_canvas.get_window()->set_cursor(*current_canvas_cursor);
+               track_canvas.get_window()->set_cursor(*current_canvas_cursor);
        }
 }
 
@@ -285,6 +302,101 @@ Editor::step_mouse_mode (bool next)
                if (next) set_mouse_mode (MouseObject);
                else set_mouse_mode (MouseTimeFX);
                break;
+       
+       case MouseNote:
+               if (next) set_mouse_mode (MouseObject);
+               else set_mouse_mode (MouseAudition);
+               break;
+       }
+}
+
+void
+Editor::midi_edit_mode_toggled (MidiEditMode m)
+{
+       if (ignore_midi_edit_mode_toggle) {
+               return;
+       }
+
+       switch (m) {
+       case MidiEditSelect:
+               if (midi_tool_select_button.get_active()) {
+                       set_midi_edit_mode (m);
+               }
+               break;
+       
+       case MidiEditPencil:
+               if (midi_tool_pencil_button.get_active()) {
+                       set_midi_edit_mode (m);
+               }
+               break;
+       
+       case MidiEditErase:
+               if (midi_tool_erase_button.get_active()) {
+                       set_midi_edit_mode (m);
+               }
+               break;
+
+       default:
+               break;
+       }
+
+       set_midi_edit_cursor(m);
+}      
+
+
+void
+Editor::set_midi_edit_mode (MidiEditMode m, bool force)
+{
+       if (drag_info.item) {
+               return;
+       }
+
+       if (!force && m == midi_edit_mode) {
+               return;
+       }
+       
+       midi_edit_mode = m;
+
+       instant_save ();
+       
+       ignore_midi_edit_mode_toggle = true;
+
+       switch (midi_edit_mode) {
+       case MidiEditSelect:
+               midi_tool_select_button.set_active (true);
+               break;
+       
+       case MidiEditPencil:
+               midi_tool_pencil_button.set_active (true);
+               break;
+       
+       case MidiEditErase:
+               midi_tool_erase_button.set_active (true);
+               break;
+       }
+
+       ignore_midi_edit_mode_toggle = false;
+
+       set_midi_edit_cursor (current_midi_edit_mode());
+
+       if (is_drawable()) {
+               track_canvas.get_window()->set_cursor(*current_canvas_cursor);
+       }
+}
+
+void
+Editor::set_midi_edit_cursor (MidiEditMode m)
+{
+       switch (midi_edit_mode) {
+       case MidiEditSelect:
+               current_canvas_cursor = midi_select_cursor;
+               break;
+       case MidiEditPencil:
+               current_canvas_cursor = midi_pencil_cursor;
+               break;
+       case MidiEditErase:
+               current_canvas_cursor = midi_erase_cursor;
+               break;
        }
 }
 
@@ -330,8 +442,23 @@ Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType it
        
        switch (item_type) {
        case RegionItem:
-       case RegionViewNameHighlight:
-       case RegionViewName:
+               if (mouse_mode != MouseRange) {
+                       commit = set_selected_regionview_from_click (press, op, true);
+               } else if (event->type == GDK_BUTTON_PRESS) {
+                       commit = set_selected_track_from_click (press, op, false);
+               }
+               break;
+               
+       case RegionViewNameHighlight:
+       case RegionViewName:
+               if (mouse_mode != MouseRange) {
+                       commit = set_selected_regionview_from_click (press, op, true);
+               } else if (event->type == GDK_BUTTON_PRESS) {
+                       commit = set_selected_track_from_click (press, op, false);
+               }
+               break;
+
+
        case FadeInHandleItem:
        case FadeInItem:
        case FadeOutHandleItem:
@@ -642,7 +769,12 @@ Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemTyp
                        break;
 
                case MouseAudition:
-                       /* handled in release */
+                       _scrubbing = true;
+                       last_scrub_frame = 0;
+                       last_scrub_time = 0;
+                       have_full_mouse_speed = false;
+                       memset (mouse_speed, 0, sizeof (double) * mouse_speed_size);
+                       /* rest handled in motion & release */
                        break;
 
                default:
@@ -794,10 +926,19 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                                break;
 
                        case StreamItem:
+                               popup_track_context_menu (1, event->button.time, where);
+                               break;
+                               
                        case RegionItem:
                        case RegionViewNameHighlight:
                        case RegionViewName:
+                               popup_track_context_menu (1, event->button.time, where);
+                               break;
+                               
                        case SelectionItem:
+                               popup_track_context_menu (1, event->button.time, where);
+                               break;
+
                        case AutomationTrackItem:
                        case CrossfadeViewItem:
                                popup_track_context_menu (1, event->button.time, where);
@@ -961,13 +1102,20 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT
                        break;
                        
                case MouseAudition:
-                       switch (item_type) {
-                       case RegionItem:
-                               audition_selected_region ();
-                               break;
-                       default:
-                               break;
-                       }
+                       _scrubbing = false;
+                       if (last_scrub_frame == 0) {
+                               /* no drag, just a click */
+                               switch (item_type) {
+                               case RegionItem:
+                                       audition_selected_region ();
+                                       break;
+                               default:
+                                       break;
+                               }
+                       } else {
+                               /* make sure we stop */
+                               session->request_transport_speed (0.0);
+                       }
                        break;
 
                default:
@@ -1019,13 +1167,13 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_
                        double at_x, at_y;
                        at_x = cp->get_x();
                        at_y = cp->get_y ();
-                       cp->item->i2w (at_x, at_y);
+                       cp->item()->i2w (at_x, at_y);
                        at_x += 20.0;
                        at_y += 20.0;
 
-                       fraction = 1.0 - (cp->get_y() / cp->line.height());
+                       fraction = 1.0 - (cp->get_y() / cp->line().height());
 
-                       set_verbose_canvas_cursor (cp->line.get_verbose_cursor_string (fraction), at_x, at_y);
+                       set_verbose_canvas_cursor (cp->line().get_verbose_cursor_string (fraction), at_x, at_y);
                        show_verbose_canvas_cursor ();
 
                        if (is_drawable()) {
@@ -1100,7 +1248,7 @@ Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_
 
        case AutomationTrackItem:
                if (is_drawable()) {
-                       Gdk::Cursor *cursor;
+                       Gdk::Cursor *cursor;
                        switch (mouse_mode) {
                        case MouseRange:
                                cursor = selector_cursor;
@@ -1196,8 +1344,8 @@ Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_
        switch (item_type) {
        case ControlPointItem:
                cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
-               if (cp->line.npoints() > 1) {
-                       if (!cp->selected) {
+               if (cp->line().the_list()->interpolation() != AutomationList::Discrete) {
+                       if (cp->line().npoints() > 1 && !cp->selected()) {
                                cp->set_visible (false);
                        }
                }
@@ -1312,6 +1460,12 @@ Editor::left_automation_track ()
        return false;
 }
 
+static gboolean
+_update_mouse_speed (void *arg)
+{
+       return static_cast<Editor*>(arg)->update_mouse_speed ();
+}
+
 bool
 Editor::motion_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type, bool from_autoscroll)
 {
@@ -1343,6 +1497,66 @@ Editor::motion_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item
        drag_info.current_pointer_frame = event_frame (event, &drag_info.current_pointer_x,
                                                       &drag_info.current_pointer_y);
 
+       switch (mouse_mode) {
+       case MouseAudition:
+               if (_scrubbing) {
+                       struct timeval tmnow;
+
+                       if (last_scrub_frame == 0) {
+
+                               /* first motion, just set up the variables */
+
+                               last_scrub_frame = (nframes64_t) drag_info.current_pointer_frame;
+                               gettimeofday (&tmnow, 0);
+                               last_scrub_time = tmnow.tv_sec * 1000000.0 + tmnow.tv_usec;
+                               session->request_locate (last_scrub_frame, true);
+
+                       } else {
+                               /* how fast is the mouse moving ? */
+
+                               double speed;
+                               nframes_t distance;
+                               double time;
+                               double dir;
+
+#if 1
+                               if (last_scrub_frame < (nframes64_t) drag_info.current_pointer_frame) {
+                                       distance = (nframes64_t) drag_info.current_pointer_frame - last_scrub_frame;
+                                       dir = 1.0;
+                               } else {
+                                       distance = last_scrub_frame - (nframes64_t) drag_info.current_pointer_frame;
+                                       dir = -1.0;
+                               }
+#else
+                               if (drag_info.grab_x < drag_info.current_pointer_x) {
+                                       distance = drag_info.current_pointer_x - drag_info.grab_x;
+                                       dir = -1.0;
+                               } else {
+                                       distance = drag_info.grab_x - drag_info.current_pointer_x;
+                                       dir = 1.0;
+                               }
+#endif
+
+                               gettimeofday (&tmnow, 0);
+                               time = (tmnow.tv_sec * 1000000.0 + tmnow.tv_usec) - last_scrub_time;
+                               last_scrub_frame = drag_info.current_pointer_frame;
+                               last_scrub_time = (tmnow.tv_sec * 1000000.0) + tmnow.tv_usec;
+                               speed = ((double)distance/session->frame_rate()) / (time/1000000.0); // frames/sec
+
+                               add_mouse_speed (speed, dir);
+
+                               if (mouse_speed_update < 0) {
+                                       mouse_speed_update = g_timeout_add (10, _update_mouse_speed, this);
+                                       update_mouse_speed ();
+                               }
+                       }
+               }
+
+       default:
+               break;
+       }
+
+
        if (!from_autoscroll && drag_info.item) {
                /* item != 0 is the best test i can think of for dragging.
                */
@@ -1444,7 +1658,7 @@ Editor::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
                cursor = grabber_cursor;
        }
 
-        // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
+       // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
 
        if (event->button.button == 2) {
                if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Alt)) {
@@ -1894,7 +2108,7 @@ Editor::update_marker_drag_item (Location *location)
        double x2 = frame_to_pixel (location->end());
 
        if (location->is_mark()) {
-               marker_drag_line_points.front().set_x(x1);
+               marker_drag_line_points.front().set_x(x1);
                marker_drag_line_points.back().set_x(x1);
                marker_drag_line->property_points() = marker_drag_line_points;
        }
@@ -2172,12 +2386,12 @@ Editor::meter_marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent*
        
        if (drag_info.copy == true) {
                begin_reversible_command (_("copy meter mark"));
-                XMLNode &before = map.get_state();
+               XMLNode &before = map.get_state();
                map.add_meter (marker->meter(), when);
                XMLNode &after = map.get_state();
-                session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
+               session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
                commit_reversible_command ();
-               
+
                // delete the dummy marker we used for visual representation of copying.
                // a new visual marker will show up automatically.
                delete marker;
@@ -2186,7 +2400,7 @@ Editor::meter_marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent*
                XMLNode &before = map.get_state();
                map.move_meter (marker->meter(), when);
                XMLNode &after = map.get_state();
-                session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
+               session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
                commit_reversible_command ();
        }
 }
@@ -2315,10 +2529,10 @@ Editor::tempo_marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent*
                delete marker;
        } else {
                begin_reversible_command (_("move tempo mark"));
-                XMLNode &before = map.get_state();
+               XMLNode &before = map.get_state();
                map.move_tempo (marker->tempo(), when);
-                XMLNode &after = map.get_state();
-                session->add_command (new MementoCommand<TempoMap>(map, &before, &after));
+               XMLNode &after = map.get_state();
+               session->add_command (new MementoCommand<TempoMap>(map, &before, &after));
                commit_reversible_command ();
        }
 }
@@ -2334,16 +2548,16 @@ Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* event)
        }
 
        // We shouldn't remove the first or last gain point
-       if (control_point->line.is_last_point(*control_point) ||
-               control_point->line.is_first_point(*control_point)) {   
+       if (control_point->line().is_last_point(*control_point) ||
+               control_point->line().is_first_point(*control_point)) { 
                return;
        }
 
-       control_point->line.remove_point (*control_point);
+       control_point->line().remove_point (*control_point);
 }
 
 void
-Editor::remove_control_point (ArdourCanvas::Item*item, GdkEvent* event)
+Editor::remove_control_point (ArdourCanvas::Item* item, GdkEvent* event)
 {
        ControlPoint* control_point;
 
@@ -2352,7 +2566,7 @@ Editor::remove_control_point (ArdourCanvas::Item*item, GdkEvent* event)
                /*NOTREACHED*/
        }
 
-       control_point->line.remove_point (*control_point);
+       control_point->line().remove_point (*control_point);
 }
 
 void
@@ -2372,10 +2586,10 @@ Editor::start_control_point_grab (ArdourCanvas::Item* item, GdkEvent* event)
 
        start_grab (event, fader_cursor);
 
-       control_point->line.start_drag (control_point, drag_info.grab_frame, 0);
+       control_point->line().start_drag (control_point, drag_info.grab_frame, 0);
 
-       double fraction = 1.0 - ((control_point->get_y() - control_point->line.y_position()) / (double)control_point->line.height());
-       set_verbose_canvas_cursor (control_point->line.get_verbose_cursor_string (fraction), 
+       double fraction = 1.0 - ((control_point->get_y() - control_point->line().y_position()) / (double)control_point->line().height());
+       set_verbose_canvas_cursor (control_point->line().get_verbose_cursor_string (fraction), 
                                   drag_info.current_pointer_x + 20, drag_info.current_pointer_y + 20);
 
        show_verbose_canvas_cursor ();
@@ -2399,11 +2613,11 @@ Editor::control_point_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent*
                cy = drag_info.grab_y;
        }
 
-       cp->line.parent_group().w2i (cx, cy);
+       cp->line().parent_group().w2i (cx, cy);
 
        cx = max (0.0, cx);
        cy = max (0.0, cy);
-       cy = min ((double) (cp->line.y_position() + cp->line.height()), cy);
+       cy = min ((double) (cp->line().y_position() + cp->line().height()), cy);
 
        //translate cx to frames
        nframes_t cx_frames = unit_to_frame (cx);
@@ -2412,7 +2626,7 @@ Editor::control_point_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent*
                snap_to (cx_frames);
        }
 
-       const double fraction = 1.0 - ((cy - cp->line.y_position()) / (double)cp->line.height());
+       const double fraction = 1.0 - ((cy - cp->line().y_position()) / (double)cp->line().height());
 
        bool push;
 
@@ -2422,9 +2636,9 @@ Editor::control_point_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent*
                push = false;
        }
 
-       cp->line.point_drag (*cp, cx_frames , fraction, push);
+       cp->line().point_drag (*cp, cx_frames , fraction, push);
        
-       set_verbose_canvas_cursor_text (cp->line.get_verbose_cursor_string (fraction));
+       set_verbose_canvas_cursor_text (cp->line().get_verbose_cursor_string (fraction));
 
        drag_info.first_move = false;
 }
@@ -2445,7 +2659,7 @@ Editor::control_point_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent
        } else {
                control_point_drag_motion_callback (item, event);
        }
-       cp->line.end_drag (cp);
+       cp->line().end_drag (cp);
 }
 
 void
@@ -2720,7 +2934,7 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
        original_pointer_order = drag_info.last_trackview->order;
                
        /************************************************************
-                 Y-Delta Computation
+            Y-Delta Computation
        ************************************************************/   
 
        if (drag_info.brushing) {
@@ -2875,7 +3089,7 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
        }
          
        /************************************************************
-                       X DELTA COMPUTATION
+           X DELTA COMPUTATION
        ************************************************************/
 
        /* compute the amount of pointer motion in frames, and where
@@ -2943,7 +3157,7 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
        }
 
        /*************************************************************
-                        PREPARE TO MOVE
+           PREPARE TO MOVE
        ************************************************************/
 
        if (x_delta == 0 && (pointer_y_span == 0)) {
@@ -2973,7 +3187,7 @@ Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
        }
 
        /*************************************************************
-                        MOTION                                                               
+           MOTION                                                                    
        ************************************************************/
 
        bool do_move;
@@ -3357,6 +3571,10 @@ Editor::region_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event
                                        rtv->reveal_dependent_views (*latest_regionview);
                                        selection->add (latest_regionview);
                                }
+
+                               /* if the original region was locked, we don't care for the new one */
+                               
+                               newregion->set_locked (false);                  
                                
                        } else {
 
@@ -3620,7 +3838,7 @@ Editor::start_selection_grab (ArdourCanvas::Item* item, GdkEvent* event)
 void
 Editor::cancel_selection ()
 {
-        for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+       for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
                (*i)->hide_selection ();
        }
        begin_reversible_command (_("cancel selection"));
@@ -3953,7 +4171,7 @@ Editor::trim_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
                        boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
                        insert_result = motion_frozen_playlists.insert (pl);
                        if (insert_result.second) {
-                                session->add_command(new MementoCommand<Playlist>(*pl, &pl->get_state(), 0));
+                               session->add_command(new MementoCommand<Playlist>(*pl, &pl->get_state(), 0));
                        }
                }
        }
@@ -3968,7 +4186,7 @@ Editor::trim_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
        case StartTrim:
                if ((left_direction == false) && (drag_info.current_pointer_frame <= rv->region()->first_frame()/speed)) {
                        break;
-                } else {
+               } else {
                        for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
                                single_start_trim (**i, frame_delta, left_direction, obey_snap);
                        }
@@ -4144,8 +4362,8 @@ Editor::trim_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
                
                for (set<boost::shared_ptr<Playlist> >::iterator p = motion_frozen_playlists.begin(); p != motion_frozen_playlists.end(); ++p) {
                        //(*p)->thaw ();
-                        session->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
-                }
+                       session->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
+               }
                
                motion_frozen_playlists.clear ();
 
@@ -4179,10 +4397,10 @@ Editor::point_trim (GdkEvent* event)
                        {
                                if (!(*i)->region()->locked()) {
                                        boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
-                                        XMLNode &before = pl->get_state();
+                                       XMLNode &before = pl->get_state();
                                        (*i)->region()->trim_front (new_bound, this);   
-                                        XMLNode &after = pl->get_state();
-                                        session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
+                                       XMLNode &after = pl->get_state();
+                                       session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
                                }
                        }
 
@@ -4192,7 +4410,7 @@ Editor::point_trim (GdkEvent* event)
                                boost::shared_ptr<Playlist> pl = rv->region()->playlist();
                                XMLNode &before = pl->get_state();
                                rv->region()->trim_front (new_bound, this);     
-                                XMLNode &after = pl->get_state();
+                               XMLNode &after = pl->get_state();
                                session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
                        }
                }
@@ -4223,7 +4441,7 @@ Editor::point_trim (GdkEvent* event)
                                boost::shared_ptr<Playlist> pl = rv->region()->playlist();
                                XMLNode &before = pl->get_state();
                                rv->region()->trim_end (new_bound, this);
-                                XMLNode &after = pl->get_state();
+                               XMLNode &after = pl->get_state();
                                session->add_command (new MementoCommand<Playlist>(*pl.get(), &before, &after));
                        }
                }
@@ -4246,7 +4464,7 @@ Editor::thaw_region_after_trim (RegionView& rv)
        }
 
        region->thaw (_("trimmed region"));
-        XMLNode &after = region->playlist()->get_state();
+       XMLNode &after = region->playlist()->get_state();
        session->add_command (new MementoCommand<Playlist>(*(region->playlist()), 0, &after));
 
        AudioRegionView* arv = dynamic_cast<AudioRegionView*>(&rv);
@@ -4389,11 +4607,11 @@ Editor::end_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event)
                case CreateRangeMarker:
                    {
                        begin_reversible_command (_("new range marker"));
-                        XMLNode &before = session->locations()->get_state();
+                       XMLNode &before = session->locations()->get_state();
                        session->locations()->next_available_name(rangename,"unnamed");
                        newloc = new Location(temp_location->start(), temp_location->end(), rangename, Location::IsRangeMarker);
                        session->locations()->add (newloc, true);
-                        XMLNode &after = session->locations()->get_state();
+                       XMLNode &after = session->locations()->get_state();
                        session->add_command(new MementoCommand<Locations>(*(session->locations()), &before, &after));
                        commit_reversible_command ();
                        
@@ -4667,7 +4885,7 @@ Editor::mouse_rename_region (ArdourCanvas::Item* item, GdkEvent* event)
        prompter.show_all ();
        switch (prompter.run ()) {
        case Gtk::RESPONSE_ACCEPT:
-        string str;
+               string str;
                prompter.get_result(str);
                if (str.length()) {
                        clicked_regionview->region()->set_name (str);
@@ -4720,6 +4938,11 @@ Editor::end_time_fx (ArdourCanvas::Item* item, GdkEvent* event)
        if (drag_info.first_move) {
                return;
        }
+
+       if (drag_info.last_pointer_frame < clicked_regionview->region()->position()) {
+               /* backwards drag of the left edge - not usable */
+               return;
+       }
        
        nframes_t newlen = drag_info.last_pointer_frame - clicked_regionview->region()->position();
        float percentage = (float) ((double) newlen - (double) clicked_regionview->region()->length()) / ((double) newlen) * 100.0f;
@@ -4772,9 +4995,9 @@ Editor::mouse_brush_insert_region (RegionView* rv, nframes_t pos)
        boost::shared_ptr<Playlist> playlist = rtv->playlist();
        double speed = rtv->get_diskstream()->speed();
        
-        XMLNode &before = playlist->get_state();
+       XMLNode &before = playlist->get_state();
        playlist->add_region (boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (arv->audio_region())), (nframes_t) (pos * speed));
-        XMLNode &after = playlist->get_state();
+       XMLNode &after = playlist->get_state();
        session->add_command(new MementoCommand<Playlist>(*playlist.get(), &before, &after));
        
        // playlist is frozen, so we have to update manually
@@ -4797,3 +5020,91 @@ Editor::track_height_step_timeout ()
        }
        return true;
 }
+
+
+void
+Editor::add_mouse_speed (double speed, double dir)
+{
+       size_t index;
+
+       mouse_direction = dir;
+
+       index = mouse_speed_entries;
+
+       if (++index >= mouse_speed_size) {
+               index = 0;
+               have_full_mouse_speed = true;
+       }
+       
+       mouse_speed[index] = speed;
+       mouse_speed_entries = index;
+}
+
+double
+Editor::compute_mouse_speed ()
+{
+       double total = 0;
+
+       if (!have_full_mouse_speed) {
+
+               /* partial speed buffer, just use whatever we have so far */
+
+               if (mouse_speed_entries == 0 ) {
+                       return 0.0;
+               }
+
+               for (size_t n = 0; n < mouse_speed_entries; ++n) {
+                       total += mouse_speed[n];
+               }
+               
+               return mouse_direction * total/mouse_speed_entries;
+       }
+
+       /* compute the average (effectively low-pass filtering) mouse speed
+          across the entire buffer.
+       */
+
+       for (size_t n = 0; n < mouse_speed_size; ++n) {
+               total += mouse_speed[n];
+       }
+
+
+       return mouse_direction * total/mouse_speed_size;
+}
+
+bool
+Editor::update_mouse_speed ()
+{
+       double speed;
+
+       if (!_scrubbing) {
+               session->request_transport_speed (0.0);
+               mouse_speed_update = -1;
+               return false;
+       }
+
+       speed = compute_mouse_speed ();
+
+       struct timeval tmnow;
+       
+       gettimeofday (&tmnow, 0);
+       double now = (tmnow.tv_sec * 1000000.0) + tmnow.tv_usec;
+
+       if (now - last_scrub_time > 250000) {
+           
+               // 0.25 seconds since last mouse motion, start to brake
+
+               if (fabs (speed) < 0.1) {
+                       /* don't asymptotically approach zero */
+                       memset (mouse_speed, 0, sizeof (double) * mouse_speed_size);
+                       speed = 0.0;
+               } else if (fabs (speed) < 0.25) {
+                       add_mouse_speed (fabs (speed * 0.2), mouse_direction);
+               } else {
+                       add_mouse_speed (fabs (speed * 0.6), mouse_direction);
+               }
+       } 
+
+       session->request_transport_speed (speed);
+       return _scrubbing;
+}