From 44203ce955f33135065456f55f1f685131174ba6 Mon Sep 17 00:00:00 2001 From: nick_m Date: Sun, 11 Jan 2015 04:07:31 +1100 Subject: [PATCH] Fix AutomationTrackItem rubberband click thinking it was unhandled. Fix several other cases where a single mouse click could cause several (not nested) selection ops. Fix missing selection memento for midi notes and midi commands. Rename some variables. Fix random style issues. --- gtk2_ardour/automation_line.cc | 2 +- gtk2_ardour/editor.cc | 56 ++++++++--- gtk2_ardour/editor.h | 4 +- gtk2_ardour/editor_canvas_events.cc | 4 + gtk2_ardour/editor_drag.cc | 26 ++++- gtk2_ardour/editor_mouse.cc | 25 +++-- gtk2_ardour/editor_selection.cc | 14 ++- gtk2_ardour/midi_region_view.cc | 102 ++++++++++++++++++-- gtk2_ardour/midi_region_view.h | 7 ++ gtk2_ardour/midi_time_axis.cc | 41 ++++++++ gtk2_ardour/midi_time_axis.h | 11 ++- gtk2_ardour/public_editor.h | 6 +- gtk2_ardour/selection.cc | 141 +++++++++++++++++++++++----- gtk2_ardour/selection.h | 2 + 14 files changed, 375 insertions(+), 66 deletions(-) diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index c0ed14f7f9..d5ed529ad8 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -156,7 +156,7 @@ void AutomationLine::update_visibility () { if (_visible & Line) { - /* Only show the line there are some points, otherwise we may show an out-of-date line + /* Only show the line when there are some points, otherwise we may show an out-of-date line when automation points have been removed (the line will still follow the shape of the old points). */ diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index da1c25f374..b18d6d6fb8 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -80,7 +80,6 @@ #include "control_protocol/control_protocol.h" -#include "actions.h" #include "actions.h" #include "analysis_window.h" #include "audio_clock.h" @@ -251,6 +250,7 @@ pane_size_watcher (Paned* pane) Editor::Editor () : _join_object_range_state (JOIN_OBJECT_RANGE_NONE) + , _mouse_changed_selection (false) /* time display buttons */ , minsec_label (_("Mins:Secs")) , bbt_label (_("Bars:Beats")) @@ -3336,7 +3336,7 @@ void Editor::begin_reversible_selection_op (string name) { if (_session) { - //cerr << name << endl; + cerr << name << endl; /* begin/commit pairs can be nested */ selection_op_cmd_depth++; } @@ -3349,20 +3349,22 @@ Editor::commit_reversible_selection_op () if (selection_op_cmd_depth == 1) { if (selection_op_history_it > 0 && selection_op_history_it < selection_op_history.size()) { + /* the user has undone some selection ops and then made a new one */ list::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; + + selection_undo_action->set_sensitive (true); + selection_redo_action->set_sensitive (false); } if (selection_op_cmd_depth > 0) { selection_op_cmd_depth--; } - - selection_undo_action->set_sensitive (true); - selection_redo_action->set_sensitive (false); } } @@ -3378,7 +3380,6 @@ Editor::undo_selection_op () selection_redo_action->set_sensitive (true); } ++n; - } /* is there an earlier entry? */ if ((selection_op_history_it + 1) >= selection_op_history.size()) { @@ -3401,7 +3402,6 @@ Editor::redo_selection_op () selection_undo_action->set_sensitive (true); } ++n; - } if (selection_op_history_it == 0) { @@ -4899,17 +4899,17 @@ Editor::get_regions_from_selection_and_entered () } void -Editor::get_regionviews_by_id (PBD::ID const & id, RegionSelection & regions) const +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; + RouteTimeAxisView* rtav; - if ((tatv = dynamic_cast (*i)) != 0) { + if ((rtav = dynamic_cast (*i)) != 0) { boost::shared_ptr pl; std::vector > results; boost::shared_ptr tr; - if ((tr = tatv->track()) == 0) { + if ((tr = rtav->track()) == 0) { /* bus */ continue; } @@ -4917,9 +4917,9 @@ Editor::get_regionviews_by_id (PBD::ID const & id, RegionSelection & regions) co if ((pl = (tr->playlist())) != 0) { boost::shared_ptr r = pl->region_by_id (id); if (r) { - RegionView* marv = tatv->view()->find_view (r); - if (marv) { - regions.push_back (marv); + RegionView* rv = rtav->view()->find_view (r); + if (rv) { + regions.push_back (rv); } } } @@ -4927,6 +4927,21 @@ Editor::get_regionviews_by_id (PBD::ID const & id, RegionSelection & regions) co } } +void +Editor::get_per_region_note_selection (list > > > > &selection) const +{ + + for (TrackViewList::const_iterator i = track_views.begin(); i != track_views.end(); ++i) { + MidiTimeAxisView* mtav; + + if ((mtav = dynamic_cast (*i)) != 0) { + + mtav->get_per_region_note_selection (selection); + } + } + +} + void Editor::get_regions_corresponding_to (boost::shared_ptr region, vector& regions, bool src_comparison) { @@ -5102,6 +5117,19 @@ Editor::region_view_added (RegionView * rv) break; } } + + MidiRegionView* mrv = dynamic_cast (rv); + if (mrv) { + list > > > >::iterator rnote; + for (rnote = selection->pending_midi_note_selection.begin(); rnote != selection->pending_midi_note_selection.end(); ++rnote) { + if (rv->region()->id () == (*rnote).first) { + mrv->select_notes ((*rnote).second); + selection->pending_midi_note_selection.erase(rnote); + break; + } + } + } + _summary->set_background_dirty (); } diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 77d28f4e12..7e1d1702f0 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -421,7 +421,8 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void get_regions_corresponding_to (boost::shared_ptr region, std::vector& regions, bool src_comparison); - void get_regionviews_by_id (PBD::ID const & id, RegionSelection & regions) const; + void get_regionviews_by_id (PBD::ID const id, RegionSelection & regions) const; + void get_per_region_note_selection (std::list > > > >&) const; void center_screen (framepos_t); @@ -706,6 +707,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type); bool button_release_can_deselect; + bool _mouse_changed_selection; void catch_vanishing_regionview (RegionView *); diff --git a/gtk2_ardour/editor_canvas_events.cc b/gtk2_ardour/editor_canvas_events.cc index 93c0abea35..c1696c9f6c 100644 --- a/gtk2_ardour/editor_canvas_events.cc +++ b/gtk2_ardour/editor_canvas_events.cc @@ -178,7 +178,9 @@ Editor::canvas_scroll_event (GdkEventScroll *event, bool from_canvas) bool Editor::track_canvas_button_press_event (GdkEventButton */*event*/) { + begin_reversible_selection_op (_("Clear Selection Click (track canvas)")); selection->clear (); + commit_reversible_selection_op(); _track_canvas->grab_focus(); return false; } @@ -1105,8 +1107,10 @@ Editor::canvas_drop_zone_event (GdkEvent* event) switch (event->type) { case GDK_BUTTON_RELEASE: if (event->button.button == 1) { + begin_reversible_selection_op (_("Nowhere Click")); selection->clear_objects (); selection->clear_tracks (); + commit_reversible_selection_op (); } break; diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 9cf0f05b60..aef59ed19e 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -4718,6 +4718,9 @@ NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *) } else { _region->unique_select (_primary); } + + _editor->begin_reversible_selection_op(_("Select Note Press")); + _editor->commit_reversible_selection_op(); } } } @@ -4799,14 +4802,17 @@ NoteDrag::finished (GdkEvent* ev, bool moved) { if (!moved) { /* no motion - select note */ - + if (_editor->current_mouse_mode() == Editing::MouseObject || _editor->current_mouse_mode() == Editing::MouseDraw) { - + + bool changed = false; + if (_was_selected) { bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier); if (add) { _region->note_deselected (_primary); + changed = true; } } else { bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier); @@ -4814,12 +4820,19 @@ NoteDrag::finished (GdkEvent* ev, bool moved) if (!extend && !add && _region->selection_size() > 1) { _region->unique_select (_primary); + changed = true; } else if (extend) { _region->note_selected (_primary, true, true); + changed = true; } else { /* it was added during button press */ } } + + if (changed) { + _editor->begin_reversible_selection_op(_("Select Note Release")); + _editor->commit_reversible_selection_op(); + } } } else { _region->note_dropped (_primary, total_dx(), total_dy()); @@ -5204,12 +5217,15 @@ EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, fram void EditorRubberbandSelectDrag::deselect_things () { - if (!getenv("ARDOUR_SAE")) { - _editor->selection->clear_tracks(); - } + _editor->begin_reversible_selection_op (_("Clear Selection (rubberband)")); + + _editor->selection->clear_tracks(); _editor->selection->clear_regions(); _editor->selection->clear_points (); _editor->selection->clear_lines (); + _editor->selection->clear_midi_notes (); + + _editor->commit_reversible_selection_op(); } NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv) diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index e093eb5426..b781832618 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -448,11 +448,15 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp Selection::Operation op = ArdourKeyboard::selection_type (event->button.state); bool press = (event->type == GDK_BUTTON_PRESS); + if (press) { + _mouse_changed_selection = false; + } + switch (item_type) { case RegionItem: if (press) { if (eff_mouse_mode != MouseRange) { - set_selected_regionview_from_click (press, op); + _mouse_changed_selection = set_selected_regionview_from_click (press, op); } else { /* don't change the selection unless the clicked track is not currently selected. if @@ -465,7 +469,7 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp } } else { if (eff_mouse_mode != MouseRange) { - set_selected_regionview_from_click (press, op); + _mouse_changed_selection = set_selected_regionview_from_click (press, op); } } break; @@ -483,7 +487,7 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp case StartCrossFadeItem: case EndCrossFadeItem: if (get_smart_mode() || eff_mouse_mode != MouseRange) { - set_selected_regionview_from_click (press, op); + _mouse_changed_selection = set_selected_regionview_from_click (press, op); } else if (event->type == GDK_BUTTON_PRESS) { set_selected_track_as_side_effect (op); } @@ -492,7 +496,7 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp case ControlPointItem: set_selected_track_as_side_effect (op); if (eff_mouse_mode != MouseRange) { - set_selected_control_point_from_click (press, op); + _mouse_changed_selection = set_selected_control_point_from_click (press, op); } break; @@ -501,6 +505,7 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp if (event->button.button == 3) { selection->clear_tracks (); set_selected_track_as_side_effect (op); + _mouse_changed_selection = true; } break; @@ -511,6 +516,12 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp default: break; } + + if ((!press) && _mouse_changed_selection) { + begin_reversible_selection_op (_("Button Selection")); + commit_reversible_selection_op (); + _mouse_changed_selection = false; + } } bool @@ -744,6 +755,7 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT case AutomationTrackItem: /* rubberband drag to select automation points */ _drags->set (new EditorRubberbandSelectDrag (this, item), event); + return true; break; default: @@ -1465,10 +1477,7 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT } /* do any (de)selection operations that should occur on button release */ - - begin_reversible_selection_op (_("Button Select")); - button_selection (item, event, item_type); - commit_reversible_selection_op (); + button_selection (item, event, item_type); return true; break; diff --git a/gtk2_ardour/editor_selection.cc b/gtk2_ardour/editor_selection.cc index 60f8b0433b..c46ef04a64 100644 --- a/gtk2_ardour/editor_selection.cc +++ b/gtk2_ardour/editor_selection.cc @@ -202,8 +202,9 @@ Editor::set_selected_track_as_side_effect (Selection::Operation op) } } else if (group && group->is_active()) { for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) { - if ((*i)->route_group() == group) + if ((*i)->route_group() == group) { selection->remove(*i); + } } } else { selection->remove (clicked_axisview); @@ -215,8 +216,9 @@ Editor::set_selected_track_as_side_effect (Selection::Operation op) } } else if (group && group->is_active()) { for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) { - if ( (*i)->route_group() == group) + if ((*i)->route_group() == group) { selection->add(*i); + } } } else { selection->add (clicked_axisview); @@ -234,8 +236,9 @@ Editor::set_selected_track_as_side_effect (Selection::Operation op) } } else if (group && group->is_active()) { for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) { - if ((*i)->route_group() == group) + if ((*i)->route_group() == group) { selection->add(*i); + } } } else { selection->add (clicked_axisview); @@ -253,8 +256,9 @@ Editor::set_selected_track_as_side_effect (Selection::Operation op) } } else if (group && group->is_active()) { for (TrackViewList::iterator i = track_views.begin(); i != track_views.end (); ++i) { - if ((*i)->route_group() == group) + if ((*i)->route_group() == group) { selection->add(*i); + } } } else { selection->set (clicked_axisview); @@ -1953,7 +1957,7 @@ Editor::get_edit_op_range (framepos_t& start, framepos_t& end) const void Editor::deselect_all () { - begin_reversible_selection_op(_("Clear Selection")); + begin_reversible_selection_op(_("Deselect All")); selection->clear (); commit_reversible_selection_op (); } diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index 97cb11529e..6e257cc0ee 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -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_ptrbutton; + _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), this), (GdkEvent *) ev); if (!Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { clear_selection (); + _mouse_changed_selection = true; } _mouse_state = SelectRectDragging; return true; @@ -747,22 +760,32 @@ MidiRegionView::key_press (GdkEventKey* ev) } else if (ev->keyval == GDK_Tab) { + 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)); } + + trackview.editor().commit_reversible_selection_op(); + 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)); } + + trackview.editor().commit_reversible_selection_op(); + return true; @@ -946,9 +969,11 @@ MidiRegionView::create_note_at (framepos_t t, double y, Evoral::Beats length, bo view->update_note_range(new_note->note()); + trackview.editor().begin_reversible_command(_("add note")); MidiModel::NoteDiffCommand* cmd = _model->new_note_diff_command(_("add note")); cmd->add (new_note); _model->apply_command(*trackview.session(), cmd); + trackview.editor().commit_reversible_command(); play_midi_note (new_note); } @@ -982,8 +1007,8 @@ MidiRegionView::display_model(boost::shared_ptr 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 +1019,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 +1070,7 @@ void MidiRegionView::apply_diff (bool as_subcommand) { bool add_or_remove; + bool commit = false; if (!_note_diff_command) { return; @@ -1060,6 +1087,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 +1098,9 @@ MidiRegionView::apply_diff (bool as_subcommand) } _marked_for_velocity.clear(); + if (commit) { + trackview.editor().commit_reversible_command (); + } } void @@ -1100,6 +1131,27 @@ MidiRegionView::find_canvas_note (boost::shared_ptr note) return 0; } +/** This version finds any canvas note matching the supplied note.*/ +NoteBase* +MidiRegionView::find_canvas_note (NoteType note) +{ + if (_optimization_iterator != _events.end()) { + ++_optimization_iterator; + } + + if (_optimization_iterator != _events.end() && (*(*_optimization_iterator)->note()) == note) { + return *_optimization_iterator; + } + + for (_optimization_iterator = _events.begin(); _optimization_iterator != _events.end(); ++_optimization_iterator) { + if (*((*_optimization_iterator)->note()) == note) { + return *_optimization_iterator; + } + } + + return 0; +} + void MidiRegionView::get_events (Events& e, Evoral::Sequence::NoteOperator op, uint8_t val, int chan_mask) { @@ -1173,6 +1225,13 @@ MidiRegionView::redisplay_model() add_note (note, visible); } + set >::iterator it; + for (it = _pending_note_selection.begin(); it != _pending_note_selection.end(); ++it) { + if (*(*it) == *note) { + add_to_selection (cne); + } + } + } else { if (!empty_when_starting && (cne = find_canvas_note (note)) != 0) { @@ -1182,7 +1241,6 @@ MidiRegionView::redisplay_model() } } - /* remove note items that are no longer valid */ if (!empty_when_starting) { @@ -1213,6 +1271,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 @@ -1909,7 +1968,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 +1982,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 +1991,9 @@ MidiRegionView::change_patch_change (PatchChange& pc, const MIDI::Name::PatchPri void MidiRegionView::change_patch_change (MidiModel::PatchChangePtr old_change, const Evoral::PatchChange & 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 +2012,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 +2027,10 @@ void MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChange const & patch) { MidiTimeAxisView* const mtv = dynamic_cast(&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 ( absolute_frames_to_source_beats (_region->position() + t), @@ -1973,6 +2040,7 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChangeapply_command (*trackview.session(), c); + trackview.editor().commit_reversible_command (); _patch_changes.clear (); display_patch_changes (); @@ -1981,9 +2049,11 @@ MidiRegionView::add_patch_change (framecnt_t t, Evoral::PatchChangenew_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 +2062,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 +2215,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 > notes) +{ + NoteBase* cne; + list >::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) { diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h index 4f64e4410f..6ffdf98c25 100644 --- a/gtk2_ardour/midi_region_view.h +++ b/gtk2_ardour/midi_region_view.h @@ -203,6 +203,7 @@ public: void move_selection(double dx, double dy, double cumulative_dy); void note_dropped (NoteBase* ev, ARDOUR::frameoffset_t, int8_t d_note); + void select_notes (std::list >); void select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend); void toggle_matching_notes (uint8_t notenum, uint16_t channel_mask); @@ -438,6 +439,9 @@ private: * when they appear after the command is applied. */ std::set< boost::shared_ptr > _marked_for_selection; + /** Notes that should be selected when the model is redisplayed. */ + std::set< boost::shared_ptr > _pending_note_selection; + /** New notes (created in the current command) which should have visible velocity * when they appear after the command is applied. */ std::set< boost::shared_ptr > _marked_for_velocity; @@ -448,6 +452,7 @@ private: PBD::ScopedConnection content_connection; NoteBase* find_canvas_note (boost::shared_ptr); + NoteBase* find_canvas_note (NoteType); Events::iterator _optimization_iterator; void update_note (NoteBase*, bool update_ghost_regions = true); @@ -499,6 +504,8 @@ private: bool _grabbed_keyboard; bool _entered; + bool _mouse_changed_selection; + framepos_t snap_frame_to_grid_underneath (framepos_t p, framecnt_t &) const; PBD::ScopedConnection _mouse_mode_connection; diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index 5d8dc83bc1..33d192aa1c 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -1308,6 +1308,8 @@ MidiTimeAxisView::set_note_selection (uint8_t note) { uint16_t chn_mask = midi_track()->get_playback_channel_mask(); + _editor.begin_reversible_selection_op(_("Set Note Selection")); + if (_view->num_selected_regionviews() == 0) { _view->foreach_regionview ( sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_note_selection_region_view), @@ -1317,6 +1319,8 @@ MidiTimeAxisView::set_note_selection (uint8_t note) sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::set_note_selection_region_view), note, chn_mask)); } + + _editor.commit_reversible_selection_op(); } void @@ -1324,6 +1328,8 @@ MidiTimeAxisView::add_note_selection (uint8_t note) { const uint16_t chn_mask = midi_track()->get_playback_channel_mask(); + _editor.begin_reversible_selection_op(_("Add Note Selection")); + if (_view->num_selected_regionviews() == 0) { _view->foreach_regionview ( sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), @@ -1333,6 +1339,8 @@ MidiTimeAxisView::add_note_selection (uint8_t note) sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::add_note_selection_region_view), note, chn_mask)); } + + _editor.commit_reversible_selection_op(); } void @@ -1340,6 +1348,8 @@ MidiTimeAxisView::extend_note_selection (uint8_t note) { const uint16_t chn_mask = midi_track()->get_playback_channel_mask(); + _editor.begin_reversible_selection_op(_("Extend Note Selection")); + if (_view->num_selected_regionviews() == 0) { _view->foreach_regionview ( sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), @@ -1349,6 +1359,8 @@ MidiTimeAxisView::extend_note_selection (uint8_t note) sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::extend_note_selection_region_view), note, chn_mask)); } + + _editor.commit_reversible_selection_op(); } void @@ -1356,6 +1368,8 @@ MidiTimeAxisView::toggle_note_selection (uint8_t note) { const uint16_t chn_mask = midi_track()->get_playback_channel_mask(); + _editor.begin_reversible_selection_op(_("Toggle Note Selection")); + if (_view->num_selected_regionviews() == 0) { _view->foreach_regionview ( sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), @@ -1365,6 +1379,15 @@ MidiTimeAxisView::toggle_note_selection (uint8_t note) sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::toggle_note_selection_region_view), note, chn_mask)); } + + _editor.commit_reversible_selection_op(); +} + +void +MidiTimeAxisView::get_per_region_note_selection (list > > > >& selection) +{ + _view->foreach_regionview ( + sigc::bind (sigc::mem_fun (*this, &MidiTimeAxisView::get_per_region_note_selection_region_view), sigc::ref(selection))); } void @@ -1391,6 +1414,24 @@ MidiTimeAxisView::toggle_note_selection_region_view (RegionView* rv, uint8_t not dynamic_cast(rv)->toggle_matching_notes (note, chn_mask); } +void +MidiTimeAxisView::get_per_region_note_selection_region_view (RegionView* rv, list > > > > &selection) +{ + Evoral::Sequence::Notes selected; + dynamic_cast(rv)->selection_as_notelist (selected, false); + + std::set > > notes; + + Evoral::Sequence::Notes::iterator sel_it; + for (sel_it = selected.begin(); sel_it != selected.end(); ++sel_it) { + notes.insert (*sel_it); + } + + if (!notes.empty()) { + selection.push_back (make_pair ((rv)->region()->id(), notes)); + } +} + void MidiTimeAxisView::set_channel_mode (ChannelMode, uint16_t) { diff --git a/gtk2_ardour/midi_time_axis.h b/gtk2_ardour/midi_time_axis.h index 1897174e11..9963ac420c 100644 --- a/gtk2_ardour/midi_time_axis.h +++ b/gtk2_ardour/midi_time_axis.h @@ -30,6 +30,8 @@ #include #include +#include "evoral/Note.hpp" + #include "ardour/types.h" #include "ardour/region.h" @@ -65,7 +67,7 @@ class MidiChannelSelectorWindow; class MidiTimeAxisView : public RouteTimeAxisView { - public: +public: MidiTimeAxisView (PublicEditor&, ARDOUR::Session*, ArdourCanvas::Canvas& canvas); virtual ~MidiTimeAxisView (); @@ -100,11 +102,13 @@ class MidiTimeAxisView : public RouteTimeAxisView uint8_t get_channel_for_add () const; - protected: + void get_per_region_note_selection (std::list > > > >&); + +protected: void start_step_editing (); void stop_step_editing (); - private: +private: sigc::signal _midi_patch_settings_changed; void model_changed(const std::string& model); @@ -165,6 +169,7 @@ class MidiTimeAxisView : public RouteTimeAxisView void add_note_selection_region_view (RegionView* rv, uint8_t note, uint16_t chn_mask); void extend_note_selection_region_view (RegionView*, uint8_t note, uint16_t chn_mask); void toggle_note_selection_region_view (RegionView*, uint8_t note, uint16_t chn_mask); + void get_per_region_note_selection_region_view (RegionView*, std::list > > > >&); void ensure_step_editor (); diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index 69a3b13fcd..b7263756a6 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -34,6 +34,7 @@ #include #include +#include "evoral/Note.hpp" #include "evoral/types.hpp" #include "pbd/statefuldestructible.h" @@ -391,6 +392,8 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible, publi virtual void stop_canvas_autoscroll () = 0; virtual bool autoscroll_active() const = 0; + virtual void begin_reversible_selection_op (std::string cmd_name) = 0; + virtual void commit_reversible_selection_op () = 0; virtual void begin_reversible_command (std::string cmd_name) = 0; virtual void begin_reversible_command (GQuark) = 0; virtual void commit_reversible_command () = 0; @@ -412,7 +415,8 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible, publi virtual void get_regions_at (RegionSelection &, framepos_t where, TrackViewList const &) const = 0; virtual RegionSelection get_regions_from_selection_and_mouse (framepos_t) = 0; - virtual void get_regionviews_by_id (PBD::ID const & id, RegionSelection & regions) const = 0; + virtual void get_regionviews_by_id (PBD::ID const id, RegionSelection & regions) const = 0; + virtual void get_per_region_note_selection (std::list > > > >&) const = 0; /// Singleton instance, set up by Editor::Editor() diff --git a/gtk2_ardour/selection.cc b/gtk2_ardour/selection.cc index 4a619e1b03..39ade0fadf 100644 --- a/gtk2_ardour/selection.cc +++ b/gtk2_ardour/selection.cc @@ -109,6 +109,7 @@ Selection::clear () clear_midi_notes (); clear_midi_regions (); clear_markers (); + pending_midi_note_selection.clear(); } void @@ -506,9 +507,9 @@ Selection::add (RegionView* r) if (find (regions.begin(), regions.end(), r) == regions.end()) { bool changed = regions.add (r); - if (changed) { - RegionsChanged (); - } + if (changed) { + RegionsChanged (); + } } } @@ -1229,12 +1230,45 @@ Selection::get_state () const for (RegionSelection::const_iterator i = regions.begin(); i != regions.end(); ++i) { XMLNode* r = node->add_child (X_("Region")); r->add_property (X_("id"), atoi ((*i)->region ()->id ().to_s ().c_str())); - + } + + /* midi region views have thir own internal selection. */ + XMLNode* n; + list > > > > rid_notes; + editor->get_per_region_note_selection (rid_notes); + if (!rid_notes.empty()) { + n = node->add_child (X_("MIDINote")); + } + list > > > >::iterator rn_it; + for (rn_it = rid_notes.begin(); rn_it != rid_notes.end(); ++rn_it) { + n->add_property (X_("region_id"), atoi((*rn_it).first.to_s().c_str())); + + for (std::set > >::iterator i = (*rn_it).second.begin(); i != (*rn_it).second.end(); ++i) { + XMLNode* nc = n->add_child(X_("note")); + snprintf(buf, sizeof(buf), "%d", (*i)->channel()); + nc->add_property(X_("channel"), string(buf)); + + snprintf(buf, sizeof(buf), "%f", (*i)->time().to_double()); + nc->add_property(X_("time"), string(buf)); + + snprintf(buf, sizeof(buf), "%d", (*i)->note()); + nc->add_property(X_("note"), string(buf)); + + snprintf(buf, sizeof(buf), "%f", (*i)->length().to_double()); + nc->add_property(X_("length"), string(buf)); + + snprintf(buf, sizeof(buf), "%d", (*i)->velocity()); + nc->add_property(X_("velocity"), string(buf)); + + snprintf(buf, sizeof(buf), "%d", (*i)->off_velocity()); + nc->add_property(X_("off-velocity"), string(buf)); + } } for (PointSelection::const_iterator i = points.begin(); i != points.end(); ++i) { AutomationTimeAxisView* atv = dynamic_cast (&(*i)->line().trackview); if (atv) { + XMLNode* r = node->add_child (X_("ControlPoint")); r->add_property (X_("type"), "track"); r->add_property (X_("route-id"), atoi (atv->parent_route()->id ().to_s ().c_str())); @@ -1243,6 +1277,7 @@ Selection::get_state () const snprintf(buf, sizeof(buf), "%d", (*i)->view_index()); r->add_property (X_("view-index"), string(buf)); + } } @@ -1304,44 +1339,106 @@ Selection::set_state (XMLNode const & node, int) add (rs); } else { /* - regionviews are being constructed - stash the region IDs + regionviews haven't been constructed - stash the region IDs so we can identify them in Editor::region_view_added () */ regions.pending.push_back (id); } + } else if ((*i)->name() == X_("MIDINote")) { + XMLProperty* prop_region_id = (*i)->property (X_("region-id")); + + assert (prop_region_id); + + PBD::ID const id (prop_region_id->value ()); + RegionSelection rs; + + editor->get_regionviews_by_id (id, rs); // there could be more than one + + std::list > > notes; + XMLNodeList children = (*i)->children (); + + for (XMLNodeList::const_iterator ci = children.begin(); ci != children.end(); ++ci) { + XMLProperty* prop_channel = (*ci)->property (X_("channel")); + XMLProperty* prop_time = (*ci)->property (X_("time")); + XMLProperty* prop_note = (*ci)->property (X_("note")); + XMLProperty* prop_length = (*ci)->property (X_("length")); + XMLProperty* prop_velocity = (*ci)->property (X_("velocity")); + XMLProperty* prop_off_velocity = (*ci)->property (X_("off-velocity")); + + assert (prop_channel); + assert (prop_time); + assert (prop_note); + assert (prop_length); + assert (prop_velocity); + assert (prop_off_velocity); + + uint8_t channel = atoi(prop_channel->value()); + Evoral::Beats time (atof(prop_time->value())); + Evoral::Beats length (atof(prop_length->value())); + uint8_t note = atoi(prop_note->value()); + uint8_t velocity = atoi(prop_velocity->value()); + uint8_t off_velocity = atoi(prop_off_velocity->value()); + boost::shared_ptr > the_note + (new Evoral::Note (channel, time, length, note, velocity)); + the_note->set_off_velocity (off_velocity); + + notes.push_back (the_note); + } + + for (RegionSelection::iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) { + MidiRegionView* mrv = dynamic_cast (*rsi); + if (mrv) { + mrv->select_notes(notes); + } + } + + if (rs.empty()) { + /* regionviews containing these notes don't yet exist on the canvas.*/ + pending_midi_note_selection.push_back (make_pair (id, notes)); + } + } else if ((*i)->name() == X_("ControlPoint")) { XMLProperty* prop_type = (*i)->property (X_("type")); - XMLProperty* prop_route_id = (*i)->property (X_("route-id")); - XMLProperty* prop_alist_id = (*i)->property (X_("automation-list-id")); - XMLProperty* prop_parameter = (*i)->property (X_("parameter")); - XMLProperty* prop_view_index = (*i)->property (X_("view-index")); - - assert (prop_type); - assert (prop_route_id); - assert (prop_alist_id); - assert (prop_parameter); - assert (prop_view_index); + + assert(prop_type); if (prop_type->value () == "track") { - PBD::ID id (prop_route_id->value ()); - RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (id); + + XMLProperty* prop_route_id = (*i)->property (X_("route-id")); + XMLProperty* prop_alist_id = (*i)->property (X_("automation-list-id")); + XMLProperty* prop_parameter = (*i)->property (X_("parameter")); + XMLProperty* prop_view_index = (*i)->property (X_("view-index")); + + assert (prop_type); + assert (prop_route_id); + assert (prop_alist_id); + assert (prop_parameter); + assert (prop_view_index); + + PBD::ID route_id (prop_route_id->value ()); + RouteTimeAxisView* rtv = editor->get_route_view_by_route_id (route_id); + vector cps; if (rtv) { boost::shared_ptr atv = rtv->automation_child (EventTypeMap::instance().from_symbol (prop_parameter->value ())); if (atv) { list > lines = atv->lines(); - for (list > ::iterator i = lines.begin(); i != lines.end(); ++i) { - if ((*i)->the_list()->id() == prop_alist_id->value()) { - ControlPoint* cp = (*i)->nth(atol(prop_view_index->value().c_str())); + for (list > ::iterator li = lines.begin(); li != lines.end(); ++li) { + if ((*li)->the_list()->id() == prop_alist_id->value()) { + ControlPoint* cp = (*li)->nth(atol(prop_view_index->value().c_str())); if (cp) { - add (cp); + cps.push_back (cp); + cp->show(); } } } } } - } + if (!cps.empty()) { + add (cps); + } + } } else if ((*i)->name() == X_("AudioRange")) { XMLProperty* prop_start = (*i)->property (X_("start")); diff --git a/gtk2_ardour/selection.h b/gtk2_ardour/selection.h index fe2be2e6e4..7585c0ae56 100644 --- a/gtk2_ardour/selection.h +++ b/gtk2_ardour/selection.h @@ -223,6 +223,8 @@ class Selection : public sigc::trackable, public PBD::ScopedConnectionList PBD::Signal0 ClearMidiNoteSelection; + std::list > > > > pending_midi_note_selection; + private: PublicEditor const * editor; uint32_t next_time_id; -- 2.30.2