amend e3da18fd
[ardour.git] / gtk2_ardour / midi_region_view.cc
index 97cb11529e64938aac281cf94fef012417efe963..e3179e0f9494dbf9eed994a762849b036e791bd7 100644 (file)
@@ -123,6 +123,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
        , _last_event_y (0)
        , _grabbed_keyboard (false)
        , _entered (false)
+       , _mouse_changed_selection (false)
 {
        CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
        _note_group->raise_to_top();
@@ -169,6 +170,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container*      parent,
        , _last_event_y (0)
        , _grabbed_keyboard (false)
        , _entered (false)
+       , _mouse_changed_selection (false)
 {
        CANVAS_DEBUG_NAME (_note_group, string_compose ("note group for %1", get_item_name()));
        _note_group->raise_to_top();
@@ -220,6 +222,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other)
        , _last_event_y (0)
        , _grabbed_keyboard (false)
        , _entered (false)
+       , _mouse_changed_selection (false)
 {
        init (false);
 }
@@ -250,6 +253,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<M
        , _last_event_y (0)
        , _grabbed_keyboard (false)
        , _entered (false)
+       , _mouse_changed_selection (false)
 {
        init (true);
 }
@@ -502,6 +506,7 @@ MidiRegionView::button_press (GdkEventButton* ev)
        }
 
        _pressed_button = ev->button;
+       _mouse_changed_selection = false;
 
        return true;
 }
@@ -532,12 +537,14 @@ MidiRegionView::button_release (GdkEventButton* ev)
                case MouseRange:
                        /* no motion occured - simple click */
                        clear_selection ();
+                       _mouse_changed_selection = true;
                        break;
 
                case MouseContent:
                case MouseTimeFX:
                        {
                                clear_selection();
+                               _mouse_changed_selection = true;
 
                                if (Keyboard::is_insert_note_event(ev)) {
 
@@ -591,6 +598,11 @@ MidiRegionView::button_release (GdkEventButton* ev)
                break;
        }
 
+       if (_mouse_changed_selection) {
+               trackview.editor().begin_reversible_selection_op (_("Mouse Selection Change"));
+               trackview.editor().commit_reversible_selection_op ();
+       }
+
        return false;
 }
 
@@ -643,6 +655,7 @@ MidiRegionView::motion (GdkEventMotion* ev)
                                editor.drags()->set (new MidiRubberbandSelectDrag (dynamic_cast<Editor *> (&editor), this), (GdkEvent *) ev);
                                if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
                                        clear_selection ();
+                                       _mouse_changed_selection = true;
                                }
                                _mouse_state = SelectRectDragging;
                                return true;
@@ -745,27 +758,19 @@ MidiRegionView::key_press (GdkEventKey* ev)
                delete_selection();
                return true;
 
-       } else if (ev->keyval == GDK_Tab) {
+       } else if (ev->keyval == GDK_Tab || ev->keyval == GDK_ISO_Left_Tab) {
 
-               if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
-                       goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
-               } else {
-                       goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
-               }
-               return true;
-
-       } else if (ev->keyval == GDK_ISO_Left_Tab) {
-
-               /* Shift-TAB generates ISO Left Tab, for some reason */
+               trackview.editor().begin_reversible_selection_op (_("Select Adjacent Note"));
 
                if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
                        goto_previous_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
                } else {
                        goto_next_note (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier));
                }
-               return true;
 
+               trackview.editor().commit_reversible_selection_op();
 
+               return true;
 
        } else if (ev->keyval == GDK_Up) {
 
@@ -946,9 +951,12 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo
 
        view->update_note_range(new_note->note());
 
-       MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note"));
-       cmd->add (new_note);
-       _model->apply_command(*trackview.session(), cmd);
+       start_note_diff_command(_("add note"));
+
+       clear_selection ();
+       note_diff_add_note (new_note, true, false);
+
+       apply_diff();
 
        play_midi_note (new_note);
 }
@@ -982,8 +990,8 @@ MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
 
        content_connection.disconnect ();
        _model->ContentsChanged.connect (content_connection, invalidator (*this), boost::bind (&MidiRegionView::redisplay_model, this), gui_context());
