Fix MIDI selection/tool issues (issue #0002415 and other bugs).

Fix selection preservation across MIDI model editing commands (for both note moving and resizing).
Fix selection breakage introduced by old selection preservation stuff (fix zombie selection).


git-svn-id: svn://localhost/ardour2/branches/3.0@3381 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2008-05-22 01:02:04 +00:00
parent 7a9b4a0aa2
commit 975d410930
4 changed files with 98 additions and 101 deletions

View File

@ -255,7 +255,9 @@ CanvasNoteEvent::on_event(GdkEvent* ev)
switch (_state) {
case Pressed: // Drag begin
if (_region.mouse_state() != MidiRegionView::SelectTouchDragging) {
if (_region.midi_view()->editor.current_midi_edit_mode() == Editing::MidiEditSelect
&& _region.mouse_state() != MidiRegionView::SelectTouchDragging
&& _region.mouse_state() != MidiRegionView::EraseTouchDragging) {
_item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
Gdk::Cursor(Gdk::FLEUR), ev->motion.time);
_state = Dragging;

View File

@ -196,13 +196,16 @@ MidiRegionView::canvas_event(GdkEvent* ev)
case GDK_BUTTON_PRESS:
if (_mouse_state != SelectTouchDragging &&
_mouse_state != EraseTouchDragging &&
ev->button.button == 1 ) {
ev->button.button == 1) {
_pressed_button = ev->button.button;
_mouse_state = Pressed;
return true;
}
_pressed_button = ev->button.button;
return false;
return true;
case GDK_2BUTTON_PRESS:
return true;
case GDK_ENTER_NOTIFY:
/* FIXME: do this on switch to note tool, too, if the pointer is already in */
@ -335,16 +338,15 @@ MidiRegionView::canvas_event(GdkEvent* ev)
break;
case MidiEditPencil:
create_note_at(event_x, event_y, _default_note_length);
default:
break;
default: break;
}
_mouse_state = None;
return true;
break;
case SelectRectDragging: // Select drag done
_mouse_state = None;
delete drag_rect;
drag_rect = NULL;
return true;
break;
case AddDragging: // Add drag done
_mouse_state = None;
if (drag_rect->property_x2() > drag_rect->property_x1() + 2) {
@ -357,13 +359,10 @@ MidiRegionView::canvas_event(GdkEvent* ev)
delete drag_rect;
drag_rect = NULL;
return true;
default:
break;
default: break;
}
default:
break;
default: break;
}
return false;
@ -409,8 +408,6 @@ MidiRegionView::create_note_at(double x, double y, double duration)
MidiModel::DeltaCommand* cmd = _model->new_delta_command("add note");
cmd->add(new_note);
_model->apply_command(cmd);
//add_note(new_note);
}
@ -442,6 +439,60 @@ MidiRegionView::display_model(boost::shared_ptr<MidiModel> model)
if (_enable_display)
redisplay_model();
}
void
MidiRegionView::start_delta_command(string name)
{
if (!_delta_command)
_delta_command = _model->new_delta_command(name);
}
void
MidiRegionView::command_add_note(const boost::shared_ptr<ARDOUR::Note> note, bool selected)
{
if (_delta_command)
_delta_command->add(note);
if (selected)
_marked_for_selection.insert(note);
}
void
MidiRegionView::command_remove_note(ArdourCanvas::CanvasNoteEvent* ev)
{
if (_delta_command && ev->note()) {
_delta_command->remove(ev->note());
}
}
void
MidiRegionView::apply_command()
{
if (!_delta_command) {
return;
}
// Mark all selected notes for selection when model reloads
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
_marked_for_selection.insert((*i)->note());
}
_model->apply_command(_delta_command);
_delta_command = NULL;
midi_view()->midi_track()->diskstream()->playlist_modified();
_marked_for_selection.clear();
}
void
MidiRegionView::abort_command()
{
delete _delta_command;
_delta_command = NULL;
clear_selection();
}
void
@ -730,26 +781,21 @@ MidiRegionView::add_note(const boost::shared_ptr<Note> note)
assert(midi_view()->note_mode() == Sustained || midi_view()->note_mode() == Percussive);
// dont display notes beyond the region bounds
if (
note->time() - _region->start() >= _region->length() ||
note->time() < _region->start() ||
note->note() < midi_stream_view()->lowest_note() ||
note->note() > midi_stream_view()->highest_note()
) {
if ( note->time() - _region->start() >= _region->length() ||
note->time() < _region->start() ||
note->note() < midi_stream_view()->lowest_note() ||
note->note() > midi_stream_view()->highest_note() ) {
return;
}
ArdourCanvas::Group* const group = (ArdourCanvas::Group*)get_canvas_group();
CanvasNoteEvent *event = 0;
CanvasNoteEvent* event = 0;
const double x = trackview.editor.frame_to_pixel((nframes_t)note->time() - _region->start());
if (midi_view()->note_mode() == Sustained) {
//cerr << "MRV::add_note sustained " << note->note() << " @ " << note->time()
// << " .. " << note->end_time() << endl;
const double y1 = midi_stream_view()->note_to_y(note->note());
const double note_endpixel =
trackview.editor.frame_to_pixel((nframes_t)note->end_time() - _region->start());
@ -817,8 +863,8 @@ MidiRegionView::add_note(const boost::shared_ptr<Note> note)
}
if (event) {
if (_marked_for_selection.find(event->note()) != _marked_for_selection.end()) {
note_selected(event, true);
if (_marked_for_selection.find(note) != _marked_for_selection.end()) {
note_selected(event, true);
}
event->on_channel_selection_change(_last_channel_selection);
}
@ -965,15 +1011,10 @@ MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
uint8_t highest_note_difference = 0;
// find highest and lowest notes first
for (Selection::iterator i = _selection.begin(); i != _selection.end() ; ) {
Selection::iterator next = i;
++next;
for (Selection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
uint8_t pitch = (*i)->note()->note();
lowest_note_in_selection = std::min(lowest_note_in_selection, pitch);
highest_note_in_selection = std::max(highest_note_in_selection, pitch);
i = next;
}
/*
@ -1031,21 +1072,20 @@ MidiRegionView::note_dropped(CanvasNoteEvent* ev, double dt, uint8_t dnote)
copy->set_note(new_pitch);
command_remove_note(*i);
command_add_note(copy);
command_add_note(copy, true);
_marked_for_selection.insert(copy);
i = next;
}
apply_command();
// care about notes being moved beyond the upper/lower bounds on the canvas
if (lowest_note_in_selection < midi_stream_view()->lowest_note() ||
highest_note_in_selection > midi_stream_view()->highest_note()
) {
highest_note_in_selection > midi_stream_view()->highest_note()) {
midi_stream_view()->set_note_range(MidiStreamView::ContentsRange);
}
}
}
}
nframes_t
MidiRegionView::snap_to_frame(double x)
@ -1109,7 +1149,7 @@ MidiRegionView::begin_resizing(CanvasNote::NoteEnd note_end)
note->y2());
// calculate the colors: get the color settings
uint fill_color =
uint32_t fill_color =
UINT_RGBA_CHANGE_A(
ARDOUR_UI::config()->canvasvar_MidiNoteSelectedOutline.get(),
128);
@ -1174,10 +1214,10 @@ MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bo
start_delta_command(_("resize notes"));
for (std::vector<NoteResizeData *>::iterator i = _resize_data.begin(); i != _resize_data.end(); ++i) {
CanvasNote *canvas_note = (*i)->canvas_note;
SimpleRect *resize_rect = (*i)->resize_rect;
double current_x = (*i)->current_x;
const double position = get_position_pixels();
CanvasNote* canvas_note = (*i)->canvas_note;
SimpleRect* resize_rect = (*i)->resize_rect;
double current_x = (*i)->current_x;
const double position = get_position_pixels();
if (!relative) {
// event_x is in track relative, transform it to region relative
@ -1196,16 +1236,13 @@ MidiRegionView::commit_resizing(CanvasNote::NoteEnd note_end, double event_x, bo
if (note_end == CanvasNote::NOTE_ON && current_frame < copy->end_time()) {
command_remove_note(canvas_note);
copy->on_event().time() = current_frame;
command_add_note(copy);
_marked_for_selection.insert(copy);
command_add_note(copy, _selection.find(canvas_note) != _selection.end());
}
// resize end of note
if (note_end == CanvasNote::NOTE_OFF && current_frame > copy->time()) {
command_remove_note(canvas_note);
command_remove_note(canvas_note);
copy->off_event().time() = current_frame;
command_add_note(copy);
_marked_for_selection.insert(copy);
command_add_note(copy, _selection.find(canvas_note) != _selection.end());
}
delete resize_rect;
@ -1238,17 +1275,11 @@ MidiRegionView::change_velocity(uint8_t velocity, bool relative)
}
command_remove_note(event);
command_add_note(copy);
command_add_note(copy, true);
_marked_for_selection.insert(copy);
i = next;
}
// dont keep notes selected if tweaking a single note
if (_marked_for_selection.size() == 1) {
_marked_for_selection.clear();
}
apply_command();
}
@ -1266,17 +1297,11 @@ MidiRegionView::change_channel(uint8_t channel)
copy->set_channel(channel);
command_remove_note(event);
command_add_note(copy);
command_add_note(copy, true);
_marked_for_selection.insert(copy);
i = next;
}
// dont keep notes selected if tweaking a single note
if (_marked_for_selection.size() == 1) {
_marked_for_selection.clear();
}
apply_command();
}

View File

@ -92,39 +92,12 @@ class MidiRegionView : public RegionView
void display_model(boost::shared_ptr<ARDOUR::MidiModel> model);
/* This stuff is a bit boilerplatey ATM. Work in progress. */
void start_delta_command(string name = "midi edit");
void command_add_note(const boost::shared_ptr<ARDOUR::Note> note, bool selected);
void command_remove_note(ArdourCanvas::CanvasNoteEvent* ev);
inline void start_delta_command(string name = "midi edit") {
if (!_delta_command)
_delta_command = _model->new_delta_command(name);
}
void command_add_note(const boost::shared_ptr<ARDOUR::Note> note) {
if (_delta_command)
_delta_command->add(note);
}
void command_remove_note(ArdourCanvas::CanvasNoteEvent* ev) {
if (_delta_command && ev->note()) {
_selection.erase(ev);
_delta_command->remove(ev->note());
ev->selected(true);
}
}
void abort_command() {
delete _delta_command;
_delta_command = NULL;
clear_selection();
}
void apply_command() {
if (_delta_command) {
_model->apply_command(_delta_command);
_delta_command = NULL;
}
midi_view()->midi_track()->diskstream()->playlist_modified();
}
void apply_command();
void abort_command();
void note_entered(ArdourCanvas::CanvasNoteEvent* ev);
void unique_select(ArdourCanvas::CanvasNoteEvent* ev);
@ -261,15 +234,12 @@ class MidiRegionView : public RegionView
MouseState _mouse_state;
int _pressed_button;
/// currently selected CanvasNoteEvents
typedef std::set<ArdourCanvas::CanvasNoteEvent*> Selection;
/// Currently selected CanvasNoteEvents
Selection _selection;
/**
* this enables vanilla notes to be marked for selection
* they are added to _selection when redisplay_model is called
* this is necessary for selecting notes during/after model manipulations
*/
/** New notes (created in the current command) which should be selected
* when they appear after the command is applied. */
std::set< boost::shared_ptr<ARDOUR::Note> > _marked_for_selection;
std::vector<NoteResizeData *> _resize_data;

View File

@ -655,11 +655,11 @@ void MidiModel::remove_note_unlocked(const boost::shared_ptr<const Note> note)
// TODO: There is still the issue, that after restarting ardour
// persisted undo does not work, because of rounding errors in the
// event times after saving/restoring to/from MIDI files
cerr << "======================================= " << endl;
/*cerr << "======================================= " << endl;
cerr << int(_n.note()) << "@" << int(_n.time()) << "[" << int(_n.channel()) << "] --" << int(_n.duration()) << "-- #" << int(_n.velocity()) << endl;
cerr << int(_note.note()) << "@" << int(_note.time()) << "[" << int(_note.channel()) << "] --" << int(_note.duration()) << "-- #" << int(_note.velocity()) << endl;
cerr << "Equal: " << bool(_n == _note) << endl;
cerr << endl << endl;
cerr << endl << endl;*/
if (_n == _note) {
_notes.erase(n);
// we have to break here, because erase invalidates all iterators, ie. n itself