Make control point selection more consistent.

- disallow simultaneous events via ControlList::editor_add ()
	- clicking on an automation line selects the points that define it.
	- don't 'flash' a region selection when using mousedraw mode.
	- cp click selection resembles region selection.
	- region gain points respect snap modifier (a la automation points).
This commit is contained in:
nick_m 2015-09-14 05:24:28 +10:00
parent 03df442d0e
commit 17294ab9ec
9 changed files with 224 additions and 116 deletions

View File

@ -1334,25 +1334,40 @@ AudioRegionView::add_gain_point_event (ArdourCanvas::Item *item, GdkEvent *ev, b
gain_line->view_to_model_coord (x, y);
trackview.editor ().snap_to_with_modifier (fx, ev);
/* XXX STATEFUL: can't convert to stateful diff until we
can represent automation data with it.
*/
trackview.editor().begin_reversible_command (_("add gain control point"));
XMLNode &before = audio_region()->envelope()->get_state();
MementoCommand<AudioRegion>* region_memento = 0;
if (!audio_region()->envelope_active()) {
XMLNode &region_before = audio_region()->get_state();
audio_region()->set_envelope_active(true);
XMLNode &region_after = audio_region()->get_state();
trackview.session()->add_command (new MementoCommand<AudioRegion>(*(audio_region().get()), &region_before, &region_after));
region_memento = new MementoCommand<AudioRegion>(*(audio_region().get()), &region_before, &region_after);
}
audio_region()->envelope()->editor_add (fx, y, with_guard_points);
if (audio_region()->envelope()->editor_add (fx, y, with_guard_points)) {
XMLNode &after = audio_region()->envelope()->get_state();
std::list<Selectable*> results;
XMLNode &after = audio_region()->envelope()->get_state();
trackview.session()->add_command (new MementoCommand<AutomationList>(*audio_region()->envelope().get(), &before, &after));
trackview.editor().commit_reversible_command ();
trackview.editor().begin_reversible_command (_("add gain control point"));
if (region_memento) {
trackview.session()->add_command (region_memento);
}
trackview.session()->add_command (new MementoCommand<AutomationList>(*audio_region()->envelope().get(), &before, &after));
gain_line->get_selectables (fx, fx, 0.0, 1.0, results);
trackview.editor ().get_selection ().set (results);
trackview.editor ().commit_reversible_command ();
trackview.session ()->set_dirty ();
}
}
void

View File

@ -187,17 +187,18 @@ AutomationRegionView::add_automation_event (GdkEvent *, framepos_t when, double
double when_d = when;
_line->view_to_model_coord (when_d, y);
view->editor().begin_reversible_command (_("add automation event"));
XMLNode& before = _line->the_list()->get_state();
_line->the_list()->editor_add (when_d, y, with_guard_points);
if (_line->the_list()->editor_add (when_d, y, with_guard_points)) {
view->editor().begin_reversible_command (_("add automation event"));
XMLNode& after = _line->the_list()->get_state();
XMLNode& after = _line->the_list()->get_state();
view->session()->add_command (new MementoCommand<ARDOUR::AutomationList> (_line->memento_command_binder(), &before, &after));
view->editor().commit_reversible_command ();
view->session()->add_command (new MementoCommand<ARDOUR::AutomationList> (_line->memento_command_binder(), &before, &after));
view->editor().commit_reversible_command ();
view->session()->set_dirty ();
view->session()->set_dirty ();
}
}
bool

View File

@ -638,18 +638,21 @@ AutomationTimeAxisView::add_automation_event (GdkEvent* event, framepos_t when,
_line->view_to_model_coord (x, y);
_editor.snap_to_with_modifier (when, event);
_editor.begin_reversible_command (_("add automation event"));
XMLNode& before = list->get_state();
std::list<Selectable*> results;
if (list->editor_add (when, y, with_guard_points)) {
XMLNode& after = list->get_state();
_editor.begin_reversible_command (_("add automation event"));
_session->add_command (new MementoCommand<ARDOUR::AutomationList> (*list.get (), &before, &after));
list->editor_add (when, y, with_guard_points);
_line->get_selectables (when, when, 0.0, 1.0, results);
_editor.get_selection ().set (results);
XMLNode& after = list->get_state();
_session->add_command (new MementoCommand<ARDOUR::AutomationList> (*list.get (), &before, &after));
_editor.commit_reversible_command ();
_session->set_dirty ();
_editor.commit_reversible_command ();
_session->set_dirty ();
}
}
bool

