Start step entry at playhead.
[ardour.git] / gtk2_ardour / editor.cc
index cfbbf6de037a36e9cf918bf8e9152cea1d06f6cd..53ad481eb3f0d02d259ca568a7385c4fd5ec3e68 100644 (file)
 #include "gui_thread.h"
 #include "keyboard.h"
 #include "marker.h"
+#include "midi_region_view.h"
 #include "midi_time_axis.h"
 #include "mixer_strip.h"
 #include "mixer_ui.h"
 #include "mouse_cursors.h"
+#include "note_base.h"
 #include "playlist_selector.h"
 #include "public_editor.h"
 #include "region_layering_order_editor.h"
 #include "sfdb_ui.h"
 #include "tempo_lines.h"
 #include "time_axis_view.h"
+#include "timers.h"
 #include "utils.h"
 #include "verbose_cursor.h"
 
@@ -138,7 +141,7 @@ using PBD::internationalize;
 using PBD::atoi;
 using Gtkmm2ext::Keyboard;
 
-const double Editor::timebar_height = 15.0;
+double Editor::timebar_height = 15.0;
 
 static const gchar *_snap_type_strings[] = {
        N_("CD Frames"),
@@ -281,6 +284,8 @@ Editor::Editor ()
        , _tools_tearoff (0)
 
        , _toolbar_viewport (*manage (new Gtk::Adjustment (0, 0, 1e10)), *manage (new Gtk::Adjustment (0, 0, 1e10)))
+       , selection_op_cmd_depth (0)
+       , selection_op_history_it (0)
 
          /* nudge */
 
@@ -305,13 +310,17 @@ Editor::Editor ()
        
        selection = new Selection (this);
        cut_buffer = new Selection (this);
+       _selection_memento = new SelectionMemento ();
+       selection_op_history.clear();
+       before.clear();
 
        clicked_regionview = 0;
        clicked_axisview = 0;
        clicked_routeview = 0;
        clicked_control_point = 0;
        last_update_frame = 0;
-        pre_press_cursor = 0;
+       last_paste_pos = 0;
+       paste_count = 0;
        _drags = new DragManager (this);
        lock_dialog = 0;
        ruler_dialog = 0;
@@ -388,20 +397,22 @@ Editor::Editor ()
 
        sfbrowser = 0;
 
-       location_marker_color = ARDOUR_UI::config()->get_canvasvar_LocationMarker();
-       location_range_color = ARDOUR_UI::config()->get_canvasvar_LocationRange();
-       location_cd_marker_color = ARDOUR_UI::config()->get_canvasvar_LocationCDMarker();
-       location_loop_color = ARDOUR_UI::config()->get_canvasvar_LocationLoop();
-       location_punch_color = ARDOUR_UI::config()->get_canvasvar_LocationPunch();
+       location_marker_color = ARDOUR_UI::config()->color ("location marker");
+       location_range_color = ARDOUR_UI::config()->color ("location range");
+       location_cd_marker_color = ARDOUR_UI::config()->color ("location cd marker");
+       location_loop_color = ARDOUR_UI::config()->color ("location loop");
+       location_punch_color = ARDOUR_UI::config()->color ("location punch");
 
        zoom_focus = ZoomFocusLeft;
        _edit_point = EditAtMouse;
-       _internal_editing = false;
-       current_canvas_cursor = 0;
-       _visible_track_count = 16;
+       _visible_track_count = -1;
 
        samples_per_pixel = 2048; /* too early to use reset_zoom () */
 
+       timebar_height = std::max(12., ceil (15. * ARDOUR_UI::config()->get_font_scale() / 102400.));
+       TimeAxisView::setup_sizes ();
+       Marker::setup_sizes (timebar_height);
+
        _scroll_callbacks = 0;
 
        bbt_label.set_name ("EditorRulerLabel");
@@ -484,6 +495,8 @@ Editor::Editor ()
 
        initialize_canvas ();
 
+       CairoWidget::set_focus_handler (sigc::mem_fun (*this, &Editor::reset_focus));
+
        _summary = new EditorSummary (this);
 
        selection->TimeChanged.connect (sigc::mem_fun(*this, &Editor::time_selection_changed));
@@ -515,6 +528,9 @@ Editor::Editor ()
        _cursors->set_cursor_set (ARDOUR_UI::config()->get_icon_set());
        cerr << "Set cursor set to " << ARDOUR_UI::config()->get_icon_set() << endl;
 
+       /* Push default cursor to ever-present bottom of cursor stack. */
+       push_canvas_cursor(_cursors->grabber);
+
        ArdourCanvas::GtkCanvas* time_pad = manage (new ArdourCanvas::GtkCanvas ());
 
        ArdourCanvas::Line* pad_line_1 = new ArdourCanvas::Line (time_pad->root());
@@ -551,6 +567,12 @@ Editor::Editor ()
        _snapshots = new EditorSnapshots (this);
        _locations = new EditorLocations (this);
 
+       /* these are static location signals */
+
+       Location::start_changed.connect (*this, invalidator (*this), boost::bind (&Editor::location_changed, this, _1), gui_context());
+       Location::end_changed.connect (*this, invalidator (*this), boost::bind (&Editor::location_changed, this, _1), gui_context());
+       Location::changed.connect (*this, invalidator (*this), boost::bind (&Editor::location_changed, this, _1), gui_context());
+
        add_notebook_page (_("Regions"), _regions->widget ());
        add_notebook_page (_("Tracks & Busses"), _routes->widget ());
        add_notebook_page (_("Snapshots"), _snapshots->widget ());
@@ -658,7 +680,6 @@ Editor::Editor ()
        _snap_mode = SnapOff;
        set_snap_mode (_snap_mode);
        set_mouse_mode (MouseObject, true);
-        pre_internal_mouse_mode = MouseObject;
         pre_internal_snap_type = _snap_type;
         pre_internal_snap_mode = _snap_mode;
         internal_snap_type = _snap_type;
@@ -865,7 +886,7 @@ Editor::set_entered_regionview (RegionView* rv)
        entered_regionview = rv;
 
        if (entered_regionview  != 0) {
-               entered_regionview->entered (internal_editing ());
+               entered_regionview->entered ();
        }
 
        if (!_all_region_actions_sensitized && _last_region_menu_was_main) {
@@ -1055,12 +1076,12 @@ Editor::control_scroll (float fraction)
                _dragging_playhead = true;
        }
 
-       if ((fraction < 0.0f) && (*_control_scroll_target < (framepos_t) fabs(step))) {
+       if ((fraction < 0.0f) && (*_control_scroll_target <= (framepos_t) fabs(step))) {
                *_control_scroll_target = 0;
        } else if ((fraction > 0.0f) && (max_framepos - *_control_scroll_target < step)) {
                *_control_scroll_target = max_framepos - (current_page_samples()*2); // allow room for slop in where the PH is on the screen
        } else {
-               *_control_scroll_target += (framepos_t) floor (step);
+               *_control_scroll_target += (framepos_t) trunc (step);
        }
 
        /* move visuals, we'll catch up with it later */
@@ -1149,9 +1170,28 @@ Editor::generic_event_handler (GdkEvent* ev)
        case GDK_KEY_RELEASE:
                gettimeofday (&last_event_time, 0);
                break;
+
+       case GDK_LEAVE_NOTIFY:
+               switch (ev->crossing.detail) {
+               case GDK_NOTIFY_UNKNOWN:
+               case GDK_NOTIFY_INFERIOR:
+               case GDK_NOTIFY_ANCESTOR:
+                       break; 
+               case GDK_NOTIFY_VIRTUAL:
+               case GDK_NOTIFY_NONLINEAR:
+               case GDK_NOTIFY_NONLINEAR_VIRTUAL:
+                       /* leaving window, so reset focus, thus ending any and
+                          all text entry operations.
+                       */
+                       reset_focus();
+                       break;
+               }
+               break;
+
        default:
                break;
        }
+
        return false;
 }
 