-
-       clear_events ();
+       /* Don't signal as nobody else needs to know until selection has been altered. */
+       clear_events (false);
 
        if (_enable_display) {
                redisplay_model();
@@ -994,6 +1002,7 @@ void
 MidiRegionView::start_note_diff_command (string name)
 {
        if (!_note_diff_command) {
+               trackview.editor().begin_reversible_command (name);
                _note_diff_command = _model->new_note_diff_command (name);
        }
 }
@@ -1044,6 +1053,7 @@ void
 MidiRegionView::apply_diff (bool as_subcommand)
 {
        bool add_or_remove;
+       bool commit = false;
 
        if (!_note_diff_command) {
                return;
@@ -1060,6 +1070,7 @@ MidiRegionView::apply_diff (bool as_subcommand)
                _model->apply_command_as_subcommand (*trackview.session(), _note_diff_command);
        } else {
                _model->apply_command (*trackview.session(), _note_diff_command);
+               commit = true;
        }
 
        _note_diff_command = 0;
@@ -1070,6 +1081,9 @@ MidiRegionView::apply_diff (bool as_subcommand)
        }
 
        _marked_for_velocity.clear();
+       if (commit) {
+               trackview.editor().commit_reversible_command ();
+       }
 }
 
 void
@@ -1100,6 +1114,21 @@ MidiRegionView::find_canvas_note (boost::shared_ptr<NoteType> note)
        return 0;
 }
 
+/** This version finds any canvas note matching the supplied note. */
+NoteBase*
+MidiRegionView::find_canvas_note (NoteType note)
+{
+       Events::iterator it;
+
+       for (it = _events.begin(); it != _events.end(); ++it) {
+               if (*((*it)->note()) == note) {
+                       return *it;
+               }
+       }
+
+       return 0;
+}
+
 void
 MidiRegionView::get_events (Events& e, Evoral::Sequence<Evoral::Beats>::NoteOperator op, uint8_t val, int chan_mask)
 {
@@ -1170,7 +1199,14 @@ MidiRegionView::redisplay_model()
 
                        } else {
 
-                               add_note (note, visible);
+                               cne = add_note (note, visible);
+                       }
+
+                       set<boost::shared_ptr<NoteType> >::iterator it;
+                       for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) {
+                               if (*(*it) == *note) {
+                                       add_to_selection (cne);
+                               }
                        }
 
                } else {
@@ -1182,7 +1218,6 @@ MidiRegionView::redisplay_model()
                }
        }
 
