From 7be70d658bf0e24cc7248dc02be09f1b6cc178e3 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 17 Jul 2025 22:38:05 -0600 Subject: [PATCH] Revert "refactor pianoroll/cueeditor/audioclipeditor to share code and do the right stuff (compile success stage)" This reverts commit 01beb00a5f7846002df43719814ccde9afe262fb. --- gtk2_ardour/ardour_ui.cc | 15 +- gtk2_ardour/ardour_ui_dependents.cc | 1 + gtk2_ardour/audio_clip_editor.cc | 523 ++++++++------ gtk2_ardour/audio_clip_editor.h | 179 +++-- gtk2_ardour/audio_trigger_properties_box.cc | 24 +- gtk2_ardour/audio_trigger_properties_box.h | 20 +- gtk2_ardour/cue_editor.cc | 748 +------------------- gtk2_ardour/cue_editor.h | 94 +-- gtk2_ardour/editing_context.h | 4 +- gtk2_ardour/editor.h | 5 + gtk2_ardour/midi_trigger_properties_box.h | 2 +- gtk2_ardour/pianoroll.cc | 715 ++++++++++++++++++- gtk2_ardour/pianoroll.h | 81 ++- gtk2_ardour/public_editor.h | 2 + gtk2_ardour/trigger_page.cc | 33 +- gtk2_ardour/trigger_page.h | 5 +- gtk2_ardour/trigger_properties_box.h | 42 -- libs/ardour/ardour/triggerbox.h | 12 +- libs/ardour/session_transport.cc | 13 - libs/ardour/triggerbox.cc | 59 +- 20 files changed, 1238 insertions(+), 1339 deletions(-) delete mode 100644 gtk2_ardour/trigger_properties_box.h diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index ea6c5fc3b2..8725f6fba3 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -1852,6 +1852,17 @@ ARDOUR_UI::get_smart_mode() const void ARDOUR_UI::spacebar_action (bool with_abort, bool roll_out_of_bounded_mode) { + if (!_session) { + return; + } + + std::shared_ptr armed_tb = _session->armed_triggerbox(); + + if (armed_tb && _session->transport_rolling()) { + armed_tb->disarm_all (); + return; + } + toggle_roll (with_abort, roll_out_of_bounded_mode); } @@ -1881,10 +1892,7 @@ ARDOUR_UI::toggle_roll (bool with_abort, bool roll_out_of_bounded_mode) if (rolling) { - std::cerr << "TR rolling\n"; - if (roll_out_of_bounded_mode) { - std::cerr << "roobm\n"; /* drop out of loop/range playback but leave transport rolling */ if (_session->get_play_loop()) { @@ -1906,7 +1914,6 @@ ARDOUR_UI::toggle_roll (bool with_abort, bool roll_out_of_bounded_mode) } } else { - std::cerr << "stip\n"; _session->request_stop (with_abort, true); } diff --git a/gtk2_ardour/ardour_ui_dependents.cc b/gtk2_ardour/ardour_ui_dependents.cc index 979d4c7110..7479d4414d 100644 --- a/gtk2_ardour/ardour_ui_dependents.cc +++ b/gtk2_ardour/ardour_ui_dependents.cc @@ -88,6 +88,7 @@ ARDOUR_UI::we_have_dependents () mixer->monitor_section().use_others_actions (); StepEntry::setup_actions_and_bindings (); + ClipEditorBox::init (); RegionEditor::setup_actions_and_bindings (); setup_action_tooltips (); diff --git a/gtk2_ardour/audio_clip_editor.cc b/gtk2_ardour/audio_clip_editor.cc index c4be5d8bb6..ce6c4217db 100644 --- a/gtk2_ardour/audio_clip_editor.cc +++ b/gtk2_ardour/audio_clip_editor.cc @@ -41,15 +41,12 @@ #include "widgets/ardour_button.h" #include "widgets/ardour_icon.h" -#include "ardour_ui.h" #include "audio_clip_editor.h" #include "audio_clock.h" #include "editor_automation_line.h" -#include "editor_cursors.h" #include "control_point.h" #include "editor.h" #include "region_view.h" -#include "verbose_cursor.h" #include "ui_config.h" #include "pbd/i18n.h" @@ -63,6 +60,27 @@ using namespace ArdourWidgets; using std::max; using std::min; +Glib::RefPtr ClipEditorBox::clip_editor_actions; + +void +ClipEditorBox::init () +{ + Bindings* bindings = Bindings::get_bindings (X_("Clip Editing")); + + register_clip_editor_actions (bindings); +} + +void +ClipEditorBox::register_clip_editor_actions (Bindings* clip_editor_bindings) +{ + clip_editor_actions = ActionManager::create_action_group (clip_editor_bindings, X_("ClipEditing")); + + /* two versions to allow same action for Delete and Backspace */ + + // ActionManager::register_action (clip_editor_actions, X_("zoom-in"), _("Zoom In"), sigc::mem_fun (*this, &ClipEditorBox::zoom_in)); + // ActionManager::register_action (clip_editor_actions, X_("zoom-in"), _("Zoom In"), sigc::mem_fun (*this, &ClipEditorBox::zoom_out)); +} + void AudioClipEditor::ClipBBTMetric::get_marks (std::vector& marks, int64_t lower, int64_t upper, int maxchars) const { @@ -103,133 +121,46 @@ AudioClipEditor::ClipBBTMetric::get_marks (std::vectorname = "audio clip editor frame"; + frame->set_fill (false); + frame->Event.connect (sigc::mem_fun (*this, &AudioClipEditor::event_handler)); -void -AudioClipEditor::pack_outer (Gtk::Box& box) -{ - if (with_transport_controls) { - box.pack_start (play_box, false, false); - } + /* Scroll bar does not scroll and it outside the frame */ - box.pack_start (rec_box, false, false); - box.pack_start (follow_playhead_button, false, false); -} + scroll_bar_trough = new ArdourCanvas::Rectangle (root ()); + scroll_bar_handle = new ArdourCanvas::Rectangle (scroll_bar_trough); + scroll_bar_handle->set_outline (false); + scroll_bar_handle->set_corner_radius (5.); + scroll_bar_handle->Event.connect (sigc::mem_fun (*this, &AudioClipEditor::scroll_event_handler)); + scroll_bar_handle->disable_scroll_translation (); -void -AudioClipEditor::build_lower_toolbar () -{ - horizontal_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &CueEditor::scrolled)); - _canvas_hscrollbar = manage (new Gtk::HScrollbar (horizontal_adjustment)); + /* A scrolling container for our waves and lines etc. */ - _toolbox.pack_start (*_canvas_hscrollbar, false, false); - _toolbox.pack_start (button_bar, false, false); -} + waves_container = new ArdourCanvas::ScrollGroup (frame, ScrollGroup::ScrollsHorizontally); + add_scroller (*waves_container); -void -AudioClipEditor::build_canvas () -{ - canvas.set_background_color (UIConfiguration::instance().color ("arrange base")); - canvas.signal_event().connect (sigc::mem_fun (*this, &CueEditor::canvas_pre_event), false); - canvas.use_nsglview (UIConfiguration::instance().get_nsgl_view_mode () == NSGLHiRes); - - canvas.PreRender.connect (sigc::mem_fun(*this, &EditingContext::pre_render)); - - /* scroll group for items that should not automatically scroll - * (e.g verbose cursor). It shares the canvas coordinate space. - */ - no_scroll_group = new ArdourCanvas::Container (canvas.root()); - - h_scroll_group = new ArdourCanvas::ScrollGroup (canvas.root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally); - CANVAS_DEBUG_NAME (h_scroll_group, "audioclip h scroll"); - canvas.add_scroller (*h_scroll_group); - - - v_scroll_group = new ArdourCanvas::ScrollGroup (canvas.root(), ArdourCanvas::ScrollGroup::ScrollsVertically); - CANVAS_DEBUG_NAME (v_scroll_group, "audioclip v scroll"); - canvas.add_scroller (*v_scroll_group); - - hv_scroll_group = new ArdourCanvas::ScrollGroup (canvas.root(), - ArdourCanvas::ScrollGroup::ScrollSensitivity (ArdourCanvas::ScrollGroup::ScrollsVertically| - ArdourCanvas::ScrollGroup::ScrollsHorizontally)); - CANVAS_DEBUG_NAME (hv_scroll_group, "audioclip hv scroll"); - canvas.add_scroller (*hv_scroll_group); - - cursor_scroll_group = new ArdourCanvas::ScrollGroup (canvas.root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally); - CANVAS_DEBUG_NAME (cursor_scroll_group, "audioclip cursor scroll"); - canvas.add_scroller (*cursor_scroll_group); - - /*a group to hold global rects like punch/loop indicators */ - global_rect_group = new ArdourCanvas::Container (hv_scroll_group); - CANVAS_DEBUG_NAME (global_rect_group, "audioclip global rect group"); - - transport_loop_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX)); - CANVAS_DEBUG_NAME (transport_loop_range_rect, "audioclip loop rect"); - transport_loop_range_rect->hide(); - - /*a group to hold time (measure) lines */ - time_line_group = new ArdourCanvas::Container (h_scroll_group); - CANVAS_DEBUG_NAME (time_line_group, "audioclip time line group"); - - n_timebars = 0; - minsec_ruler = new ArdourCanvas::Ruler (time_line_group, clip_metric, ArdourCanvas::Rect (0, 0, ArdourCanvas::COORD_MAX, timebar_height)); - // minsec_ruler->set_name ("audio clip editor ruler"); - minsec_ruler->set_font_description (UIConfiguration::instance ().get_SmallerFont ()); - minsec_ruler->set_fill_color (UIConfiguration::instance().color (X_("theme:bg1"))); - minsec_ruler->set_outline_color (UIConfiguration::instance().color (X_("theme:contrasting less"))); - n_timebars++; - - minsec_ruler->Event.connect (sigc::mem_fun (*this, &AudioClipEditor::minsec_ruler_event)); - - data_group = new ArdourCanvas::Container (hv_scroll_group); - CANVAS_DEBUG_NAME (data_group, "cue data group"); - - data_group->set_position (ArdourCanvas::Duple (_timeline_origin, timebar_height * n_timebars)); - no_scroll_group->set_position (ArdourCanvas::Duple (_timeline_origin, timebar_height * n_timebars)); - cursor_scroll_group->set_position (ArdourCanvas::Duple (_timeline_origin, timebar_height * n_timebars)); - h_scroll_group->set_position (Duple (_timeline_origin, 0.)); - - _verbose_cursor = new VerboseCursor (*this); - - // _playhead_cursor = new EditorCursor (*this, &Editor::canvas_playhead_cursor_event, X_("playhead")); - _playhead_cursor = new EditorCursor (*this, X_("playhead")); - _playhead_cursor->set_sensitive (UIConfiguration::instance().get_sensitize_playhead()); - _playhead_cursor->set_color (UIConfiguration::instance().color ("play head")); - _playhead_cursor->canvas_item().raise_to_top(); - h_scroll_group->raise_to_top (); - - canvas.set_name ("AudioClipCanvas"); - canvas.add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK); - canvas.set_can_focus (); - canvas.signal_show().connect (sigc::mem_fun (*this, &CueEditor::catch_pending_show_region)); - _viewport.signal_size_allocate().connect (sigc::mem_fun(*this, &AudioClipEditor::canvas_allocate), false); - - _toolbox.pack_start (_viewport, true, true); + ruler_container = new ArdourCanvas::Container (waves_container); + ruler = new ArdourCanvas::Ruler (ruler_container, 0); + ruler->name = "Clip Editor"; + ruler->set_font_description (UIConfiguration::instance ().get_SmallerFont ()); + ruler->set_fill_color (UIConfiguration::instance().color (X_("theme:bg1"))); + ruler->set_outline_color (UIConfiguration::instance().color (X_("theme:contrasting less"))); /* the lines */ - line_container = new ArdourCanvas::Container (data_group); + line_container = new ArdourCanvas::Container (waves_container); const double line_width = 3.; - double scale = UIConfiguration::instance().get_ui_scale(); start_line = new Line (line_container); start_line->set_outline_width (line_width * scale); @@ -291,6 +222,41 @@ AudioClipEditor::line_event_handler (GdkEvent* ev, ArdourCanvas::Line* l) return false; } +bool +AudioClipEditor::scroll_event_handler (GdkEvent* ev) +{ + switch (ev->type) { + case GDK_BUTTON_PRESS: + current_scroll_drag = new ScrollDrag (*this); + current_scroll_drag->begin (&ev->button); + return true; + + case GDK_BUTTON_RELEASE: + if (current_scroll_drag) { + current_scroll_drag->end (&ev->button); + delete current_scroll_drag; + current_scroll_drag = 0; + return true; + } + break; + + case GDK_MOTION_NOTIFY: + if (current_scroll_drag) { + current_scroll_drag->motion (&ev->motion); + return true; + } + break; + + case GDK_KEY_PRESS: + return key_press (&ev->key); + + default: + break; + } + + return false; +} + bool AudioClipEditor::key_press (GdkEventKey* ev) { @@ -311,6 +277,18 @@ AudioClipEditor::position_lines () end_line->set_x1 (sample_to_pixel (audio_region->end ().samples ())); } +double +AudioClipEditor::sample_to_pixel (samplepos_t s) +{ + return round (s / _spp); +} + +samplepos_t +AudioClipEditor::pixel_to_sample (double p) +{ + return round (p * _spp); +} + AudioClipEditor::LineDrag::LineDrag (AudioClipEditor& ed, ArdourCanvas::Line& l) : editor (ed) , line (l) @@ -339,15 +317,78 @@ AudioClipEditor::LineDrag::motion (GdkEventMotion* ev) void AudioClipEditor::set_colors () { - canvas.set_background_color (UIConfiguration::instance ().color (X_("theme:bg"))); + set_background_color (UIConfiguration::instance ().color (X_("theme:bg"))); + + frame->set_outline_color (UIConfiguration::instance ().color (X_("neutral:midground"))); start_line->set_outline_color (UIConfiguration::instance ().color (X_("theme:contrasting clock"))); end_line->set_outline_color (UIConfiguration::instance ().color (X_("theme:contrasting alt"))); loop_line->set_outline_color (UIConfiguration::instance ().color (X_("theme:contrasting selection"))); + scroll_bar_trough->set_fill_color (UIConfiguration::instance ().color (X_("theme:bg"))); + scroll_bar_trough->set_outline_color (UIConfiguration::instance ().color (X_("theme:contrasting less"))); + scroll_bar_handle->set_fill_color (UIConfiguration::instance ().color (X_("theme:contrasting clock"))); + set_waveform_colors (); } +void +AudioClipEditor::scroll_changed () +{ + if (!audio_region) { + return; + } + + const double right_edge = scroll_bar_handle->get ().x0; + const double avail_width = scroll_bar_trough->get ().width () - scroll_bar_handle->get ().width (); + scroll_fraction = right_edge / avail_width; + scroll_fraction = std::min (1., std::max (0., scroll_fraction)); + const samplepos_t s = llrintf (audio_region->source (0)->length ().samples () * scroll_fraction); + + ruler->set_range (s, s + pixel_to_sample (frame->get().width() - 2.)); + + scroll_to (sample_to_pixel (s), 0); + + queue_draw (); +} + +AudioClipEditor::ScrollDrag::ScrollDrag (AudioClipEditor& e) + : editor (e) +{ + e.scroll_bar_handle->grab (); +} + +void +AudioClipEditor::ScrollDrag::begin (GdkEventButton* ev) +{ + last_x = ev->x; +} + +void +AudioClipEditor::ScrollDrag::end (GdkEventButton* ev) +{ + editor.scroll_bar_handle->ungrab (); + editor.scroll_changed (); +} + +void +AudioClipEditor::ScrollDrag::motion (GdkEventMotion* ev) +{ + ArdourCanvas::Rectangle& r (*editor.scroll_bar_handle); + const double xdelta = ev->x - last_x; + ArdourCanvas::Rect n (r.get ()); + const double handle_width = n.width (); + const double avail_width = editor.scroll_bar_trough->get ().width () - handle_width; + + n.x0 = std::max (0., std::min (avail_width, n.x0 + xdelta)); + n.x1 = n.x0 + handle_width; + + r.set (n); + last_x = ev->x; + + editor.scroll_changed (); +} + void AudioClipEditor::drop_waves () { @@ -359,50 +400,12 @@ AudioClipEditor::drop_waves () } void -AudioClipEditor::set_region (std::shared_ptr r) -{ - set_region (std::dynamic_pointer_cast (r)); -} - -void -AudioClipEditor::set_trigger (TriggerReference& tr) -{ - TriggerPtr trigger (tr.trigger()); - - if (trigger == ref.trigger()) { - return; - } - - ref = tr; - - std::shared_ptr at = std::dynamic_pointer_cast (trigger); - if (at) { - minsec_ruler->show (); - minsec_ruler->set_range (0, pixel_to_sample (_visible_canvas_width - 2.)); - } else { - minsec_ruler->hide (); - } - - if (ref.trigger()->the_region()) { - std::shared_ptr ar = std::dynamic_pointer_cast (ref.trigger()->the_region()); - if (ar) { - set_region (ar); - } - } - -} - -void -AudioClipEditor::set_region (std::shared_ptr r) +AudioClipEditor::set_region (std::shared_ptr r, TriggerReference tr) { drop_waves (); audio_region = r; - if (!audio_region) { - return; - } - /* Ruler has to reflect tempo of the region, so we have to recreate it * every time. Note that we retain ownership of the metric, and that * because the GUI is single-threaded, we can set it and delete it @@ -411,8 +414,8 @@ AudioClipEditor::set_region (std::shared_ptr r) */ delete clip_metric; - clip_metric = new ClipBBTMetric (ref); - minsec_ruler->set_metric (clip_metric); + clip_metric = new ClipBBTMetric (tr); + ruler->set_metric (clip_metric); uint32_t n_chans = r->n_channels (); samplecnt_t len; @@ -430,7 +433,7 @@ AudioClipEditor::set_region (std::shared_ptr r) continue; } - WaveView* wv = new WaveView (data_group, war); + WaveView* wv = new WaveView (waves_container, war); wv->set_channel (n); wv->set_show_zero_line (false); wv->set_clip_level (1.0); @@ -439,44 +442,75 @@ AudioClipEditor::set_region (std::shared_ptr r) waves.push_back (wv); } + TriggerPtr trigger (tr.trigger()); + std::shared_ptr at = std::dynamic_pointer_cast (trigger); + if (at) { + if (at->segment_tempo() == 0.) { + /* tempo unknown, hide ruler */ + ruler->hide (); + } else { + ruler->show (); + ruler->set_range (0, pixel_to_sample (frame->get().width() - 2.)); + } + } else { + ruler->hide (); + } set_spp_from_length (len); set_wave_heights (); set_waveform_colors (); line_container->show (); line_container->raise_to_top (); - - set_session (&r->session ()); - state_connection.disconnect (); - - PBD::PropertyChange interesting_stuff; - region_changed (interesting_stuff); - audio_region->PropertyChanged.connect (state_connection, invalidator (*this), std::bind (&AudioClipEditor::region_changed, this, _1), gui_context ()); } void -AudioClipEditor::canvas_allocate (Gtk::Allocation& alloc) +AudioClipEditor::on_size_allocate (Gtk::Allocation& alloc) { - canvas.size_allocate (alloc); + GtkCanvas::on_size_allocate (alloc); - _visible_canvas_width = alloc.get_width(); - _visible_canvas_height = alloc.get_height(); + ArdourCanvas::Rect r (1, 1, alloc.get_width () - 2, alloc.get_height () - 2); + frame->set (r); - minsec_ruler->set (ArdourCanvas::Rect (2, 2, alloc.get_width() - 4, timebar_height)); + const double ruler_height = 25.; + ruler->set (ArdourCanvas::Rect (2, 2, alloc.get_width() - 4, ruler_height)); + + const double scroll_bar_height = 10.; + const double scroll_bar_width = alloc.get_width () - 2; + const double scroll_bar_handle_left = scroll_bar_width * scroll_fraction; + + scroll_bar_trough->set (ArdourCanvas::Rect (1, alloc.get_height () - scroll_bar_height, scroll_bar_width, alloc.get_height ())); + scroll_bar_handle->set (ArdourCanvas::Rect (scroll_bar_handle_left, scroll_bar_trough->get ().y0 + 1, scroll_bar_handle_left + 30., scroll_bar_trough->get ().y1 - 1)); position_lines (); - start_line->set_y1 (_visible_canvas_height - 2.); - end_line->set_y1 (_visible_canvas_height - 2.); - loop_line->set_y1 (_visible_canvas_height - 2.); + start_line->set_y1 (frame->get ().height () - 2. - scroll_bar_height); + end_line->set_y1 (frame->get ().height () - 2. - scroll_bar_height); + loop_line->set_y1 (frame->get ().height () - 2. - scroll_bar_height); set_wave_heights (); } +void +AudioClipEditor::set_spp (double samples_per_pixel) +{ + _spp = samples_per_pixel; + + clip_metric->units_per_pixel = _spp; + + position_lines (); + + for (auto& wave : waves) { + wave->set_samples_per_pixel (_spp); + } +} + void AudioClipEditor::set_spp_from_length (samplecnt_t len) { - set_samples_per_pixel (floor (len / _visible_canvas_width)); + double available_width = frame->get ().width (); + double s = floor (len / available_width); + + set_spp (s); } void @@ -487,12 +521,12 @@ AudioClipEditor::set_wave_heights () } uint32_t n = 0; - const Distance w = _visible_canvas_height - (n_timebars * timebar_height); + const Distance w = frame->get ().height () - scroll_bar_trough->get ().height () - 2. - ruler->get().height(); Distance ht = w / waves.size (); for (auto& wave : waves) { wave->set_height (ht); - wave->set_y_position ((n_timebars * timebar_height) + (n * ht)); + wave->set_y_position (ruler->get ().height () + (n * ht)); ++n; } } @@ -513,69 +547,86 @@ AudioClipEditor::set_waveform_colors () } } -Gtk::Widget& -AudioClipEditor::viewport() -{ - return _viewport; -} - -Gtk::Widget& -AudioClipEditor::contents () -{ - return _contents; -} - -void -AudioClipEditor::region_changed (const PBD::PropertyChange& what_changed) -{ -} - -void -AudioClipEditor::set_samples_per_pixel (samplecnt_t spp) -{ - CueEditor::set_samples_per_pixel (spp); - - clip_metric->units_per_pixel = samples_per_pixel; - - position_lines (); - - for (auto& wave : waves) { - wave->set_samples_per_pixel (samples_per_pixel); - } - - horizontal_adjustment.set_upper (max_zoom_extent().second.samples() / samples_per_pixel); - horizontal_adjustment.set_page_size (current_page_samples()/ samples_per_pixel / 10); - horizontal_adjustment.set_page_increment (current_page_samples()/ samples_per_pixel / 20); - horizontal_adjustment.set_step_increment (current_page_samples() / samples_per_pixel / 100); -} - -samplecnt_t -AudioClipEditor::current_page_samples() const -{ - return (samplecnt_t) _track_canvas_width * samples_per_pixel; -} - bool -AudioClipEditor::canvas_enter_leave (GdkEventCrossing* ev) +AudioClipEditor::event_handler (GdkEvent* ev) { switch (ev->type) { - case GDK_ENTER_NOTIFY: - if (ev->detail != GDK_NOTIFY_INFERIOR) { - canvas.grab_focus (); - // ActionManager::set_sensitive (_midi_actions, true); - within_track_canvas = true; - } - break; - case GDK_LEAVE_NOTIFY: - if (ev->detail != GDK_NOTIFY_INFERIOR) { - // ActionManager::set_sensitive (_midi_actions, false); - within_track_canvas = false; - ARDOUR_UI::instance()->reset_focus (&_viewport); - gdk_window_set_cursor (_viewport.get_window()->gobj(), nullptr); - } - default: - break; + case GDK_BUTTON_PRESS: + break; + case GDK_ENTER_NOTIFY: + break; + case GDK_LEAVE_NOTIFY: + break; + default: + break; } return false; } +AudioClipEditorBox::AudioClipEditorBox () +{ + _header_label.set_text (_("AUDIO Region Trimmer:")); + _header_label.set_alignment (0.0, 0.5); + + zoom_in_button.set_icon (ArdourIcon::ZoomIn); + zoom_out_button.set_icon (ArdourIcon::ZoomOut); + + zoom_in_button.signal_clicked.connect (sigc::mem_fun (*this, &AudioClipEditorBox::zoom_in_click)); + zoom_out_button.signal_clicked.connect (sigc::mem_fun (*this, &AudioClipEditorBox::zoom_out_click)); + + header_box.pack_start (_header_label, false, false); + header_box.pack_start (zoom_in_button, false, false); + header_box.pack_start (zoom_out_button, false, false); + + pack_start (header_box, false, false, 6); + + editor = manage (new AudioClipEditor); + editor->set_size_request (600, 120); + + pack_start (*editor, true, true); + editor->show (); +} + +AudioClipEditorBox::~AudioClipEditorBox () +{ + delete editor; +} + +void +AudioClipEditorBox::zoom_in_click () +{ + editor->set_spp (editor->spp () / 2.); +} + +void +AudioClipEditorBox::zoom_out_click () +{ + editor->set_spp (editor->spp () * 2.); +} + +void +AudioClipEditorBox::set_region (std::shared_ptr r, TriggerReference tref) +{ + std::shared_ptr ar = std::dynamic_pointer_cast (r); + + if (!ar) { + return; + } + + set_session (&r->session ()); + + state_connection.disconnect (); + + _region = r; + editor->set_region (ar, tref); + + PBD::PropertyChange interesting_stuff; + region_changed (interesting_stuff); + + _region->PropertyChanged.connect (state_connection, invalidator (*this), std::bind (&AudioClipEditorBox::region_changed, this, _1), gui_context ()); +} + +void +AudioClipEditorBox::region_changed (const PBD::PropertyChange& what_changed) +{ +} diff --git a/gtk2_ardour/audio_clip_editor.h b/gtk2_ardour/audio_clip_editor.h index 02563805f3..52b126a404 100644 --- a/gtk2_ardour/audio_clip_editor.h +++ b/gtk2_ardour/audio_clip_editor.h @@ -25,8 +25,6 @@ #include #include -#include "pbd/history_owner.h" - #include "ardour/ardour.h" #include "ardour/session_handle.h" #include "ardour/triggerbox.h" @@ -46,7 +44,6 @@ #include "canvas/scroll_group.h" #include "audio_clock.h" -#include "cue_editor.h" namespace ARDOUR { @@ -66,104 +63,59 @@ namespace ArdourWaveView class WaveView; } -class AudioClipEditor : public CueEditor +class ClipEditorBox : public Gtk::VBox, public ARDOUR::SessionHandlePtr { public: - AudioClipEditor (std::string const &, bool with_transport); + ClipEditorBox () {} + ~ClipEditorBox () {} + + virtual void set_region (std::shared_ptr, ARDOUR::TriggerReference) = 0; + + static void init (); + static void register_clip_editor_actions (Gtkmm2ext::Bindings*); + static Glib::RefPtr clip_editor_actions; +}; + +class ClipEditor +{ +public: + virtual ~ClipEditor () {} + + virtual void zoom_in () = 0; + virtual void zoom_out () = 0; +}; + +class AudioClipEditor : public ArdourCanvas::GtkCanvas +{ +public: + AudioClipEditor (); ~AudioClipEditor (); - void canvas_allocate (Gtk::Allocation&); - - Gtk::Widget& viewport(); - Gtk::Widget& contents (); - - void set_trigger (ARDOUR::TriggerReference&); - void set_region (std::shared_ptr); - void set_region (std::shared_ptr r); - void region_changed (const PBD::PropertyChange& what_changed); + void set_region (std::shared_ptr, ARDOUR::TriggerReference); + void on_size_allocate (Gtk::Allocation&); double sample_to_pixel (ARDOUR::samplepos_t); samplepos_t pixel_to_sample (double); + void set_spp (double); + double spp () const + { + return _spp; + } + bool key_press (GdkEventKey*); - bool minsec_ruler_event (GdkEvent*); - - /* EditingContext API. As of July 2025, we do not implement most of - * these - */ - - bool button_press_handler (ArdourCanvas::Item*, GdkEvent*, ItemType) { return true; } - bool button_press_handler_1 (ArdourCanvas::Item*, GdkEvent*, ItemType) { return true; } - bool button_press_handler_2 (ArdourCanvas::Item*, GdkEvent*, ItemType) { return true; } - bool button_release_handler (ArdourCanvas::Item*, GdkEvent*, ItemType) { return true; } - bool button_press_dispatch (GdkEventButton*) { return true; } - bool button_release_dispatch (GdkEventButton*) { return true; } - bool motion_handler (ArdourCanvas::Item*, GdkEvent*, bool from_autoscroll = false) { return true; } - bool enter_handler (ArdourCanvas::Item*, GdkEvent*, ItemType) { return true; } - bool leave_handler (ArdourCanvas::Item*, GdkEvent*, ItemType) { return true; } - bool key_press_handler (ArdourCanvas::Item*, GdkEvent*, ItemType) { return true; } - bool key_release_handler (ArdourCanvas::Item*, GdkEvent*, ItemType) { return true; } - - bool canvas_note_event (GdkEvent* event, ArdourCanvas::Item*) { return true; } - bool canvas_velocity_base_event (GdkEvent* event, ArdourCanvas::Item*) { return true; } - bool canvas_velocity_event (GdkEvent* event, ArdourCanvas::Item*) { return true; } - bool canvas_control_point_event (GdkEvent* event, ArdourCanvas::Item*, ControlPoint*) { return true; } - bool canvas_bg_event (GdkEvent* event, ArdourCanvas::Item*) { return true; } - - ArdourCanvas::Container* get_trackview_group () const; - ArdourCanvas::Container* get_noscroll_group() const; - ArdourCanvas::ScrollGroup* get_hscroll_group () const; - ArdourCanvas::ScrollGroup* get_cursor_scroll_group () const; - - samplecnt_t current_page_samples() const; - double visible_canvas_width() const; - void set_samples_per_pixel (samplecnt_t); - - ArdourCanvas::GtkCanvasViewport* get_canvas_viewport() const { return const_cast (&_viewport); } - ArdourCanvas::GtkCanvas* get_canvas() const { return &canvas; } - - std::pair max_zoom_extent() const; - - Gdk::Cursor* which_track_cursor () const { return nullptr; } - Gdk::Cursor* which_mode_cursor () const { return nullptr; } - Gdk::Cursor* which_trim_cursor (bool left_side) const { return nullptr; } - Gdk::Cursor* which_canvas_cursor (ItemType type) const { return nullptr; } - - RegionSelection region_selection(); - - Temporal::timepos_t snap_to_grid (Temporal::timepos_t const & start, Temporal::RoundMode direction, ARDOUR::SnapPref gpref) const { return start; } - void snap_to_internal (Temporal::timepos_t& first, Temporal::RoundMode direction = Temporal::RoundNearest, ARDOUR::SnapPref gpref = ARDOUR::SnapToAny_Visual, bool ensure_snap = false) const {} - - void select_all_within (Temporal::timepos_t const &, Temporal::timepos_t const &, double, double, std::list const &, ARDOUR::SelectionOperation, bool) {} - void get_per_region_note_selection (std::list > > > >&) const {} - void get_regionviews_by_id (PBD::ID const id, RegionSelection & regions) const {} - void maybe_autoscroll (bool, bool, bool from_headers) {}; - void stop_canvas_autoscroll () {} - void redisplay_grid (bool immediate_redraw) {}; - void instant_save() {}; - - void point_selection_changed () {} - void step_mouse_mode (bool next); - void mouse_mode_toggled (Editing::MouseMode); - void delete_ () {} - void paste (float times, bool from_context_menu) {} - void keyboard_paste () {} - void cut_copy (Editing::CutCopyOp) {} - - void register_actions() {} - void visual_changer (const VisualChange&) {} - void build_zoom_focus_menu () {} private: - ArdourCanvas::GtkCanvasViewport _viewport; - ArdourCanvas::GtkCanvas& canvas; - - ArdourCanvas::Container* line_container; - ArdourCanvas::Line* start_line; - ArdourCanvas::Line* end_line; - ArdourCanvas::Line* loop_line; - ArdourCanvas::Container* ruler_container; - ArdourCanvas::Ruler* minsec_ruler; + ArdourCanvas::Rectangle* frame; + ArdourCanvas::ScrollGroup* waves_container; + ArdourCanvas::Container* line_container; + ArdourCanvas::Line* start_line; + ArdourCanvas::Line* end_line; + ArdourCanvas::Line* loop_line; + ArdourCanvas::Rectangle* scroll_bar_trough; + ArdourCanvas::Rectangle* scroll_bar_handle; + ArdourCanvas::Container* ruler_container; + ArdourCanvas::Ruler* ruler; class ClipBBTMetric : public ArdourCanvas::Ruler::Metric { @@ -183,6 +135,7 @@ private: std::vector waves; double non_wave_height; samplepos_t left_origin; + double _spp; double scroll_fraction; std::shared_ptr audio_region; @@ -197,6 +150,7 @@ private: bool event_handler (GdkEvent* ev); bool line_event_handler (GdkEvent* ev, ArdourCanvas::Line*); + bool scroll_event_handler (GdkEvent* ev); void drop_waves (); void set_wave_heights (); void set_spp_from_length (ARDOUR::samplecnt_t); @@ -222,12 +176,47 @@ private: friend class LineDrag; LineDrag* current_line_drag; + class ScrollDrag + { + public: + ScrollDrag (AudioClipEditor&); + + void begin (GdkEventButton*); + void end (GdkEventButton*); + void motion (GdkEventMotion*); + + private: + AudioClipEditor& editor; + double last_x; + }; + + friend class ScrollDrag; + ScrollDrag* current_scroll_drag; +}; + +class AudioClipEditorBox : public ClipEditorBox +{ +public: + AudioClipEditorBox (); + ~AudioClipEditorBox (); + + void set_region (std::shared_ptr, ARDOUR::TriggerReference); + void region_changed (const PBD::PropertyChange& what_changed); + +private: + Gtk::HBox header_box; + ArdourWidgets::ArdourButton zoom_in_button; + ArdourWidgets::ArdourButton zoom_out_button; + Gtk::Label _header_label; + Gtk::Table table; + + AudioClipEditor* editor; + PBD::ScopedConnection state_connection; - void build_canvas (); - void build_lower_toolbar (); - void pack_inner (Gtk::Box&); - void pack_outer (Gtk::Box&); + std::shared_ptr _region; - bool canvas_enter_leave (GdkEventCrossing* ev); + void zoom_in_click (); + void zoom_out_click (); }; + diff --git a/gtk2_ardour/audio_trigger_properties_box.cc b/gtk2_ardour/audio_trigger_properties_box.cc index 5328d053bf..e387a98e02 100644 --- a/gtk2_ardour/audio_trigger_properties_box.cc +++ b/gtk2_ardour/audio_trigger_properties_box.cc @@ -112,31 +112,29 @@ AudioTriggerPropertiesBox::AudioTriggerPropertiesBox () eTempoBox->show_all(); - Gtk::Table* audio_t = manage (new Gtk::Table ()); - audio_t->set_homogeneous (true); - audio_t->set_spacings (4); - /* -------------- Clip start&length (redundant with the trimmer gui handles?) ----------*/ row = 0; label = manage (new Gtk::Label (_("Start:"))); label->set_alignment (1.0, 0.5); - audio_t->attach (*label, 0, 1, row, row + 1, Gtk::SHRINK, Gtk::SHRINK); - audio_t->attach (_start_clock, 1, 2, row, row + 1, Gtk::SHRINK, Gtk::SHRINK); + _table.attach (*label, 0, 1, row, row + 1, Gtk::SHRINK, Gtk::SHRINK); + _table.attach (_start_clock, 1, 2, row, row + 1, Gtk::SHRINK, Gtk::SHRINK); row++; label = manage (new Gtk::Label (_("Clip Length:"))); label->set_alignment (1.0, 0.5); - audio_t->attach (*label, 0, 1, row, row + 1, Gtk::SHRINK, Gtk::SHRINK); - audio_t->attach (_length_clock, 1, 2, row, row + 1, Gtk::SHRINK, Gtk::SHRINK); + _table.attach (*label, 0, 1, row, row + 1, Gtk::SHRINK, Gtk::SHRINK); + _table.attach (_length_clock, 1, 2, row, row + 1, Gtk::SHRINK, Gtk::SHRINK); row++; - audio_t->set_homogeneous (false); - audio_t->set_spacings (4); - audio_t->set_border_width (2); + _table.set_homogeneous (false); + _table.set_spacings (4); + _table.set_border_width (2); - attach (*eTempoBox, 0,1, 0,1, Gtk::FILL, Gtk::EXPAND | Gtk::FILL); - attach (*audio_t, 0,1, 1,2, Gtk::FILL, Gtk::SHRINK); + attach (*eTempoBox, 0,1, 0,1, Gtk::FILL, Gtk::EXPAND | Gtk::FILL); +#if 0 + attach (_table, 0,1, 1,2, Gtk::FILL, Gtk::SHRINK); +#endif _start_clock.ValueChanged.connect (sigc::mem_fun (*this, &AudioTriggerPropertiesBox::start_clock_changed)); _length_clock.ValueChanged.connect (sigc::mem_fun (*this, &AudioTriggerPropertiesBox::length_clock_changed)); diff --git a/gtk2_ardour/audio_trigger_properties_box.h b/gtk2_ardour/audio_trigger_properties_box.h index 78566a4658..62c5a1cacc 100644 --- a/gtk2_ardour/audio_trigger_properties_box.h +++ b/gtk2_ardour/audio_trigger_properties_box.h @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#pragma once +#ifndef _gtk_ardour_audio_trigger_properties_box_h_ +#define _gtk_ardour_audio_trigger_properties_box_h_ #include #include @@ -33,7 +34,18 @@ #include "audio_clock.h" #include "trigger_ui.h" -#include "trigger_properties_box.h" + +class TriggerPropertiesBox : public Gtk::Table, public ARDOUR::SessionHandlePtr, public TriggerUI +{ +public: + TriggerPropertiesBox () {} + ~TriggerPropertiesBox () {} + +protected: + Gtk::Label _header_label; + + PBD::ScopedConnection _state_connection; +}; class AudioTriggerPropertiesBox : public TriggerPropertiesBox { @@ -60,6 +72,8 @@ private: void MultiplyTempo(float mult); + Gtk::Table _table; + AudioClock _length_clock; AudioClock _start_clock; @@ -84,3 +98,5 @@ private: bool _ignore_changes; }; + +#endif diff --git a/gtk2_ardour/cue_editor.cc b/gtk2_ardour/cue_editor.cc index d67a30e93f..ee96e4b5ac 100644 --- a/gtk2_ardour/cue_editor.cc +++ b/gtk2_ardour/cue_editor.cc @@ -1,49 +1,15 @@ -/* - * 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 "widgets/ardour_icon.h" -#include "widgets/tooltips.h" - -#include "ardour/types.h" - -#include "canvas/canvas.h" - #include "cue_editor.h" #include "editor_drag.h" #include "gui_thread.h" -#include "ui_config.h" #include "pbd/i18n.h" -using namespace ARDOUR; -using namespace ArdourWidgets; - -CueEditor::CueEditor (std::string const & name, bool with_transport) +CueEditor::CueEditor (std::string const & name) : EditingContext (name) - , HistoryOwner (name) - , with_transport_controls (with_transport) - , length_label (X_("Record:")) - , solo_button (S_("Solo|S")) - , zoom_in_allocate (false) - , timebar_height (15.) - , n_timebars (0) + , HistoryOwner (X_("cue-editor")) { _history.Changed.connect (history_connection, invalidator (*this), std::bind (&CueEditor::history_changed, this), gui_context()); + set_zoom_focus (Editing::ZoomFocusLeft); } @@ -105,6 +71,22 @@ CueEditor::find_marker_for_meter (Temporal::MeterPoint const &) return nullptr; } +void +CueEditor::maybe_autoscroll (bool, bool, bool from_headers) +{ +} + +void +CueEditor::stop_canvas_autoscroll () +{ +} + +bool +CueEditor::autoscroll_active() const +{ + return false; +} + void CueEditor::redisplay_grid (bool immediate_redraw) { @@ -281,695 +263,3 @@ CueEditor::_get_preferred_edit_position (Editing::EditIgnoreOption ignore, bool return Temporal::timepos_t (where); } - -void -CueEditor::build_upper_toolbar () -{ - using namespace Gtk::Menu_Helpers; - - Gtk::HBox* mode_box = manage(new Gtk::HBox); - mode_box->set_border_width (2); - mode_box->set_spacing(2); - - Gtk::HBox* mouse_mode_box = manage (new Gtk::HBox); - Gtk::HBox* mouse_mode_hbox = manage (new Gtk::HBox); - Gtk::VBox* mouse_mode_vbox = manage (new Gtk::VBox); - Gtk::Alignment* mouse_mode_align = manage (new Gtk::Alignment); - - Glib::RefPtr mouse_mode_size_group = Gtk::SizeGroup::create (Gtk::SIZE_GROUP_VERTICAL); - mouse_mode_size_group->add_widget (mouse_draw_button); - mouse_mode_size_group->add_widget (mouse_content_button); - - mouse_mode_size_group->add_widget (grid_type_selector); - mouse_mode_size_group->add_widget (draw_length_selector); - mouse_mode_size_group->add_widget (draw_velocity_selector); - mouse_mode_size_group->add_widget (draw_channel_selector); - mouse_mode_size_group->add_widget (snap_mode_button); - - mouse_mode_hbox->set_spacing (2); - mouse_mode_hbox->pack_start (mouse_draw_button, false, false); - mouse_mode_hbox->pack_start (mouse_content_button, false, false); - - mouse_mode_vbox->pack_start (*mouse_mode_hbox); - - mouse_mode_align->add (*mouse_mode_vbox); - mouse_mode_align->set (0.5, 1.0, 0.0, 0.0); - - mouse_mode_box->pack_start (*mouse_mode_align, false, false); - - pack_snap_box (); - pack_draw_box (false); - - Gtk::HBox* _toolbar_inner = manage (new Gtk::HBox); - Gtk::HBox* _toolbar_outer = manage (new Gtk::HBox); - Gtk::HBox* _toolbar_left = manage (new Gtk::HBox); - - _toolbar_inner->pack_start (*mouse_mode_box, false, false); - pack_inner (*_toolbar_inner); - - set_tooltip (full_zoom_button, _("Zoom to full clip")); - set_tooltip (note_mode_button, _("Toggle between drum and regular note drawing")); - - play_button.set_icon (ArdourIcon::TransportPlay); - play_button.set_name ("transport button"); - play_button.show(); - - if (with_transport_controls) { - loop_button.set_icon (ArdourIcon::TransportLoop); - loop_button.set_name ("transport button"); - - solo_button.set_name ("solo button"); - - play_box.set_spacing (8); - play_box.pack_start (play_button, false, false); - play_box.pack_start (loop_button, false, false); - play_box.pack_start (solo_button, false, false); - loop_button.show(); - solo_button.show(); - play_box.set_no_show_all (true); - play_box.show (); - - play_button.signal_button_release_event().connect (sigc::mem_fun (*this, &CueEditor::play_button_press), false); - solo_button.signal_button_release_event().connect (sigc::mem_fun (*this, &CueEditor::solo_button_press), false); - loop_button.signal_button_release_event().connect (sigc::mem_fun (*this, &CueEditor::loop_button_press), false); - } else { - rec_box.pack_start (play_button, false, false); - play_button.signal_button_release_event().connect (sigc::mem_fun (*this, &CueEditor::bang_button_press), false); - } - - rec_enable_button.set_icon (ArdourIcon::RecButton); - rec_enable_button.set_sensitive (false); - rec_enable_button.signal_button_release_event().connect (sigc::mem_fun (*this, &CueEditor::rec_button_press), false); - rec_enable_button.set_name ("record enable button"); - - length_selector.add_menu_elem (MenuElem (_("Until Stopped"), sigc::bind (sigc::mem_fun (*this, &CueEditor::set_recording_length), Temporal::BBT_Offset ()))); - length_selector.add_menu_elem (MenuElem (_("1 Bar"), sigc::bind (sigc::mem_fun (*this, &CueEditor::set_recording_length), Temporal::BBT_Offset (1, 0, 0)))); - std::vector b ({ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 20, 24, 32 }); - for (auto & n : b) { - length_selector.add_menu_elem (MenuElem (string_compose (_("%1 Bars"), n), sigc::bind (sigc::mem_fun (*this, &CueEditor::set_recording_length), Temporal::BBT_Offset (n, 0, 0)))); - } - length_selector.set_active (_("Until Stopped")); - - rec_box.set_spacing (12); - rec_box.pack_start (rec_enable_button, false, false); - rec_box.pack_start (length_label, false, false); - rec_box.pack_start (length_selector, false, false); - rec_enable_button.show(); - length_label.show (); - length_selector.show (); - rec_box.set_no_show_all (true); - /* rec box not shown */ - - _toolbar_outer->set_border_width (6); - _toolbar_outer->set_spacing (12); - - pack_outer (*_toolbar_outer); - - _toolbar_outer->pack_start (*_toolbar_inner, true, false); - - build_zoom_focus_menu (); - zoom_focus_selector.set_text (zoom_focus_strings[(int)_zoom_focus]); - - _toolbar_left->pack_start (zoom_in_button, false, false); - _toolbar_left->pack_start (zoom_out_button, false, false); - _toolbar_left->pack_start (full_zoom_button, false, false); - _toolbar_left->pack_start (zoom_focus_selector, false, false); - - _toolbar_outer->pack_start (*_toolbar_left, true, false); - _toolbox.pack_start (*_toolbar_outer, false, false); - - _contents.add (_toolbox); - _contents.signal_unmap().connect ([this]() {viewport().unmap (); }, false); - _contents.signal_map().connect ([this]() { viewport().map (); }, false); -} - -void -CueEditor::build_zoom_focus_menu () -{ - using namespace Gtk::Menu_Helpers; - using namespace Editing; - - zoom_focus_selector.add_menu_elem (MenuElem (zoom_focus_strings[(int)ZoomFocusLeft], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusLeft))); - zoom_focus_selector.add_menu_elem (MenuElem (zoom_focus_strings[(int)ZoomFocusRight], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusRight))); - zoom_focus_selector.add_menu_elem (MenuElem (zoom_focus_strings[(int)ZoomFocusCenter], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusCenter))); - zoom_focus_selector.add_menu_elem (MenuElem (zoom_focus_strings[(int)ZoomFocusMouse], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusMouse))); - zoom_focus_selector.set_sizing_texts (zoom_focus_strings); -} - - -bool -CueEditor::play_button_press (GdkEventButton* ev) -{ -#warning paul fix lookup region via CueEditor 87 -#if 0 - if (_session) { - _session->request_locate (view->midi_region()->position().samples()); - _session->request_roll (); - } -#endif - return true; -} - -bool -CueEditor::loop_button_press (GdkEventButton* ev) -{ -#warning paul fix region lookup via CueEditor 1 -#if 0 - if (!view) { - return true; - } - if (!view->midi_region()) { - return true; - } - - if (_session->get_play_loop()) { - _session->request_play_loop (false); - } else { - set_loop_range (view->midi_region()->position(), view->midi_region()->end(), _("loop region")); - _session->request_play_loop (true); - } -#endif - return true; -} - -bool -CueEditor::solo_button_press (GdkEventButton* ev) -{ -#warning paul fix region lookup via CueEditor 2 -#if 0 - if (!view) { - return true; - } - - if (!view->midi_track()) { - return true; - } - - view->midi_track()->solo_control()->set_value (!view->midi_track()->solo_control()->get_value(), Controllable::NoGroup); -#endif - return true; -} - -bool -CueEditor::rec_button_press (GdkEventButton* ev) -{ -#warning paul fix trigger lookup via CueEditor 1 -#if 0 - if (ev->button != 1) { - return false; - } - - TriggerPtr trigger (ref.trigger()); - - if (!trigger) { - return true; - } - - if (trigger->armed()) { - trigger->disarm (); - } else { - trigger->arm (rec_length); - } -#endif - return true; -} - -void -CueEditor::blink_rec_enable (bool onoff) -{ - if (onoff) { - rec_enable_button.set_active_state (Gtkmm2ext::ExplicitActive); - } else { - rec_enable_button.set_active_state (Gtkmm2ext::Off); - } -} - -void -CueEditor::trigger_arm_change () -{ -#warning paul fix trigger lookup via CueEditor 1 -#if 0 - if (!ref.trigger()) { - return; - } - - if (!ref.trigger()->armed()) { - view->end_write (); - } else { - maybe_set_count_in (); - } -#endif - rec_enable_change (); -} - -void -CueEditor::rec_enable_change () -{ -#warning paul fix trigger lookup via CueEditor 1 -#if 0 - if (!ref.box()) { - std::cerr << "no box!\n"; - return; - } - - rec_blink_connection.disconnect (); - count_in_connection.disconnect (); - - std::cerr << "REC, state " << ref.box()->record_enabled() << std::endl; - - switch (ref.box()->record_enabled()) { - case Recording: - rec_enable_button.set_active_state (Gtkmm2ext::ExplicitActive); - rec_blink_connection.disconnect (); - if (view) { - view->begin_write (); - } - break; - case Enabled: - if (!UIConfiguration::instance().get_no_strobe() && ref.trigger()->armed()) { - rec_blink_connection = Timers::blink_connect (sigc::mem_fun (*this, &CueEditor::blink_rec_enable)); - } else { - rec_enable_button.set_active_state (Gtkmm2ext::Off); - } - maybe_set_count_in (); - break; - case Disabled: - rec_enable_button.set_active_state (Gtkmm2ext::Off); - break; - } -#endif -} - -void -CueEditor::set_recording_length (Temporal::BBT_Offset dur) -{ - rec_length = dur; -} - -bool -CueEditor::bang_button_press (GdkEventButton* ev) -{ -#warning paul fix trigger look from CueEditor 93 -#if 0 - if (!ref.trigger()) { - return true; - } - - ref.trigger()->bang (); -#endif - return true; -} - -void -CueEditor::scrolled () -{ - pending_visual_change.add (VisualChange::TimeOrigin); - pending_visual_change.time_origin = horizontal_adjustment.get_value() * samples_per_pixel; - ensure_visual_change_idle_handler (); -} - -bool -CueEditor::canvas_pre_event (GdkEvent* ev) -{ - switch (ev->type) { - case GDK_ENTER_NOTIFY: - case GDK_LEAVE_NOTIFY: - if (canvas_enter_leave (&ev->crossing)) { - return true; - } - break; - default: - break; - } - - return false; -} - -bool -CueEditor::autoscroll_active () const -{ - return autoscroll_connection.connected (); -} - -/** @param allow_horiz true to allow horizontal autoscroll, otherwise false. - * - * @param allow_vert true to allow vertical autoscroll, otherwise false. - * - */ -void -CueEditor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers) -{ - if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) { - return; - } - - /* define a rectangular boundary for scrolling. If the mouse moves - * outside of this area and/or continue to be outside of this area, - * then we will continuously auto-scroll the canvas in the appropriate - * direction(s) - * - * the boundary is defined in coordinates relative to canvas' own - * window since that is what we're going to call ::get_pointer() on - * during autoscrolling to determine if we're still outside the - * boundary or not. - */ - - ArdourCanvas::Rect scrolling_boundary; - Gtk::Allocation alloc; - - alloc = get_canvas()->get_allocation (); - - alloc.set_x (0); - alloc.set_y (0); - - if (allow_vert) { - /* reduce height by the height of the timebars, which happens - to correspond to the position of the data_group. - */ - - alloc.set_height (alloc.get_height() - data_group->position().y); - alloc.set_y (alloc.get_y() + data_group->position().y); - - /* now reduce it again so that we start autoscrolling before we - * move off the top or bottom of the canvas - */ - - alloc.set_height (alloc.get_height() - 20); - alloc.set_y (alloc.get_y() + 10); - } - - if (allow_horiz && (alloc.get_width() > 20)) { - -#warning paul fix use of PRH in CueEditor context -#if 0 - if (prh) { - double w, h; - prh->size_request (w, h); - - alloc.set_width (alloc.get_width() - w); - alloc.set_x (alloc.get_x() + w); - } -#endif - /* the effective width of the autoscroll boundary so - that we start scrolling before we hit the edge. - - this helps when the window is slammed up against the - right edge of the screen, making it hard to scroll - effectively. - */ - - alloc.set_width (alloc.get_width() - 20); - alloc.set_x (alloc.get_x() + 10); - } - - scrolling_boundary = ArdourCanvas::Rect (alloc.get_x(), alloc.get_y(), alloc.get_x() + alloc.get_width(), alloc.get_y() + alloc.get_height()); - - int x, y; - Gdk::ModifierType mask; - - get_canvas()->get_window()->get_pointer (x, y, mask); - - if ((allow_horiz && ((x < scrolling_boundary.x0 && _leftmost_sample > 0) || x >= scrolling_boundary.x1)) || - (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) { - start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary); - } -} - -bool -CueEditor::autoscroll_canvas () -{ - using std::max; - using std::min; - int x, y; - Gdk::ModifierType mask; - sampleoffset_t dx = 0; - bool no_stop = false; - Gtk::Window* toplevel = dynamic_cast (viewport().get_toplevel()); - - if (!toplevel) { - return false; - } - - get_canvas()->get_window()->get_pointer (x, y, mask); - - VisualChange vc; - bool vertical_motion = false; - - if (autoscroll_horizontal_allowed) { - - samplepos_t new_sample = _leftmost_sample; - - /* horizontal */ - - if (x > autoscroll_boundary.x1) { - - /* bring it back into view */ - dx = x - autoscroll_boundary.x1; - dx += 10 + (2 * (autoscroll_cnt/2)); - - dx = pixel_to_sample (dx); - - dx *= UIConfiguration::instance().get_draggable_playhead_speed(); - - if (_leftmost_sample < max_samplepos - dx) { - new_sample = _leftmost_sample + dx; - } else { - new_sample = max_samplepos; - } - - no_stop = true; - - } else if (x < autoscroll_boundary.x0) { - - dx = autoscroll_boundary.x0 - x; - dx += 10 + (2 * (autoscroll_cnt/2)); - - dx = pixel_to_sample (dx); - - dx *= UIConfiguration::instance().get_draggable_playhead_speed(); - - if (_leftmost_sample >= dx) { - new_sample = _leftmost_sample - dx; - } else { - new_sample = 0; - } - - no_stop = true; - } - - if (new_sample != _leftmost_sample) { - vc.time_origin = new_sample; - vc.add (VisualChange::TimeOrigin); - } - } - - if (autoscroll_vertical_allowed) { - - // const double vertical_pos = vertical_adjustment.get_value(); - const int speed_factor = 10; - - /* vertical */ - - if (y < autoscroll_boundary.y0) { - - /* scroll to make higher tracks visible */ - - if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) { - // XXX SCROLL UP - vertical_motion = true; - } - no_stop = true; - - } else if (y > autoscroll_boundary.y1) { - - if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) { - // XXX SCROLL DOWN - vertical_motion = true; - } - no_stop = true; - } - - } - - if (vc.pending || vertical_motion) { - - /* change horizontal first */ - - if (vc.pending) { - visual_changer (vc); - } - - /* now send a motion event to notify anyone who cares - that we have moved to a new location (because we scrolled) - */ - - GdkEventMotion ev; - - ev.type = GDK_MOTION_NOTIFY; - ev.state = Gdk::BUTTON1_MASK; - - /* the motion handler expects events in canvas coordinate space */ - - /* we asked for the mouse position above (::get_pointer()) via - * our own top level window (we being the Editor). Convert into - * coordinates within the canvas window. - */ - - int cx; - int cy; - - //toplevel->translate_coordinates (*get_canvas(), x, y, cx, - //cy); - cx = x; - cy = y; - - /* clamp x and y to remain within the autoscroll boundary, - * which is defined in window coordinates - */ - - x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1); - y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1); - - /* now convert from Editor window coordinates to canvas - * window coordinates - */ - - ArdourCanvas::Duple d = get_canvas()->window_to_canvas (ArdourCanvas::Duple (cx, cy)); - ev.x = d.x; - ev.y = d.y; - ev.state = mask; - - motion_handler (0, (GdkEvent*) &ev, true); - - } else if (no_stop) { - - /* not changing visual state but pointer is outside the scrolling boundary - * so we still need to deliver a fake motion event - */ - - GdkEventMotion ev; - - ev.type = GDK_MOTION_NOTIFY; - ev.state = Gdk::BUTTON1_MASK; - - /* the motion handler expects events in canvas coordinate space */ - - /* first convert from Editor window coordinates to canvas - * window coordinates - */ - - int cx; - int cy; - - /* clamp x and y to remain within the visible area. except - * .. if horizontal scrolling is allowed, always allow us to - * move back to zero - */ - - if (autoscroll_horizontal_allowed) { - x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1); - } else { - x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1); - } - y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1); - - // toplevel->translate_coordinates (*get_canvas_viewport(), x, - // y, cx, cy); - cx = x; - cy = y; - - ArdourCanvas::Duple d = get_canvas()->window_to_canvas (ArdourCanvas::Duple (cx, cy)); - ev.x = d.x; - ev.y = d.y; - ev.state = mask; - - motion_handler (0, (GdkEvent*) &ev, true); - - } else { - stop_canvas_autoscroll (); - return false; - } - - autoscroll_cnt++; - - return true; /* call me again */ -} - -void -CueEditor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary) -{ - if (!_session) { - return; - } - - stop_canvas_autoscroll (); - - autoscroll_horizontal_allowed = allow_horiz; - autoscroll_vertical_allowed = allow_vert; - autoscroll_boundary = boundary; - - /* do the first scroll right now - */ - - autoscroll_canvas (); - - /* scroll again at very very roughly 30FPS */ - - autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &CueEditor::autoscroll_canvas), 30); -} - -void -CueEditor::stop_canvas_autoscroll () -{ - autoscroll_connection.disconnect (); - autoscroll_cnt = 0; -} - -void -CueEditor::visual_changer (const VisualChange& vc) -{ - /** - * Changed first so the correct horizontal canvas position is calculated in - * EditingContext::set_horizontal_position - */ - if (vc.pending & VisualChange::ZoomLevel) { - set_samples_per_pixel (vc.samples_per_pixel); - } - - if (vc.pending & VisualChange::TimeOrigin) { - double new_time_origin = sample_to_pixel_unrounded (vc.time_origin); - set_horizontal_position (new_time_origin); - update_rulers (); - } - - if (vc.pending & VisualChange::YOrigin) { - vertical_adjustment.set_value (vc.y_origin); - } - - if (vc.pending & VisualChange::ZoomLevel) { - if (!(vc.pending & VisualChange::TimeOrigin)) { - update_rulers (); - } - } else { - /* If the canvas is not being zoomed then the canvas items will not change - * and cause Item::prepare_for_render to be called so do it here manually. - * Not ideal, but I can't think of a better solution atm. - */ - get_canvas()->prepare_for_render(); - } - - /* If we are only scrolling vertically there is no need to update these */ - if (vc.pending != VisualChange::YOrigin) { - // XXX update_fixed_rulers (); - redisplay_grid (true); - } -} - -void -CueEditor::catch_pending_show_region () -{ - if (_visible_pending_region) { - set_region (_visible_pending_region); - _visible_pending_region.reset (); - } -} diff --git a/gtk2_ardour/cue_editor.h b/gtk2_ardour/cue_editor.h index 58132a2152..dbf6eae95b 100644 --- a/gtk2_ardour/cue_editor.h +++ b/gtk2_ardour/cue_editor.h @@ -20,25 +20,15 @@ #include "pbd/history_owner.h" -#include "widgets/ardour_button.h" -#include "widgets/eventboxext.h" - #include "editing.h" #include "editing_context.h" -namespace Gtk { - class HScrollbar; -} - class CueEditor : public EditingContext, public PBD::HistoryOwner { public: - CueEditor (std::string const & name, bool with_transport_controls); + CueEditor (std::string const & name); ~CueEditor (); - virtual Gtk::Widget& viewport() = 0; - virtual Gtk::Widget& contents () = 0; - void get_regionviews_by_id (PBD::ID const id, RegionSelection & regions) const; StripableTimeAxisView* get_stripable_time_axis_by_id (const PBD::ID& id) const; TrackViewList axis_views_from_routes (std::shared_ptr) const; @@ -56,7 +46,6 @@ class CueEditor : public EditingContext, public PBD::HistoryOwner void redisplay_grid (bool immediate_redraw); Temporal::timecnt_t get_nudge_distance (Temporal::timepos_t const & pos, Temporal::timecnt_t& next) const; - std::list selectable_owners() { return std::list(); } void instant_save(); @@ -108,89 +97,10 @@ class CueEditor : public EditingContext, public PBD::HistoryOwner std::shared_ptr start_local_tempo_map (std::shared_ptr); void end_local_tempo_map (std::shared_ptr); - void scrolled (); - bool canvas_pre_event (GdkEvent*); - void catch_pending_show_region (); - protected: - ARDOUR::TriggerReference ref; - std::shared_ptr _track; - bool with_transport_controls; - ArdourWidgets::EventBoxExt _contents; - Gtk::VBox _toolbox; - Gtk::HBox button_bar; - Gtk::HScrollbar* _canvas_hscrollbar; - - /* The group containing all other groups that are scrolled vertically - and horizontally. - */ - ArdourCanvas::ScrollGroup* hv_scroll_group; - - /* The group containing all other groups that are scrolled horizontally ONLY - */ - ArdourCanvas::ScrollGroup* h_scroll_group; - ArdourCanvas::ScrollGroup* v_scroll_group; - - /* Scroll group for cursors, scrolled horizontally, above everything else - */ - ArdourCanvas::ScrollGroup* cursor_scroll_group; - - ArdourCanvas::Container* global_rect_group; - ArdourCanvas::Container* no_scroll_group; - ArdourCanvas::Container* data_group; - - Gtk::Label length_label; - Gtk::HBox rec_box; - Gtk::HBox play_box; - - virtual void pack_inner (Gtk::Box&) = 0; - virtual void pack_outer (Gtk::Box&) = 0; - void build_zoom_focus_menu (); - - virtual void update_rulers() {} - virtual bool canvas_enter_leave (GdkEventCrossing* ev) = 0; - - void build_upper_toolbar (); void do_undo (uint32_t n); void do_redo (uint32_t n); Temporal::timepos_t _get_preferred_edit_position (Editing::EditIgnoreOption, bool use_context_click, bool from_outside_canvas); - - ArdourWidgets::ArdourButton rec_enable_button; - ArdourWidgets::ArdourButton play_button; - ArdourWidgets::ArdourButton solo_button; - ArdourWidgets::ArdourButton loop_button; - - ArdourCanvas::Rectangle* transport_loop_range_rect; - - bool play_button_press (GdkEventButton*); - bool solo_button_press (GdkEventButton*); - bool bang_button_press (GdkEventButton*); - bool loop_button_press (GdkEventButton*); - - ArdourWidgets::ArdourDropdown length_selector; - Temporal::BBT_Offset rec_length; - - bool zoom_in_allocate; - - void set_recording_length (Temporal::BBT_Offset bars); - virtual void set_region (std::shared_ptr) = 0; - - bool rec_button_press (GdkEventButton*); - void rec_enable_change (); - void blink_rec_enable (bool); - sigc::connection rec_blink_connection; - - void trigger_arm_change (); - - double timebar_height; - size_t n_timebars; - - /* autoscrolling */ - - bool autoscroll_canvas (); - void start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary); - void visual_changer (const VisualChange&); - - std::shared_ptr _visible_pending_region; }; + diff --git a/gtk2_ardour/editing_context.h b/gtk2_ardour/editing_context.h index 64a734d6f6..5ded2331b4 100644 --- a/gtk2_ardour/editing_context.h +++ b/gtk2_ardour/editing_context.h @@ -258,9 +258,6 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider, double timeline_to_canvas (double p) const { return p + _timeline_origin; } double canvas_to_timeline (double p) const { return p - _timeline_origin; } - double visible_canvas_width () const { return _visible_canvas_width; } - double visible_canvas_height () const { return _visible_canvas_height; } - /** computes the timeline position for an event whose coordinates * are in canvas units (pixels, scroll offset included). The time * domain used by the return value will match ::default_time_domain() @@ -636,6 +633,7 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider, bool ensure_snap = false) const = 0; void check_best_snap (Temporal::timepos_t const & presnap, Temporal::timepos_t &test, Temporal::timepos_t &dist, Temporal::timepos_t &best) const; + virtual double visible_canvas_width() const = 0; enum BBTRulerScale { bbt_show_many, diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index c67a0e0328..485374512f 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -177,6 +177,9 @@ public: return (samplecnt_t) _visible_canvas_width* samples_per_pixel; } + double visible_canvas_height () const { + return _visible_canvas_height; + } double trackviews_height () const; XMLNode& get_state () const; @@ -2122,6 +2125,8 @@ private: Temporal::timepos_t snap_to_marker (Temporal::timepos_t const & presnap, Temporal::RoundMode direction = Temporal::RoundNearest) const; + double visible_canvas_width() const { return _visible_canvas_width; } + RhythmFerret* rhythm_ferret; void fit_tracks (TrackViewList &); diff --git a/gtk2_ardour/midi_trigger_properties_box.h b/gtk2_ardour/midi_trigger_properties_box.h index 53aec52ad0..15e59c3358 100644 --- a/gtk2_ardour/midi_trigger_properties_box.h +++ b/gtk2_ardour/midi_trigger_properties_box.h @@ -25,7 +25,7 @@ #include "ardour/ardour.h" #include "ardour/triggerbox.h" -#include "trigger_properties_box.h" +#include "audio_trigger_properties_box.h" class MidiTriggerPropertiesBox : public TriggerPropertiesBox { diff --git a/gtk2_ardour/pianoroll.cc b/gtk2_ardour/pianoroll.cc index ab5ac72c63..273964f135 100644 --- a/gtk2_ardour/pianoroll.cc +++ b/gtk2_ardour/pianoroll.cc @@ -66,13 +66,19 @@ using namespace Gtkmm2ext; using namespace Temporal; Pianoroll::Pianoroll (std::string const & name, bool with_transport) - : CueEditor (name, with_transport) + : CueEditor (name) + , timebar_height (15.) + , n_timebars (0) , prh (nullptr) , bg (nullptr) , view (nullptr) , bbt_metric (*this) , _note_mode (Sustained) + , zoom_in_allocate (false) + , solo_button (S_("Solo|S")) + , length_label (X_("Record:")) , ignore_channel_changes (false) + , with_transport_controls (with_transport) , show_source (false) { mouse_mode = Editing::MouseContent; @@ -142,6 +148,23 @@ Pianoroll::get_canvas() const return _canvas; } +bool +Pianoroll::canvas_pre_event (GdkEvent* ev) +{ + switch (ev->type) { + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + if (canvas_enter_leave (&ev->crossing)) { + return true; + } + break; + default: + break; + } + + return false; +} + void Pianoroll::rebuild_parameter_button_map() { @@ -251,6 +274,15 @@ Pianoroll::add_multi_controller_item (Gtk::Menu_Helpers::MenuList&, mb->add_item (name, menu_text, *chn_menu, [](){}); } + +void +Pianoroll::scrolled () +{ + pending_visual_change.add (VisualChange::TimeOrigin); + pending_visual_change.time_origin = horizontal_adjustment.get_value() * samples_per_pixel; + ensure_visual_change_idle_handler (); +} + void Pianoroll::build_lower_toolbar () { @@ -323,24 +355,145 @@ Pianoroll::build_lower_toolbar () } void -Pianoroll::pack_inner (Gtk::Box& box) +Pianoroll::build_upper_toolbar () { - box.pack_start (snap_box, false, false); - box.pack_start (grid_box, false, false); - box.pack_start (draw_box, false, false); + using namespace Gtk::Menu_Helpers; + + Gtk::HBox* mode_box = manage(new Gtk::HBox); + mode_box->set_border_width (2); + mode_box->set_spacing(2); + + Gtk::HBox* mouse_mode_box = manage (new Gtk::HBox); + Gtk::HBox* mouse_mode_hbox = manage (new Gtk::HBox); + Gtk::VBox* mouse_mode_vbox = manage (new Gtk::VBox); + Gtk::Alignment* mouse_mode_align = manage (new Gtk::Alignment); + + Glib::RefPtr mouse_mode_size_group = Gtk::SizeGroup::create (Gtk::SIZE_GROUP_VERTICAL); + mouse_mode_size_group->add_widget (mouse_draw_button); + mouse_mode_size_group->add_widget (mouse_content_button); + + mouse_mode_size_group->add_widget (grid_type_selector); + mouse_mode_size_group->add_widget (draw_length_selector); + mouse_mode_size_group->add_widget (draw_velocity_selector); + mouse_mode_size_group->add_widget (draw_channel_selector); + mouse_mode_size_group->add_widget (snap_mode_button); + + mouse_mode_hbox->set_spacing (2); + mouse_mode_hbox->pack_start (mouse_draw_button, false, false); + mouse_mode_hbox->pack_start (mouse_content_button, false, false); + + mouse_mode_vbox->pack_start (*mouse_mode_hbox); + + mouse_mode_align->add (*mouse_mode_vbox); + mouse_mode_align->set (0.5, 1.0, 0.0, 0.0); + + mouse_mode_box->pack_start (*mouse_mode_align, false, false); + + pack_snap_box (); + pack_draw_box (false); + + Gtk::HBox* _toolbar_inner = manage (new Gtk::HBox); + Gtk::HBox* _toolbar_outer = manage (new Gtk::HBox); + Gtk::HBox* _toolbar_left = manage (new Gtk::HBox); + + _toolbar_inner->pack_start (*mouse_mode_box, false, false); + _toolbar_inner->pack_start (snap_box, false, false); + _toolbar_inner->pack_start (grid_box, false, false); + _toolbar_inner->pack_start (draw_box, false, false); + + set_tooltip (full_zoom_button, _("Zoom to full clip")); + set_tooltip (note_mode_button, _("Toggle between drum and regular note drawing")); + note_mode_button.set_icon (ArdourIcon::Drum); + + play_note_selection_button.set_icon (ArdourIcon::ToolAudition); + +#define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale())) + note_mode_button.set_size_request (PX_SCALE(50), -1); + note_mode_button.set_active_color (UIConfiguration::instance().color ("alert:yellow")); + + play_button.set_icon (ArdourIcon::TransportPlay); + play_button.set_name ("transport button"); + play_button.show(); + + if (with_transport_controls) { + loop_button.set_icon (ArdourIcon::TransportLoop); + loop_button.set_name ("transport button"); + + solo_button.set_name ("solo button"); + + play_box.set_spacing (8); + play_box.pack_start (play_button, false, false); + play_box.pack_start (loop_button, false, false); + play_box.pack_start (solo_button, false, false); + loop_button.show(); + solo_button.show(); + play_box.set_no_show_all (true); + play_box.show (); + + play_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::play_button_press), false); + solo_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::solo_button_press), false); + loop_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::loop_button_press), false); + } else { + rec_box.pack_start (play_button, false, false); + play_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::bang_button_press), false); + } + + rec_enable_button.set_icon (ArdourIcon::RecButton); + rec_enable_button.set_sensitive (false); + rec_enable_button.signal_button_release_event().connect (sigc::mem_fun (*this, &Pianoroll::rec_button_press), false); + rec_enable_button.set_name ("record enable button"); + + length_selector.add_menu_elem (MenuElem (_("Until Stopped"), sigc::bind (sigc::mem_fun (*this, &Pianoroll::set_recording_length), Temporal::BBT_Offset ()))); + length_selector.add_menu_elem (MenuElem (_("1 Bar"), sigc::bind (sigc::mem_fun (*this, &Pianoroll::set_recording_length), Temporal::BBT_Offset (1, 0, 0)))); + std::vector b ({ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 20, 24, 32 }); + for (auto & n : b) { + length_selector.add_menu_elem (MenuElem (string_compose (_("%1 Bars"), n), sigc::bind (sigc::mem_fun (*this, &Pianoroll::set_recording_length), Temporal::BBT_Offset (n, 0, 0)))); + } + length_selector.set_active (_("Until Stopped")); + + rec_box.set_spacing (12); + rec_box.pack_start (rec_enable_button, false, false); + rec_box.pack_start (length_label, false, false); + rec_box.pack_start (length_selector, false, false); + rec_enable_button.show(); + length_label.show (); + length_selector.show (); + rec_box.set_no_show_all (true); + /* rec box not shown */ + + _toolbar_outer->set_border_width (6); + _toolbar_outer->set_spacing (12); + if (with_transport_controls) { + _toolbar_outer->pack_start (play_box, false, false); + } + _toolbar_outer->pack_start (rec_box, false, false); + _toolbar_outer->pack_start (visible_channel_label, false, false); + _toolbar_outer->pack_start (visible_channel_selector, false, false); + _toolbar_outer->pack_start (play_note_selection_button, false, false); + _toolbar_outer->pack_start (note_mode_button, false, false); + _toolbar_outer->pack_start (follow_playhead_button, false, false); + _toolbar_outer->pack_start (*_toolbar_inner, true, false); + + build_zoom_focus_menu (); + zoom_focus_selector.set_text (zoom_focus_strings[(int)_zoom_focus]); + + _toolbar_left->pack_start (zoom_in_button, false, false); + _toolbar_left->pack_start (zoom_out_button, false, false); + _toolbar_left->pack_start (full_zoom_button, false, false); + _toolbar_left->pack_start (zoom_focus_selector, false, false); + + _toolbar_outer->pack_start (*_toolbar_left, true, false); + _toolbox.pack_start (*_toolbar_outer, false, false); + + _contents.add (_toolbox); + _contents.signal_unmap().connect ([this]() {_canvas_viewport->unmap ();}, false); + _contents.signal_map().connect ([this]() {_canvas_viewport->map ();}, false); } void -Pianoroll::pack_outer (Gtk::Box& box) +Pianoroll::set_recording_length (Temporal::BBT_Offset dur) { - if (with_transport_controls) { - box.pack_start (play_box, false, false); - } - - box.pack_start (rec_box, false, false); - box.pack_start (visible_channel_label, false, false); - box.pack_start (visible_channel_selector, false, false); - box.pack_start (follow_playhead_button, false, false); + rec_length = dur; } void @@ -504,6 +657,15 @@ Pianoroll::build_canvas () _toolbox.pack_start (*_canvas_viewport, true, true); } +void +Pianoroll::catch_pending_show_region () +{ + if (_visible_pending_region) { + set_region (_visible_pending_region); + _visible_pending_region.reset (); + } +} + bool Pianoroll::bbt_ruler_event (GdkEvent* ev) { @@ -1538,6 +1700,365 @@ Pianoroll::get_state () const get_common_editing_state (*node); return *node; } + +/** @param allow_horiz true to allow horizontal autoscroll, otherwise false. + * + * @param allow_vert true to allow vertical autoscroll, otherwise false. + * + */ +void +Pianoroll::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers) +{ + if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) { + return; + } + + /* define a rectangular boundary for scrolling. If the mouse moves + * outside of this area and/or continue to be outside of this area, + * then we will continuously auto-scroll the canvas in the appropriate + * direction(s) + * + * the boundary is defined in coordinates relative to canvas' own + * window since that is what we're going to call ::get_pointer() on + * during autoscrolling to determine if we're still outside the + * boundary or not. + */ + + ArdourCanvas::Rect scrolling_boundary; + Gtk::Allocation alloc; + + alloc = get_canvas()->get_allocation (); + + alloc.set_x (0); + alloc.set_y (0); + + if (allow_vert) { + /* reduce height by the height of the timebars, which happens + to correspond to the position of the data_group. + */ + + alloc.set_height (alloc.get_height() - data_group->position().y); + alloc.set_y (alloc.get_y() + data_group->position().y); + + /* now reduce it again so that we start autoscrolling before we + * move off the top or bottom of the canvas + */ + + alloc.set_height (alloc.get_height() - 20); + alloc.set_y (alloc.get_y() + 10); + } + + if (allow_horiz && (alloc.get_width() > 20)) { + + if (prh) { + double w, h; + prh->size_request (w, h); + + alloc.set_width (alloc.get_width() - w); + alloc.set_x (alloc.get_x() + w); + } + + /* the effective width of the autoscroll boundary so + that we start scrolling before we hit the edge. + + this helps when the window is slammed up against the + right edge of the screen, making it hard to scroll + effectively. + */ + + alloc.set_width (alloc.get_width() - 20); + alloc.set_x (alloc.get_x() + 10); + } + + scrolling_boundary = ArdourCanvas::Rect (alloc.get_x(), alloc.get_y(), alloc.get_x() + alloc.get_width(), alloc.get_y() + alloc.get_height()); + + int x, y; + Gdk::ModifierType mask; + + get_canvas()->get_window()->get_pointer (x, y, mask); + + if ((allow_horiz && ((x < scrolling_boundary.x0 && _leftmost_sample > 0) || x >= scrolling_boundary.x1)) || + (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) { + start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary); + } +} + +bool +Pianoroll::autoscroll_active () const +{ + return autoscroll_connection.connected (); +} + +bool +Pianoroll::autoscroll_canvas () +{ + using std::max; + using std::min; + int x, y; + Gdk::ModifierType mask; + sampleoffset_t dx = 0; + bool no_stop = false; + Gtk::Window* toplevel = dynamic_cast(_canvas_viewport->get_toplevel()); + + if (!toplevel) { + return false; + } + + get_canvas()->get_window()->get_pointer (x, y, mask); + + VisualChange vc; + bool vertical_motion = false; + + if (autoscroll_horizontal_allowed) { + + samplepos_t new_sample = _leftmost_sample; + + /* horizontal */ + + if (x > autoscroll_boundary.x1) { + + /* bring it back into view */ + dx = x - autoscroll_boundary.x1; + dx += 10 + (2 * (autoscroll_cnt/2)); + + dx = pixel_to_sample (dx); + + dx *= UIConfiguration::instance().get_draggable_playhead_speed(); + + if (_leftmost_sample < max_samplepos - dx) { + new_sample = _leftmost_sample + dx; + } else { + new_sample = max_samplepos; + } + + no_stop = true; + + } else if (x < autoscroll_boundary.x0) { + + dx = autoscroll_boundary.x0 - x; + dx += 10 + (2 * (autoscroll_cnt/2)); + + dx = pixel_to_sample (dx); + + dx *= UIConfiguration::instance().get_draggable_playhead_speed(); + + if (_leftmost_sample >= dx) { + new_sample = _leftmost_sample - dx; + } else { + new_sample = 0; + } + + no_stop = true; + } + + if (new_sample != _leftmost_sample) { + vc.time_origin = new_sample; + vc.add (VisualChange::TimeOrigin); + } + } + + if (autoscroll_vertical_allowed) { + + // const double vertical_pos = vertical_adjustment.get_value(); + const int speed_factor = 10; + + /* vertical */ + + if (y < autoscroll_boundary.y0) { + + /* scroll to make higher tracks visible */ + + if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) { + // XXX SCROLL UP + vertical_motion = true; + } + no_stop = true; + + } else if (y > autoscroll_boundary.y1) { + + if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) { + // XXX SCROLL DOWN + vertical_motion = true; + } + no_stop = true; + } + + } + + if (vc.pending || vertical_motion) { + + /* change horizontal first */ + + if (vc.pending) { + visual_changer (vc); + } + + /* now send a motion event to notify anyone who cares + that we have moved to a new location (because we scrolled) + */ + + GdkEventMotion ev; + + ev.type = GDK_MOTION_NOTIFY; + ev.state = Gdk::BUTTON1_MASK; + + /* the motion handler expects events in canvas coordinate space */ + + /* we asked for the mouse position above (::get_pointer()) via + * our own top level window (we being the Editor). Convert into + * coordinates within the canvas window. + */ + + int cx; + int cy; + + //toplevel->translate_coordinates (*get_canvas(), x, y, cx, + //cy); + cx = x; + cy = y; + + /* clamp x and y to remain within the autoscroll boundary, + * which is defined in window coordinates + */ + + x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1); + y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1); + + /* now convert from Editor window coordinates to canvas + * window coordinates + */ + + ArdourCanvas::Duple d = get_canvas()->window_to_canvas (ArdourCanvas::Duple (cx, cy)); + ev.x = d.x; + ev.y = d.y; + ev.state = mask; + + motion_handler (0, (GdkEvent*) &ev, true); + + } else if (no_stop) { + + /* not changing visual state but pointer is outside the scrolling boundary + * so we still need to deliver a fake motion event + */ + + GdkEventMotion ev; + + ev.type = GDK_MOTION_NOTIFY; + ev.state = Gdk::BUTTON1_MASK; + + /* the motion handler expects events in canvas coordinate space */ + + /* first convert from Editor window coordinates to canvas + * window coordinates + */ + + int cx; + int cy; + + /* clamp x and y to remain within the visible area. except + * .. if horizontal scrolling is allowed, always allow us to + * move back to zero + */ + + if (autoscroll_horizontal_allowed) { + x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1); + } else { + x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1); + } + y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1); + + // toplevel->translate_coordinates (*get_canvas_viewport(), x, + // y, cx, cy); + cx = x; + cy = y; + + ArdourCanvas::Duple d = get_canvas()->window_to_canvas (ArdourCanvas::Duple (cx, cy)); + ev.x = d.x; + ev.y = d.y; + ev.state = mask; + + motion_handler (0, (GdkEvent*) &ev, true); + + } else { + stop_canvas_autoscroll (); + return false; + } + + autoscroll_cnt++; + + return true; /* call me again */ +} + +void +Pianoroll::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary) +{ + if (!_session) { + return; + } + + stop_canvas_autoscroll (); + + autoscroll_horizontal_allowed = allow_horiz; + autoscroll_vertical_allowed = allow_vert; + autoscroll_boundary = boundary; + + /* do the first scroll right now + */ + + autoscroll_canvas (); + + /* scroll again at very very roughly 30FPS */ + + autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Pianoroll::autoscroll_canvas), 30); +} + +void +Pianoroll::stop_canvas_autoscroll () +{ + autoscroll_connection.disconnect (); + autoscroll_cnt = 0; +} + +void +Pianoroll::visual_changer (const VisualChange& vc) +{ + /** + * Changed first so the correct horizontal canvas position is calculated in + * EditingContext::set_horizontal_position + */ + if (vc.pending & VisualChange::ZoomLevel) { + set_samples_per_pixel (vc.samples_per_pixel); + } + + if (vc.pending & VisualChange::TimeOrigin) { + double new_time_origin = sample_to_pixel_unrounded (vc.time_origin); + set_horizontal_position (new_time_origin); + update_tempo_based_rulers (); + } + + if (vc.pending & VisualChange::YOrigin) { + vertical_adjustment.set_value (vc.y_origin); + } + + if (vc.pending & VisualChange::ZoomLevel) { + if (!(vc.pending & VisualChange::TimeOrigin)) { + update_tempo_based_rulers (); + } + } else { + /* If the canvas is not being zoomed then the canvas items will not change + * and cause Item::prepare_for_render to be called so do it here manually. + * Not ideal, but I can't think of a better solution atm. + */ + get_canvas()->prepare_for_render(); + } + + /* If we are only scrolling vertically there is no need to update these */ + if (vc.pending != VisualChange::YOrigin) { + // XXX update_fixed_rulers (); + redisplay_grid (true); + } +} + void Pianoroll::midi_action (void (MidiView::*method)()) { @@ -1775,15 +2296,65 @@ Pianoroll::region_prop_change (PBD::PropertyChange const & what_changed) } void -Pianoroll::maybe_set_count_in () +Pianoroll::blink_rec_enable (bool onoff) { - if (!ref.box()) { - std::cerr << "msci no box\n"; + if (onoff) { + rec_enable_button.set_active_state (Gtkmm2ext::ExplicitActive); + } else { + rec_enable_button.set_active_state (Gtkmm2ext::Off); + } +} + +void +Pianoroll::trigger_arm_change () +{ + if (!ref.trigger()) { return; } - if (ref.box()->record_enabled() == Disabled) { - std::cerr << "msci RE\n"; + if (!ref.trigger()->armed()) { + view->end_write (); + } + + rec_enable_change (); +} + +void +Pianoroll::rec_enable_change () +{ + if (!ref.box()) { + return; + } + + rec_blink_connection.disconnect (); + count_in_connection.disconnect (); + + switch (ref.box()->record_enabled()) { + case Recording: + rec_enable_button.set_active_state (Gtkmm2ext::ExplicitActive); + rec_blink_connection.disconnect (); + if (view) { + view->begin_write (); + } + break; + case Enabled: + if (!UIConfiguration::instance().get_no_strobe() && ref.trigger()->armed()) { + rec_blink_connection = Timers::blink_connect (sigc::mem_fun (*this, &Pianoroll::blink_rec_enable)); + } else { + rec_enable_button.set_active_state (Gtkmm2ext::Off); + } + maybe_set_count_in (); + break; + case Disabled: + rec_enable_button.set_active_state (Gtkmm2ext::Off); + break; + } +} + +void +Pianoroll::maybe_set_count_in () +{ + if (!ref.box()) { return; } @@ -1794,7 +2365,6 @@ Pianoroll::maybe_set_count_in () count_in_to = ref.box()->start_time (valid); if (!valid) { - std::cerr << "no start time\n"; return; } @@ -1802,12 +2372,10 @@ Pianoroll::maybe_set_count_in () Temporal::Beats const & a_q (tmap->quarters_at_sample (audible)); if ((count_in_to - a_q).get_beats() == 0) { - std::cerr << "not enough time\n"; return; } count_in_connection = ARDOUR_UI::Clock.connect (sigc::bind (sigc::mem_fun (*this, &Pianoroll::count_in), ARDOUR_UI::clock_signal_interval())); - std::cerr << "count in started, with view " << view << std::endl; } void @@ -1850,23 +2418,92 @@ Pianoroll::count_in (Temporal::timepos_t audible, unsigned int clock_interval_ms } std::string str (string_compose ("%1", current_delta.get_beats())); - std::cerr << str << std::endl; view->set_overlay_text (str); } } -void -Pianoroll::set_region (std::shared_ptr r) +bool +Pianoroll::play_button_press (GdkEventButton* ev) { - set_region (std::dynamic_pointer_cast (r)); + _session->request_locate (view->midi_region()->position().samples()); + _session->request_roll (); + return true; +} + + +bool +Pianoroll::bang_button_press (GdkEventButton* ev) +{ + if (!ref.trigger()) { + return true; + } + + ref.trigger()->bang (); + + return true; +} + +bool +Pianoroll::loop_button_press (GdkEventButton* ev) +{ + if (!view) { + return true; + } + if (!view->midi_region()) { + return true; + } + + if (_session->get_play_loop()) { + _session->request_play_loop (false); + } else { + set_loop_range (view->midi_region()->position(), view->midi_region()->end(), _("loop region")); + _session->request_play_loop (true); + } + + return true; +} + +bool +Pianoroll::solo_button_press (GdkEventButton* ev) +{ + if (!view) { + return true; + } + + if (!view->midi_track()) { + return true; + } + + view->midi_track()->solo_control()->set_value (!view->midi_track()->solo_control()->get_value(), Controllable::NoGroup); + + return true; +} + +bool +Pianoroll::rec_button_press (GdkEventButton* ev) +{ + if (ev->button != 1) { + return false; + } + + TriggerPtr trigger (ref.trigger()); + + if (!trigger) { + return true; + } + + if (trigger->armed()) { + trigger->disarm (); + } else { + trigger->arm (rec_length); + } + + return true; } void Pianoroll::set_trigger (TriggerReference & tref) { - std::cerr << "set trigger\n"; - PBD::stacktrace (std::cerr, 17); - if (tref.trigger() == ref.trigger()) { return; } @@ -1885,8 +2522,6 @@ Pianoroll::set_trigger (TriggerReference & tref) /* Don't bind a shared_ptr within the lambda */ TriggerBox* tb (ref.box().get()); tb->RecEnableChanged.connect (object_connections, invalidator (*this), std::bind (&Pianoroll::rec_enable_change, this), gui_context()); - std::cerr << "connected to box " << tb->order() << std::endl; - maybe_set_count_in (); Stripable* st = dynamic_cast (ref.box()->owner()); assert (st); @@ -1942,7 +2577,6 @@ Pianoroll::unset (bool trigger_too) _history.clear (); _update_connection.disconnect(); object_connections.drop_connections (); - std::cerr << "disconnected\n"; _track.reset (); view->set_region (nullptr); if (trigger_too) { @@ -1997,9 +2631,6 @@ Pianoroll::set_region (std::shared_ptr r) return; } - std::cerr << editor_name() << " set region to " << r << std::endl; - PBD::stacktrace (std::cerr, 19); - unset (false); if (!r) { @@ -2195,6 +2826,20 @@ Pianoroll::set_note_mode (NoteMode nm) } } +void +Pianoroll::build_zoom_focus_menu () +{ + using namespace Gtk::Menu_Helpers; + using namespace Editing; + + zoom_focus_selector.add_menu_elem (MenuElem (zoom_focus_strings[(int)ZoomFocusLeft], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusLeft))); + zoom_focus_selector.add_menu_elem (MenuElem (zoom_focus_strings[(int)ZoomFocusRight], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusRight))); + zoom_focus_selector.add_menu_elem (MenuElem (zoom_focus_strings[(int)ZoomFocusCenter], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusCenter))); + zoom_focus_selector.add_menu_elem (MenuElem (zoom_focus_strings[(int)ZoomFocusMouse], sigc::bind (sigc::mem_fun(*this, &EditingContext::zoom_focus_selection_done), (ZoomFocus) ZoomFocusMouse))); + zoom_focus_selector.set_sizing_texts (zoom_focus_strings); +} + + std::pair Pianoroll::max_zoom_extent() const { diff --git a/gtk2_ardour/pianoroll.h b/gtk2_ardour/pianoroll.h index 6eed69d582..46b8604797 100644 --- a/gtk2_ardour/pianoroll.h +++ b/gtk2_ardour/pianoroll.h @@ -63,6 +63,7 @@ class Pianoroll : public CueEditor Gtk::Widget& viewport(); Gtk::Widget& contents (); + double visible_canvas_width() const { return _visible_canvas_width; } samplecnt_t current_page_samples() const; void get_per_region_note_selection (std::list > > > >&) const {} @@ -83,7 +84,6 @@ class Pianoroll : public CueEditor void set_trigger (ARDOUR::TriggerReference&); void set_region (std::shared_ptr); - void set_region (std::shared_ptr r); void set_track (std::shared_ptr); ArdourCanvas::ScrollGroup* get_hscroll_group () const { return h_scroll_group; } @@ -97,12 +97,20 @@ class Pianoroll : public CueEditor Editing::MouseMode current_mouse_mode () const; bool internal_editing() const; + void trigger_arm_change (); + + double timebar_height; + size_t n_timebars; + ArdourCanvas::GtkCanvasViewport* get_canvas_viewport() const; ArdourCanvas::GtkCanvas* get_canvas() const; int set_state (const XMLNode&, int version); XMLNode& get_state () const; + void maybe_autoscroll (bool, bool, bool); + bool autoscroll_active() const; + void midi_action (void (MidiView::*method)()); std::list selectable_owners(); @@ -175,14 +183,41 @@ class Pianoroll : public CueEditor void escape (); private: + ARDOUR::TriggerReference ref; + std::shared_ptr _track; ArdourCanvas::GtkCanvasViewport* _canvas_viewport; ArdourCanvas::GtkCanvas* _canvas; + Gtk::HScrollbar* _canvas_hscrollbar; + + /* The group containing all other groups that are scrolled vertically + and horizontally. + */ + ArdourCanvas::ScrollGroup* hv_scroll_group; + + /* The group containing all other groups that are scrolled horizontally ONLY + */ + ArdourCanvas::ScrollGroup* h_scroll_group; + ArdourCanvas::ScrollGroup* v_scroll_group; + + /* Scroll group for cursors, scrolled horizontally, above everything else + */ + ArdourCanvas::ScrollGroup* cursor_scroll_group; + + ArdourCanvas::Container* global_rect_group; + ArdourCanvas::Container* no_scroll_group; + ArdourCanvas::Container* data_group; ArdourCanvas::Ruler* bbt_ruler; ArdourCanvas::Rectangle* tempo_bar; ArdourCanvas::Rectangle* meter_bar; ArdourCanvas::PianoRollHeader* prh; + ArdourCanvas::Rectangle* transport_loop_range_rect; + + ArdourWidgets::EventBoxExt _contents; + Gtk::VBox _toolbox; + + Gtk::HBox button_bar; ArdourWidgets::ArdourButton* velocity_button; ArdourWidgets::ArdourButton* bender_button; ArdourWidgets::ArdourButton* pressure_button; @@ -225,6 +260,14 @@ class Pianoroll : public CueEditor BBTMetric bbt_metric; + bool canvas_pre_event (GdkEvent*); + + /* autoscrolling */ + + bool autoscroll_canvas (); + void start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary); + void stop_canvas_autoscroll (); + sigc::connection _update_connection; PBD::ScopedConnectionList object_connections; PBD::ScopedConnectionList view_connections; @@ -234,6 +277,7 @@ class Pianoroll : public CueEditor void unset (bool trigger_too); + void visual_changer (const VisualChange&); void bindings_changed (); void data_captured (samplecnt_t); @@ -255,10 +299,37 @@ class Pianoroll : public CueEditor void automation_state_changed (); + void build_zoom_focus_menu (); + std::pair max_zoom_extent() const; void point_selection_changed (); + bool zoom_in_allocate; + + ArdourWidgets::ArdourButton rec_enable_button; + ArdourWidgets::ArdourButton play_button; + ArdourWidgets::ArdourButton solo_button; + ArdourWidgets::ArdourButton loop_button; + + bool play_button_press (GdkEventButton*); + bool bang_button_press (GdkEventButton*); + bool solo_button_press (GdkEventButton*); + bool loop_button_press (GdkEventButton*); + + ArdourWidgets::ArdourDropdown length_selector; + Temporal::BBT_Offset rec_length; + Gtk::Label length_label; + Gtk::HBox rec_box; + Gtk::HBox play_box; + + void set_recording_length (Temporal::BBT_Offset bars); + + bool rec_button_press (GdkEventButton*); + void rec_enable_change (); + void blink_rec_enable (bool); + sigc::connection rec_blink_connection; + void add_single_controller_item (Gtk::Menu_Helpers::MenuList& ctl_items, int ctl, const std::string& name, ArdourWidgets::MetaButton*); void add_multi_controller_item (Gtk::Menu_Helpers::MenuList& ctl_items, uint16_t channels, int ctl, const std::string& name, ArdourWidgets::MetaButton*); void reset_user_cc_choice (std::string, Evoral::Parameter param, ArdourWidgets::MetaButton*); @@ -278,18 +349,18 @@ class Pianoroll : public CueEditor bool bbt_ruler_event (GdkEvent*); void ruler_locate (GdkEventButton*); + void scrolled (); void update_tempo_based_rulers (); - void update_rulers() { update_tempo_based_rulers (); } Gtk::Menu _region_context_menu; void popup_region_context_menu (ArdourCanvas::Item* item, GdkEvent* event); + std::shared_ptr _visible_pending_region; + void catch_pending_show_region (); + bool show_source; void set_note_selection (uint8_t note); void add_note_selection (uint8_t note); void extend_note_selection (uint8_t note); void toggle_note_selection (uint8_t note); - - void pack_inner (Gtk::Box&); - void pack_outer (Gtk::Box&); }; diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index 4a173937e1..295a0081a6 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -287,6 +287,8 @@ public: /** @return true if the playhead is currently being dragged, otherwise false */ virtual bool dragging_playhead () const = 0; virtual samplepos_t leftmost_sample() const = 0; + virtual samplecnt_t current_page_samples() const = 0; + virtual double visible_canvas_height () const = 0; virtual void ensure_time_axis_view_is_visible (TimeAxisView const & tav, bool at_top = false) = 0; virtual void override_visible_track_count () = 0; virtual void scroll_tracks_down_line () = 0; diff --git a/gtk2_ardour/trigger_page.cc b/gtk2_ardour/trigger_page.cc index 960af89f6b..9fd6180308 100644 --- a/gtk2_ardour/trigger_page.cc +++ b/gtk2_ardour/trigger_page.cc @@ -144,7 +144,6 @@ TriggerPage::TriggerPage () _sidebar_pager2.set_index (3); _midi_editor = new Pianoroll (X_("MIDICueEditor")); - _audio_editor = new AudioClipEditor (X_("AudioClipEditor"), true); /* Bottom -- Properties of selected Slot/Region */ @@ -476,7 +475,9 @@ TriggerPage::trigger_arm_changed (Trigger const * trigger) /* hide everything */ - hide_all (); + _audio_trig_box.hide (); + _midi_trig_box.hide (); + _midi_editor->viewport().hide (); Tabbable::showhide_att_bottom (false); @@ -484,12 +485,10 @@ TriggerPage::trigger_arm_changed (Trigger const * trigger) TriggerReference ref (trigger->boxptr(), trigger->index()); if (box.data_type () == DataType::AUDIO) { - - _audio_trig_box.set_trigger (ref); - _audio_trig_box.show (); - - // _audio_editor->set_trigger (ref); - _audio_editor->viewport().show (); + if (trigger->the_region()) { + _audio_trig_box.set_trigger (ref); + _audio_trig_box.show (); + } } else { @@ -505,30 +504,20 @@ TriggerPage::trigger_arm_changed (Trigger const * trigger) } } -void -TriggerPage::hide_all () -{ - _audio_trig_box.hide (); - _audio_editor->viewport().hide (); - _audio_trig_box.hide (); - _midi_trig_box.hide (); -} - void TriggerPage::selection_changed () { Selection& selection (Editor::instance ().get_selection ()); - hide_all (); + /* hide everything */ + + _audio_trig_box.hide (); + _midi_trig_box.hide (); if (_midi_editor->contents().get_parent()) { _midi_editor->contents().get_parent()->remove (_midi_editor->contents()); } - if (_audio_editor->contents().get_parent()) { - _audio_editor->contents().get_parent()->remove (_audio_editor->contents()); - } - Tabbable::showhide_att_bottom (false); if (selection.triggers.empty ()) { diff --git a/gtk2_ardour/trigger_page.h b/gtk2_ardour/trigger_page.h index 2be4d4b8ce..2d963983f9 100644 --- a/gtk2_ardour/trigger_page.h +++ b/gtk2_ardour/trigger_page.h @@ -30,7 +30,6 @@ #include "widgets/tabbable.h" #include "application_bar.h" -#include "audio_clip_editor.h" #include "audio_region_operations_box.h" #include "audio_trigger_properties_box.h" #include "axis_provider.h" @@ -77,7 +76,7 @@ private: void remove_route (TriggerStrip*); void clear_selected_slot (); - void hide_all (); + void redisplay_track_list (); void pi_property_changed (PBD::PropertyChange const&); void stripable_property_changed (PBD::PropertyChange const&, std::weak_ptr); @@ -141,10 +140,10 @@ private: #if REGION_PROPERTIES_BOX_TODO AudioRegionOperationsBox _audio_ops_box; + AudioClipEditorBox _audio_trim_box; #endif Pianoroll* _midi_editor; - AudioClipEditor* _audio_editor; RouteProcessorSelection _selection; std::list _strips; diff --git a/gtk2_ardour/trigger_properties_box.h b/gtk2_ardour/trigger_properties_box.h deleted file mode 100644 index ced266d82b..0000000000 --- a/gtk2_ardour/trigger_properties_box.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021 Paul Davis - * Copyright (C) 2021 Ben Loftis - * - * 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. - */ - -#pragma once - -#include -#include - -#include "pbd/signals.h" - -#include "ardour/session_handle.h" - -#include "trigger_ui.h" - -class TriggerPropertiesBox : public Gtk::Table, public ARDOUR::SessionHandlePtr, public TriggerUI -{ -public: - TriggerPropertiesBox () {} - ~TriggerPropertiesBox () {} - -protected: - Gtk::Label _header_label; - - PBD::ScopedConnection _state_connection; -}; - diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index 34d1d4b38f..aaffc821ab 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -300,7 +300,7 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { void arm (Temporal::BBT_Offset duration = Temporal::BBT_Offset()) { _arm (duration); } - virtual void disarm (bool disarm_box = true); + virtual void disarm (); bool armed() const { return _armed; } PBD::Signal ArmChanged; static PBD::Signal TriggerArmChanged; @@ -353,11 +353,11 @@ class LIBARDOUR_API Trigger : public PBD::Stateful { bool compute_quantized_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end, Temporal::BBT_Argument& t_bbt, Temporal::Beats& t_beats, samplepos_t& t_samples, - Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Offset const & q, int multiple = 0); + Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Offset const & q); pframes_t compute_next_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t nframes, Temporal::BBT_Argument& t_bbt, Temporal::Beats& t_beats, samplepos_t& t_samples, - Temporal::TempoMap::SharedPtr const & tmap, int multiple = 0); + Temporal::TempoMap::SharedPtr const & tmap); template @@ -616,7 +616,7 @@ class LIBARDOUR_API MIDITrigger : public Trigger { bool playable() const { return rt_midibuffer.load() || _region; } void captured (SlotArmInfo&, BufferSet&); - void disarm (bool disarm_box); + void disarm (); template pframes_t midi_run (BufferSet&, samplepos_t start_sample, samplepos_t end_sample, Temporal::Beats const & start_beats, Temporal::Beats const & end_beats, pframes_t nframes, pframes_t offset, double bpm, pframes_t& quantize_offset); @@ -848,8 +848,8 @@ class LIBARDOUR_API TriggerBox : public Processor, public std::enable_shared_fro static PBD::Signal TriggerRecEnableChanged; void arm_from_another_thread (Trigger& slot, samplepos_t, uint32_t chans, Temporal::BBT_Offset const &); - void disarm (); - void disarm_all(bool disarm_box); + void disarm(); + void disarm_all(); bool armed() const { return (bool) _arm_info.load(); } PBD::Signal ArmedChanged; diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 827d13860d..68d8321024 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -909,19 +909,6 @@ Session::request_stop (bool abort, bool clear_state, TransportRequestSource orig return; } - std::shared_ptr rl (routes.reader()); - for (auto & r : *rl) { - std::shared_ptr tb = r->triggerbox(); - bool was_clip_recording = false; - if (tb && tb->record_enabled() == Recording) { - tb->disarm_all (false); - was_clip_recording = true; - } - if (was_clip_recording) { - return; - } - } - /* clear our solo-selection, if there is one */ if ( solo_selection_active() ) { solo_selection ( _soloSelection, false ); diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index f4960f1022..7c5bd4ad7f 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -309,7 +309,7 @@ Trigger::_arm (Temporal::BBT_Offset const & duration) /* trigger arming is mutually exclusive within a given TriggerBox */ - _box.disarm_all (true); + _box.disarm_all (); int chns; @@ -329,17 +329,13 @@ Trigger::_arm (Temporal::BBT_Offset const & duration) } void -Trigger::disarm (bool disarm_box) +Trigger::disarm () { if (_armed) { _armed = false; - if (disarm_box) { - _box.disarm (); - } + _box.disarm (); ArmChanged(); /* EMIT SIGNAL */ - if (disarm_box) { - TriggerArmChanged (this); - } + TriggerArmChanged (this); } } @@ -1044,7 +1040,7 @@ Trigger::compute_start (Temporal::TempoMap::SharedPtr const & tmap, samplepos_t bool Trigger::compute_quantized_transition (samplepos_t start_sample, Temporal::Beats const & start_beats, Temporal::Beats const & end_beats, Temporal::BBT_Argument& t_bbt, Temporal::Beats& t_beats, samplepos_t& t_samples, - Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Offset const & q, int multiplier) + Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Offset const & q) { /* XXX need to use global grid here is quantization == zero */ @@ -1065,9 +1061,7 @@ Trigger::compute_quantized_transition (samplepos_t start_sample, Temporal::Beats } else if (q.bars == 0) { - Temporal::Beats qb (q.beats, q.ticks); - possible_beats = start_beats.round_up_to_multiple (qb); - possible_beats += (qb * multiplier); + possible_beats = start_beats.round_up_to_multiple (Temporal::Beats (q.beats, q.ticks)); possible_bbt = tmap->bbt_at (possible_beats); possible_samples = tmap->sample_at (possible_beats); @@ -1084,7 +1078,6 @@ Trigger::compute_quantized_transition (samplepos_t start_sample, Temporal::Beats if (possible_beats % qb != Temporal::Beats()) { possible_beats = ((tmap->quarters_at (start) + (qb/2)) / qb) * qb; } - possible_beats += (qb * multiplier); possible_bbt = tmap->bbt_at (possible_beats); possible_samples = tmap->sample_at (possible_beats); @@ -1109,7 +1102,7 @@ Trigger::compute_quantized_transition (samplepos_t start_sample, Temporal::Beats pframes_t Trigger::compute_next_transition (samplepos_t start_sample, Temporal::Beats const & start, Temporal::Beats const & end, pframes_t nframes, Temporal::BBT_Argument& t_bbt, Temporal::Beats& t_beats, samplepos_t& t_samples, - Temporal::TempoMap::SharedPtr const & tmap, int multiple) + Temporal::TempoMap::SharedPtr const & tmap) { using namespace Temporal; @@ -1134,7 +1127,7 @@ Trigger::compute_next_transition (samplepos_t start_sample, Temporal::Beats cons } - if (!compute_quantized_transition (start_sample, start, end, t_bbt, t_beats, t_samples, tmap, q, multiple)) { + if (!compute_quantized_transition (start_sample, start, end, t_bbt, t_beats, t_samples, tmap, q)) { /* no transition */ return 0; } @@ -2542,16 +2535,16 @@ MIDITrigger::_arm (Temporal::BBT_Offset const & duration) } void -MIDITrigger::disarm (bool disarm_box) +MIDITrigger::disarm () { - Trigger::disarm (disarm_box); + Trigger::disarm (); } void MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs) { if (ai.midi_buf->size() == 0) { - disarm (true); + disarm (); DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 captured but with no MIDI data\n", _box.order(), index())); return; } @@ -2580,7 +2573,7 @@ MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs) // std::cerr << "capture done, ask for a source of length " << dur.beats().str() << std::endl; TriggerBox::worker->request_build_source (this, timecnt_t (dur.beats())); - disarm (true); + disarm (); } void @@ -3700,8 +3693,6 @@ TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, uint32_t ch { using namespace Temporal; - std::cerr << "AFAT...\n"; - SlotArmInfo* ai = &_the_arm_info; /* Delete any dangling RTMidiBuffer and Stretcher from previous capture @@ -3729,9 +3720,8 @@ TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, uint32_t ch Beats now_beats = tmap->quarters_at (timepos_t (now)); slot.compute_quantized_transition (now, now_beats, std::numeric_limits::max(), - t_bbt, t_beats, t_samples, tmap, slot.quantization(), 2); + t_bbt, t_beats, t_samples, tmap, slot.quantization()); - std::cerr << " from " << now_beats.str() << " compute start at " << t_beats.str() << std::endl; ai->start_samples = t_samples; ai->start_beats = t_beats; @@ -3748,10 +3738,10 @@ TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, uint32_t ch } void -TriggerBox::disarm_all (bool disarm_box) +TriggerBox::disarm_all () { for (auto & t : all_triggers) { - t->disarm (disarm_box); + t->disarm (); } } @@ -3760,8 +3750,6 @@ TriggerBox::disarm () { /* This must be called as an alternative to ::finish_recording() */ - std::cerr << "disarmed\n"; - PBD::stacktrace (std::cerr, 17); _arm_info = nullptr; } @@ -3780,7 +3768,6 @@ TriggerBox::finish_recording (BufferSet& bufs) /* XXX this should likely be dependent on what the post-record action is */ _record_state = Disabled; - std::cerr << "all done!\n"; RecEnableChanged (); /* EMIT SIGNAL */ } @@ -3792,7 +3779,6 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_ SlotArmInfo* ai = _arm_info.load(); if (!ai) { - std::cerr << "no AI\n"; return; } @@ -3800,7 +3786,6 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_ bool reached_end = false; if (!ai->slot->armed()) { - std::cerr << "not armed!\n"; /* since _arm_info is set, we have been capturing for a slot, but now the slot is no longer armed. */ @@ -3820,13 +3805,10 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_ Beats now_beats = tmap->quarters_at (timepos_t (start_sample)); ai->slot->compute_quantized_transition (start_sample, now_beats, std::numeric_limits::max(), - t_bbt, t_beats, t_samples, tmap, ai->slot->quantization()); + t_bbt, t_beats, t_samples, tmap, ai->slot->quantization()); ai->end_samples = t_samples; ai->end_beats = t_beats; - std::cerr << "will stop at " << t_beats.str() << std::endl; } - } else { - std::cerr << "still armed\n"; } if (speed <= 0.) { @@ -3834,7 +3816,7 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_ /* We stopped the transport, so just stop immediately (no quantization) */ finish_recording (bufs); } - /* we stopped or reversed, but were not recording. Nothing to do here */ + /* we stopped or reversed, but were not recording. Nothing to do here */ return; } @@ -3942,7 +3924,9 @@ TriggerBox::set_record_enabled (bool yn) _record_state = yn ? Enabled : Disabled; if (_record_state == Disabled) { - disarm_all (false); + for (auto & trig : all_triggers) { + trig->disarm (); + } } RecEnableChanged (); /* EMIT SIGNAL */ @@ -5974,11 +5958,10 @@ TriggerBox::start_time (bool& is_set) const { SlotArmInfo* ai = _arm_info.load (); if (!ai) { - std::cerr << "no slot\n"; is_set = false; return Temporal::Beats(); } - std::cerr << "have slot\n"; + is_set = true; return ai->start_beats; }