@@ -1329,7 +1369,6 @@ Editor::set_session (Session *t)
        _session->locations()->added.connect (_session_connections, invalidator (*this), boost::bind (&Editor::add_new_location, this, _1), gui_context());
        _session->locations()->removed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::location_gone, this, _1), gui_context());
        _session->locations()->changed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::refresh_location_display, this), gui_context());
-       _session->locations()->StateChanged.connect (_session_connections, invalidator (*this), boost::bind (&Editor::refresh_location_display, this), gui_context());
        _session->history().Changed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::history_changed, this), gui_context());
 
        playhead_cursor->show ();
@@ -1346,7 +1385,7 @@ Editor::set_session (Session *t)
                (static_cast<TimeAxisView*>(*i))->set_samples_per_pixel (samples_per_pixel);
        }
 
-       super_rapid_screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (
+       super_rapid_screen_update_connection = Timers::super_rapid_connect (
                sigc::mem_fun (*this, &Editor::super_rapid_screen_update)
                );
 
@@ -1364,6 +1403,7 @@ Editor::set_session (Session *t)
 
        /* register for undo history */
        _session->register_with_memento_command_factory(id(), this);
+       _session->register_with_memento_command_factory(_selection_memento->id(), _selection_memento);
 
        ActionManager::ui_manager->signal_pre_activate().connect (sigc::mem_fun (*this, &Editor::action_pre_activated));
 
@@ -1456,8 +1496,10 @@ void
 Editor::popup_xfade_in_context_menu (int button, int32_t time, ArdourCanvas::Item* item, ItemType /*item_type*/)
 {
        using namespace Menu_Helpers;
-       AudioRegionView* arv = static_cast<AudioRegionView*> (item->get_data ("regionview"));
-       assert(arv);
+       AudioRegionView* arv = dynamic_cast<AudioRegionView*> ((RegionView*)item->get_data ("regionview"));
+       if (!arv) {
+               return;
+       }
 
        MenuList& items (xfade_in_context_menu.items());
        items.clear ();
@@ -1479,8 +1521,10 @@ void
 Editor::popup_xfade_out_context_menu (int button, int32_t time, ArdourCanvas::Item* item, ItemType /*item_type*/)
 {
        using namespace Menu_Helpers;
-       AudioRegionView* arv = static_cast<AudioRegionView*> (item->get_data ("regionview"));
-       assert(arv);
+       AudioRegionView* arv = dynamic_cast<AudioRegionView*> ((RegionView*)item->get_data ("regionview"));
+       if (!arv) {
+               return;
+       }
 
        MenuList& items (xfade_out_context_menu.items());
        items.clear ();
@@ -1808,15 +1852,16 @@ Editor::add_selection_context_items (Menu_Helpers::MenuList& edit_items)
                );
 
        edit_items.push_back (SeparatorElem());
-       edit_items.push_back (MenuElem (_("Convert to Region In-Place"), mem_fun(*this, &Editor::separate_region_from_selection)));
+       edit_items.push_back (MenuElem (_("Separate"), mem_fun(*this, &Editor::separate_region_from_selection)));
        edit_items.push_back (MenuElem (_("Convert to Region in Region List"), sigc::mem_fun(*this, &Editor::new_region_from_selection)));
 
        edit_items.push_back (SeparatorElem());
        edit_items.push_back (MenuElem (_("Select All in Range"), sigc::mem_fun(*this, &Editor::select_all_selectables_using_time_selection)));
 
        edit_items.push_back (SeparatorElem());
-       edit_items.push_back (MenuElem (_("Set Loop from Range"), sigc::bind (sigc::mem_fun(*this, &Editor::set_loop_from_selection), false)));
-       edit_items.push_back (MenuElem (_("Set Punch from Range"), sigc::mem_fun(*this, &Editor::set_punch_from_selection)));
+       edit_items.push_back (MenuElem (_("Set Loop from Selection"), sigc::bind (sigc::mem_fun(*this, &Editor::set_loop_from_selection), false)));
+       edit_items.push_back (MenuElem (_("Set Punch from Selection"), sigc::mem_fun(*this, &Editor::set_punch_from_selection)));
+       edit_items.push_back (MenuElem (_("Set Session Start/End from Selection"), sigc::mem_fun(*this, &Editor::set_session_extents_from_selection)));
 
        edit_items.push_back (SeparatorElem());
        edit_items.push_back (MenuElem (_("Add Range Markers"), sigc::mem_fun (*this, &Editor::add_location_from_selection)));
@@ -1992,6 +2037,12 @@ Editor::set_snap_to (SnapType st)
 {
        unsigned int snap_ind = (unsigned int)st;
 
+       if (internal_editing()) {
+               internal_snap_type = st;
+       } else {
+               pre_internal_snap_type = st;
+       }
+
        _snap_type = st;
 
        if (snap_ind > snap_type_strings.size() - 1) {
@@ -2048,6 +2099,8 @@ Editor::set_snap_to (SnapType st)
                break;
        }
 
+       redisplay_tempo (false);
+
        SnapChanged (); /* EMIT SIGNAL */
 }
 
@@ -2056,7 +2109,7 @@ Editor::set_snap_mode (SnapMode mode)
 {
        string str = snap_mode_strings[(int)mode];
 
-       if (_internal_editing) {
+       if (internal_editing()) {
                internal_snap_mode = mode;
        } else {
                pre_internal_snap_mode = mode;
@@ -2076,17 +2129,16 @@ Editor::set_edit_point_preference (EditPoint ep, bool force)
        bool changed = (_edit_point != ep);
 
        _edit_point = ep;
-       string str = edit_point_strings[(int)ep];
-
        if (Profile->get_mixbus())
                if (ep == EditAtSelectedMarker)
                        ep = EditAtPlayhead;
-       
+
+       string str = edit_point_strings[(int)ep];
        if (str != edit_point_selector.get_text ()) {
                edit_point_selector.set_text (str);
        }
 
-       reset_canvas_cursor ();
+       update_all_enter_cursors();
 
        if (!force && !changed) {
                return;
@@ -2176,7 +2228,12 @@ Editor::set_state (const XMLNode& node, int /*version*/)
        if (_session && (prop = node.property ("playhead"))) {
                framepos_t pos;
                sscanf (prop->value().c_str(), "%" PRIi64, &pos);
-               playhead_cursor->set_position (pos);
+               if (pos >= 0) {
+                       playhead_cursor->set_position (pos);
+               } else {
+                       warning << _("Playhead position stored with a negative value - ignored (use zero instead)") << endmsg;
+                       playhead_cursor->set_position (0);
+               }
        } else {
                playhead_cursor->set_position (0);
        }
@@ -2221,7 +2278,6 @@ Editor::set_state (const XMLNode& node, int /*version*/)
                pre_internal_snap_type = (SnapType) string_2_enum (prop->value(), pre_internal_snap_type);
        }
 
-
        if ((prop = node.property ("pre-internal-snap-mode"))) {
                pre_internal_snap_mode = (SnapMode) string_2_enum (prop->value(), pre_internal_snap_mode);
        }
@@ -2247,16 +2303,6 @@ Editor::set_state (const XMLNode& node, int /*version*/)
                reset_y_origin (atof (prop->value ()));
        }
 
-       if ((prop = node.property ("internal-edit"))) {
-               bool yn = string_is_affirmative (prop->value());
-               RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("toggle-internal-edit"));
-               if (act) {
-                       RefPtr<ToggleAction> tact = RefPtr<ToggleAction>::cast_dynamic(act);
-                       tact->set_active (!yn);
-                       tact->set_active (yn);
-               }
-       }
-
        if ((prop = node.property ("join-object-range"))) {
                RefPtr<Action> act = ActionManager::get_action (X_("MouseMode"), X_("set-mouse-mode-object-range"));
                bool yn = string_is_affirmative (prop->value());
@@ -2448,7 +2494,6 @@ Editor::get_state ()
        node->add_property ("stationary-playhead", _stationary_playhead ? "yes" : "no");
        node->add_property ("region-list-sort-type", enum_2_string (_regions->sort_type ()));
        node->add_property ("mouse-mode", enum2str(mouse_mode));
-       node->add_property ("internal-edit", _internal_editing ? "yes" : "no");
        node->add_property ("join-object-range", smart_mode_action->get_active () ? "yes" : "no");
 
        Glib::RefPtr<Action> act = ActionManager::get_action (X_("Editor"), X_("show-editor-mixer"));
@@ -2520,7 +2565,7 @@ Editor::trackview_by_y_position (double y, bool trackview_relative_offset) const
  *  @param event Event to get current key modifier information from, or 0.
  */
 void
-Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, int32_t direction, bool for_mark)
+Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, RoundMode direction, bool for_mark)
 {
        if (!_session || !event) {
                return;
@@ -2538,7 +2583,7 @@ Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, int32_
 }
 
 void
-Editor::snap_to (framepos_t& start, int32_t direction, bool for_mark)
+Editor::snap_to (framepos_t& start, RoundMode direction, bool for_mark)
 {
        if (!_session || _snap_mode == SnapOff) {
                return;
@@ -2548,14 +2593,17 @@ Editor::snap_to (framepos_t& start, int32_t direction, bool for_mark)
 }
 
 void
-Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*for_mark*/)
+Editor::timecode_snap_to_internal (framepos_t& start, RoundMode direction, bool /*for_mark*/)
 {
        const framepos_t one_timecode_second = (framepos_t)(rint(_session->timecode_frames_per_second()) * _session->frames_per_timecode_frame());
        framepos_t one_timecode_minute = (framepos_t)(rint(_session->timecode_frames_per_second()) * _session->frames_per_timecode_frame() * 60);
 
        switch (_snap_type) {
        case SnapToTimecodeFrame:
-               if (((direction == 0) && (fmod((double)start, (double)_session->frames_per_timecode_frame()) > (_session->frames_per_timecode_frame() / 2))) || (direction > 0)) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   fmod((double)start, (double)_session->frames_per_timecode_frame()) == 0) {
+                       /* start is already on a whole timecode frame, do nothing */
+               } else if (((direction == 0) && (fmod((double)start, (double)_session->frames_per_timecode_frame()) > (_session->frames_per_timecode_frame() / 2))) || (direction > 0)) {
                        start = (framepos_t) (ceil ((double) start / _session->frames_per_timecode_frame()) * _session->frames_per_timecode_frame());
                } else {
                        start = (framepos_t) (floor ((double) start / _session->frames_per_timecode_frame()) *  _session->frames_per_timecode_frame());
@@ -2568,7 +2616,10 @@ Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*
                } else {
                        start -= _session->config.get_timecode_offset ();
                }
-               if (((direction == 0) && (start % one_timecode_second > one_timecode_second / 2)) || direction > 0) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   (start % one_timecode_second == 0)) {
+                       /* start is already on a whole second, do nothing */
+               } else if (((direction == 0) && (start % one_timecode_second > one_timecode_second / 2)) || direction > 0) {
                        start = (framepos_t) ceil ((double) start / one_timecode_second) * one_timecode_second;
                } else {
                        start = (framepos_t) floor ((double) start / one_timecode_second) * one_timecode_second;
@@ -2587,7 +2638,10 @@ Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*
                } else {
                        start -= _session->config.get_timecode_offset ();
                }
-               if (((direction == 0) && (start % one_timecode_minute > one_timecode_minute / 2)) || direction > 0) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   (start % one_timecode_minute == 0)) {
+                       /* start is already on a whole minute, do nothing */
+               } else if (((direction == 0) && (start % one_timecode_minute > one_timecode_minute / 2)) || direction > 0) {
                        start = (framepos_t) ceil ((double) start / one_timecode_minute) * one_timecode_minute;
                } else {
                        start = (framepos_t) floor ((double) start / one_timecode_minute) * one_timecode_minute;
@@ -2600,12 +2654,12 @@ Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*
                break;
        default:
                fatal << "Editor::smpte_snap_to_internal() called with non-timecode snap type!" << endmsg;
-               /*NOTREACHED*/
+               abort(); /*NOTREACHED*/
        }
 }
 
 void
-Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
+Editor::snap_to_internal (framepos_t& start, RoundMode direction, bool for_mark)
 {
        const framepos_t one_second = _session->frame_rate();
        const framepos_t one_minute = _session->frame_rate() * 60;
@@ -2620,7 +2674,10 @@ Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
                return timecode_snap_to_internal (start, direction, for_mark);
 
        case SnapToCDFrame:
-               if (((direction == 0) && (start % (one_second/75) > (one_second/75) / 2)) || (direction > 0)) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   start % (one_second/75) == 0) {
+                       /* start is already on a whole CD frame, do nothing */
+               } else if (((direction == 0) && (start % (one_second/75) > (one_second/75) / 2)) || (direction > 0)) {
                        start = (framepos_t) ceil ((double) start / (one_second / 75)) * (one_second / 75);
                } else {
                        start = (framepos_t) floor ((double) start / (one_second / 75)) * (one_second / 75);
@@ -2628,7 +2685,10 @@ Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
                break;
 
        case SnapToSeconds:
-               if (((direction == 0) && (start % one_second > one_second / 2)) || (direction > 0)) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   start % one_second == 0) {
+                       /* start is already on a whole second, do nothing */
+               } else if (((direction == 0) && (start % one_second > one_second / 2)) || (direction > 0)) {
                        start = (framepos_t) ceil ((double) start / one_second) * one_second;
                } else {
                        start = (framepos_t) floor ((double) start / one_second) * one_second;
@@ -2636,7 +2696,10 @@ Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
                break;
 
        case SnapToMinutes:
-               if (((direction == 0) && (start % one_minute > one_minute / 2)) || (direction > 0)) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   start % one_minute == 0) {
+                       /* start is already on a whole minute, do nothing */
+               } else if (((direction == 0) && (start % one_minute > one_minute / 2)) || (direction > 0)) {
                        start = (framepos_t) ceil ((double) start / one_minute) * one_minute;
                } else {
                        start = (framepos_t) floor ((double) start / one_minute) * one_minute;
@@ -2802,12 +2865,10 @@ Editor::setup_toolbar ()
        mouse_mode_size_group->add_widget (mouse_move_button);
        mouse_mode_size_group->add_widget (mouse_cut_button);
        mouse_mode_size_group->add_widget (mouse_select_button);
-       mouse_mode_size_group->add_widget (mouse_zoom_button);
-       mouse_mode_size_group->add_widget (mouse_gain_button);
        mouse_mode_size_group->add_widget (mouse_timefx_button);
        mouse_mode_size_group->add_widget (mouse_audition_button);
        mouse_mode_size_group->add_widget (mouse_draw_button);
-       mouse_mode_size_group->add_widget (internal_edit_button);
+       mouse_mode_size_group->add_widget (mouse_content_button);
 
        mouse_mode_size_group->add_widget (zoom_in_button);
        mouse_mode_size_group->add_widget (zoom_out_button);
@@ -2840,15 +2901,13 @@ Editor::setup_toolbar ()
 
        if (!ARDOUR::Profile->get_mixbus()) {
                mouse_mode_hbox->pack_start (mouse_cut_button, false, false);
-               mouse_mode_hbox->pack_start (mouse_zoom_button, false, false);
        }
        
        if (!ARDOUR::Profile->get_trx()) {
-               mouse_mode_hbox->pack_start (mouse_gain_button, false, false);
                mouse_mode_hbox->pack_start (mouse_timefx_button, false, false);
                mouse_mode_hbox->pack_start (mouse_audition_button, false, false);
                mouse_mode_hbox->pack_start (mouse_draw_button, false, false);
-               mouse_mode_hbox->pack_start (internal_edit_button, false, false, 0);
+               mouse_mode_hbox->pack_start (mouse_content_button, false, false);
        }
 
        mouse_mode_vbox->pack_start (*mouse_mode_hbox);
@@ -3135,13 +3194,11 @@ Editor::setup_tooltips ()
        ARDOUR_UI::instance()->set_tip (smart_mode_button, _("Smart Mode (add Range functions to Object mode)"));
        ARDOUR_UI::instance()->set_tip (mouse_move_button, _("Object Mode (select/move Objects)"));
        ARDOUR_UI::instance()->set_tip (mouse_cut_button, _("Cut Mode (split Regions)"));
-       ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Range Mode (select/move Ranges)"));
-       ARDOUR_UI::instance()->set_tip (mouse_draw_button, _("Draw/Edit MIDI Notes"));
-       ARDOUR_UI::instance()->set_tip (mouse_gain_button, _("Draw Region Gain"));
-       ARDOUR_UI::instance()->set_tip (mouse_zoom_button, _("Select Zoom Range"));
+       ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Grab/Select Objects"));
+       ARDOUR_UI::instance()->set_tip (mouse_draw_button, _("Draw/Edit Gain/Notes/Automation"));
        ARDOUR_UI::instance()->set_tip (mouse_timefx_button, _("Stretch/Shrink Regions and MIDI Notes"));
        ARDOUR_UI::instance()->set_tip (mouse_audition_button, _("Listen to Specific Regions"));
-       ARDOUR_UI::instance()->set_tip (internal_edit_button, _("Note Level Editing"));
+       ARDOUR_UI::instance()->set_tip (mouse_content_button, _("Edit Contents (notes and automation)"));
        ARDOUR_UI::instance()->set_tip (*_group_tabs, _("Groups: click to (de)activate\nContext-click for other operations"));
        ARDOUR_UI::instance()->set_tip (nudge_forward_button, _("Nudge Region/Selection Later"));
        ARDOUR_UI::instance()->set_tip (nudge_backward_button, _("Nudge Region/Selection Earlier"));
@@ -3264,10 +3321,100 @@ Editor::map_transport_state ()
 
 /* UNDO/REDO */
 
+void
+Editor::begin_selection_op_history ()
+{
+       selection_op_cmd_depth = 0;
+       selection_op_history_it = 0;
+       selection_op_history.clear();
+       selection_undo_action->set_sensitive (false);
+       selection_redo_action->set_sensitive (false);
+       selection_op_history.push_front (&_selection_memento->get_state ());
+}
+
+void
+Editor::begin_reversible_selection_op (string name)
+{
+       if (_session) {
+               //cerr << name << endl;
+               /* begin/commit pairs can be nested */
+               selection_op_cmd_depth++;
+       }
+}
+
+void
+Editor::commit_reversible_selection_op ()
+{
+       if (_session) {
+               if (selection_op_cmd_depth == 1) {
+
+                       if (selection_op_history_it > 0 && selection_op_history_it < selection_op_history.size()) {
+                               list<XMLNode *>::iterator it = selection_op_history.begin();
+                               advance (it, selection_op_history_it);
+                               selection_op_history.erase (selection_op_history.begin(), it);
+                       }
+                       selection_op_history.push_front (&_selection_memento->get_state ());
+                       selection_op_history_it = 0;
+               }
+
+               if (selection_op_cmd_depth > 0) {
+                       selection_op_cmd_depth--;
+               }
+
+               selection_undo_action->set_sensitive (true);
+               selection_redo_action->set_sensitive (false);
+       }
+}
+
+void
+Editor::undo_selection_op ()
+{
+       if (_session) {
+               selection_op_history_it++;
+               uint32_t n = 0;
+               for (std::list<XMLNode *>::iterator i = selection_op_history.begin(); i != selection_op_history.end(); ++i) {
+                       if (n == selection_op_history_it) {
+                               _selection_memento->set_state (*(*i), Stateful::current_state_version);
+                               selection_redo_action->set_sensitive (true);
+                       }
+                       ++n;
+
+               }
+               /* is there an earlier entry? */
+               if ((selection_op_history_it + 1) >= selection_op_history.size()) {
+                       selection_undo_action->set_sensitive (false);
+               }
+       }
+}
+
+void
+Editor::redo_selection_op ()
+{
+       if (_session) {
+               if (selection_op_history_it > 0) {
+                       selection_op_history_it--;
+               }
+               uint32_t n = 0;
+               for (std::list<XMLNode *>::iterator i = selection_op_history.begin(); i != selection_op_history.end(); ++i) {
+                       if (n == selection_op_history_it) {
+                               _selection_memento->set_state (*(*i), Stateful::current_state_version);
+                               selection_undo_action->set_sensitive (true);
+                       }
+                       ++n;
+
+               }
+
+               if (selection_op_history_it == 0) {
+                       selection_redo_action->set_sensitive (false);
+               }
+       }
+}
+
 void
 Editor::begin_reversible_command (string name)
 {
        if (_session) {
+               before.push_back (&_selection_memento->get_state ());
                _session->begin_reversible_command (name);
        }
 }
@@ -3276,6 +3423,7 @@ void
 Editor::begin_reversible_command (GQuark q)
 {
        if (_session) {
+               before.push_back (&_selection_memento->get_state ());
                _session->begin_reversible_command (q);
        }
 }
@@ -3284,6 +3432,18 @@ void
 Editor::commit_reversible_command ()
 {
        if (_session) {
+               if (before.size() == 1) {
+                       _session->add_command (new MementoCommand<SelectionMemento>(*(_selection_memento), before.front(), &_selection_memento->get_state ()));
+                       undo_action->set_sensitive(true);
+                       begin_selection_op_history ();
+               }
+
+               if (before.empty()) {
+                       cerr << "Please call begin_reversible_command() before commit_reversible_command()." << endl;
+               } else {
+                       before.pop_back();
+               }
+
                _session->commit_reversible_command ();
        }
 }
@@ -3557,6 +3717,7 @@ Editor::set_visible_track_count (int32_t n)
 
        int h;
        string str;
+       DisplaySuspender ds;
        
        if (_visible_track_count > 0) {
                h = trackviews_height() / _visible_track_count;
@@ -3592,7 +3753,7 @@ Editor::set_visible_track_count (int32_t n)
 void
 Editor::override_visible_track_count ()
 {
-       _visible_track_count = -_visible_track_count;
+       _visible_track_count = -1;
        visible_tracks_selector.set_text ( _("*") );
 }
 
@@ -3828,90 +3989,75 @@ Editor::playlist_selector () const
        return *_playlist_selector;
 }
 
-Evoral::MusicalTime
+framecnt_t
+Editor::get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t duration)
+{
+       if (paste_count == 0) {
+               /* don't bother calculating an offset that will be zero anyway */
+               return 0;
+       }
+
+       /* calculate basic unsnapped multi-paste offset */
+       framecnt_t offset = paste_count * duration;
+
+       /* snap offset so pos + offset is aligned to the grid */
+       framepos_t offset_pos = pos + offset;
+       snap_to(offset_pos, RoundUpMaybe);
+       offset = offset_pos - pos;
+
+       return offset;
+}
+
+unsigned
+Editor::get_grid_beat_divisions(framepos_t position)
+{
+       switch (_snap_type) {
+       case SnapToBeatDiv128: return 128;
+       case SnapToBeatDiv64:  return 64;
+       case SnapToBeatDiv32:  return 32;
+       case SnapToBeatDiv28:  return 28;
+       case SnapToBeatDiv24:  return 24;
+       case SnapToBeatDiv20:  return 20;
+       case SnapToBeatDiv16:  return 16;
+       case SnapToBeatDiv14:  return 14;
+       case SnapToBeatDiv12:  return 12;
+       case SnapToBeatDiv10:  return 10;
+       case SnapToBeatDiv8:   return 8;
+       case SnapToBeatDiv7:   return 7;
+       case SnapToBeatDiv6:   return 6;
+       case SnapToBeatDiv5:   return 5;
+       case SnapToBeatDiv4:   return 4;
+       case SnapToBeatDiv3:   return 3;
+       case SnapToBeatDiv2:   return 2;
+       default:               return 0;
+       }
+       return 0;
+}
+
+Evoral::Beats
 Editor::get_grid_type_as_beats (bool& success, framepos_t position)
 {
        success = true;
 
+       const unsigned divisions = get_grid_beat_divisions(position);
+       if (divisions) {
+               return Evoral::Beats(1.0 / (double)get_grid_beat_divisions(position));
+       }
+
        switch (_snap_type) {
        case SnapToBeat:
-               return 1.0;
-               break;
-
-       case SnapToBeatDiv128:
-               return 1.0/128.0;
-               break;
-       case SnapToBeatDiv64:
-               return 1.0/64.0;
-               break;
-       case SnapToBeatDiv32:
-               return 1.0/32.0;
-               break;
-       case SnapToBeatDiv28:
-               return 1.0/28.0;
-               break;
-       case SnapToBeatDiv24:
-               return 1.0/24.0;
-               break;
-       case SnapToBeatDiv20:
-               return 1.0/20.0;
-               break;
-       case SnapToBeatDiv16:
-               return 1.0/16.0;
-               break;
-       case SnapToBeatDiv14:
-               return 1.0/14.0;
-               break;
-       case SnapToBeatDiv12:
-               return 1.0/12.0;
-               break;
-       case SnapToBeatDiv10:
-               return 1.0/10.0;
-               break;
-       case SnapToBeatDiv8:
-               return 1.0/8.0;
-               break;
-       case SnapToBeatDiv7:
-               return 1.0/7.0;
-               break;
-       case SnapToBeatDiv6:
-               return 1.0/6.0;
-               break;
-       case SnapToBeatDiv5:
-               return 1.0/5.0;
-               break;
-       case SnapToBeatDiv4:
-               return 1.0/4.0;
-               break;
-       case SnapToBeatDiv3:
-               return 1.0/3.0;
-               break;
-       case SnapToBeatDiv2:
-               return 1.0/2.0;
-               break;
-
+               return Evoral::Beats(1.0);
        case SnapToBar:
                if (_session) {
-                       return _session->tempo_map().meter_at (position).divisions_per_bar();
+                       return Evoral::Beats(_session->tempo_map().meter_at (position).divisions_per_bar());
                }
                break;
-
-       case SnapToCDFrame:
-       case SnapToTimecodeFrame:
-       case SnapToTimecodeSeconds:
-       case SnapToTimecodeMinutes:
-       case SnapToSeconds:
-       case SnapToMinutes:
-       case SnapToRegionStart:
-       case SnapToRegionEnd:
-       case SnapToRegionSync:
-       case SnapToRegionBoundary:
        default:
                success = false;
                break;
        }
 
-       return 0.0;
+       return Evoral::Beats();
 }
 
 framecnt_t
@@ -4034,7 +4180,7 @@ Editor::session_state_saved (string)
 void
 Editor::update_tearoff_visibility()
 {
-       bool visible = Config->get_keep_tearoffs();
+       bool visible = ARDOUR_UI::config()->get_keep_tearoffs();
        _mouse_mode_tearoff->set_visible (visible);
        _tools_tearoff->set_visible (visible);
        if (_zoom_tearoff) {
@@ -4042,6 +4188,14 @@ Editor::update_tearoff_visibility()
        }
 }
 
+void
+Editor::reattach_all_tearoffs ()
+{
+       if (_mouse_mode_tearoff) _mouse_mode_tearoff->put_it_back ();
+       if (_tools_tearoff) _tools_tearoff->put_it_back ();
+       if (_zoom_tearoff) _zoom_tearoff->put_it_back ();
+}
+
 void
 Editor::maximise_editing_space ()
 {
@@ -4106,7 +4260,7 @@ Editor::copy_playlists (TimeAxisView* v)
 void
 Editor::clear_playlists (TimeAxisView* v)
 {
-       begin_reversible_command (_("clear playlists"));
+       begin_reversible_command (_("clear playlists"));        
        vector<boost::shared_ptr<ARDOUR::Playlist> > playlists;
        _session->playlists->get (playlists);
        mapover_tracks (sigc::mem_fun (*this, &Editor::mapped_clear_playlist), v, ARDOUR::Properties::select.property_id);
@@ -4144,6 +4298,12 @@ Editor::on_key_release_event (GdkEventKey* ev)
        // return key_press_focus_accelerator_handler (*this, ev);
 }
 
+double
+Editor::get_y_origin () const
+{
+       return vertical_adjustment.get_value ();
+}
+
 /** Queue up a change to the viewport x origin.
  *  @param frame New x origin.
  */
@@ -4225,7 +4385,9 @@ Editor::undo_visual_state ()
 
        redo_visual_stack.push_back (current_visual_state (vs ? vs->gui_state != 0 : false));
 
-       use_visual_state (*vs);
+       if (vs) {
+               use_visual_state (*vs);
+       }
 }
 
 void
@@ -4238,9 +4400,13 @@ Editor::redo_visual_state ()
        VisualState* vs = redo_visual_stack.back();
        redo_visual_stack.pop_back();
 
-       undo_visual_stack.push_back (current_visual_state (vs ? vs->gui_state != 0 : false));
+       // can 'vs' really be 0? Is there a place that puts NULL pointers onto the stack?
+       // why do we check here?
+       undo_visual_stack.push_back (current_visual_state (vs ? (vs->gui_state != 0) : false));
 
-       use_visual_state (*vs);
+       if (vs) {
+               use_visual_state (*vs);
+       }
 }
 
 void