View File

@ -5557,92 +5557,6 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
}
}
} else {
for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
framecnt_t const half = (i->start + i->end) / 2;
/* find the line that this audio range starts in */
list<Line>::iterator j = _lines.begin();
while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
++j;
}
if (j != _lines.end()) {
boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
/* j is the line that this audio range starts in; fade into it;
64 samples length plucked out of thin air.
*/
framepos_t a = i->start + 64;
if (a > half) {
a = half;
}
double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
the_list->editor_add (p, value (the_list, p), false);
the_list->editor_add (q, value (the_list, q), false);
}
/* same thing for the end */
j = _lines.begin();
while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
++j;
}
if (j != _lines.end()) {
boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
/* j is the line that this audio range starts in; fade out of it;
64 samples length plucked out of thin air.
*/
framepos_t b = i->end - 64;
if (b < half) {
b = half;
}
double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
the_list->editor_add (p, value (the_list, p), false);
the_list->editor_add (q, value (the_list, q), false);
}
}
_nothing_to_drag = true;
/* Find all the points that should be dragged and put them in the relevant
points lists in the Line structs.
*/
for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
uint32_t const N = i->line->npoints ();
for (uint32_t j = 0; j < N; ++j) {
/* here's a control point on this line */
ControlPoint* p = i->line->nth (j);
double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
/* see if it's inside a range */
list<AudioRange>::const_iterator k = _ranges.begin ();
while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
++k;
}
if (k != _ranges.end()) {
/* dragging this point */
_nothing_to_drag = false;
i->points.push_back (p);
}
}
}
}
if (_nothing_to_drag) {
@ -5653,12 +5567,113 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
void
AutomationRangeDrag::motion (GdkEvent*, bool first_move)
{
if (_nothing_to_drag) {
if (_nothing_to_drag && !first_move) {
return;
}
if (first_move) {
_editor->begin_reversible_command (_("automation range move"));
if (!_ranges.empty()) {
for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
framecnt_t const half = (i->start + i->end) / 2;
/* find the line that this audio range starts in */
list<Line>::iterator j = _lines.begin();
while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
++j;
}
if (j != _lines.end()) {
boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
/* j is the line that this audio range starts in; fade into it;
64 samples length plucked out of thin air.
*/
framepos_t a = i->start + 64;
if (a > half) {
a = half;
}
double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
XMLNode &before = the_list->get_state();
bool const add_p = the_list->editor_add (p, value (the_list, p), false);
bool const add_q = the_list->editor_add (q, value (the_list, q), false);
if (add_p || add_q) {
_editor->session()->add_command (
new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
}
}
/* same thing for the end */
j = _lines.begin();
while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
++j;
}
if (j != _lines.end()) {
boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
/* j is the line that this audio range starts in; fade out of it;
64 samples length plucked out of thin air.
*/
framepos_t b = i->end - 64;
if (b < half) {
b = half;
}
double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
XMLNode &before = the_list->get_state();
bool const add_p = the_list->editor_add (p, value (the_list, p), false);
bool const add_q = the_list->editor_add (q, value (the_list, q), false);
if (add_p || add_q) {
_editor->session()->add_command (
new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
}
}
}
_nothing_to_drag = true;
/* Find all the points that should be dragged and put them in the relevant
points lists in the Line structs.
*/
for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
uint32_t const N = i->line->npoints ();
for (uint32_t j = 0; j < N; ++j) {
/* here's a control point on this line */
ControlPoint* p = i->line->nth (j);
double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
/* see if it's inside a range */
list<AudioRange>::const_iterator k = _ranges.begin ();
while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
++k;
}
if (k != _ranges.end()) {
/* dragging this point */
_nothing_to_drag = false;
i->points.push_back (p);
}
}
}
}
for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
}

View File