-
        /* remove note items that are no longer valid */
 
        if (!empty_when_starting) {
@@ -1213,6 +1248,7 @@ MidiRegionView::redisplay_model()
 
        _marked_for_selection.clear ();
        _marked_for_velocity.clear ();
+       _pending_note_selection.clear ();
 
        /* we may have caused _events to contain things out of order (e.g. if a note
           moved earlier or later). we don't generally need them in time order, but
@@ -1621,8 +1657,12 @@ MidiRegionView::start_playing_midi_chord (vector<boost::shared_ptr<NoteType> > n
 bool
 MidiRegionView::note_in_region_range (const boost::shared_ptr<NoteType> note, bool& visible) const
 {
+       /* This is imprecise due to all the conversion conversion involved, so only
+          hide notes if they seem to start more than one tick before the start. */
+       const framecnt_t tick_frames       = Evoral::Beats::tick().to_ticks(trackview.session()->frame_rate());
        const framepos_t note_start_frames = source_beats_to_region_frames (note->time());
-       bool outside = (note_start_frames  < 0) || (note_start_frames > _region->last_frame());
+       const bool       outside           = ((note_start_frames <= -tick_frames) ||
+                                             (note_start_frames > _region->last_frame()));
 
        visible = (note->note() >= midi_stream_view()->lowest_note()) &&
                (note->note() <= midi_stream_view()->highest_note());
@@ -1735,7 +1775,7 @@ MidiRegionView::update_hit (Hit* ev, bool update_ghost_regions)
  * notes, and resolve_note should be called when the corresponding note off
  * event arrives, to properly display the note.
  */
-void
+NoteBase*
 MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
 {
        NoteBase* event = 0;
@@ -1793,6 +1833,7 @@ MidiRegionView::add_note(const boost::shared_ptr<NoteType> note, bool visible)
        MidiStreamView* const view = mtv->midi_view();
 
        view->update_note_range (note->note());
+       return event;
 }
 
 void
@@ -1816,10 +1857,12 @@ MidiRegionView::step_add_note (uint8_t channel, uint8_t number, uint8_t velocity
        view->update_note_range(new_note->note());
 
        _marked_for_selection.clear ();
-       clear_selection ();
 
        start_note_diff_command (_("step add"));
+
+       clear_selection ();
        note_diff_add_note (new_note, true, false);
+
        apply_diff();
 
        // last_step_edit_note = new_note;
@@ -1909,7 +1952,9 @@ MidiRegionView::get_patch_key_at (Evoral::Beats time, uint8_t channel, MIDI::Nam
 void
 MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPrimaryKey& new_patch)
 {
-       MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
+       string name = _("alter patch change");
+       trackview.editor().begin_reversible_command (name);
+       MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
 
        if (pc.patch()->program() != new_patch.program()) {
                c->change_program (pc.patch (), new_patch.program());
@@ -1921,6 +1966,7 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri
        }
 
        _model->apply_command (*trackview.session(), c);
+       trackview.editor().commit_reversible_command ();
 
        _patch_changes.clear ();
        display_patch_changes ();
@@ -1929,7 +1975,9 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri
 void
 MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange<Evoral::Beats> & new_change)
 {
-       MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("alter patch change"));
+       string name = _("alter patch change");
+       trackview.editor().begin_reversible_command (name);
+       MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
 
        if (old_change->time() != new_change.time()) {
                c->change_time (old_change, new_change.time());
@@ -1948,6 +1996,7 @@ MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const
        }
 
        _model->apply_command (*trackview.session(), c);
+       trackview.editor().commit_reversible_command ();
 
        _patch_changes.clear ();
        display_patch_changes ();
@@ -1962,8 +2011,10 @@ void
 MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beats> const & patch)
 {
        MidiTimeAxisView* const mtv = dynamic_cast<MidiTimeAxisView*>(&trackview);
+       string name = _("add patch change");
 
-       MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("add patch change"));
+       trackview.editor().begin_reversible_command (name);
+       MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (name);
        c->add (MidiModel::PatchChangePtr (
                        new Evoral::PatchChange<Evoral::Beats> (
                                absolute_frames_to_source_beats (_region->position() + t),
@@ -1973,6 +2024,7 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beat
                );
 
        _model->apply_command (*trackview.session(), c);
+       trackview.editor().commit_reversible_command ();
 
        _patch_changes.clear ();
        display_patch_changes ();
@@ -1981,9 +2033,11 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange<Evoral::Beat
 void
 MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
 {
+       trackview.editor().begin_reversible_command (_("move patch change"));
        MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("move patch change"));
        c->change_time (pc.patch (), t);
        _model->apply_command (*trackview.session(), c);
+       trackview.editor().commit_reversible_command ();
 
        _patch_changes.clear ();
        display_patch_changes ();
@@ -1992,9 +2046,11 @@ MidiRegionView::move_patch_change (PatchChange& pc, Evoral::Beats t)
 void
 MidiRegionView::delete_patch_change (PatchChange* pc)
 {
+       trackview.editor().begin_reversible_command (_("delete patch change"));
        MidiModel::PatchChangeDiffCommand* c = _model->new_patch_change_diff_command (_("delete patch change"));
        c->remove (pc->patch ());
        _model->apply_command (*trackview.session(), c);
+       trackview.editor().commit_reversible_command ();
 
        _patch_changes.clear ();
        display_patch_changes ();
@@ -2143,6 +2199,24 @@ MidiRegionView::invert_selection ()
        }
 }
 
+/** Used for selection undo/redo.
+    The requested notes most likely won't exist in the view until the next model redisplay.
+*/
+void
+MidiRegionView::select_notes (list<boost::shared_ptr<NoteType> > notes)
+{
+       NoteBase* cne;
+       list<boost::shared_ptr<NoteType> >::iterator n;
+
+       for (n = notes.begin(); n != notes.end(); ++n) {
+               if ((cne = find_canvas_note(*(*n))) != 0) {
+                       add_to_selection (cne);
+               } else {
+                       _pending_note_selection.insert(*n);
+               }
+       }
+}
+
 void
 MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
 {
@@ -2712,7 +2786,7 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_
 void
 MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative)
 {
-       start_note_diff_command (_("resize notes"));
+       _note_diff_command = _model->new_note_diff_command (_("resize notes"));
 
        for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
                Note*  canvas_note = (*i)->note;
@@ -2773,7 +2847,7 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_
        }
 
        _resize_data.clear();
-       apply_diff();
+       apply_diff(true);
 }
 
 void
@@ -3351,10 +3425,12 @@ MidiRegionView::selection_as_cut_buffer () const
 bool
 MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContext& ctx)
 {
+       bool commit = false;
        // Paste notes, if available
        MidiNoteSelection::const_iterator m = selection.midi_notes.get_nth(ctx.counts.n_notes());
        if (m != selection.midi_notes.end()) {
                ctx.counts.increase_n_notes();
+               if (!(*m)->empty()) { commit = true; }
                paste_internal(pos, ctx.count, ctx.times, **m);
        }
 
@@ -3362,9 +3438,14 @@ MidiRegionView::paste (framepos_t pos, const ::Selection& selection, PasteContex
        typedef RouteTimeAxisView::AutomationTracks ATracks;
        const ATracks& atracks = midi_view()->automation_tracks();
        for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
-               a->second->paste(pos, selection, ctx);
+               if (a->second->paste(pos, selection, ctx)) {
+                       commit = true;
+               }
        }
 
+       if (commit) {
+               trackview.editor().commit_reversible_command ();
+       }
        return true;
 }