@@ -4349,7 +4515,8 @@ void
 Editor::ensure_visual_change_idle_handler ()
 {
        if (pending_visual_change.idle_handler_id < 0) {
-               pending_visual_change.idle_handler_id = g_idle_add (_idle_visual_changer, this);
+               // see comment in add_to_idle_resize above.
+               pending_visual_change.idle_handler_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, _idle_visual_changer, this, NULL);
                pending_visual_change.being_handled = false;
        }
 }
@@ -4444,7 +4611,7 @@ Editor::sort_track_selection (TrackViewList& sel)
 }
 
 framepos_t
-Editor::get_preferred_edit_position (bool ignore_playhead, bool from_context_menu)
+Editor::get_preferred_edit_position (bool ignore_playhead, bool from_context_menu, bool from_outside_canvas)
 {
        bool ignored;
        framepos_t where = 0;
@@ -4453,8 +4620,10 @@ Editor::get_preferred_edit_position (bool ignore_playhead, bool from_context_men
        if(Profile->get_mixbus())
                if (ep == EditAtSelectedMarker)
                        ep=EditAtPlayhead;
-               
-       if (from_context_menu && (ep == EditAtMouse)) {
+
+       if (from_outside_canvas && (ep == EditAtMouse)) {
+               ep = EditAtPlayhead;
+       } else if (from_context_menu && (ep == EditAtMouse)) {
                return  canvas_event_sample (&context_click_event, 0, 0);
        }
 
@@ -4546,8 +4715,7 @@ Editor::set_punch_range (framepos_t start, framepos_t end, string cmd)
                _session->set_auto_punch_location (loc);
                XMLNode &after = _session->locations()->get_state();
                _session->add_command (new MementoCommand<Locations>(*(_session->locations()), &before, &after));
-       }
-       else {
+       } else {
                XMLNode &before = tpl->get_state();
                tpl->set_hidden (false, this);
                tpl->set (start, end);
@@ -4724,6 +4892,35 @@ Editor::get_regions_from_selection_and_entered ()
        return regions;
 }
 
+void
+Editor::get_regionviews_by_id (PBD::ID const & id, RegionSelection & regions) const
+{
+       for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) {
+               RouteTimeAxisView* tatv;
+               
+               if ((tatv = dynamic_cast<RouteTimeAxisView*> (*i)) != 0) {
+                       boost::shared_ptr<Playlist> pl;
+                       std::vector<boost::shared_ptr<Region> > results;
+                       boost::shared_ptr<Track> tr;
+                       
+                       if ((tr = tatv->track()) == 0) {
+                               /* bus */
+                               continue;
+                       }
+                       
+                       if ((pl = (tr->playlist())) != 0) {
+                               boost::shared_ptr<Region> r = pl->region_by_id (id);
+                               if (r) {
+                                       RegionView* marv = tatv->view()->find_view (r);
+                                       if (marv) {
+                                               regions.push_back (marv);
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
 void
 Editor::get_regions_corresponding_to (boost::shared_ptr<Region> region, vector<RegionView*>& regions, bool src_comparison)
 {
@@ -4796,6 +4993,13 @@ Editor::first_idle ()
        _routes->redisplay ();
 
        delete dialog;
+
+       if (_session->undo_depth() == 0) {
+               undo_action->set_sensitive(false);
+       }
+       redo_action->set_sensitive(false);
+       begin_selection_op_history ();
+
        _have_idled = true;
 }
 
@@ -4809,7 +5013,11 @@ void
 Editor::add_to_idle_resize (TimeAxisView* view, int32_t h)
 {
        if (resize_idle_id < 0) {
-               resize_idle_id = g_idle_add (_idle_resize, this);
+               /* https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#G-PRIORITY-HIGH-IDLE:CAPS
+                * GTK+ uses G_PRIORITY_HIGH_IDLE + 10 for resizing operations, and G_PRIORITY_HIGH_IDLE + 20 for redrawing operations.
+                * (This is done to ensure that any pending resizes are processed before any pending redraws, so that widgets are not redrawn twice unnecessarily.)
+                */
+               resize_idle_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, _idle_resize, this, NULL);
                _pending_resize_amount = 0;
        }
 
@@ -4879,8 +5087,15 @@ Editor::located ()
 }
 
 void
-Editor::region_view_added (RegionView *)
+Editor::region_view_added (RegionView * rv)
 {
+       for (list<PBD::ID>::iterator pr = selection->regions.pending.begin (); pr != selection->regions.pending.end (); ++pr) {
+               if (rv->region ()->id () == (*pr)) {
+                       selection->add (rv);
+                       selection->regions.pending.erase (pr);
+                       break;
+               }
+       }
        _summary->set_background_dirty ();
 }
 
@@ -4969,12 +5184,6 @@ Editor::add_routes (RouteList& routes)
 
                rtv->effective_gain_display ();
 
-                if (internal_editing()) {
-                        rtv->enter_internal_edit_mode ();
-                } else {
-                        rtv->leave_internal_edit_mode ();
-                }
-
                rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &Editor::region_view_added));
                rtv->view()->RegionViewRemoved.connect (sigc::mem_fun (*this, &Editor::region_view_removed));
        }
@@ -5611,6 +5820,42 @@ Editor::popup_control_point_context_menu (ArdourCanvas::Item* item, GdkEvent* ev
        _control_point_context_menu.popup (event->button.button, event->button.time);
 }
 
+void
+Editor::popup_note_context_menu (ArdourCanvas::Item* item, GdkEvent* event)
+{
+       using namespace Menu_Helpers;
+
+       NoteBase* note = reinterpret_cast<NoteBase*>(item->get_data("notebase"));
+       if (!note) {
+               return;
+       }
+
+       /* We need to get the selection here and pass it to the operations, since
+          popping up the menu will cause a region leave event which clears
+          entered_regionview. */
+
+       MidiRegionView&       mrv = note->region_view();
+       const RegionSelection rs  = get_regions_from_selection_and_entered ();
+
+       MenuList& items = _note_context_menu.items();
+       items.clear();
+
+       items.push_back(MenuElem(_("Delete"),
+                                sigc::mem_fun(mrv, &MidiRegionView::delete_selection)));
+       items.push_back(MenuElem(_("Edit..."),
+                                sigc::bind(sigc::mem_fun(*this, &Editor::edit_notes), &mrv)));
+       items.push_back(MenuElem(_("Legatize"),
+                                sigc::bind(sigc::mem_fun(*this, &Editor::legatize_regions), rs, false)));
+       items.push_back(MenuElem(_("Quantize..."),
+                                sigc::bind(sigc::mem_fun(*this, &Editor::quantize_regions), rs)));
+       items.push_back(MenuElem(_("Remove Overlap"),
+                                sigc::bind(sigc::mem_fun(*this, &Editor::legatize_regions), rs, true)));
+       items.push_back(MenuElem(_("Transform..."),
+                                sigc::bind(sigc::mem_fun(*this, &Editor::transform_regions), rs)));
+
+       _note_context_menu.popup (event->button.button, event->button.time);
+}
+
 void
 Editor::zoom_vertical_modifier_released()
 {
@@ -5622,9 +5867,10 @@ Editor::ui_parameter_changed (string parameter)
 {
        if (parameter == "icon-set") {
                while (!_cursor_stack.empty()) {
-                       _cursor_stack.pop();
+                       _cursor_stack.pop_back();
                }
                _cursors->set_cursor_set (ARDOUR_UI::config()->get_icon_set());
+               _cursor_stack.push_back(_cursors->grabber);
        } else if (parameter == "draggable-playhead") {
                if (_verbose_cursor) {
                        playhead_cursor->set_sensitive (ARDOUR_UI::config()->get_draggable_playhead());