@ -399,8 +399,9 @@ Editor::step_mouse_mode (bool next)
}
void
Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemType item_type)
Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
{
/* in object/audition/timefx/gain-automation mode,
any button press sets the selection if the object
can be selected. this is a bit of hack, because
@ -472,6 +473,9 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
switch (item_type) {
case RegionItem:
if (eff_mouse_mode == MouseDraw) {
break;
}
if (press) {
if (eff_mouse_mode != MouseRange) {
_mouse_changed_selection = set_selected_regionview_from_click (press, op);
@ -519,6 +523,51 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
}
break;
case GainLineItem:
case AutomationLineItem:
if (eff_mouse_mode != MouseRange) {
AutomationLine* al;
std::list<Selectable*> selectables;
uint32_t before, after;
framecnt_t const where = (framecnt_t) floor (event->button.x * samples_per_pixel);
if ((al = reinterpret_cast<AutomationLine*> (item->get_data ("line")))) {
if (!al->control_points_adjacent (where, before, after)) {
break;
}
}
selectables.push_back (al->nth (before));
selectables.push_back (al->nth (after));
switch (op) {
case Selection::Set:
if (press) {
selection->set (selectables);
_mouse_changed_selection = true;
}
break;
case Selection::Add:
if (press) {
selection->add (selectables);
_mouse_changed_selection = true;
}
break;
case Selection::Toggle:
if (press) {
selection->toggle (selectables);
_mouse_changed_selection = true;
}
break;
case Selection::Extend:
/* XXX */
break;
}
}
break;
case StreamItem:
/* for context click, select track */
if (event->button.button == 3) {
@ -532,7 +581,9 @@ Editor::button_selection (ArdourCanvas::Item* /*item*/, GdkEvent* event, ItemTyp
break;
case AutomationTrackItem:
set_selected_track_as_side_effect (op);
if (eff_mouse_mode != MouseDraw) {
set_selected_track_as_side_effect (op);
}
break;
default:

View File

@ -327,15 +327,27 @@ Editor::set_selected_control_point_from_click (bool press, Selection::Operation
if (!clicked_control_point) {
return false;
}
bool ret = false;
switch (op) {
case Selection::Set:
if (press) {
if (!selection->selected (clicked_control_point)) {
selection->set (clicked_control_point);
ret = true;
} else {
/* clicked on an already selected point */
if (press) {
break;
} else {
if (selection->points.size() > 1) {
selection->set (clicked_control_point);
ret = true;
}
}
}
break;
case Selection::Add:
if (press) {
selection->add (clicked_control_point);

View File

@ -1113,7 +1113,7 @@ Selection::set (ControlPoint* cp)
clear_time (); //enforce region/object exclusivity
clear_tracks(); //enforce object/track exclusivity
if (cp->get_selected()) {
if (cp->get_selected () && points.size () == 1) {
return;
}

View File

@ -125,7 +125,8 @@ public:
void shift (double before, double distance);
virtual void add (double when, double value, bool with_guards=true, bool with_initial=true);
virtual void editor_add (double when, double value, bool with_guard);
virtual bool editor_add (double when, double value, bool with_guard);
void fast_simple_add (double when, double value);

View File

@ -451,12 +451,19 @@ ControlList::in_write_pass () const
return _in_write_pass;
}
void
bool
ControlList::editor_add (double when, double value, bool with_guard)
{
/* this is for making changes from a graphical line editor
*/
ControlEvent cp (when, 0.0f);
iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
if (i != _events.end () && (*i)->when == when) {
return false;
}
if (_events.empty()) {
/* as long as the point we're adding is not at zero,
@ -477,15 +484,18 @@ ControlList::editor_add (double when, double value, bool with_guard)
maybe_add_insert_guard (when);
}
ControlEvent cp (when, 0.0f);
iterator i = lower_bound (_events.begin(), _events.end(), &cp, time_comparator);
iterator result;
DEBUG_TRACE (DEBUG::ControlList, string_compose ("editor_add: actually add when= %1 value= %2\n", when, value));
_events.insert (i, new ControlEvent (when, value));
result = _events.insert (i, new ControlEvent (when, value));
if (i == result) {
return false;
}
mark_dirty ();
maybe_signal_changed ();
return true;
}
void