diff --git a/gtk2_ardour/cue_editor.cc b/gtk2_ardour/cue_editor.cc index e31e1e4f52..8d9566ba6b 100644 --- a/gtk2_ardour/cue_editor.cc +++ b/gtk2_ardour/cue_editor.cc @@ -242,3 +242,21 @@ CueEditor::history_changed () update_undo_redo_actions (_history); } +std::pair +CueEditor::max_zoom_extent() const +{ + return std::make_pair (Temporal::timepos_t (Temporal::Beats()), Temporal::timepos_t (Temporal::Beats (32, 0))); +} + +Temporal::timepos_t +CueEditor::_get_preferred_edit_position (Editing::EditIgnoreOption ignore, bool from_context_menu, bool from_outside_canvas) +{ + samplepos_t where; + bool in_track_canvas = false; + + if (!mouse_sample (where, in_track_canvas)) { + return Temporal::timepos_t (0); + } + + return Temporal::timepos_t (where); +} diff --git a/gtk2_ardour/cue_editor.h b/gtk2_ardour/cue_editor.h index f62fb46ec6..15e7a12d32 100644 --- a/gtk2_ardour/cue_editor.h +++ b/gtk2_ardour/cue_editor.h @@ -104,5 +104,9 @@ class CueEditor : public EditingContext, public PBD::HistoryOwner, public sigc:: void do_undo (uint32_t n); void do_redo (uint32_t n); + + std::pair max_zoom_extent() const; + + Temporal::timepos_t _get_preferred_edit_position (Editing::EditIgnoreOption, bool use_context_click, bool from_outside_canvas); }; diff --git a/gtk2_ardour/editing_context.cc b/gtk2_ardour/editing_context.cc index 2fbc4e6f0d..15a5d9ba8f 100644 --- a/gtk2_ardour/editing_context.cc +++ b/gtk2_ardour/editing_context.cc @@ -20,6 +20,7 @@ #include "pbd/error.h" #include "pbd/stacktrace.h" +#include "pbd/unwind.h" #include "ardour/legatize.h" #include "ardour/midi_region.h" @@ -112,6 +113,7 @@ Glib::RefPtr EditingContext::alternate_alternate_redo_action; EditingContext::EditingContext (std::string const & name) : rubberband_rect (0) , _name (name) + , within_track_canvas (false) , pre_internal_grid_type (GridTypeBeat) , pre_internal_snap_mode (SnapOff) , internal_grid_type (GridTypeBeat) @@ -2762,3 +2764,392 @@ void EditingContext::follow_playhead_clicked () { } + +void +EditingContext::cycle_zoom_focus () +{ + switch (zoom_focus) { + case ZoomFocusLeft: + set_zoom_focus (ZoomFocusRight); + break; + case ZoomFocusRight: + set_zoom_focus (ZoomFocusCenter); + break; + case ZoomFocusCenter: + set_zoom_focus (ZoomFocusPlayhead); + break; + case ZoomFocusPlayhead: + set_zoom_focus (ZoomFocusMouse); + break; + case ZoomFocusMouse: + set_zoom_focus (ZoomFocusEdit); + break; + case ZoomFocusEdit: + set_zoom_focus (ZoomFocusLeft); + break; + } +} + +void +EditingContext::temporal_zoom_step_mouse_focus_scale (bool zoom_out, double scale) +{ + PBD::Unwinder zf (zoom_focus, Editing::ZoomFocusMouse); + temporal_zoom_step_scale (zoom_out, scale); +} + +void +EditingContext::temporal_zoom_step_mouse_focus (bool zoom_out) +{ + temporal_zoom_step_mouse_focus_scale (zoom_out, 2.0); +} + +void +EditingContext::temporal_zoom_step (bool zoom_out) +{ + temporal_zoom_step_scale (zoom_out, 2.0); +} + +void +EditingContext::temporal_zoom_step_scale (bool zoom_out, double scale) +{ + ENSURE_GUI_THREAD (*this, &EditingContext::temporal_zoom_step, zoom_out, scale) + + samplecnt_t nspp = samples_per_pixel; + + if (zoom_out) { + nspp *= scale; + if (nspp == samples_per_pixel) { + nspp *= 2.0; + } + } else { + nspp /= scale; + if (nspp == samples_per_pixel) { + nspp /= 2.0; + } + } + + //zoom-behavior-tweaks + //limit our maximum zoom to the session gui extents value + std::pair ext = max_zoom_extent(); + samplecnt_t session_extents_pp = (ext.second.samples() - ext.first.samples()) / _visible_canvas_width; + if (nspp > session_extents_pp) { + nspp = session_extents_pp; + } + + temporal_zoom (nspp); +} + +void +EditingContext::temporal_zoom (samplecnt_t spp) +{ + if (!_session) { + return; + } + + samplepos_t current_page = current_page_samples(); + samplepos_t current_leftmost = _leftmost_sample; + samplepos_t current_rightmost; + samplepos_t current_center; + samplepos_t new_page_size; + samplepos_t half_page_size; + samplepos_t leftmost_after_zoom = 0; + samplepos_t where; + bool in_track_canvas; + bool use_mouse_sample = true; + samplecnt_t nspp; + double l; + + if (spp == samples_per_pixel) { + return; + } + + // Imposing an arbitrary limit to zoom out as too much zoom out produces + // segfaults for lack of memory. If somebody decides this is not high enough I + // believe it can be raisen to higher values but some limit must be in place. + // + // This constant represents 1 day @ 48kHz on a 1600 pixel wide display + // all of which is used for the editor track displays. The whole day + // would be 4147200000 samples, so 2592000 samples per pixel. + + nspp = std::min (spp, (samplecnt_t) 2592000); + nspp = std::max ((samplecnt_t) 1, nspp); + + new_page_size = (samplepos_t) floor (_visible_canvas_width * nspp); + half_page_size = new_page_size / 2; + + Editing::ZoomFocus zf = effective_zoom_focus(); + + switch (zf) { + case ZoomFocusLeft: + leftmost_after_zoom = current_leftmost; + break; + + case ZoomFocusRight: + current_rightmost = _leftmost_sample + current_page; + if (current_rightmost < new_page_size) { + leftmost_after_zoom = 0; + } else { + leftmost_after_zoom = current_rightmost - new_page_size; + } + break; + + case ZoomFocusCenter: + current_center = current_leftmost + (current_page/2); + if (current_center < half_page_size) { + leftmost_after_zoom = 0; + } else { + leftmost_after_zoom = current_center - half_page_size; + } + break; + + case ZoomFocusPlayhead: + /* centre playhead */ + l = _session->transport_sample() - (new_page_size * 0.5); + + if (l < 0) { + leftmost_after_zoom = 0; + } else if (l > max_samplepos) { + leftmost_after_zoom = max_samplepos - new_page_size; + } else { + leftmost_after_zoom = (samplepos_t) l; + } + break; + + case ZoomFocusMouse: + /* try to keep the mouse over the same point in the display */ + + if (_drags->active()) { + where = _drags->current_pointer_sample (); + } else if (!mouse_sample (where, in_track_canvas)) { + use_mouse_sample = false; + } + + if (use_mouse_sample) { + l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where); + + if (l < 0) { + leftmost_after_zoom = 0; + } else if (l > max_samplepos) { + leftmost_after_zoom = max_samplepos - new_page_size; + } else { + leftmost_after_zoom = (samplepos_t) l; + } + } else { + /* use playhead instead */ + where = _session->transport_sample(); + + if (where < half_page_size) { + leftmost_after_zoom = 0; + } else { + leftmost_after_zoom = where - half_page_size; + } + } + break; + + case ZoomFocusEdit: + /* try to keep the edit point in the same place */ + where = get_preferred_edit_position ().samples(); + { + double l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where); + + if (l < 0) { + leftmost_after_zoom = 0; + } else if (l > max_samplepos) { + leftmost_after_zoom = max_samplepos - new_page_size; + } else { + leftmost_after_zoom = (samplepos_t) l; + } + } + break; + + } + + // leftmost_after_zoom = min (leftmost_after_zoom, _session->current_end_sample()); + + reposition_and_zoom (leftmost_after_zoom, nspp); +} + +void +EditingContext::calc_extra_zoom_edges (samplepos_t &start, samplepos_t &end) +{ + /* this func helps make sure we leave a little space + at each end of the editor so that the zoom doesn't fit the region + precisely to the screen. + */ + + GdkScreen* screen = gdk_screen_get_default (); + const gint pixwidth = gdk_screen_get_width (screen); + const gint mmwidth = gdk_screen_get_width_mm (screen); + const double pix_per_mm = (double) pixwidth/ (double) mmwidth; + const double one_centimeter_in_pixels = pix_per_mm * 10.0; + + const samplepos_t range = end - start; + const samplecnt_t new_fpp = (samplecnt_t) ceil ((double) range / (double) _visible_canvas_width); + const samplepos_t extra_samples = (samplepos_t) floor (one_centimeter_in_pixels * new_fpp); + + if (start > extra_samples) { + start -= extra_samples; + } else { + start = 0; + } + + if (max_samplepos - extra_samples > end) { + end += extra_samples; + } else { + end = max_samplepos; + } +} + + +void +EditingContext::temporal_zoom_by_sample (samplepos_t start, samplepos_t end) +{ + if (!_session) return; + + if ((start == 0 && end == 0) || end < start) { + return; + } + + samplepos_t range = end - start; + + const samplecnt_t new_fpp = (samplecnt_t) ceil ((double) range / (double) _visible_canvas_width); + + samplepos_t new_page = range; + samplepos_t middle = (samplepos_t) floor ((double) start + ((double) range / 2.0f)); + samplepos_t new_leftmost = (samplepos_t) floor ((double) middle - ((double) new_page / 2.0f)); + + if (new_leftmost > middle) { + new_leftmost = 0; + } + + if (new_leftmost < 0) { + new_leftmost = 0; + } + + reposition_and_zoom (new_leftmost, new_fpp); +} + +void +EditingContext::temporal_zoom_to_sample (bool coarser, samplepos_t sample) +{ + if (!_session) { + return; + } + + samplecnt_t range_before = sample - _leftmost_sample; + samplecnt_t new_spp; + + if (coarser) { + if (samples_per_pixel <= 1) { + new_spp = 2; + } else { + new_spp = samples_per_pixel + (samples_per_pixel/2); + } + range_before += range_before/2; + } else { + if (samples_per_pixel >= 1) { + new_spp = samples_per_pixel - (samples_per_pixel/2); + } else { + /* could bail out here since we cannot zoom any finer, + but leave that to the equality test below + */ + new_spp = samples_per_pixel; + } + + range_before -= range_before/2; + } + + if (new_spp == samples_per_pixel) { + return; + } + + /* zoom focus is automatically taken as @p sample when this + method is used. + */ + + samplepos_t new_leftmost = sample - (samplepos_t)range_before; + + if (new_leftmost > sample) { + new_leftmost = 0; + } + + if (new_leftmost < 0) { + new_leftmost = 0; + } + + reposition_and_zoom (new_leftmost, new_spp); +} + +bool +EditingContext::mouse_sample (samplepos_t& where, bool& in_track_canvas) const +{ + /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only + * pays attentions to subwindows. this means that menu windows are ignored, and + * if the pointer is in a menu, the return window from the call will be the + * the regular subwindow *under* the menu. + * + * this matters quite a lot if the pointer is moving around in a menu that overlaps + * the track canvas because we will believe that we are within the track canvas + * when we are not. therefore, we track enter/leave events for the track canvas + * and allow that to override the result of gdk_window_get_pointer(). + */ + + if (!within_track_canvas) { + return false; + } + + int x, y; + Glib::RefPtr canvas_window = const_cast(this)->get_canvas()->get_window(); + + if (!canvas_window) { + return false; + } + + Glib::RefPtr pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y); + + if (!pointer_window) { + return false; + } + + if (pointer_window != canvas_window) { + in_track_canvas = false; + return false; + } + + in_track_canvas = true; + + GdkEvent event; + event.type = GDK_BUTTON_RELEASE; + event.button.x = x; + event.button.y = y; + + where = window_event_sample (&event, 0, 0); + + return true; +} + +samplepos_t +EditingContext::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const +{ + ArdourCanvas::Duple d; + + if (!gdk_event_get_coords (event, &d.x, &d.y)) { + return 0; + } + + /* event coordinates are in window units, so convert to canvas + */ + + d = get_canvas()->window_to_canvas (d); + + if (pcx) { + *pcx = d.x; + } + + if (pcy) { + *pcy = d.y; + } + + return pixel_to_sample (d.x); +} + diff --git a/gtk2_ardour/editing_context.h b/gtk2_ardour/editing_context.h index a2d60f8551..2e11913d85 100644 --- a/gtk2_ardour/editing_context.h +++ b/gtk2_ardour/editing_context.h @@ -146,6 +146,12 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider /** @return true if the editor is following the playhead */ bool follow_playhead () const { return _follow_playhead; } + Temporal::timepos_t get_preferred_edit_position (Editing::EditIgnoreOption eio = Editing::EDIT_IGNORE_NONE, + bool use_context_click = false, + bool from_outside_canvas = false) { + return _get_preferred_edit_position (eio, use_context_click, from_outside_canvas); + } + virtual void instant_save() = 0; virtual void begin_selection_op_history () = 0; @@ -172,9 +178,18 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider virtual void set_selected_midi_region_view (MidiRegionView&); - virtual void temporal_zoom_step (bool zoom_out) = 0; samplecnt_t get_current_zoom () const { return samples_per_pixel; } + void temporal_zoom_step (bool zoom_out); + void temporal_zoom_step_scale (bool zoom_out, double scale); + void temporal_zoom_step_mouse_focus (bool zoom_out); + void temporal_zoom_step_mouse_focus_scale (bool zoom_out, double scale); + + void calc_extra_zoom_edges(samplepos_t &start, samplepos_t &end); + void temporal_zoom (samplecnt_t samples_per_pixel); + void temporal_zoom_by_sample (samplepos_t start, samplepos_t end); + void temporal_zoom_to_sample (bool coarser, samplepos_t sample); + double timeline_origin() const { return _timeline_origin; } /* NOTE: these functions assume that the "pixel" coordinate is @@ -309,6 +324,8 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider virtual void set_zoom_focus (Editing::ZoomFocus) = 0; virtual Editing::ZoomFocus get_zoom_focus () const = 0; + void cycle_zoom_focus (); + virtual void reposition_and_zoom (samplepos_t, double) = 0; sigc::signal ZoomChanged; @@ -429,8 +446,18 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider virtual void set_canvas_cursor (Gdk::Cursor*); + /** computes the timeline sample (sample) of an event whose coordinates + * are in window units (pixels, no scroll offset). + */ + samplepos_t window_event_sample (GdkEvent const*, double* px = 0, double* py = 0) const; + + /* returns false if mouse pointer is not in track or marker canvas + */ + bool mouse_sample (samplepos_t&, bool& in_track_canvas) const; + protected: std::string _name; + bool within_track_canvas; static Glib::RefPtr _midi_actions; static Glib::RefPtr _common_actions; @@ -531,6 +558,7 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider samplecnt_t samples_per_pixel; Editing::ZoomFocus zoom_focus; + virtual Editing::ZoomFocus effective_zoom_focus() const { return zoom_focus; } Temporal::timepos_t _snap_to_bbt (Temporal::timepos_t const & start, Temporal::RoundMode direction, @@ -699,6 +727,12 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider virtual void set_entered_track (TimeAxisView*) {}; + virtual std::pair max_zoom_extent() const = 0; + + virtual Temporal::timepos_t _get_preferred_edit_position (Editing::EditIgnoreOption, + bool use_context_click, + bool from_outside_canvas) = 0; + PBD::ScopedConnection escape_connection; virtual void escape () {} @@ -719,5 +753,3 @@ class EditingContext : public ARDOUR::SessionHandlePtr, public AxisViewProvider static EditingContext* _current_editing_context; }; - - diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index ea704656d7..5907cca639 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -247,7 +247,6 @@ Editor::Editor () , _popup_region_menu_item (0) , _track_canvas (0) , _track_canvas_viewport (0) - , within_track_canvas (false) , _region_peak_cursor (0) , tempo_group (0) , meter_group (0) @@ -3378,31 +3377,6 @@ Editor::set_zoom_focus (ZoomFocus f) } } -void -Editor::cycle_zoom_focus () -{ - switch (zoom_focus) { - case ZoomFocusLeft: - set_zoom_focus (ZoomFocusRight); - break; - case ZoomFocusRight: - set_zoom_focus (ZoomFocusCenter); - break; - case ZoomFocusCenter: - set_zoom_focus (ZoomFocusPlayhead); - break; - case ZoomFocusPlayhead: - set_zoom_focus (ZoomFocusMouse); - break; - case ZoomFocusMouse: - set_zoom_focus (ZoomFocusEdit); - break; - case ZoomFocusEdit: - set_zoom_focus (ZoomFocusLeft); - break; - } -} - void Editor::set_marker_click_behavior (MarkerClickBehavior m) { @@ -4062,7 +4036,7 @@ Editor::sort_track_selection (TrackViewList& sel) } timepos_t -Editor::get_preferred_edit_position (EditIgnoreOption ignore, bool from_context_menu, bool from_outside_canvas) +Editor::_get_preferred_edit_position (EditIgnoreOption ignore, bool from_context_menu, bool from_outside_canvas) { bool ignored; timepos_t where; diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 156317aafb..b095ec90aa 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -274,13 +274,6 @@ public: bool process_midi_export_dialog (MidiExportDialog& dialog, std::shared_ptr midi_region); - void set_zoom_focus (Editing::ZoomFocus); - Editing::ZoomFocus get_zoom_focus () const { return zoom_focus; } - void cycle_zoom_focus (); - void temporal_zoom_step (bool zoom_out); - void temporal_zoom_step_scale (bool zoom_out, double scale); - void temporal_zoom_step_mouse_focus (bool zoom_out); - void temporal_zoom_step_mouse_focus_scale (bool zoom_out, double scale); void ensure_time_axis_view_is_visible (TimeAxisView const & tav, bool at_top); void tav_zoom_step (bool coarser); void tav_zoom_smooth (bool coarser, bool force_all); @@ -377,10 +370,6 @@ public: void reset_x_origin_to_follow_playhead (); - Temporal::timepos_t get_preferred_edit_position (Editing::EditIgnoreOption = Editing::EDIT_IGNORE_NONE, - bool use_context_click = false, - bool from_outside_canvas = false); - void toggle_meter_updating(); void show_rhythm_ferret(); @@ -514,6 +503,13 @@ public: void focus_on_clock(); + void set_zoom_focus (Editing::ZoomFocus); + Editing::ZoomFocus get_zoom_focus () const { return zoom_focus; } + + void temporal_zoom_selection (Editing::ZoomAxis); + void temporal_zoom_session (); + void temporal_zoom_extents (); + protected: void map_transport_state (); void map_position_change (samplepos_t); @@ -529,6 +525,8 @@ protected: 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); + private: void color_handler (); @@ -778,8 +776,6 @@ private: ArdourCanvas::GtkCanvas* _track_canvas; ArdourCanvas::GtkCanvasViewport* _track_canvas_viewport; - bool within_track_canvas; - RegionPeakCursor* _region_peak_cursor; void parameter_changed (std::string); @@ -1242,14 +1238,6 @@ private: void group_selected_regions (); void ungroup_selected_regions (); - void calc_extra_zoom_edges(samplepos_t &start, samplepos_t &end); - void temporal_zoom_selection (Editing::ZoomAxis); - void temporal_zoom_session (); - void temporal_zoom_extents (); - void temporal_zoom (samplecnt_t samples_per_pixel); - void temporal_zoom_by_sample (samplepos_t start, samplepos_t end); - void temporal_zoom_to_sample (bool coarser, samplepos_t sample); - std::shared_ptr current_playlist () const; void insert_source_list_selection (float times); void cut_copy_section (ARDOUR::SectionOperation const op); @@ -1799,6 +1787,8 @@ private: Gtk::HBox _box; //zoom focus menu stuff + Editing::ZoomFocus effective_zoom_focus() const; + ArdourWidgets::ArdourDropdown zoom_focus_selector; void zoom_focus_selection_done (Editing::ZoomFocus); void build_zoom_focus_menu (); @@ -1989,15 +1979,6 @@ private: void duplicate_range (bool with_dialog); void duplicate_regions (float times); - /** computes the timeline sample (sample) of an event whose coordinates - * are in window units (pixels, no scroll offset). - */ - samplepos_t window_event_sample (GdkEvent const*, double* px = 0, double* py = 0) const; - - /* returns false if mouse pointer is not in track or marker canvas - */ - bool mouse_sample (samplepos_t&, bool& in_track_canvas) const; - TimeFXDialog* current_timefx; static void* timefx_thread (void* arg); void do_timefx (bool fixed_end); @@ -2062,6 +2043,8 @@ private: void set_entered_regionview (RegionView*); gint left_automation_track (); + std::pair max_zoom_extent() const { return session_gui_extents(); } + void reset_canvas_action_sensitivity (bool); void set_gain_envelope_visibility (); void set_region_gain_visibility (RegionView*); diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index 88eafc7eef..f391b1a653 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -98,79 +98,6 @@ using namespace Temporal; using Gtkmm2ext::Keyboard; -bool -Editor::mouse_sample (samplepos_t& where, bool& in_track_canvas) const -{ - /* gdk_window_get_pointer() has X11's XQueryPointer semantics in that it only - * pays attentions to subwindows. this means that menu windows are ignored, and - * if the pointer is in a menu, the return window from the call will be the - * the regular subwindow *under* the menu. - * - * this matters quite a lot if the pointer is moving around in a menu that overlaps - * the track canvas because we will believe that we are within the track canvas - * when we are not. therefore, we track enter/leave events for the track canvas - * and allow that to override the result of gdk_window_get_pointer(). - */ - - if (!within_track_canvas) { - return false; - } - - int x, y; - Glib::RefPtr canvas_window = const_cast(this)->_track_canvas->get_window(); - - if (!canvas_window) { - return false; - } - - Glib::RefPtr pointer_window = Gdk::Display::get_default()->get_window_at_pointer (x, y); - - if (!pointer_window) { - return false; - } - - if (pointer_window != canvas_window) { - in_track_canvas = false; - return false; - } - - in_track_canvas = true; - - GdkEvent event; - event.type = GDK_BUTTON_RELEASE; - event.button.x = x; - event.button.y = y; - - where = window_event_sample (&event, 0, 0); - - return true; -} - -samplepos_t -Editor::window_event_sample (GdkEvent const * event, double* pcx, double* pcy) const -{ - ArdourCanvas::Duple d; - - if (!gdk_event_get_coords (event, &d.x, &d.y)) { - return 0; - } - - /* event coordinates are in window units, so convert to canvas - */ - - d = _track_canvas->window_to_canvas (d); - - if (pcx) { - *pcx = d.x; - } - - if (pcy) { - *pcy = d.y; - } - - return pixel_to_sample (d.x); -} - void Editor::set_current_trimmable (std::shared_ptr t) { diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index e965cad850..fbb4ad8c57 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -1853,219 +1853,6 @@ Editor::tav_zoom_smooth (bool coarser, bool force_all) } } -void -Editor::temporal_zoom_step_mouse_focus_scale (bool zoom_out, double scale) -{ - PBD::Unwinder zf (zoom_focus, Editing::ZoomFocusMouse); - temporal_zoom_step_scale (zoom_out, scale); -} - -void -Editor::temporal_zoom_step_mouse_focus (bool zoom_out) -{ - temporal_zoom_step_mouse_focus_scale (zoom_out, 2.0); -} - -void -Editor::temporal_zoom_step (bool zoom_out) -{ - temporal_zoom_step_scale (zoom_out, 2.0); -} - -void -Editor::temporal_zoom_step_scale (bool zoom_out, double scale) -{ - ENSURE_GUI_THREAD (*this, &Editor::temporal_zoom_step, zoom_out, scale) - - samplecnt_t nspp = samples_per_pixel; - - if (zoom_out) { - nspp *= scale; - if (nspp == samples_per_pixel) { - nspp *= 2.0; - } - } else { - nspp /= scale; - if (nspp == samples_per_pixel) { - nspp /= 2.0; - } - } - - //zoom-behavior-tweaks - //limit our maximum zoom to the session gui extents value - std::pair ext = session_gui_extents(); - samplecnt_t session_extents_pp = (ext.second.samples() - ext.first.samples()) / _visible_canvas_width; - if (nspp > session_extents_pp) { - nspp = session_extents_pp; - } - - temporal_zoom (nspp); -} - -void -Editor::temporal_zoom (samplecnt_t spp) -{ - if (!_session) { - return; - } - - samplepos_t current_page = current_page_samples(); - samplepos_t current_leftmost = _leftmost_sample; - samplepos_t current_rightmost; - samplepos_t current_center; - samplepos_t new_page_size; - samplepos_t half_page_size; - samplepos_t leftmost_after_zoom = 0; - samplepos_t where; - bool in_track_canvas; - bool use_mouse_sample = true; - samplecnt_t nspp; - double l; - - if (spp == samples_per_pixel) { - return; - } - - // Imposing an arbitrary limit to zoom out as too much zoom out produces - // segfaults for lack of memory. If somebody decides this is not high enough I - // believe it can be raisen to higher values but some limit must be in place. - // - // This constant represents 1 day @ 48kHz on a 1600 pixel wide display - // all of which is used for the editor track displays. The whole day - // would be 4147200000 samples, so 2592000 samples per pixel. - - nspp = min (spp, (samplecnt_t) 2592000); - nspp = max ((samplecnt_t) 1, nspp); - - new_page_size = (samplepos_t) floor (_visible_canvas_width * nspp); - half_page_size = new_page_size / 2; - - Editing::ZoomFocus zf = zoom_focus; - - if (zf == ZoomFocusEdit && _edit_point == EditAtMouse) { - zf = ZoomFocusMouse; - } - - switch (zf) { - case ZoomFocusLeft: - leftmost_after_zoom = current_leftmost; - break; - - case ZoomFocusRight: - current_rightmost = _leftmost_sample + current_page; - if (current_rightmost < new_page_size) { - leftmost_after_zoom = 0; - } else { - leftmost_after_zoom = current_rightmost - new_page_size; - } - break; - - case ZoomFocusCenter: - current_center = current_leftmost + (current_page/2); - if (current_center < half_page_size) { - leftmost_after_zoom = 0; - } else { - leftmost_after_zoom = current_center - half_page_size; - } - break; - - case ZoomFocusPlayhead: - /* centre playhead */ - l = _playhead_cursor->current_sample () - (new_page_size * 0.5); - - if (l < 0) { - leftmost_after_zoom = 0; - } else if (l > max_samplepos) { - leftmost_after_zoom = max_samplepos - new_page_size; - } else { - leftmost_after_zoom = (samplepos_t) l; - } - break; - - case ZoomFocusMouse: - /* try to keep the mouse over the same point in the display */ - - if (_drags->active()) { - where = _drags->current_pointer_sample (); - } else if (!mouse_sample (where, in_track_canvas)) { - use_mouse_sample = false; - } - - if (use_mouse_sample) { - l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where); - - if (l < 0) { - leftmost_after_zoom = 0; - } else if (l > max_samplepos) { - leftmost_after_zoom = max_samplepos - new_page_size; - } else { - leftmost_after_zoom = (samplepos_t) l; - } - } else { - /* use playhead instead */ - where = _playhead_cursor->current_sample (); - - if (where < half_page_size) { - leftmost_after_zoom = 0; - } else { - leftmost_after_zoom = where - half_page_size; - } - } - break; - - case ZoomFocusEdit: - /* try to keep the edit point in the same place */ - where = get_preferred_edit_position ().samples(); - { - double l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where); - - if (l < 0) { - leftmost_after_zoom = 0; - } else if (l > max_samplepos) { - leftmost_after_zoom = max_samplepos - new_page_size; - } else { - leftmost_after_zoom = (samplepos_t) l; - } - } - break; - - } - - // leftmost_after_zoom = min (leftmost_after_zoom, _session->current_end_sample()); - - reposition_and_zoom (leftmost_after_zoom, nspp); -} - -void -Editor::calc_extra_zoom_edges(samplepos_t &start, samplepos_t &end) -{ - /* this func helps make sure we leave a little space - at each end of the editor so that the zoom doesn't fit the region - precisely to the screen. - */ - - GdkScreen* screen = gdk_screen_get_default (); - const gint pixwidth = gdk_screen_get_width (screen); - const gint mmwidth = gdk_screen_get_width_mm (screen); - const double pix_per_mm = (double) pixwidth/ (double) mmwidth; - const double one_centimeter_in_pixels = pix_per_mm * 10.0; - - const samplepos_t range = end - start; - const samplecnt_t new_fpp = (samplecnt_t) ceil ((double) range / (double) _visible_canvas_width); - const samplepos_t extra_samples = (samplepos_t) floor (one_centimeter_in_pixels * new_fpp); - - if (start > extra_samples) { - start -= extra_samples; - } else { - start = 0; - } - - if (max_samplepos - extra_samples > end) { - end += extra_samples; - } else { - end = max_samplepos; - } -} bool Editor::get_selection_extents (timepos_t &start, timepos_t &end) const @@ -2107,205 +1894,6 @@ Editor::get_selection_extents (timepos_t &start, timepos_t &end) const return ret; } - -void -Editor::temporal_zoom_selection (Editing::ZoomAxis axes) -{ - if (!selection) return; - - if (selection->regions.empty() && selection->time.empty()) { - if (axes == Horizontal || axes == Both) { - temporal_zoom_step(true); - } - if (axes == Vertical || axes == Both) { - if (!track_views.empty()) { - - TrackViewList tvl; - - //implicit hack: by extending the top & bottom check outside the current view limits, we include the trackviews immediately above & below what is visible - const double top = vertical_adjustment.get_value() - 10; - const double btm = top + _visible_canvas_height + 10; - - for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { - if ((*iter)->covered_by_y_range (top, btm)) { - tvl.push_back(*iter); - } - } - - fit_tracks (tvl); - } - } - return; - } - - //ToDo: if notes are selected, zoom to that - - //ToDo: if control points are selected, zoom to that - - if (axes == Horizontal || axes == Both) { - - timepos_t start, end; - if (get_selection_extents (start, end)) { - samplepos_t s = start.samples(); - samplepos_t e = end.samples(); - calc_extra_zoom_edges (s, e); - temporal_zoom_by_sample (s, e); - } - } - - if (axes == Vertical || axes == Both) { - fit_selection (); - } - - //normally, we don't do anything "automatic" to the user's selection. - //but in this case, we will clear the selection after a zoom-to-selection. - selection->clear(); -} - -void -Editor::temporal_zoom_session () -{ - ENSURE_GUI_THREAD (*this, &Editor::temporal_zoom_session) - - if (_session) { - samplecnt_t start = _session->current_start_sample(); - samplecnt_t end = _session->current_end_sample(); - - if (_session->actively_recording ()) { - samplepos_t cur = _playhead_cursor->current_sample (); - if (cur > end) { - /* recording beyond the end marker; zoom out - * by 5 seconds more so that if 'follow - * playhead' is active we don't immediately - * scroll. - */ - end = cur + _session->sample_rate() * 5; - } - } - - if ((start == 0 && end == 0) || end < start) { - return; - } - - calc_extra_zoom_edges(start, end); - - temporal_zoom_by_sample (start, end); - } -} - -void -Editor::temporal_zoom_extents () -{ - ENSURE_GUI_THREAD (*this, &Editor::temporal_zoom_extents) - - if (_session) { - std::pair ext = session_gui_extents (false); //in this case we want to zoom to the extents explicitly; ignore the users prefs for extra padding - - samplecnt_t start = ext.first.samples(); - samplecnt_t end = ext.second.samples(); - - if (_session->actively_recording ()) { - samplepos_t cur = _playhead_cursor->current_sample (); - if (cur > end) { - /* recording beyond the end marker; zoom out - * by 5 seconds more so that if 'follow - * playhead' is active we don't immediately - * scroll. - */ - end = cur + _session->sample_rate() * 5; - } - } - - if ((start == 0 && end == 0) || end < start) { - return; - } - - calc_extra_zoom_edges(start, end); - - temporal_zoom_by_sample (start, end); - } -} - -void -Editor::temporal_zoom_by_sample (samplepos_t start, samplepos_t end) -{ - if (!_session) return; - - if ((start == 0 && end == 0) || end < start) { - return; - } - - samplepos_t range = end - start; - - const samplecnt_t new_fpp = (samplecnt_t) ceil ((double) range / (double) _visible_canvas_width); - - samplepos_t new_page = range; - samplepos_t middle = (samplepos_t) floor ((double) start + ((double) range / 2.0f)); - samplepos_t new_leftmost = (samplepos_t) floor ((double) middle - ((double) new_page / 2.0f)); - - if (new_leftmost > middle) { - new_leftmost = 0; - } - - if (new_leftmost < 0) { - new_leftmost = 0; - } - - reposition_and_zoom (new_leftmost, new_fpp); -} - -void -Editor::temporal_zoom_to_sample (bool coarser, samplepos_t sample) -{ - if (!_session) { - return; - } - - samplecnt_t range_before = sample - _leftmost_sample; - samplecnt_t new_spp; - - if (coarser) { - if (samples_per_pixel <= 1) { - new_spp = 2; - } else { - new_spp = samples_per_pixel + (samples_per_pixel/2); - } - range_before += range_before/2; - } else { - if (samples_per_pixel >= 1) { - new_spp = samples_per_pixel - (samples_per_pixel/2); - } else { - /* could bail out here since we cannot zoom any finer, - but leave that to the equality test below - */ - new_spp = samples_per_pixel; - } - - range_before -= range_before/2; - } - - if (new_spp == samples_per_pixel) { - return; - } - - /* zoom focus is automatically taken as @p sample when this - method is used. - */ - - samplepos_t new_leftmost = sample - (samplepos_t)range_before; - - if (new_leftmost > sample) { - new_leftmost = 0; - } - - if (new_leftmost < 0) { - new_leftmost = 0; - } - - reposition_and_zoom (new_leftmost, new_spp); -} - - bool Editor::choose_new_marker_name(string &name, bool is_range) { @@ -9804,3 +9392,131 @@ Editor::ripple_marks (std::shared_ptr target_playlist, timepos_t at, t _session->locations()->ripple (at, distance, false); _session->add_command (new MementoCommand (*_session->locations(), &before, &_session->locations()->get_state())); } + +Editing::ZoomFocus +Editor::effective_zoom_focus() const +{ + if (zoom_focus == ZoomFocusEdit && _edit_point == EditAtMouse) { + return ZoomFocusMouse; + } + + return zoom_focus; +} + +void +Editor::temporal_zoom_selection (Editing::ZoomAxis axes) +{ + if (!selection) return; + + if (selection->regions.empty() && selection->time.empty()) { + if (axes == Horizontal || axes == Both) { + temporal_zoom_step(true); + } + if (axes == Vertical || axes == Both) { + if (!track_views.empty()) { + + TrackViewList tvl; + + //implicit hack: by extending the top & bottom check outside the current view limits, we include the trackviews immediately above & below what is visible + const double top = vertical_adjustment.get_value() - 10; + const double btm = top + _visible_canvas_height + 10; + + for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { + if ((*iter)->covered_by_y_range (top, btm)) { + tvl.push_back(*iter); + } + } + + fit_tracks (tvl); + } + } + return; + } + + //ToDo: if notes are selected, zoom to that + + //ToDo: if control points are selected, zoom to that + + if (axes == Horizontal || axes == Both) { + + timepos_t start, end; + if (get_selection_extents (start, end)) { + samplepos_t s = start.samples(); + samplepos_t e = end.samples(); + calc_extra_zoom_edges (s, e); + temporal_zoom_by_sample (s, e); + } + } + + if (axes == Vertical || axes == Both) { + fit_selection (); + } + + //normally, we don't do anything "automatic" to the user's selection. + //but in this case, we will clear the selection after a zoom-to-selection. + selection->clear(); +} + +void +Editor::temporal_zoom_session () +{ + ENSURE_GUI_THREAD (*this, &Editor::temporal_zoom_session) + + if (_session) { + samplecnt_t start = _session->current_start_sample(); + samplecnt_t end = _session->current_end_sample(); + + if (_session->actively_recording ()) { + samplepos_t cur = _playhead_cursor->current_sample (); + if (cur > end) { + /* recording beyond the end marker; zoom out + * by 5 seconds more so that if 'follow + * playhead' is active we don't immediately + * scroll. + */ + end = cur + _session->sample_rate() * 5; + } + } + + if ((start == 0 && end == 0) || end < start) { + return; + } + + calc_extra_zoom_edges(start, end); + + temporal_zoom_by_sample (start, end); + } +} + +void +Editor::temporal_zoom_extents () +{ + ENSURE_GUI_THREAD (*this, &Editor::temporal_zoom_extents) + + if (_session) { + std::pair ext = session_gui_extents (false); //in this case we want to zoom to the extents explicitly; ignore the users prefs for extra padding + + samplecnt_t start = ext.first.samples(); + samplecnt_t end = ext.second.samples(); + + if (_session->actively_recording ()) { + samplepos_t cur = _playhead_cursor->current_sample (); + if (cur > end) { + /* recording beyond the end marker; zoom out + * by 5 seconds more so that if 'follow + * playhead' is active we don't immediately + * scroll. + */ + end = cur + _session->sample_rate() * 5; + } + } + + if ((start == 0 && end == 0) || end < start) { + return; + } + + calc_extra_zoom_edges(start, end); + + temporal_zoom_by_sample (start, end); + } +} diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index 3be2222a72..41802c9d8d 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -290,7 +290,6 @@ public: virtual samplepos_t leftmost_sample() const = 0; virtual samplecnt_t current_page_samples() const = 0; virtual double visible_canvas_height () const = 0; - virtual void temporal_zoom_step (bool coarser) = 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; @@ -306,7 +305,6 @@ public: virtual void remove_last_capture () = 0; virtual void maximise_editing_space () = 0; virtual void restore_editing_space () = 0; - virtual Temporal::timepos_t get_preferred_edit_position (Editing::EditIgnoreOption = Editing::EDIT_IGNORE_NONE, bool from_context_menu = false, bool from_outside_canvas = false) = 0; virtual void toggle_meter_updating() = 0; virtual void split_regions_at (Temporal::timepos_t const &, RegionSelection&) = 0; virtual void split_region_at_points (std::shared_ptr, ARDOUR::AnalysisFeatureList&, bool can_ferret, bool select_new = false) = 0;