Start step entry at playhead.
[ardour.git] / gtk2_ardour / editor.cc
index 5e5c323f6a71b0a434bced4b8b13e51949e0e48b..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 */
 
@@ -306,6 +311,7 @@ Editor::Editor ()
        selection = new Selection (this);
        cut_buffer = new Selection (this);
        _selection_memento = new SelectionMemento ();
+       selection_op_history.clear();
        before.clear();
 
        clicked_regionview = 0;
@@ -313,7 +319,6 @@ Editor::Editor ()
        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);
@@ -400,11 +405,14 @@ Editor::Editor ()
 
        zoom_focus = ZoomFocusLeft;
        _edit_point = EditAtMouse;
-       current_canvas_cursor = 0;
        _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");
@@ -520,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());
@@ -669,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;
@@ -1375,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)
                );
 
@@ -1842,16 +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 Session Start/End from Range"), sigc::mem_fun(*this, &Editor::set_session_extents_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)));
@@ -2027,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) {
@@ -2083,6 +2099,8 @@ Editor::set_snap_to (SnapType st)
                break;
        }
 
+       redisplay_tempo (false);
+
        SnapChanged (); /* EMIT SIGNAL */
 }
 
@@ -2120,7 +2138,7 @@ Editor::set_edit_point_preference (EditPoint ep, bool force)
                edit_point_selector.set_text (str);
        }
 
-       reset_canvas_cursor ();
+       update_all_enter_cursors();
 
        if (!force && !changed) {
                return;
@@ -3303,6 +3321,95 @@ 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)
 {
@@ -3327,9 +3434,13 @@ 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()) {
+               if (before.empty()) {
+                       cerr << "Please call begin_reversible_command() before commit_reversible_command()." << endl;
+               } else {
                        before.pop_back();
                }
 
@@ -3606,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;
@@ -3896,90 +4008,56 @@ Editor::get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t durat
        return offset;
 }
 
-Evoral::MusicalTime
+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 Evoral::MusicalTime(1.0);
-               break;
-
-       case SnapToBeatDiv128:
-               return Evoral::MusicalTime(1.0/128.0);
-               break;
-       case SnapToBeatDiv64:
-               return Evoral::MusicalTime(1.0/64.0);
-               break;
-       case SnapToBeatDiv32:
-               return Evoral::MusicalTime(1.0/32.0);
-               break;
-       case SnapToBeatDiv28:
-               return Evoral::MusicalTime(1.0/28.0);
-               break;
-       case SnapToBeatDiv24:
-               return Evoral::MusicalTime(1.0/24.0);
-               break;
-       case SnapToBeatDiv20:
-               return Evoral::MusicalTime(1.0/20.0);
-               break;
-       case SnapToBeatDiv16:
-               return Evoral::MusicalTime(1.0/16.0);
-               break;
-       case SnapToBeatDiv14:
-               return Evoral::MusicalTime(1.0/14.0);
-               break;
-       case SnapToBeatDiv12:
-               return Evoral::MusicalTime(1.0/12.0);
-               break;
-       case SnapToBeatDiv10:
-               return Evoral::MusicalTime(1.0/10.0);
-               break;
-       case SnapToBeatDiv8:
-               return Evoral::MusicalTime(1.0/8.0);
-               break;
-       case SnapToBeatDiv7:
-               return Evoral::MusicalTime(1.0/7.0);
-               break;
-       case SnapToBeatDiv6:
-               return Evoral::MusicalTime(1.0/6.0);
-               break;
-       case SnapToBeatDiv5:
-               return Evoral::MusicalTime(1.0/5.0);
-               break;
-       case SnapToBeatDiv4:
-               return Evoral::MusicalTime(1.0/4.0);
-               break;
-       case SnapToBeatDiv3:
-               return Evoral::MusicalTime(1.0/3.0);
-               break;
-       case SnapToBeatDiv2:
-               return Evoral::MusicalTime(1.0/2.0);
-               break;
-
+               return Evoral::Beats(1.0);
        case SnapToBar:
                if (_session) {
-                       return Evoral::MusicalTime(_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 Evoral::MusicalTime();
+       return Evoral::Beats();
 }
 
 framecnt_t
@@ -4102,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) {
@@ -4533,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;
@@ -4542,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);
        }
 
@@ -4913,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;
 }
 
@@ -5733,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()
 {
@@ -5744,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());