Make control point selection more consistent.
authornick_m <mainsbridge@gmail.com>
Sun, 13 Sep 2015 19:24:28 +0000 (05:24 +1000)
committernick_m <mainsbridge@gmail.com>
Mon, 19 Oct 2015 13:53:27 +0000 (00:53 +1100)
- 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).

gtk2_ardour/audio_region_view.cc
gtk2_ardour/automation_region_view.cc
gtk2_ardour/automation_time_axis.cc
gtk2_ardour/editor_drag.cc
gtk2_ardour/editor_mouse.cc
gtk2_ardour/editor_selection.cc
gtk2_ardour/selection.cc
libs/evoral/evoral/ControlList.hpp
libs/evoral/src/ControlList.cpp

index 706611a81d23e8a36b59f4c709184ede7a7ca391..dd1552901994c102fd03cf6a86a8cf216f485467 100644 (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;
+
+               trackview.editor().begin_reversible_command (_("add gain control point"));
+
+               if (region_memento) {
+                       trackview.session()->add_command (region_memento);
+               }
 
-       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.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
index 54508153295b3c764ed93b32aab51d98c5409f11..f1f9d68b987f8af60b3df5c067dea5cb9e643bf8 100644 (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
index 1c79593b3444b27441cf80fc21e5a6c6170c2c4c..1ff3024ac6164881f85bf84bb36fd03181c0616e 100644 (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
index 90e4dc48e4ae82247c6418f55e9ce7640f757b0e..d3e40adf122d5594a0c86e65b5056bd65650768f 100644 (file)
@@ -5557,108 +5557,123 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
                        }
                }
 
-       } else {
+       }
 
-               for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
+       if (_nothing_to_drag) {
+               return;
+       }
+}
 
-                       framecnt_t const half = (i->start + i->end) / 2;
+void
+AutomationRangeDrag::motion (GdkEvent*, bool first_move)
+{
+       if (_nothing_to_drag && !first_move) {
+               return;
+       }
 
-                       /* 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 (first_move) {
+               _editor->begin_reversible_command (_("automation range move"));
+
+               if (!_ranges.empty()) {
 
-                       if (j != _lines.end()) {
-                               boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
+                       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;
-                               }
+                                       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 ());
+                                       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);
-                       }
+                                       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);
 
-                       /* same thing for the end */
+                                       if (add_p || add_q) {
+                                               _editor->session()->add_command (
+                                                       new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
+                                       }
+                               }
 
-                       j = _lines.begin();
-                       while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
-                               ++j;
-                       }
+                               /* same thing for the end */
 
-                       if (j != _lines.end()) {
-                               boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
+                               j = _lines.begin();
+                               while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
+                                       ++j;
+                               }
 
-                               /* j is the line that this audio range starts in; fade out of it;
-                                  64 samples length plucked out of thin air.
-                               */
+                               if (j != _lines.end()) {
+                                       boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
 
-                               framepos_t b = i->end - 64;
-                               if (b < half) {
-                                       b = half;
-                               }
+                                       /* j is the line that this audio range starts in; fade out of it;
+                                          64 samples length plucked out of thin air.
+                                       */
 
-                               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 ());
+                                       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);
+                                       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;
+                       _nothing_to_drag = true;
 
-               /* Find all the points that should be dragged and put them in the relevant
-                  points lists in the Line structs.
-               */
+                       /* 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) {
+                       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) {
+                               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 ();
+                                       /* 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;
-                               }
+                                       /* 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 (k != _ranges.end()) {
+                                               /* dragging this point */
+                                               _nothing_to_drag = false;
+                                               i->points.push_back (p);
+                                       }
                                }
                        }
                }
-       }
 
-       if (_nothing_to_drag) {
-               return;
-       }
-}
-
-void
-AutomationRangeDrag::motion (GdkEvent*, bool first_move)
-{
-       if (_nothing_to_drag) {
-               return;
-       }
-
-       if (first_move) {
-               _editor->begin_reversible_command (_("automation range move"));
                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);
                }
index ccf63839e2cf369a2491ce5af46638c3cf458001..b4e4cc81af055d240cae425af3c3db4dab4d2691 100644 (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:
index 3a6277ececf16b2d4d19df1435caa481f196b08b..27e17d5fcca7f5525c80a23833cf78ac483ef28e 100644 (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);
index 784f646f39a651583ceb2eff43eef0c1d5200efd..822aca03847b52e628181f204e15d2234a30dc8a 100644 (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;
        }
 
index 3784b2572054331806376a1e0b7fe05ebdf49e19..80096b65a43d093afbdfa15039134ff66ea6b56b 100644 (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);
 
index d8665d339631a65bf2f6af7bbe04cf669ce17df5..d9c1b993bd69e5174c97c05a0faa2c92289be4f5 100644 (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));
 
-       mark_dirty ();
+       if (i == result) {
+               return false;
+       }
 
+       mark_dirty ();
        maybe_signal_changed ();
 
+       return true;
 }
 
 void