diff --git a/gtk2_ardour/audio_region_view.cc b/gtk2_ardour/audio_region_view.cc index aa50627378..4f8f4ef9a0 100644 --- a/gtk2_ardour/audio_region_view.cc +++ b/gtk2_ardour/audio_region_view.cc @@ -63,6 +63,7 @@ #include "public_editor.h" #include "audio_region_editor.h" #include "audio_streamview.h" +#include "mergeable_line.h" #include "region_gain_line.h" #include "control_point.h" #include "ghostregion.h" @@ -276,6 +277,8 @@ AudioRegionView::init (bool wfd) setup_waveform_visibility (); + get_canvas_frame()->set_data ("linemerger", (LineMerger*) this); + /* XXX sync mark drag? */ } @@ -1850,3 +1853,9 @@ AudioRegionView::parameter_changed (string const & p) setup_waveform_visibility (); } } + +MergeableLine* +AudioRegionView::make_merger () +{ + return new MergeableLine (gain_line, std::shared_ptr(), boost::bind (&Region::absolute_time_to_region_time, _region, _1), nullptr, nullptr); +} diff --git a/gtk2_ardour/audio_region_view.h b/gtk2_ardour/audio_region_view.h index 903af2cf57..07b7265d64 100644 --- a/gtk2_ardour/audio_region_view.h +++ b/gtk2_ardour/audio_region_view.h @@ -38,6 +38,7 @@ #include "waveview/wave_view.h" +#include "line_merger.h" #include "region_view.h" #include "time_axis_view_item.h" #include "automation_line.h" @@ -54,7 +55,7 @@ class GhostRegion; class AutomationTimeAxisView; class RouteTimeAxisView; -class AudioRegionView : public RegionView +class AudioRegionView : public RegionView, public LineMerger { public: AudioRegionView (ArdourCanvas::Container *, @@ -147,6 +148,8 @@ public: return _end_xfade_visible; } + MergeableLine* make_merger(); + protected: /* this constructor allows derived types diff --git a/gtk2_ardour/automation_region_view.cc b/gtk2_ardour/automation_region_view.cc index c370bcccf5..a636a8cf78 100644 --- a/gtk2_ardour/automation_region_view.cc +++ b/gtk2_ardour/automation_region_view.cc @@ -38,6 +38,7 @@ #include "editor.h" #include "editor_drag.h" #include "gui_thread.h" +#include "mergeable_line.h" #include "midi_automation_line.h" #include "public_editor.h" #include "ui_config.h" @@ -88,6 +89,8 @@ AutomationRegionView::init (bool /*wfd*/) set_height (trackview.current_height()); set_colors (); + + get_canvas_frame()->set_data ("linemerger", (LineMerger*) this); } void @@ -132,43 +135,37 @@ AutomationRegionView::canvas_group_event (GdkEvent* ev) return false; } + return RegionView::canvas_group_event (ev); +} + +void +AutomationRegionView::add_automation_event (GdkEvent* ev) +{ + double x = ev->button.x; + double y = ev->button.y; + + /* convert to item coordinates in the time axis view */ + automation_view()->canvas_display()->canvas_to_item (x, y); + + /* clamp y */ + y = std::max (y, 0.0); + y = std::min (y, _height - NAME_HIGHLIGHT_SIZE); + + /* the time domain doesn't matter here, because the automation + * list will force the position to its own time domain when + * adding the point. + */ + PublicEditor& e = trackview.editor (); - if (ev->type == GDK_BUTTON_RELEASE && - ev->button.button == 1 && - (e.current_mouse_mode() == Editing::MouseDraw || e.current_mouse_mode() == Editing::MouseObject) && - !e.drags()->active()) { - - double x = ev->button.x; - double y = ev->button.y; - - /* convert to item coordinates in the time axis view */ - automation_view()->canvas_display()->canvas_to_item (x, y); - - /* clamp y */ - y = std::max (y, 0.0); - y = std::min (y, _height - NAME_HIGHLIGHT_SIZE); - - /* guard points only if primary modifier is used */ - bool with_guard_points = Gtkmm2ext::Keyboard::modifier_state_equals (ev->button.state, Gtkmm2ext::Keyboard::PrimaryModifier); - - /* the time domain doesn't matter here, because the automation - * list will force the position to its own time domain when - * adding the point. - */ - - add_automation_event (ev, timepos_t (e.pixel_to_sample (x)), y, with_guard_points); - return true; - } - - return RegionView::canvas_group_event (ev); + add_automation_event (timepos_t (e.pixel_to_sample (x)), y, false); } /** @param when Position is global time position * @param y y position, relative to our TimeAxisView. */ void -AutomationRegionView::add_automation_event (GdkEvent *, timepos_t const & w, double y, bool with_guard_points) +AutomationRegionView::add_automation_event (timepos_t const & w, double y, bool with_guard_points) { std::shared_ptr c = _region->control(_parameter, true); std::shared_ptr ac = std::dynamic_pointer_cast(c); @@ -352,3 +349,17 @@ AutomationRegionView::set_selected (bool yn) group->raise_to_top (); } } + +timepos_t +AutomationRegionView::drawn_time_filter (timepos_t const & t) +{ + return timepos_t (_region->absolute_time_to_source_beats (t)); +} + +MergeableLine* +AutomationRegionView::make_merger() +{ + std::shared_ptr c = _region->control(_parameter, true); + std::shared_ptr ac = std::dynamic_pointer_cast(c); + return new MergeableLine (_line, ac, boost::bind (&AutomationRegionView::drawn_time_filter, this, _1), nullptr, nullptr); +} diff --git a/gtk2_ardour/automation_region_view.h b/gtk2_ardour/automation_region_view.h index a357337b8d..c850ce93f8 100644 --- a/gtk2_ardour/automation_region_view.h +++ b/gtk2_ardour/automation_region_view.h @@ -27,6 +27,7 @@ #include "automation_time_axis.h" #include "automation_line.h" #include "enums.h" +#include "line_merger.h" namespace ARDOUR { class AutomationList; @@ -35,7 +36,7 @@ namespace ARDOUR { class TimeAxisView; -class AutomationRegionView : public RegionView +class AutomationRegionView : public RegionView, public LineMerger { public: AutomationRegionView(ArdourCanvas::Container*, @@ -72,12 +73,17 @@ public: void tempo_map_changed (); + MergeableLine* make_merger (); + + void add_automation_event (GdkEvent* event); + Temporal::timepos_t drawn_time_filter (Temporal::timepos_t const &); + protected: void create_line(std::shared_ptr list); bool set_position(Temporal::timepos_t const & pos, void* src, double* ignored); void region_resized (const PBD::PropertyChange&); bool canvas_group_event(GdkEvent* ev); - void add_automation_event (GdkEvent* event, Temporal::timepos_t const & when, double y, bool with_guard_points); + void add_automation_event (Temporal::timepos_t const & when, double y, bool with_guard_points); void mouse_mode_changed (); void entered(); void exited(); diff --git a/gtk2_ardour/automation_streamview.cc b/gtk2_ardour/automation_streamview.cc index dd50d1e894..dcb80b4a4f 100644 --- a/gtk2_ardour/automation_streamview.cc +++ b/gtk2_ardour/automation_streamview.cc @@ -79,11 +79,12 @@ AutomationStreamView::add_region_view_internal (std::shared_ptr region, return 0; } + std::shared_ptr list; + const std::shared_ptr control = std::dynamic_pointer_cast ( region->control (_automation_view.parameter(), true) ); - std::shared_ptr list; if (control) { list = std::dynamic_pointer_cast(control->list()); if (control->list() && !list) { diff --git a/gtk2_ardour/automation_time_axis.cc b/gtk2_ardour/automation_time_axis.cc index c64066461a..e2efa6c499 100644 --- a/gtk2_ardour/automation_time_axis.cc +++ b/gtk2_ardour/automation_time_axis.cc @@ -167,6 +167,7 @@ AutomationTimeAxisView::AutomationTimeAxisView ( _base_rect->set_outline (false); _base_rect->set_fill_color (UIConfiguration::instance().color_mod (fill_color_name, "automation track fill")); _base_rect->set_data ("trackview", this); + _base_rect->set_data ("linemerger", (LineMerger*) this); _base_rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_automation_track_event), _base_rect, this)); if (!a) { _base_rect->lower_to_bottom(); @@ -1285,6 +1286,6 @@ AutomationTimeAxisView::set_selected_regionviews (RegionSelection& rs) MergeableLine* AutomationTimeAxisView::make_merger () { - return new MergeableLine (_line, _control, boost::bind (&AutomationTimeAxisView::set_automation_state, this, _1), boost::bind (RouteTimeAxisView::signal_ctrl_touched, false)); + return new MergeableLine (_line, _control, nullptr, boost::bind (&AutomationTimeAxisView::set_automation_state, this, _1), boost::bind (RouteTimeAxisView::signal_ctrl_touched, false)); } diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 8c48a97ea3..3c0a0f76b7 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -7329,10 +7329,20 @@ FreehandLineDrag::maybe_add_point (GdkEvent* ev, ArdourCanvas::Rect r = base_rect.item_to_canvas (base_rect.get()); + /* Adjust event coordinates to be relative to the base rectangle */ + double x = pointer_x - r.x0; double y = ev->motion.y - r.y0; - x = std::max (0., x); + if (x < 0) { + dragging_line->clear (); + drawn_points.clear (); + edge_x = 0; + return; + } + + /* Clamp y coordinate to the area of the base rect */ + y = std::max (0., std::min (r.height(), y)); bool add_point = false; @@ -7341,7 +7351,7 @@ FreehandLineDrag::maybe_add_point (GdkEvent* ev, const bool line = Keyboard::modifier_state_equals (ev->motion.state, Keyboard::PrimaryModifier); if (direction > 0) { - if (line || (pointer_x > edge_x) || (pointer_x == edge_x && ev->motion.y != last_pointer_y())) { + if (x < r.width() && (line || (pointer_x > edge_x) || (pointer_x == edge_x && ev->motion.y != last_pointer_y()))) { if (line && dragging_line->get().size() > 1) { pop_point = true; @@ -7352,7 +7362,7 @@ FreehandLineDrag::maybe_add_point (GdkEvent* ev, } else if (direction < 0) { - if (line || (pointer_x < edge_x) || (pointer_x == edge_x && ev->motion.y != last_pointer_y())) { + if (x >= 0. && (line || (pointer_x < edge_x) || (pointer_x == edge_x && ev->motion.y != last_pointer_y()))) { if (line && dragging_line->get().size() > 1) { pop_point = true; @@ -7461,15 +7471,15 @@ AutomationDrawDrag::finished (GdkEvent* event, bool motion_occured) return; } - AutomationTimeAxisView* atv = static_cast(base_rect.get_data ("trackview")); + LineMerger* lm = static_cast(base_rect.get_data ("linemerger")); - if (!atv) { + if (!lm) { return; } FreehandLineDrag::finished (event, motion_occured); - MergeableLine* ml = atv->make_merger (); + MergeableLine* ml = lm->make_merger(); ml->merge_drawn_line (*_editor, *_editor->session(), drawn_points, !did_snap); delete ml; } diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index 8e7e3a7bf8..2be02cc396 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -1388,7 +1388,7 @@ Editor::button_press_handler_1 (ArdourCanvas::Item* item, GdkEvent* event, ItemT case RegionItem: { RegionView* rv; if ((rv = dynamic_cast (clicked_regionview))) { - ArdourCanvas::Rectangle* r = static_cast (rv->get_canvas_frame()); + ArdourCanvas::Rectangle* r = dynamic_cast (rv->get_canvas_frame()); _drags->set (new AutomationDrawDrag (this, *r, Temporal::AudioTime), event); } } @@ -1925,10 +1925,20 @@ Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemT points when doing this. */ AudioRegionView* arv = dynamic_cast (clicked_regionview); - if (!were_dragging && arv) { - bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier); - arv->add_gain_point_event (item, event, with_guard_points); + + if (!were_dragging) { + if (arv) { + bool with_guard_points = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier); + arv->add_gain_point_event (item, event, with_guard_points); + } + } else { + AutomationRegionView* atv = dynamic_cast (clicked_regionview); + + if (atv) { + atv->add_automation_event (event); + } } + return true; break; } @@ -3116,7 +3126,7 @@ Editor::choose_mapping_drag (ArdourCanvas::Item* item, GdkEvent* event) if (after_after) { after = after_after; } else { - at_end = true; + at_end = true; } } else if (ramped) { diff --git a/gtk2_ardour/mergeable_line.cc b/gtk2_ardour/mergeable_line.cc new file mode 100644 index 0000000000..de29da4d8a --- /dev/null +++ b/gtk2_ardour/mergeable_line.cc @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "ardour/session.h" + +#include "automation_line.h" +#include "editor.h" +#include "mergeable_line.h" +#include "route_time_axis.h" +#include "selectable.h" +#include "ui_config.h" + +#include "pbd/i18n.h" + +using namespace ARDOUR; + +void +MergeableLine::merge_drawn_line (Editor& e, Session& s, Evoral::ControlList::OrderedPoints& points, bool thin) +{ + if (points.empty()) { + return; + } + + if (!_line) { + return; + } + + std::shared_ptr list = _line->the_list (); + + if (list->in_write_pass()) { + /* do not allow the GUI to add automation events during an + automation write pass. + */ + return; + } + + XMLNode& before = list->get_state(); + std::list results; + + /* If necessary convert all point times. This is necessary + for region-based automation data, because the time values for the + points drawn are in absolute time, but the ControlList expects data + in source-reference time. + */ + + if (time_filter) { + for (auto & p : points) { + p.when = time_filter (p.when); + } + } + + Temporal::timepos_t earliest = points.front().when; + Temporal::timepos_t latest = points.back().when; + + if (earliest > latest) { + std::swap (earliest, latest); + } + + /* Convert each point's "value" from geometric coordinate space to + * value space for the control + */ + + for (auto & dp : points) { + /* compute vertical fractional position */ + dp.value = 1.0 - (dp.value / _line->height()); + /* map using line */ + _line->view_to_model_coord_y (dp.value); + } + + list->freeze (); + list->editor_add_ordered (points, false); + if (thin) { + list->thin (Config->get_automation_thinning_factor()); + } + list->thaw (); + + if (_control && _control->automation_state () == ARDOUR::Off) { + automation_state_callback (ARDOUR::Play); + } + + if (UIConfiguration::instance().get_automation_edit_cancels_auto_hide () && _control == s.recently_touched_controllable ()) { + control_touched_callback (); + } + + XMLNode& after = list->get_state(); + e.begin_reversible_command (_("draw automation")); + s.add_command (new MementoCommand (*list.get (), &before, &after)); + + _line->get_selectables (earliest, latest, 0.0, 1.0, results); + e.get_selection ().set (results); + + e.commit_reversible_command (); + s.set_dirty (); +} diff --git a/gtk2_ardour/mergeable_line.h b/gtk2_ardour/mergeable_line.h new file mode 100644 index 0000000000..7f3c76f065 --- /dev/null +++ b/gtk2_ardour/mergeable_line.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __gtk2_ardour_mergeable_line__ +#define __gtk2_ardour_mergeable_line__ + +#include +#include + +#include "evoral/ControlList.h" + +#include "ardour/types.h" + +class AutomationLine; +class RouteTimeAxisView; +class Editor; + +namespace ARDOUR { + class Session; +class AutomationControl; +} + +class MergeableLine +{ + public: + MergeableLine (std::shared_ptr l, std::shared_ptr c, + std::function tf, + std::function asc, + std::function ctc) + : _line (l) + , _control (c) + , time_filter (tf) + , automation_state_callback (asc) + , control_touched_callback (ctc) {} + + virtual ~MergeableLine() {} + + void merge_drawn_line (Editor& e, ARDOUR::Session& s, Evoral::ControlList::OrderedPoints& points, bool thin); + + private: + std::shared_ptr _line; + std::shared_ptr _control; + std::function time_filter; + std::function automation_state_callback; + std::function control_touched_callback; +}; + +#endif /* __gtk2_ardour_mergeable_line__ */ diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index e69687c38b..9a21247ee6 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -1492,7 +1492,6 @@ MidiTimeAxisView::create_automation_child (const Evoral::Parameter& param, bool _route->describe_parameter(param))); if (_view) { - std::cerr << "Adding ghosts of each MIDI region\n"; _view->foreach_regionview (sigc::mem_fun (*track.get(), &TimeAxisView::add_ghost)); }