diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index 8725f6fba3..ea6c5fc3b2 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -1852,17 +1852,6 @@ 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); } @@ -1892,7 +1881,10 @@ 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()) { @@ -1914,6 +1906,7 @@ 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 7479d4414d..979d4c7110 100644 --- a/gtk2_ardour/ardour_ui_dependents.cc +++ b/gtk2_ardour/ardour_ui_dependents.cc @@ -88,7 +88,6 @@ 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 ce6c4217db..c4be5d8bb6 100644 --- a/gtk2_ardour/audio_clip_editor.cc +++ b/gtk2_ardour/audio_clip_editor.cc @@ -41,12 +41,15 @@ #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" @@ -60,27 +63,6 @@ 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 { @@ -121,46 +103,133 @@ 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_inner (Gtk::Box& box) +{ + box.pack_start (snap_box, false, false); + box.pack_start (grid_box, false, false); + box.pack_start (draw_box, false, false); +} - /* Scroll bar does not scroll and it outside the frame */ +void +AudioClipEditor::pack_outer (Gtk::Box& box) +{ + if (with_transport_controls) { + box.pack_start (play_box, 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 (); + box.pack_start (rec_box, false, false); + box.pack_start (follow_playhead_button, false, false); +} - /* A scrolling container for our waves and lines etc. */ +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)); - waves_container = new ArdourCanvas::ScrollGroup (frame, ScrollGroup::ScrollsHorizontally); - add_scroller (*waves_container); + _toolbox.pack_start (*_canvas_hscrollbar, false, false); + _toolbox.pack_start (button_bar, false, false); +} - 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"))); +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); /* the lines */ - line_container = new ArdourCanvas::Container (waves_container); + line_container = new ArdourCanvas::Container (data_group); 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); @@ -222,41 +291,6 @@ 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) { @@ -277,18 +311,6 @@ 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) @@ -317,78 +339,15 @@ AudioClipEditor::LineDrag::motion (GdkEventMotion* ev) void AudioClipEditor::set_colors () { - set_background_color (UIConfiguration::instance ().color (X_("theme:bg"))); - - frame->set_outline_color (UIConfiguration::instance ().color (X_("neutral:midground"))); + canvas.set_background_color (UIConfiguration::instance ().color (X_("theme:bg"))); 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 () { @@ -400,12 +359,50 @@ AudioClipEditor::drop_waves () } void -AudioClipEditor::set_region (std::shared_ptr r, TriggerReference tr) +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) { 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 @@ -414,8 +411,8 @@ AudioClipEditor::set_region (std::shared_ptr r, TriggerReference tr */ delete clip_metric; - clip_metric = new ClipBBTMetric (tr); - ruler->set_metric (clip_metric); + clip_metric = new ClipBBTMetric (ref); + minsec_ruler->set_metric (clip_metric); uint32_t n_chans = r->n_channels (); samplecnt_t len; @@ -433,7 +430,7 @@ AudioClipEditor::set_region (std::shared_ptr r, TriggerReference tr continue; } - WaveView* wv = new WaveView (waves_container, war); + WaveView* wv = new WaveView (data_group, war); wv->set_channel (n); wv->set_show_zero_line (false); wv->set_clip_level (1.0); @@ -442,75 +439,44 @@ AudioClipEditor::set_region (std::shared_ptr r, TriggerReference tr 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::on_size_allocate (Gtk::Allocation& alloc) +AudioClipEditor::canvas_allocate (Gtk::Allocation& alloc) { - GtkCanvas::on_size_allocate (alloc); + canvas.size_allocate (alloc); - ArdourCanvas::Rect r (1, 1, alloc.get_width () - 2, alloc.get_height () - 2); - frame->set (r); + _visible_canvas_width = alloc.get_width(); + _visible_canvas_height = alloc.get_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)); + minsec_ruler->set (ArdourCanvas::Rect (2, 2, alloc.get_width() - 4, timebar_height)); position_lines (); - 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); + start_line->set_y1 (_visible_canvas_height - 2.); + end_line->set_y1 (_visible_canvas_height - 2.); + loop_line->set_y1 (_visible_canvas_height - 2.); 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) { - double available_width = frame->get ().width (); - double s = floor (len / available_width); - - set_spp (s); + set_samples_per_pixel (floor (len / _visible_canvas_width)); } void @@ -521,12 +487,12 @@ AudioClipEditor::set_wave_heights () } uint32_t n = 0; - const Distance w = frame->get ().height () - scroll_bar_trough->get ().height () - 2. - ruler->get().height(); + const Distance w = _visible_canvas_height - (n_timebars * timebar_height); Distance ht = w / waves.size (); for (auto& wave : waves) { wave->set_height (ht); - wave->set_y_position (ruler->get ().height () + (n * ht)); + wave->set_y_position ((n_timebars * timebar_height) + (n * ht)); ++n; } } @@ -547,86 +513,69 @@ 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::event_handler (GdkEvent* ev) +AudioClipEditor::canvas_enter_leave (GdkEventCrossing* ev) { switch (ev->type) { - case GDK_BUTTON_PRESS: - break; - case GDK_ENTER_NOTIFY: - break; - case GDK_LEAVE_NOTIFY: - break; - default: - break; + 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; } 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 52b126a404..02563805f3 100644 --- a/gtk2_ardour/audio_clip_editor.h +++ b/gtk2_ardour/audio_clip_editor.h @@ -25,6 +25,8 @@ #include #include +#include "pbd/history_owner.h" + #include "ardour/ardour.h" #include "ardour/session_handle.h" #include "ardour/triggerbox.h" @@ -44,6 +46,7 @@ #include "canvas/scroll_group.h" #include "audio_clock.h" +#include "cue_editor.h" namespace ARDOUR { @@ -63,59 +66,104 @@ namespace ArdourWaveView class WaveView; } -class ClipEditorBox : public Gtk::VBox, public ARDOUR::SessionHandlePtr +class AudioClipEditor : public CueEditor { public: - 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 (std::string const &, bool with_transport); ~AudioClipEditor (); - void set_region (std::shared_ptr, ARDOUR::TriggerReference); - void on_size_allocate (Gtk::Allocation&); + 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); 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::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; + 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; class ClipBBTMetric : public ArdourCanvas::Ruler::Metric { @@ -135,7 +183,6 @@ private: std::vector waves; double non_wave_height; samplepos_t left_origin; - double _spp; double scroll_fraction; std::shared_ptr audio_region; @@ -150,7 +197,6 @@ 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); @@ -176,47 +222,12 @@ 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; - std::shared_ptr _region; + void build_canvas (); + void build_lower_toolbar (); + void pack_inner (Gtk::Box&); + void pack_outer (Gtk::Box&); - void zoom_in_click (); - void zoom_out_click (); + bool canvas_enter_leave (GdkEventCrossing* ev); }; - diff --git a/gtk2_ardour/audio_trigger_properties_box.cc b/gtk2_ardour/audio_trigger_properties_box.cc index e387a98e02..5328d053bf 100644 --- a/gtk2_ardour/audio_trigger_properties_box.cc +++ b/gtk2_ardour/audio_trigger_properties_box.cc @@ -112,29 +112,31 @@ 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); - _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); + 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); row++; label = manage (new Gtk::Label (_("Clip Length:"))); label->set_alignment (1.0, 0.5); - _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); + 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); row++; - _table.set_homogeneous (false); - _table.set_spacings (4); - _table.set_border_width (2); + audio_t->set_homogeneous (false); + audio_t->set_spacings (4); + audio_t->set_border_width (2); - 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 + attach (*eTempoBox, 0,1, 0,1, Gtk::FILL, Gtk::EXPAND | Gtk::FILL); + attach (*audio_t, 0,1, 1,2, Gtk::FILL, Gtk::SHRINK); _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 62c5a1cacc..78566a4658 100644 --- a/gtk2_ardour/audio_trigger_properties_box.h +++ b/gtk2_ardour/audio_trigger_properties_box.h @@ -17,8 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef _gtk_ardour_audio_trigger_properties_box_h_ -#define _gtk_ardour_audio_trigger_properties_box_h_ +#pragma once #include #include @@ -34,18 +33,7 @@ #include "audio_clock.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; -}; +#include "trigger_properties_box.h" class AudioTriggerPropertiesBox : public TriggerPropertiesBox { @@ -72,8 +60,6 @@ private: void MultiplyTempo(float mult); - Gtk::Table _table; - AudioClock _length_clock; AudioClock _start_clock; @@ -98,5 +84,3 @@ private: bool _ignore_changes; }; - -#endif diff --git a/gtk2_ardour/cue_editor.cc b/gtk2_ardour/cue_editor.cc index ee96e4b5ac..d67a30e93f 100644 --- a/gtk2_ardour/cue_editor.cc +++ b/gtk2_ardour/cue_editor.cc @@ -1,15 +1,49 @@ +/* + * 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" -CueEditor::CueEditor (std::string const & name) +using namespace ARDOUR; +using namespace ArdourWidgets; + +CueEditor::CueEditor (std::string const & name, bool with_transport) : EditingContext (name) - , HistoryOwner (X_("cue-editor")) + , 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) { _history.Changed.connect (history_connection, invalidator (*this), std::bind (&CueEditor::history_changed, this), gui_context()); - set_zoom_focus (Editing::ZoomFocusLeft); } @@ -71,22 +105,6 @@ 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) { @@ -263,3 +281,695 @@ 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 dbf6eae95b..58132a2152 100644 --- a/gtk2_ardour/cue_editor.h +++ b/gtk2_ardour/cue_editor.h @@ -20,15 +20,25 @@ #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); + CueEditor (std::string const & name, bool with_transport_controls); ~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; @@ -46,6 +56,7 @@ 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(); @@ -97,10 +108,89 @@ 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 5ded2331b4..64a734d6f6 100644 --- a/gtk2_ardour/editing_context.h +++ b/gtk2_ardour/editing_context.h @@ -258,6 +258,9 @@ 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() @@ -633,7 +636,6 @@ 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 485374512f..c67a0e0328 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -177,9 +177,6 @@ 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; @@ -2125,8 +2122,6 @@ 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 15e59c3358..53aec52ad0 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 "audio_trigger_properties_box.h" +#include "trigger_properties_box.h" class MidiTriggerPropertiesBox : public TriggerPropertiesBox { diff --git a/gtk2_ardour/pianoroll.cc b/gtk2_ardour/pianoroll.cc index 273964f135..ab5ac72c63 100644 --- a/gtk2_ardour/pianoroll.cc +++ b/gtk2_ardour/pianoroll.cc @@ -66,19 +66,13 @@ using namespace Gtkmm2ext; using namespace Temporal; Pianoroll::Pianoroll (std::string const & name, bool with_transport) - : CueEditor (name) - , timebar_height (15.) - , n_timebars (0) + : CueEditor (name, with_transport) , 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; @@ -148,23 +142,6 @@ 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() { @@ -274,15 +251,6 @@ 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 () { @@ -355,145 +323,24 @@ Pianoroll::build_lower_toolbar () } void -Pianoroll::build_upper_toolbar () +Pianoroll::pack_inner (Gtk::Box& box) { - 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); + box.pack_start (snap_box, false, false); + box.pack_start (grid_box, false, false); + box.pack_start (draw_box, false, false); } void -Pianoroll::set_recording_length (Temporal::BBT_Offset dur) +Pianoroll::pack_outer (Gtk::Box& box) { - rec_length = 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); } void @@ -657,15 +504,6 @@ 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) { @@ -1700,365 +1538,6 @@ 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)()) { @@ -2295,66 +1774,16 @@ Pianoroll::region_prop_change (PBD::PropertyChange const & what_changed) } } -void -Pianoroll::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 -Pianoroll::trigger_arm_change () -{ - if (!ref.trigger()) { - return; - } - - 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()) { + std::cerr << "msci no box\n"; + return; + } + + if (ref.box()->record_enabled() == Disabled) { + std::cerr << "msci RE\n"; return; } @@ -2365,6 +1794,7 @@ Pianoroll::maybe_set_count_in () count_in_to = ref.box()->start_time (valid); if (!valid) { + std::cerr << "no start time\n"; return; } @@ -2372,10 +1802,12 @@ 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 @@ -2418,92 +1850,23 @@ 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); } } -bool -Pianoroll::play_button_press (GdkEventButton* ev) +void +Pianoroll::set_region (std::shared_ptr 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; + set_region (std::dynamic_pointer_cast (r)); } void Pianoroll::set_trigger (TriggerReference & tref) { + std::cerr << "set trigger\n"; + PBD::stacktrace (std::cerr, 17); + if (tref.trigger() == ref.trigger()) { return; } @@ -2522,6 +1885,8 @@ 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); @@ -2577,6 +1942,7 @@ 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) { @@ -2631,6 +1997,9 @@ 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) { @@ -2826,20 +2195,6 @@ 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 46b8604797..6eed69d582 100644 --- a/gtk2_ardour/pianoroll.h +++ b/gtk2_ardour/pianoroll.h @@ -63,7 +63,6 @@ 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 {} @@ -84,6 +83,7 @@ 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,20 +97,12 @@ 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(); @@ -183,41 +175,14 @@ 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; @@ -260,14 +225,6 @@ 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; @@ -277,7 +234,6 @@ class Pianoroll : public CueEditor void unset (bool trigger_too); - void visual_changer (const VisualChange&); void bindings_changed (); void data_captured (samplecnt_t); @@ -299,37 +255,10 @@ 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*); @@ -349,18 +278,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 295a0081a6..4a173937e1 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -287,8 +287,6 @@ 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 9fd6180308..960af89f6b 100644 --- a/gtk2_ardour/trigger_page.cc +++ b/gtk2_ardour/trigger_page.cc @@ -144,6 +144,7 @@ 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 */ @@ -475,9 +476,7 @@ TriggerPage::trigger_arm_changed (Trigger const * trigger) /* hide everything */ - _audio_trig_box.hide (); - _midi_trig_box.hide (); - _midi_editor->viewport().hide (); + hide_all (); Tabbable::showhide_att_bottom (false); @@ -485,10 +484,12 @@ TriggerPage::trigger_arm_changed (Trigger const * trigger) TriggerReference ref (trigger->boxptr(), trigger->index()); if (box.data_type () == DataType::AUDIO) { - if (trigger->the_region()) { - _audio_trig_box.set_trigger (ref); - _audio_trig_box.show (); - } + + _audio_trig_box.set_trigger (ref); + _audio_trig_box.show (); + + // _audio_editor->set_trigger (ref); + _audio_editor->viewport().show (); } else { @@ -504,20 +505,30 @@ 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 everything */ - - _audio_trig_box.hide (); - _midi_trig_box.hide (); + hide_all (); 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 2d963983f9..2be4d4b8ce 100644 --- a/gtk2_ardour/trigger_page.h +++ b/gtk2_ardour/trigger_page.h @@ -30,6 +30,7 @@ #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" @@ -76,7 +77,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); @@ -140,10 +141,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 new file mode 100644 index 0000000000..ced266d82b --- /dev/null +++ b/gtk2_ardour/trigger_properties_box.h @@ -0,0 +1,42 @@ +/* + * 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 aaffc821ab..34d1d4b38f 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 (); + virtual void disarm (bool disarm_box = true); 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); + Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Offset const & q, int multiple = 0); 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); + Temporal::TempoMap::SharedPtr const & tmap, int multiple = 0); 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 (); + void disarm (bool disarm_box); 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(); + void disarm (); + void disarm_all(bool disarm_box); 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 68d8321024..827d13860d 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -909,6 +909,19 @@ 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 7c5bd4ad7f..f4960f1022 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 (); + _box.disarm_all (true); int chns; @@ -329,13 +329,17 @@ Trigger::_arm (Temporal::BBT_Offset const & duration) } void -Trigger::disarm () +Trigger::disarm (bool disarm_box) { if (_armed) { _armed = false; - _box.disarm (); + if (disarm_box) { + _box.disarm (); + } ArmChanged(); /* EMIT SIGNAL */ - TriggerArmChanged (this); + if (disarm_box) { + TriggerArmChanged (this); + } } } @@ -1040,7 +1044,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) + Temporal::TempoMap::SharedPtr const & tmap, Temporal::BBT_Offset const & q, int multiplier) { /* XXX need to use global grid here is quantization == zero */ @@ -1061,7 +1065,9 @@ Trigger::compute_quantized_transition (samplepos_t start_sample, Temporal::Beats } else if (q.bars == 0) { - possible_beats = start_beats.round_up_to_multiple (Temporal::Beats (q.beats, q.ticks)); + Temporal::Beats qb (q.beats, q.ticks); + possible_beats = start_beats.round_up_to_multiple (qb); + possible_beats += (qb * multiplier); possible_bbt = tmap->bbt_at (possible_beats); possible_samples = tmap->sample_at (possible_beats); @@ -1078,6 +1084,7 @@ 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); @@ -1102,7 +1109,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) + Temporal::TempoMap::SharedPtr const & tmap, int multiple) { using namespace Temporal; @@ -1127,7 +1134,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)) { + if (!compute_quantized_transition (start_sample, start, end, t_bbt, t_beats, t_samples, tmap, q, multiple)) { /* no transition */ return 0; } @@ -2535,16 +2542,16 @@ MIDITrigger::_arm (Temporal::BBT_Offset const & duration) } void -MIDITrigger::disarm () +MIDITrigger::disarm (bool disarm_box) { - Trigger::disarm (); + Trigger::disarm (disarm_box); } void MIDITrigger::captured (SlotArmInfo& ai, BufferSet& bufs) { if (ai.midi_buf->size() == 0) { - disarm (); + disarm (true); DEBUG_TRACE (DEBUG::Triggers, string_compose ("%1/%2 captured but with no MIDI data\n", _box.order(), index())); return; } @@ -2573,7 +2580,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 (); + disarm (true); } void @@ -3693,6 +3700,8 @@ 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 @@ -3720,8 +3729,9 @@ 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()); + t_bbt, t_beats, t_samples, tmap, slot.quantization(), 2); + std::cerr << " from " << now_beats.str() << " compute start at " << t_beats.str() << std::endl; ai->start_samples = t_samples; ai->start_beats = t_beats; @@ -3738,10 +3748,10 @@ TriggerBox::arm_from_another_thread (Trigger& slot, samplepos_t now, uint32_t ch } void -TriggerBox::disarm_all () +TriggerBox::disarm_all (bool disarm_box) { for (auto & t : all_triggers) { - t->disarm (); + t->disarm (disarm_box); } } @@ -3750,6 +3760,8 @@ TriggerBox::disarm () { /* This must be called as an alternative to ::finish_recording() */ + std::cerr << "disarmed\n"; + PBD::stacktrace (std::cerr, 17); _arm_info = nullptr; } @@ -3768,6 +3780,7 @@ 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 */ } @@ -3779,6 +3792,7 @@ TriggerBox::maybe_capture (BufferSet& bufs, samplepos_t start_sample, samplepos_ SlotArmInfo* ai = _arm_info.load(); if (!ai) { + std::cerr << "no AI\n"; return; } @@ -3786,6 +3800,7 @@ 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. */ @@ -3805,10 +3820,13 @@ 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.) { @@ -3816,7 +3834,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; } @@ -3924,9 +3942,7 @@ TriggerBox::set_record_enabled (bool yn) _record_state = yn ? Enabled : Disabled; if (_record_state == Disabled) { - for (auto & trig : all_triggers) { - trig->disarm (); - } + disarm_all (false); } RecEnableChanged (); /* EMIT SIGNAL */ @@ -5958,10 +5974,11 @@ 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; }