diff options
author | nick_m <mainsbridge@gmail.com> | 2015-05-24 04:15:32 +1000 |
---|---|---|
committer | nick_m <mainsbridge@gmail.com> | 2015-05-24 04:15:32 +1000 |
commit | a14c7ff066476aae1a30b3573c90153ed1565e66 (patch) | |
tree | b2599569d53c39dfe6a5fe43ca88e12acf6fc694 /gtk2_ardour | |
parent | e44e0b2c9a00035608c10b02696e53cc8d0e535e (diff) | |
parent | b86c3f97b09779e516939c40983977e5e5a0e458 (diff) |
Merge branch 'relative_snap'
- Many changes to the "User Interaction" pane wrt key modifiers.
- Snap is now relative by default (override to absolute with the
"Snap to absolute using:" modifier).
- Midi notes now obey both snap modifiers (disable snap and absolute snap)
- Timefx drag now selects the primary region (less user confusion).
- Includes MIDI event id fix.
Diffstat (limited to 'gtk2_ardour')
-rw-r--r-- | gtk2_ardour/editor.cc | 18 | ||||
-rw-r--r-- | gtk2_ardour/editor.h | 6 | ||||
-rw-r--r-- | gtk2_ardour/editor_drag.cc | 288 | ||||
-rw-r--r-- | gtk2_ardour/editor_drag.h | 19 | ||||
-rw-r--r-- | gtk2_ardour/keyboard.cc | 149 | ||||
-rw-r--r-- | gtk2_ardour/keyboard.h | 56 | ||||
-rw-r--r-- | gtk2_ardour/midi_region_view.cc | 102 | ||||
-rw-r--r-- | gtk2_ardour/midi_region_view.h | 17 | ||||
-rw-r--r-- | gtk2_ardour/public_editor.h | 8 | ||||
-rw-r--r-- | gtk2_ardour/rc_option_editor.cc | 431 | ||||
-rw-r--r-- | gtk2_ardour/region_view.cc | 8 | ||||
-rw-r--r-- | gtk2_ardour/region_view.h | 4 |
12 files changed, 934 insertions, 172 deletions
diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index df1421b6ce..474fb4c6f9 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -2133,6 +2133,7 @@ Editor::set_snap_mode (SnapMode mode) instant_save (); } + void Editor::set_edit_point_preference (EditPoint ep, bool force) { @@ -2596,25 +2597,28 @@ Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, RoundM return; } - if (Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + if (ArdourKeyboard::indicates_snap (event->button.state)) { if (_snap_mode == SnapOff) { snap_to_internal (start, direction, for_mark); } } else { if (_snap_mode != SnapOff) { snap_to_internal (start, direction, for_mark); + } else if (ArdourKeyboard::indicates_snap_delta (event->button.state)) { + /* SnapOff, but we pressed the snap_delta modifier */ + snap_to_internal (start, direction, for_mark); } } } void -Editor::snap_to (framepos_t& start, RoundMode direction, bool for_mark) +Editor::snap_to (framepos_t& start, RoundMode direction, bool for_mark, bool ensure_snap) { - if (!_session || _snap_mode == SnapOff) { + if (!_session || (_snap_mode == SnapOff && !ensure_snap)) { return; } - snap_to_internal (start, direction, for_mark); + snap_to_internal (start, direction, for_mark, ensure_snap); } void @@ -2684,7 +2688,7 @@ Editor::timecode_snap_to_internal (framepos_t& start, RoundMode direction, bool } void -Editor::snap_to_internal (framepos_t& start, RoundMode direction, bool for_mark) +Editor::snap_to_internal (framepos_t& start, RoundMode direction, bool for_mark, bool ensure_snap) { const framepos_t one_second = _session->frame_rate(); const framepos_t one_minute = _session->frame_rate() * 60; @@ -2854,6 +2858,10 @@ Editor::snap_to_internal (framepos_t& start, RoundMode direction, bool for_mark) case SnapMagnetic: + if (ensure_snap) { + return; + } + if (presnap > start) { if (presnap > (start + pixel_to_sample(snap_threshold))) { start = presnap; diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index eaa9132e86..603103532f 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -436,7 +436,8 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void snap_to (framepos_t& first, ARDOUR::RoundMode direction = ARDOUR::RoundNearest, - bool for_mark = false); + bool for_mark = false, + bool ensure_snap = false); void snap_to_with_modifier (framepos_t& first, GdkEvent const * ev, @@ -2131,7 +2132,8 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void snap_to_internal (framepos_t& first, ARDOUR::RoundMode direction = ARDOUR::RoundNearest, - bool for_mark = false); + bool for_mark = false, + bool ensure_snap = false); void timecode_snap_to_internal (framepos_t& first, ARDOUR::RoundMode direction = ARDOUR::RoundNearest, diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 9fbf2efce8..00811f6eea 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -218,6 +218,8 @@ Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only) : _editor (e) , _item (i) , _pointer_frame_offset (0) + , _x_constrained (false) + , _y_constrained (false) , _trackview_only (trackview_only) , _move_threshold_passed (false) , _starting_point_passed (false) @@ -226,6 +228,7 @@ Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only) , _raw_grab_frame (0) , _grab_frame (0) , _last_pointer_frame (0) + , _snap_delta (0) { } @@ -248,22 +251,11 @@ Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*t void Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor) { - // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained - if (Keyboard::is_button2_event (&event->button)) { - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) { - _y_constrained = true; - _x_constrained = false; - } else { - _y_constrained = false; - _x_constrained = true; - } - } else { - _x_constrained = false; - _y_constrained = false; - } + /* we set up x/y dragging constraints on first move */ _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y); + setup_pointer_frame_offset (); _grab_frame = adjusted_frame (_raw_grab_frame, event); _last_pointer_frame = _grab_frame; @@ -343,6 +335,16 @@ Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const return adjusted_frame (_drags->current_pointer_frame (), event, snap); } +frameoffset_t +Drag::snap_delta (guint state) const +{ + if (ArdourKeyboard::indicates_snap_delta (state)) { + return 0; + } + + return _snap_delta; +} + double Drag::current_pointer_x() const { @@ -359,6 +361,14 @@ Drag::current_pointer_y () const return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y; } +void +Drag::setup_snap_delta (framepos_t pos) +{ + framepos_t temp = pos; + _editor->snap_to (temp, ARDOUR::RoundNearest, false, true); + _snap_delta = temp - pos; +} + bool Drag::motion_handler (GdkEvent* event, bool from_autoscroll) { @@ -394,6 +404,39 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll) } else { _initially_vertical = false; } + /** check constraints for this drag. + * Note that the current convention is to use "contains" for + * key modifiers during motion and "equals" when initiating a drag. + * In this case we haven't moved yet, so "equals" applies here. + */ + if (Config->get_edit_mode() != Lock) { + if (event->motion.state & Gdk::BUTTON2_MASK) { + // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained + if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::constraint_modifier ())) { + _x_constrained = false; + _y_constrained = true; + } else { + _x_constrained = true; + _y_constrained = false; + } + } else if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::constraint_modifier ())) { + // if dragging normally, the motion is constrained to the first direction of movement. + if (_initially_vertical) { + _x_constrained = true; + _y_constrained = false; + } else { + _x_constrained = false; + _y_constrained = true; + } + } + } else { + if (event->button.state & Gdk::BUTTON2_MASK) { + _x_constrained = false; + } else { + _x_constrained = true; + } + _y_constrained = false; + } } if (!from_autoscroll) { @@ -555,7 +598,6 @@ RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView , _total_x_delta (0) , _last_pointer_time_axis_view (0) , _last_pointer_layer (0) - , _single_axis (false) , _ndropzone (0) , _pdropzone (0) , _ddropzone (0) @@ -567,10 +609,7 @@ void RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { Drag::start_grab (event, cursor); - - if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) { - _single_axis = true; - } + setup_snap_delta (_last_frame_position); show_verbose_cursor_time (_last_frame_position); @@ -588,7 +627,7 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r /* compute the amount of pointer motion in frames, and where the region would be if we moved it by that much. */ - *pending_region_position = adjusted_current_frame (event); + *pending_region_position = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true); framepos_t sync_frame; framecnt_t sync_offset; @@ -600,11 +639,11 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r */ if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) { - sync_frame = *pending_region_position + (sync_dir*sync_offset); + sync_frame = *pending_region_position + (sync_dir * sync_offset); _editor->snap_to_with_modifier (sync_frame, event); - *pending_region_position = _primary->region()->adjust_to_sync (sync_frame); + *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - snap_delta (event->button.state); } else { *pending_region_position = _last_frame_position; @@ -616,8 +655,7 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r double dx = 0; - /* in locked edit mode, reverse the usual meaning of _x_constrained */ - bool const x_move_allowed = Config->get_edit_mode() == Lock ? _x_constrained : !_x_constrained; + bool const x_move_allowed = !_x_constrained; if ((*pending_region_position != _last_frame_position) && x_move_allowed) { @@ -750,18 +788,6 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) assert (!_views.empty ()); - if (first_move) { - if (_single_axis) { - if (initially_vertical()) { - _y_constrained = false; - _x_constrained = true; - } else { - _y_constrained = true; - _x_constrained = false; - } - } - } - /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */ /* Find the TimeAxisView that the pointer is now over */ @@ -1304,14 +1330,6 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred) return; } - /* reverse this here so that we have the correct logic to finalize - the drag. - */ - - if (Config->get_edit_mode() == Lock) { - _x_constrained = !_x_constrained; - } - assert (!_views.empty ()); /* We might have hidden region views so that they weren't visible during the drag @@ -2318,6 +2336,7 @@ RegionCreateDrag::aborted (bool) NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i) : Drag (e, i) , region (0) + , _snap_delta (0) { DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n"); } @@ -2342,9 +2361,13 @@ NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/) region = &cnote->region_view(); + double temp; + temp = region->snap_to_pixel (cnote->x0 (), true); + _snap_delta = temp - cnote->x0 (); + _item->grab (); - if (event->motion.state & Keyboard::PrimaryModifier) { + if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) { relative = false; } else { relative = true; @@ -2377,7 +2400,7 @@ NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/) } void -NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/) +NoteResizeDrag::motion (GdkEvent* event, bool /*first_move*/) { MidiRegionSelection& ms (_editor->get_selection().midi_regions); for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) { @@ -2385,21 +2408,62 @@ NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/) assert (nb); MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r); if (mrv) { - mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative); + double sd = 0.0; + bool snap = true; + bool apply_snap_delta = !ArdourKeyboard::indicates_snap_delta (event->button.state); + + if (ArdourKeyboard::indicates_snap (event->button.state)) { + if (_editor->snap_mode () != SnapOff) { + snap = false; + } + } else { + if (_editor->snap_mode () == SnapOff) { + snap = false; + /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */ + if (!apply_snap_delta) { + snap = true; + } + } + } + + if (apply_snap_delta) { + sd = _snap_delta; + } + + mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap); } } } void -NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/) +NoteResizeDrag::finished (GdkEvent* event, bool /*movement_occurred*/) { MidiRegionSelection& ms (_editor->get_selection().midi_regions); for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) { NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase")); assert (nb); MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r); + double sd = 0.0; + bool snap = true; + bool apply_snap_delta = !ArdourKeyboard::indicates_snap_delta (event->button.state); if (mrv) { - mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative); + if (ArdourKeyboard::indicates_snap (event->button.state)) { + if (_editor->snap_mode () != SnapOff) { + snap = false; + } + } else { + if (_editor->snap_mode () == SnapOff) { + snap = false; + /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */ + if (!apply_snap_delta) { + snap = true; + } + } + } + if (apply_snap_delta) { + sd = _snap_delta; + } + mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap); } } @@ -2597,8 +2661,9 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*) framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed); framepos_t const pf = adjusted_current_frame (event); + setup_snap_delta (region_start); - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) { + if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) { /* Move the contents of the region around without changing the region bounds */ _operation = ContentsTrim; Drag::start_grab (event, _editor->cursors()->trimmer); @@ -2607,8 +2672,7 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*) if (pf < (region_start + region_length/2)) { /* closer to front */ _operation = StartTrim; - - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) { + if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) { Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim); } else { Drag::start_grab (event, _editor->cursors()->left_side_trim); @@ -2616,17 +2680,18 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*) } else { /* closer to end */ _operation = EndTrim; - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) { + if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) { Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim); } else { Drag::start_grab (event, _editor->cursors()->right_side_trim); } } } - - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) { + /* jump trim disabled for now + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) { _jump_position_when_done = true; } + */ switch (_operation) { case StartTrim: @@ -2636,7 +2701,7 @@ TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*) } break; case EndTrim: - show_verbose_cursor_time (region_end); + show_verbose_cursor_duration (region_start, region_end); break; case ContentsTrim: show_verbose_cursor_time (pf); @@ -2662,8 +2727,8 @@ TrimDrag::motion (GdkEvent* event, bool first_move) if (tv && tv->is_track()) { speed = tv->track()->speed(); } - - framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset; + framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true); + framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state); if (first_move) { @@ -2709,7 +2774,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move) bool non_overlap_trim = false; - if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) { + if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) { non_overlap_trim = true; } @@ -2795,7 +2860,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move) show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed)); break; case EndTrim: - show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed)); + show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed); break; case ContentsTrim: // show_verbose_cursor_time (frame_delta); @@ -2803,7 +2868,6 @@ TrimDrag::motion (GdkEvent* event, bool first_move) } } - void TrimDrag::finished (GdkEvent* event, bool movement_occurred) { @@ -3247,10 +3311,11 @@ void CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c) { Drag::start_grab (event, c); + setup_snap_delta (_editor->playhead_cursor->current_frame ()); _grab_zoom = _editor->samples_per_pixel; - framepos_t where = _editor->canvas_event_sample (event); + framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state); _editor->snap_to_with_modifier (where, event); @@ -3288,15 +3353,16 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c) } } - fake_locate (where); + fake_locate (where - snap_delta (event->button.state)); } void CursorDrag::motion (GdkEvent* event, bool) { - framepos_t const adjusted_frame = adjusted_current_frame (event); - if (adjusted_frame != last_pointer_frame()) { - fake_locate (adjusted_frame); + framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state); + _editor->snap_to_with_modifier (where, event); + if (where != last_pointer_frame()) { + fake_locate (where - snap_delta (event->button.state)); } } @@ -3347,6 +3413,7 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary); boost::shared_ptr<AudioRegion> const r = arv->audio_region (); + setup_snap_delta (r->position ()); show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32); } @@ -3363,7 +3430,11 @@ void FadeInDrag::motion (GdkEvent* event, bool) { framecnt_t fade_length; - framepos_t const pos = adjusted_current_frame (event); + + framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state); + _editor->snap_to_with_modifier (pos, event); + pos -= snap_delta (event->button.state); + boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ()); if (pos < (region->position() + 64)) { @@ -3396,8 +3467,9 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred) } framecnt_t fade_length; - - framepos_t const pos = adjusted_current_frame (event); + framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state); + _editor->snap_to_with_modifier (pos, event); + pos -= snap_delta (event->button.state); boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ()); @@ -3459,6 +3531,7 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary); boost::shared_ptr<AudioRegion> r = arv->audio_region (); + setup_snap_delta (r->last_frame ()); show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame()); } @@ -3476,7 +3549,9 @@ FadeOutDrag::motion (GdkEvent* event, bool) { framecnt_t fade_length; - framepos_t const pos = adjusted_current_frame (event); + framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state); + _editor->snap_to_with_modifier (pos, event); + pos -= snap_delta (event->button.state); boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ()); @@ -3511,7 +3586,9 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred) framecnt_t fade_length; - framepos_t const pos = adjusted_current_frame (event); + framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state); + _editor->snap_to_with_modifier (pos, event); + pos -= snap_delta (event->button.state); boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ()); @@ -3707,7 +3784,7 @@ MarkerDrag::motion (GdkEvent* event, bool) framepos_t const newframe = adjusted_current_frame (event); framepos_t next = newframe; - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) { + if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) { move_both = true; } @@ -3965,13 +4042,16 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) _fixed_grab_x = _point->get_x(); _fixed_grab_y = _point->get_y(); + framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x); + setup_snap_delta (pos); + float const fraction = 1 - (_point->get_y() / _point->line().height()); _point->line().start_drag_single (_point, _fixed_grab_x, fraction); show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction)); - _pushing = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier); + _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ()); if (!_point->can_slide ()) { _x_constrained = true; @@ -3984,7 +4064,7 @@ ControlPointDrag::motion (GdkEvent* event, bool) double dx = _drags->current_pointer_x() - last_pointer_x(); double dy = current_pointer_y() - last_pointer_y(); - if (event->button.state & Keyboard::SecondaryModifier) { + if (event->button.state & ArdourKeyboard::fine_adjust_modifier ()) { dx *= 0.1; dy *= 0.1; } @@ -4017,12 +4097,13 @@ ControlPointDrag::motion (GdkEvent* event, bool) cy = max (0.0, cy); cy = min ((double) _point->line().height(), cy); - framepos_t cx_frames = _editor->pixel_to_sample (cx); + framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state); if (!_x_constrained) { _editor->snap_to_with_modifier (cx_frames, event); } + cx_frames -= snap_delta (event->button.state); cx_frames = min (cx_frames, _point->line().maximum_time()); float const fraction = 1.0 - (cy / _point->line().height()); @@ -4038,8 +4119,7 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred) if (!movement_occurred) { /* just a click */ - - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) { + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) { _editor->reset_point_selection (); } @@ -4070,9 +4150,9 @@ ControlPointDrag::active (Editing::MouseMode m) } LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i) - : Drag (e, i), - _line (0), - _cumulative_y_drag (0) + : Drag (e, i) + , _line (0) + , _cumulative_y_drag (0) { DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n"); } @@ -4123,7 +4203,7 @@ LineDrag::motion (GdkEvent* event, bool) { double dy = current_pointer_y() - last_pointer_y(); - if (event->button.state & Keyboard::SecondaryModifier) { + if (event->button.state & ArdourKeyboard::fine_adjust_modifier ()) { dy *= 0.1; } @@ -4432,7 +4512,12 @@ TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { Drag::start_grab (event, cursor); - show_verbose_cursor_time (adjusted_current_frame (event)); + _editor->get_selection().add (_primary); + + framepos_t where = _primary->region()->position(); + setup_snap_delta (where); + + show_verbose_cursor_duration (where, adjusted_current_frame (event), 0); } void @@ -4444,14 +4529,15 @@ TimeFXDrag::motion (GdkEvent* event, bool) pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y()); int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second; int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers(); - - framepos_t const pf = adjusted_current_frame (event); + framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state); + _editor->snap_to_with_modifier (pf, event); + pf -= snap_delta (event->button.state); if (pf > rv->region()->position()) { rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer); } - show_verbose_cursor_time (pf); + show_verbose_cursor_duration (_primary->region()->position(), pf, 0); } void @@ -4909,7 +4995,7 @@ RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *) case CreateTransportMarker: case CreateCDMarker: - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) { + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) { _copy = true; } else { _copy = false; @@ -5130,6 +5216,7 @@ void NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *) { Drag::start_grab (event); + setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ())); if (!(_was_selected = _primary->selected())) { @@ -5157,7 +5244,7 @@ NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *) /** @return Current total drag x change in frames */ frameoffset_t -NoteDrag::total_dx () const +NoteDrag::total_dx (const guint state) const { /* dx in frames */ frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x()); @@ -5166,15 +5253,32 @@ NoteDrag::total_dx () const frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ()); /* new time of the primary note in session frames */ - frameoffset_t st = n + dx; + frameoffset_t st = n + dx + snap_delta (state); framepos_t const rp = _region->region()->position (); /* prevent the note being dragged earlier than the region's position */ st = max (st, rp); - /* snap and return corresponding delta */ - return _region->snap_frame_to_frame (st - rp) + rp - n; + /* possibly snap and return corresponding delta */ + + bool snap = true; + + if (ArdourKeyboard::indicates_snap (state)) { + if (_editor->snap_mode () != SnapOff) { + snap = false; + } + } else { + if (_editor->snap_mode () == SnapOff) { + snap = false; + /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */ + if (ArdourKeyboard::indicates_snap_delta (state)) { + snap = true; + } + } + } + + return _region->snap_frame_to_frame (st - rp, snap) + rp - n - snap_delta (state); } /** @return Current total drag y change in note number */ @@ -5193,10 +5297,10 @@ NoteDrag::total_dy () const } void -NoteDrag::motion (GdkEvent *, bool) +NoteDrag::motion (GdkEvent * event, bool) { /* Total change in x and y since the start of the drag */ - frameoffset_t const dx = total_dx (); + frameoffset_t const dx = total_dx (event->button.state); int8_t const dy = total_dy (); /* Now work out what we have to do to the note canvas items to set this new drag delta */ @@ -5265,7 +5369,7 @@ NoteDrag::finished (GdkEvent* ev, bool moved) } } } else { - _region->note_dropped (_primary, total_dx(), total_dy()); + _region->note_dropped (_primary, total_dx (ev->button.state), total_dy()); } } diff --git a/gtk2_ardour/editor_drag.h b/gtk2_ardour/editor_drag.h index e6f800cb52..6fcd92751a 100644 --- a/gtk2_ardour/editor_drag.h +++ b/gtk2_ardour/editor_drag.h @@ -213,13 +213,18 @@ protected: return _last_pointer_y; } - double last_pointer_frame () const { + ARDOUR::framepos_t last_pointer_frame () const { return _last_pointer_frame; } + ARDOUR::frameoffset_t snap_delta (guint const) const; + double current_pointer_x () const; double current_pointer_y () const; + /* sets snap delta from unsnapped pos */ + void setup_snap_delta (framepos_t pos); + boost::shared_ptr<ARDOUR::Region> add_midi_region (MidiTimeAxisView*); void show_verbose_cursor_time (framepos_t); @@ -248,6 +253,11 @@ private: ARDOUR::framepos_t _raw_grab_frame; ///< unsnapped frame that the mouse was at when start_grab was called, or 0 ARDOUR::framepos_t _grab_frame; ///< adjusted_frame that the mouse was at when start_grab was called, or 0 ARDOUR::framepos_t _last_pointer_frame; ///< adjusted_frame the last time a motion occurred + + /* difference between some key position's snapped and unsnapped + * framepos. used for relative snap. + */ + ARDOUR::frameoffset_t _snap_delta; CursorContext::Handle _cursor_ctx; ///< cursor change context }; @@ -335,8 +345,6 @@ protected: double _total_x_delta; int _last_pointer_time_axis_view; double _last_pointer_layer; - bool _single_axis; - private: uint32_t _ndropzone; uint32_t _pdropzone; @@ -503,6 +511,7 @@ private: MidiRegionView* region; bool relative; bool at_front; + double _snap_delta; }; /** Drags to move MIDI notes */ @@ -518,7 +527,7 @@ class NoteDrag : public Drag private: - ARDOUR::frameoffset_t total_dx () const; + ARDOUR::frameoffset_t total_dx (const guint) const; int8_t total_dy () const; MidiRegionView* _region; @@ -541,7 +550,7 @@ public: void aborted (bool); bool active (Editing::MouseMode mode) { - return mode == Editing::MouseDraw; + return mode == Editing::MouseDraw || mode == Editing::MouseContent; } bool y_movement_matters () const { diff --git a/gtk2_ardour/keyboard.cc b/gtk2_ardour/keyboard.cc index ca65a2b3f2..96c73f63d4 100644 --- a/gtk2_ardour/keyboard.cc +++ b/gtk2_ardour/keyboard.cc @@ -48,6 +48,14 @@ accel_map_changed (GtkAccelMap* /*map*/, me->ui.setup_tooltips (); } +guint ArdourKeyboard::constraint_mod = Keyboard::SecondaryModifier; +guint ArdourKeyboard::trim_contents_mod = Keyboard::PrimaryModifier; +guint ArdourKeyboard::trim_overlap_mod = Keyboard::TertiaryModifier; +guint ArdourKeyboard::trim_anchored_mod = Keyboard::TertiaryModifier; +guint ArdourKeyboard::fine_adjust_mod = Keyboard::SecondaryModifier; +guint ArdourKeyboard::push_points_mod = Keyboard::PrimaryModifier; +guint ArdourKeyboard::note_size_relative_mod = Keyboard::PrimaryModifier; + void ArdourKeyboard::setup_keybindings () { @@ -174,6 +182,145 @@ ArdourKeyboard::setup_keybindings () g_signal_connect (accelmap, "changed", (GCallback) accel_map_changed, this); } +XMLNode& +ArdourKeyboard::get_state (void) +{ + XMLNode* node = &Keyboard::get_state (); + char buf[32]; + + snprintf (buf, sizeof (buf), "%d", constraint_mod); + node->add_property ("constraint-modifier", buf); + snprintf (buf, sizeof (buf), "%d", trim_contents_mod); + node->add_property ("trim-contents-modifier", buf); + snprintf (buf, sizeof (buf), "%d", trim_overlap_mod); + node->add_property ("trim-overlap-modifier", buf); + snprintf (buf, sizeof (buf), "%d", trim_anchored_mod); + node->add_property ("trim-anchored-modifier", buf); + snprintf (buf, sizeof (buf), "%d", fine_adjust_mod); + node->add_property ("fine-adjust-modifier", buf); + snprintf (buf, sizeof (buf), "%d", push_points_mod); + node->add_property ("push-points-modifier", buf); + snprintf (buf, sizeof (buf), "%d", note_size_relative_mod); + node->add_property ("note-size-relative-modifier", buf); + + return *node; +} + +int +ArdourKeyboard::set_state (const XMLNode& node, int version) +{ + const XMLProperty* prop; + + if ((prop = node.property ("constraint-modifier")) != 0) { + sscanf (prop->value().c_str(), "%d", &constraint_mod); + } + + if ((prop = node.property ("trim-contents-modifier")) != 0) { + sscanf (prop->value().c_str(), "%d", &trim_contents_mod); + } + + if ((prop = node.property ("trim-overlap-modifier")) != 0) { + sscanf (prop->value().c_str(), "%d", &trim_overlap_mod); + } + + if ((prop = node.property ("trim-anchored-modifier")) != 0) { + sscanf (prop->value().c_str(), "%d", &trim_anchored_mod); + } + + if ((prop = node.property ("fine-adjust-modifier")) != 0) { + sscanf (prop->value().c_str(), "%d", &fine_adjust_mod); + } + + if ((prop = node.property ("push-points-modifier")) != 0) { + sscanf (prop->value().c_str(), "%d", &push_points_mod); + } + + if ((prop = node.property ("note-size-relative-modifier")) != 0) { + sscanf (prop->value().c_str(), "%d", ¬e_size_relative_mod); + } + + return Keyboard::set_state (node, version); +} + +/* Snap and snap delta modifiers may contain each other, so we use the + * following two methods to sort that out: + */ +bool +ArdourKeyboard::indicates_snap (guint state) +{ + const bool contains_s = Keyboard::modifier_state_contains (state, Keyboard::snap_modifier()); + const bool contains_d = Keyboard::modifier_state_contains (state, Keyboard::snap_delta_modifier()); + const bool equals_d = Keyboard::modifier_state_equals (state, Keyboard::snap_delta_modifier()); + + return (contains_s && ((contains_d && !equals_d) || !contains_d)); +} + +bool +ArdourKeyboard::indicates_snap_delta (guint state) +{ + const bool contains_d = Keyboard::modifier_state_contains (state, Keyboard::snap_delta_modifier()); + const bool contains_s = Keyboard::modifier_state_contains (state, Keyboard::snap_modifier()); + const bool equals_s = Keyboard::modifier_state_equals (state, Keyboard::snap_modifier()); + + return (contains_d && ((contains_s && !equals_s) || !contains_s)); +} + +void +ArdourKeyboard::set_constraint_modifier (guint mod) +{ + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~constraint_mod); + constraint_mod = mod; + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | constraint_mod); +} + +void +ArdourKeyboard::set_trim_contents_modifier (guint mod) +{ + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~trim_contents_mod); + trim_contents_mod = mod; + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | trim_contents_mod); +} + +void +ArdourKeyboard::set_trim_overlap_modifier (guint mod) +{ + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~trim_overlap_mod); + trim_overlap_mod = mod; + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | trim_overlap_mod); +} + +void +ArdourKeyboard::set_trim_anchored_modifier (guint mod) +{ + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~trim_anchored_mod); + trim_anchored_mod = mod; + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | trim_anchored_mod); +} + +void +ArdourKeyboard::set_fine_adjust_modifier (guint mod) +{ + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~fine_adjust_mod); + fine_adjust_mod = mod; + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | fine_adjust_mod); +} + +void +ArdourKeyboard::set_push_points_modifier (guint mod) +{ + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~push_points_mod); + push_points_mod = mod; + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | push_points_mod); +} + +void +ArdourKeyboard::set_note_size_relative_modifier (guint mod) +{ + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask & ~note_size_relative_mod); + note_size_relative_mod = mod; + RelevantModifierKeyMask = GdkModifierType (RelevantModifierKeyMask | note_size_relative_mod); +} + Selection::Operation ArdourKeyboard::selection_type (guint state) { @@ -187,5 +334,3 @@ ArdourKeyboard::selection_type (guint state) return Selection::Set; } } - - diff --git a/gtk2_ardour/keyboard.h b/gtk2_ardour/keyboard.h index 6a689567e5..9ca60950bb 100644 --- a/gtk2_ardour/keyboard.h +++ b/gtk2_ardour/keyboard.h @@ -32,11 +32,67 @@ class ArdourKeyboard : public Gtkmm2ext::Keyboard public: ArdourKeyboard(ARDOUR_UI& ardour_ui) : ui(ardour_ui) {} + XMLNode& get_state (void); + int set_state (const XMLNode&, int version); + void setup_keybindings (); static Selection::Operation selection_type (guint state); ARDOUR_UI& ui; + + /** @param state The button state from a GdkEvent. + * @return true if the modifier state indicates snap modifier + */ + static bool indicates_snap (guint state); + + /** @param state The button state from a GdkEvent. + * @return true if the modifier state indicates snap delta + */ + static bool indicates_snap_delta (guint state); + + static void set_constraint_modifier (guint); + /** @return Modifier mask to constrain drags in a particular direction; + */ + static ModifierMask constraint_modifier () { return ModifierMask (constraint_mod); } + + static void set_trim_contents_modifier (guint); + /** @return Modifier mask to move contents rather than region bounds during trim; + */ + static ModifierMask trim_contents_modifier () { return ModifierMask (trim_contents_mod); } + + static void set_trim_overlap_modifier (guint); + /** @return Modifier mask to remove region overlaps during trim; + */ + static ModifierMask trim_overlap_modifier () { return ModifierMask (trim_overlap_mod); } + + static void set_trim_anchored_modifier (guint); + /** @return Modifier mask to use anchored trim; + */ + static ModifierMask trim_anchored_modifier () { return ModifierMask (trim_anchored_mod); } + + static void set_fine_adjust_modifier (guint); + /** @return Modifier mask to fine adjust (control points only atm); + */ + static ModifierMask fine_adjust_modifier () { return ModifierMask (fine_adjust_mod); } + + static void set_push_points_modifier (guint); + /** @return Modifier mask to push proceeding points; + */ + static ModifierMask push_points_modifier () { return ModifierMask (push_points_mod); } + + static void set_note_size_relative_modifier (guint); + /** @return Modifier mask to resize notes relatively; + */ + static ModifierMask note_size_relative_modifier () { return ModifierMask (note_size_relative_mod); } +private: + static guint constraint_mod; + static guint trim_contents_mod; + static guint trim_overlap_mod; + static guint trim_anchored_mod; + static guint fine_adjust_mod; + static guint push_points_mod; + static guint note_size_relative_mod; }; #endif /* __ardour_keyboard_h__ */ diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index eed9017d0d..7c74b5f1e0 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -104,6 +104,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _current_range_max(0) , _region_relative_time_converter(r->session().tempo_map(), r->position()) , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start()) + , _region_relative_time_converter_double(r->session().tempo_map(), r->position()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (group)) , _note_diff_command (0) @@ -151,6 +152,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Container* parent, , _current_range_max(0) , _region_relative_time_converter(r->session().tempo_map(), r->position()) , _source_relative_time_converter(r->session().tempo_map(), r->position() - r->start()) + , _region_relative_time_converter_double(r->session().tempo_map(), r->position()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (group)) , _note_diff_command (0) @@ -203,6 +205,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other) , _current_range_max(0) , _region_relative_time_converter(other.region_relative_time_converter()) , _source_relative_time_converter(other.source_relative_time_converter()) + , _region_relative_time_converter_double(other.region_relative_time_converter_double()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (get_canvas_group())) , _note_diff_command (0) @@ -234,6 +237,7 @@ MidiRegionView::MidiRegionView (const MidiRegionView& other, boost::shared_ptr<M , _current_range_max(0) , _region_relative_time_converter(other.region_relative_time_converter()) , _source_relative_time_converter(other.source_relative_time_converter()) + , _region_relative_time_converter_double(other.region_relative_time_converter_double()) , _active_notes(0) , _note_group (new ArdourCanvas::Container (get_canvas_group())) , _note_diff_command (0) @@ -1408,6 +1412,7 @@ MidiRegionView::region_resized (const PropertyChange& what_changed) if (what_changed.contains (ARDOUR::Properties::position)) { _region_relative_time_converter.set_origin_b(_region->position()); + _region_relative_time_converter_double.set_origin_b(_region->position()); set_duration(_region->length(), 0); if (_enable_display) { redisplay_model(); @@ -2619,22 +2624,25 @@ MidiRegionView::note_dropped(NoteBase *, frameoffset_t dt, int8_t dnote) } /** @param x Pixel relative to the region position. + * @param ensure_snap defaults to false. true = snap always, ignoring snap mode and magnetic snap. + * Used for inverting the snap logic with key modifiers and snap delta calculation. * @return Snapped frame relative to the region position. */ framepos_t -MidiRegionView::snap_pixel_to_sample(double x) +MidiRegionView::snap_pixel_to_sample(double x, bool ensure_snap) { PublicEditor& editor (trackview.editor()); - return snap_frame_to_frame (editor.pixel_to_sample (x)); + return snap_frame_to_frame (editor.pixel_to_sample (x), ensure_snap); } /** @param x Pixel relative to the region position. + * @param ensure_snap defaults to false. true = ignore magnetic snap and snap mode (used for snap delta calculation). * @return Snapped pixel relative to the region position. */ double -MidiRegionView::snap_to_pixel(double x) +MidiRegionView::snap_to_pixel(double x, bool ensure_snap) { - return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x)); + return (double) trackview.editor().sample_to_pixel(snap_pixel_to_sample(x, ensure_snap)); } double @@ -2685,6 +2693,12 @@ MidiRegionView::region_frames_to_region_beats(framepos_t frames) const return _region_relative_time_converter.from(frames); } +double +MidiRegionView::region_frames_to_region_beats_double (framepos_t frames) const +{ + return _region_relative_time_converter_double.from(frames); +} + void MidiRegionView::begin_resizing (bool /*at_front*/) { @@ -2733,9 +2747,11 @@ MidiRegionView::begin_resizing (bool /*at_front*/) * a difference when multiple notes are being resized; in relative mode, each note's length is changed by the * amount of the drag. In non-relative mode, all selected notes are set to have the same start or end point * as the \a primary note. + * @param snap_delta snap offset of the primary note in pixels. used in SnapRelative SnapDelta mode. + * @param with_snap true if snap is to be used to determine the position, false if no snap is to be used. */ void -MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative) +MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { bool cursor_set = false; @@ -2746,15 +2762,15 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ if (at_front) { if (relative) { - current_x = canvas_note->x0() + delta_x; + current_x = canvas_note->x0() + delta_x + snap_delta; } else { - current_x = primary->x0() + delta_x; + current_x = primary->x0() + delta_x + snap_delta; } } else { if (relative) { - current_x = canvas_note->x1() + delta_x; + current_x = canvas_note->x1() + delta_x + snap_delta; } else { - current_x = primary->x1() + delta_x; + current_x = primary->x1() + delta_x + snap_delta; } } @@ -2768,26 +2784,47 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ } if (at_front) { - resize_rect->set_x0 (snap_to_pixel(current_x)); + if (with_snap) { + resize_rect->set_x0 (snap_to_pixel(current_x, true) - snap_delta); + } else { + resize_rect->set_x0 (current_x - snap_delta); + } resize_rect->set_x1 (canvas_note->x1()); } else { - resize_rect->set_x1 (snap_to_pixel(current_x)); + if (with_snap) { + resize_rect->set_x1 (snap_to_pixel(current_x, true) - snap_delta); + } else { + resize_rect->set_x1 (current_x - snap_delta); + } resize_rect->set_x0 (canvas_note->x0()); } if (!cursor_set) { - const double snapped_x = snap_pixel_to_sample (current_x); + /* Convert snap delta from pixels to beats. */ + framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta); + double snap_delta_beats; + int sign = 1; + + /* negative beat offsets aren't allowed */ + if (snap_delta_samps > 0) { + snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps); + } else if (snap_delta_samps < 0) { + snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps); + sign = -1; + } + + const double snapped_x = (with_snap ? snap_pixel_to_sample (current_x, true) : trackview.editor ().pixel_to_sample (current_x)); Evoral::Beats beats = region_frames_to_region_beats (snapped_x); Evoral::Beats len = Evoral::Beats(); if (at_front) { if (beats < canvas_note->note()->end_time()) { - len = canvas_note->note()->time() - beats; + len = canvas_note->note()->time() - beats + (sign * snap_delta_beats); len += canvas_note->note()->length(); } } else { if (beats >= canvas_note->note()->time()) { - len = beats - canvas_note->note()->time(); + len = beats - canvas_note->note()->time() - (sign * snap_delta_beats); } } @@ -2808,7 +2845,7 @@ MidiRegionView::update_resizing (NoteBase* primary, bool at_front, double delta_ * Parameters the same as for \a update_resizing(). */ void -MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative) +MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap) { _note_diff_command = _model->new_note_diff_command (_("resize notes")); @@ -2824,15 +2861,15 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ if (at_front) { if (relative) { - current_x = canvas_note->x0() + delta_x; + current_x = canvas_note->x0() + delta_x + snap_delta; } else { - current_x = primary->x0() + delta_x; + current_x = primary->x0() + delta_x + snap_delta; } } else { if (relative) { - current_x = canvas_note->x1() + delta_x; + current_x = canvas_note->x1() + delta_x + snap_delta; } else { - current_x = primary->x1() + delta_x; + current_x = primary->x1() + delta_x + snap_delta; } } @@ -2843,16 +2880,27 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ current_x = trackview.editor().sample_to_pixel(_region->length()); } - /* Convert that to a frame within the source */ - current_x = snap_pixel_to_sample (current_x) + _region->start (); + /* Convert snap delta from pixels to beats with sign. */ + framepos_t snap_delta_samps = trackview.editor().pixel_to_sample (snap_delta); + double snap_delta_beats; + int sign = 1; + + if (snap_delta_samps > 0) { + snap_delta_beats = region_frames_to_region_beats_double (snap_delta_samps); + } else if (snap_delta_samps < 0) { + snap_delta_beats = region_frames_to_region_beats_double ( - snap_delta_samps); + sign = -1; + } + + /* Convert the new x position to a frame within the source */ + const framepos_t current_fr = snap_pixel_to_sample (current_x, with_snap) + _region->start (); /* and then to beats */ - const Evoral::Beats x_beats = region_frames_to_region_beats (current_x); + const Evoral::Beats x_beats = region_frames_to_region_beats (current_fr); if (at_front && x_beats < canvas_note->note()->end_time()) { - note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats); - - Evoral::Beats len = canvas_note->note()->time() - x_beats; + note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::StartTime, x_beats - (sign * snap_delta_beats)); + Evoral::Beats len = canvas_note->note()->time() - x_beats + (sign * snap_delta_beats); len += canvas_note->note()->length(); if (!!len) { @@ -2861,8 +2909,8 @@ MidiRegionView::commit_resizing (NoteBase* primary, bool at_front, double delta_ } if (!at_front) { - const Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0), - x_beats - canvas_note->note()->time()); + Evoral::Beats len = std::max(Evoral::Beats(1 / 512.0), + x_beats - canvas_note->note()->time() - (sign * snap_delta_beats)); note_diff_add_change (canvas_note, MidiModel::NoteDiffCommand::Length, len); } diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h index 776543041c..4cab6b16bf 100644 --- a/gtk2_ardour/midi_region_view.h +++ b/gtk2_ardour/midi_region_view.h @@ -223,8 +223,8 @@ public: */ void begin_resizing(bool at_front); - void update_resizing (NoteBase*, bool, double, bool); - void commit_resizing (NoteBase*, bool, double, bool); + void update_resizing (NoteBase* primary, bool at_front, double delta_x, bool relative, double snap_delta, bool with_snap); + void commit_resizing (NoteBase* primary, bool at_front, double delat_x, bool relative, double snap_delta, bool with_snap); void abort_resizing (); /** Change the channel of the selection. @@ -250,15 +250,18 @@ public: /** Snap a region relative pixel coordinate to pixel units. * @param x a pixel coordinate relative to region start + * @param ensure_snap do not use magnetic snap (required for snap delta calculation) * @return the snapped pixel coordinate relative to region start */ - double snap_to_pixel(double x); + double snap_to_pixel(double x, bool ensure_snap = false); /** Snap a region relative pixel coordinate to frame units. * @param x a pixel coordinate relative to region start + * @param ensure_snap ignore SnapOff and magnetic snap. + * Required for inverting snap logic with modifier keys and snap delta calculation. * @return the snapped framepos_t coordinate relative to region start */ - framepos_t snap_pixel_to_sample(double x); + framepos_t snap_pixel_to_sample(double x, bool ensure_snap = false); /** Convert a timestamp in beats into frames (both relative to region position) */ framepos_t region_beats_to_region_frames(Evoral::Beats beats) const; @@ -268,6 +271,7 @@ public: } /** Convert a timestamp in frames to beats (both relative to region position) */ Evoral::Beats region_frames_to_region_beats(framepos_t) const; + double region_frames_to_region_beats_double(framepos_t) const; /** Convert a timestamp in beats measured from source start into absolute frames */ framepos_t source_beats_to_absolute_frames(Evoral::Beats beats) const; @@ -286,6 +290,10 @@ public: return _source_relative_time_converter; } + ARDOUR::DoubleBeatsFramesConverter const & region_relative_time_converter_double () const { + return _region_relative_time_converter_double; + } + void goto_previous_note (bool add_to_selection); void goto_next_note (bool add_to_selection); void change_note_lengths (bool, bool, Evoral::Beats beats, bool start, bool end); @@ -400,6 +408,7 @@ private: ARDOUR::BeatsFramesConverter _region_relative_time_converter; ARDOUR::BeatsFramesConverter _source_relative_time_converter; + ARDOUR::DoubleBeatsFramesConverter _region_relative_time_converter_double; boost::shared_ptr<ARDOUR::MidiModel> _model; Events _events; diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index cf68f35fb9..951c3e9b0e 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -141,10 +141,14 @@ class PublicEditor : public Gtk::Window, public PBD::StatefulDestructible, publi */ virtual void set_snap_threshold (double t) = 0; - /** Snap a value according to the current snap setting. */ + /** + * Snap a value according to the current snap setting. + * ensure_snap overrides SnapOff and magnetic snap + */ virtual void snap_to (framepos_t& first, ARDOUR::RoundMode direction = ARDOUR::RoundNearest, - bool for_mark = false) = 0; + bool for_mark = false, + bool ensure_snap = false) = 0; /** Undo some transactions. * @param n Number of transactions to undo. diff --git a/gtk2_ardour/rc_option_editor.cc b/gtk2_ardour/rc_option_editor.cc index bcefc1260b..9b38b558c9 100644 --- a/gtk2_ardour/rc_option_editor.cc +++ b/gtk2_ardour/rc_option_editor.cc @@ -271,20 +271,22 @@ static const struct { { "Option", GDK_MOD1_MASK }, { "Command-Shift", GDK_META_MASK|GDK_SHIFT_MASK }, { "Command-Option", GDK_MOD1_MASK|GDK_META_MASK }, - { "Shift-Option", GDK_SHIFT_MASK|GDK_MOD1_MASK }, + { "Option-Shift", GDK_MOD1_MASK|GDK_SHIFT_MASK }, + { "Control-Shift", GDK_CONTROL_MASK|GDK_SHIFT_MASK }, { "Shift-Command-Option", GDK_MOD5_MASK|GDK_SHIFT_MASK|GDK_META_MASK }, #else { "Key|Shift", GDK_SHIFT_MASK }, { "Control", GDK_CONTROL_MASK }, - { "Alt (Mod1)", GDK_MOD1_MASK }, + { "Alt", GDK_MOD1_MASK }, { "Control-Shift", GDK_CONTROL_MASK|GDK_SHIFT_MASK }, { "Control-Alt", GDK_CONTROL_MASK|GDK_MOD1_MASK }, { "Shift-Alt", GDK_SHIFT_MASK|GDK_MOD1_MASK }, { "Control-Shift-Alt", GDK_CONTROL_MASK|GDK_SHIFT_MASK|GDK_MOD1_MASK }, + { "Alt-Windows", GDK_MOD1_MASK|GDK_MOD4_MASK }, { "Mod2", GDK_MOD2_MASK }, { "Mod3", GDK_MOD3_MASK }, - { "Mod4", GDK_MOD4_MASK }, + { "Windows", GDK_MOD4_MASK }, { "Mod5", GDK_MOD5_MASK }, #endif { 0, 0 } @@ -319,25 +321,57 @@ public: } } - Table* t = manage (new Table (4, 4)); + Table* t = manage (new Table (5, 11)); t->set_spacings (4); - Label* l = manage (left_aligned_label (_("Edit using:"))); + int row = 0; + int col = 0; + + Label* l = manage (left_aligned_label (_("Select Keyboard layout:"))); l->set_name ("OptionsLabel"); - t->attach (*l, 0, 1, 0, 1, FILL | EXPAND, FILL); - t->attach (_edit_modifier_combo, 1, 2, 0, 1, FILL | EXPAND, FILL); + vector<string> strs; + + for (map<string,string>::iterator bf = Keyboard::binding_files.begin(); bf != Keyboard::binding_files.end(); ++bf) { + strs.push_back (bf->first); + } + + set_popdown_strings (_keyboard_layout_selector, strs); + _keyboard_layout_selector.set_active_text (Keyboard::current_binding_name()); + _keyboard_layout_selector.signal_changed().connect (sigc::mem_fun (*this, &KeyboardOptions::bindings_changed)); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_keyboard_layout_selector, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 0; + + l = manage (left_aligned_label (_("When Clicking:"))); + l->set_name ("OptionEditorHeading"); + t->attach (*l, col, col + 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + l = manage (left_aligned_label (_("Edit using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_edit_modifier_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); l = manage (new Label (_("+ button"))); l->set_name ("OptionsLabel"); - t->attach (*l, 3, 4, 0, 1, FILL | EXPAND, FILL); - t->attach (_edit_button_spin, 4, 5, 0, 1, FILL | EXPAND, FILL); + t->attach (*l, col + 3, col + 4, row, row + 1, FILL | EXPAND, FILL); + t->attach (_edit_button_spin, col + 4, col + 5, row, row + 1, FILL | EXPAND, FILL); _edit_button_spin.set_name ("OptionsEntry"); _edit_button_adjustment.set_value (Keyboard::edit_button()); _edit_button_adjustment.signal_value_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::edit_button_changed)); + ++row; + col = 1; + set_popdown_strings (_delete_modifier_combo, dumb); _delete_modifier_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::delete_modifier_chosen)); @@ -351,19 +385,21 @@ public: l = manage (left_aligned_label (_("Delete using:"))); l->set_name ("OptionsLabel"); - t->attach (*l, 0, 1, 1, 2, FILL | EXPAND, FILL); - t->attach (_delete_modifier_combo, 1, 2, 1, 2, FILL | EXPAND, FILL); + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_delete_modifier_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); l = manage (new Label (_("+ button"))); l->set_name ("OptionsLabel"); - t->attach (*l, 3, 4, 1, 2, FILL | EXPAND, FILL); - t->attach (_delete_button_spin, 4, 5, 1, 2, FILL | EXPAND, FILL); + t->attach (*l, col + 3, col + 4, row, row + 1, FILL | EXPAND, FILL); + t->attach (_delete_button_spin, col + 4, col + 5, row, row + 1, FILL | EXPAND, FILL); _delete_button_spin.set_name ("OptionsEntry"); _delete_button_adjustment.set_value (Keyboard::delete_button()); _delete_button_adjustment.signal_value_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::delete_button_changed)); + ++row; + col = 1; set_popdown_strings (_insert_note_modifier_combo, dumb); _insert_note_modifier_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::insert_note_modifier_chosen)); @@ -378,20 +414,165 @@ public: l = manage (left_aligned_label (_("Insert note using:"))); l->set_name ("OptionsLabel"); - t->attach (*l, 0, 1, 2, 3, FILL | EXPAND, FILL); - t->attach (_insert_note_modifier_combo, 1, 2, 2, 3, FILL | EXPAND, FILL); + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_insert_note_modifier_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); l = manage (new Label (_("+ button"))); l->set_name ("OptionsLabel"); - t->attach (*l, 3, 4, 2, 3, FILL | EXPAND, FILL); - t->attach (_insert_note_button_spin, 4, 5, 2, 3, FILL | EXPAND, FILL); + t->attach (*l, col + 3, col + 4, row, row + 1, FILL | EXPAND, FILL); + t->attach (_insert_note_button_spin, col + 4, col + 5, row, row + 1, FILL | EXPAND, FILL); _insert_note_button_spin.set_name ("OptionsEntry"); _insert_note_button_adjustment.set_value (Keyboard::insert_note_button()); _insert_note_button_adjustment.signal_value_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::insert_note_button_changed)); + ++row; + + l = manage (left_aligned_label (_("When Beginning a Drag:"))); + l->set_name ("OptionEditorHeading"); + t->attach (*l, 0, 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + /* copy modifier */ + set_popdown_strings (_copy_modifier_combo, dumb); + _copy_modifier_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::copy_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) Keyboard::CopyModifier) { + _copy_modifier_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } + + l = manage (left_aligned_label (_("Copy items using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_copy_modifier_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + /* constraint modifier */ + set_popdown_strings (_constraint_modifier_combo, dumb); + _constraint_modifier_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::constraint_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) ArdourKeyboard::constraint_modifier ()) { + _constraint_modifier_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } + + l = manage (left_aligned_label (_("Constrain drag using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_constraint_modifier_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + + l = manage (left_aligned_label (_("When Beginning a Trim:"))); + l->set_name ("OptionEditorHeading"); + t->attach (*l, 0, 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + /* trim_contents */ + set_popdown_strings (_trim_contents_combo, dumb); + _trim_contents_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::trim_contents_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) ArdourKeyboard::trim_contents_modifier ()) { + _trim_contents_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } + + l = manage (left_aligned_label (_("Trim contents using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_trim_contents_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + /* anchored trim */ + set_popdown_strings (_trim_anchored_combo, dumb); + _trim_anchored_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::trim_anchored_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) ArdourKeyboard::trim_anchored_modifier ()) { + _trim_anchored_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } + + l = manage (left_aligned_label (_("Anchored trim using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + ++col; + t->attach (_trim_anchored_combo, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + /* jump trim disabled for now + set_popdown_strings (_trim_jump_combo, dumb); + _trim_jump_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::trim_jump_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) Keyboard::trim_jump_modifier ()) { + _trim_jump_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } + + l = manage (left_aligned_label (_("Jump after trim using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + ++col; + t->attach (_trim_jump_combo, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + */ + + /* note resize relative */ + set_popdown_strings (_note_size_relative_combo, dumb); + _note_size_relative_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::note_size_relative_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) ArdourKeyboard::note_size_relative_modifier ()) { + _note_size_relative_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } + + l = manage (left_aligned_label (_("Resize notes relatively using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + ++col; + t->attach (_note_size_relative_combo, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + + ++row; + + l = manage (left_aligned_label (_("While Dragging:"))); + l->set_name ("OptionEditorHeading"); + t->attach (*l, 0, 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + /* ignore snap */ set_popdown_strings (_snap_modifier_combo, dumb); _snap_modifier_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::snap_modifier_chosen)); @@ -405,24 +586,100 @@ public: l = manage (left_aligned_label (_("Ignore snap using:"))); l->set_name ("OptionsLabel"); - t->attach (*l, 0, 1, 3, 4, FILL | EXPAND, FILL); - t->attach (_snap_modifier_combo, 1, 2, 3, 4, FILL | EXPAND, FILL); + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_snap_modifier_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); - vector<string> strs; + ++row; + col = 1; - for (map<string,string>::iterator bf = Keyboard::binding_files.begin(); bf != Keyboard::binding_files.end(); ++bf) { - strs.push_back (bf->first); + /* snap delta */ + set_popdown_strings (_snap_delta_combo, dumb); + _snap_delta_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::snap_delta_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) Keyboard::snap_delta_modifier ()) { + _snap_delta_combo.set_active_text (S_(modifiers[x].name)); + break; + } } - set_popdown_strings (_keyboard_layout_selector, strs); - _keyboard_layout_selector.set_active_text (Keyboard::current_binding_name()); - _keyboard_layout_selector.signal_changed().connect (sigc::mem_fun (*this, &KeyboardOptions::bindings_changed)); + l = manage (left_aligned_label (_("Snap to absolute using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_snap_delta_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + + l = manage (left_aligned_label (_("While Trimming:"))); + l->set_name ("OptionEditorHeading"); + t->attach (*l, 0, 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + /* trim_overlap */ + set_popdown_strings (_trim_overlap_combo, dumb); + _trim_overlap_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::trim_overlap_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) ArdourKeyboard::trim_overlap_modifier ()) { + _trim_overlap_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } + + l = manage (left_aligned_label (_("Resize overlaped regions using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_trim_overlap_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + + l = manage (left_aligned_label (_("While Dragging Control Points:"))); + l->set_name ("OptionEditorHeading"); + t->attach (*l, 0, 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + /* fine adjust */ + set_popdown_strings (_fine_adjust_combo, dumb); + _fine_adjust_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::fine_adjust_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) ArdourKeyboard::fine_adjust_modifier ()) { + _fine_adjust_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } - l = manage (left_aligned_label (_("Keyboard layout:"))); + l = manage (left_aligned_label (_("Fine adjust using:"))); l->set_name ("OptionsLabel"); - t->attach (*l, 0, 1, 4, 5, FILL | EXPAND, FILL); - t->attach (_keyboard_layout_selector, 1, 2, 4, 5, FILL | EXPAND, FILL); + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_fine_adjust_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); + + ++row; + col = 1; + + /* push points */ + set_popdown_strings (_push_points_combo, dumb); + _push_points_combo.signal_changed().connect (sigc::mem_fun(*this, &KeyboardOptions::push_points_modifier_chosen)); + + for (int x = 0; modifiers[x].name; ++x) { + if (modifiers[x].modifier == (guint) ArdourKeyboard::push_points_modifier ()) { + _push_points_combo.set_active_text (S_(modifiers[x].name)); + break; + } + } + + l = manage (left_aligned_label (_("Push points using:"))); + l->set_name ("OptionsLabel"); + + t->attach (*l, col, col + 1, row, row + 1, FILL | EXPAND, FILL); + t->attach (_push_points_combo, col + 1, col + 2, row, row + 1, FILL | EXPAND, FILL); _box->pack_start (*t, false, false); } @@ -478,6 +735,18 @@ private: } } + void copy_modifier_chosen () + { + string const txt = _copy_modifier_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + Keyboard::set_copy_modifier (modifiers[i].modifier); + break; + } + } + } + void insert_note_modifier_chosen () { string const txt = _insert_note_modifier_combo.get_active_text(); @@ -502,6 +771,102 @@ private: } } + void snap_delta_modifier_chosen () + { + string const txt = _snap_delta_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + Keyboard::set_snap_delta_modifier (modifiers[i].modifier); + break; + } + } + } + + void constraint_modifier_chosen () + { + string const txt = _constraint_modifier_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + ArdourKeyboard::set_constraint_modifier (modifiers[i].modifier); + break; + } + } + } + + void trim_contents_modifier_chosen () + { + string const txt = _trim_contents_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + ArdourKeyboard::set_trim_contents_modifier (modifiers[i].modifier); + break; + } + } + } + + void trim_overlap_modifier_chosen () + { + string const txt = _trim_overlap_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + ArdourKeyboard::set_trim_overlap_modifier (modifiers[i].modifier); + break; + } + } + } + + void trim_anchored_modifier_chosen () + { + string const txt = _trim_anchored_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + ArdourKeyboard::set_trim_anchored_modifier (modifiers[i].modifier); + break; + } + } + } + + void fine_adjust_modifier_chosen () + { + string const txt = _fine_adjust_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + ArdourKeyboard::set_fine_adjust_modifier (modifiers[i].modifier); + break; + } + } + } + + void push_points_modifier_chosen () + { + string const txt = _push_points_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + ArdourKeyboard::set_push_points_modifier (modifiers[i].modifier); + break; + } + } + } + + void note_size_relative_modifier_chosen () + { + string const txt = _note_size_relative_combo.get_active_text(); + + for (int i = 0; modifiers[i].name; ++i) { + if (txt == _(modifiers[i].name)) { + ArdourKeyboard::set_note_size_relative_modifier (modifiers[i].modifier); + break; + } + } + } + void delete_button_changed () { Keyboard::set_delete_button (_delete_button_spin.get_value_as_int()); @@ -520,8 +885,18 @@ private: ComboBoxText _keyboard_layout_selector; ComboBoxText _edit_modifier_combo; ComboBoxText _delete_modifier_combo; + ComboBoxText _copy_modifier_combo; ComboBoxText _insert_note_modifier_combo; ComboBoxText _snap_modifier_combo; + ComboBoxText _snap_delta_combo; + ComboBoxText _constraint_modifier_combo; + ComboBoxText _trim_contents_combo; + ComboBoxText _trim_overlap_combo; + ComboBoxText _trim_anchored_combo; + ComboBoxText _trim_jump_combo; + ComboBoxText _fine_adjust_combo; + ComboBoxText _push_points_combo; + ComboBoxText _note_size_relative_combo; Adjustment _delete_button_adjustment; SpinButton _delete_button_spin; Adjustment _edit_button_adjustment; diff --git a/gtk2_ardour/region_view.cc b/gtk2_ardour/region_view.cc index be96cd4058..75e091f2d0 100644 --- a/gtk2_ardour/region_view.cc +++ b/gtk2_ardour/region_view.cc @@ -939,10 +939,12 @@ RegionView::move_contents (frameoffset_t distance) /** Snap a frame offset within our region using the current snap settings. * @param x Frame offset from this region's position. + * @param ensure_snap whether to ignore snap_mode (in the case of SnapOff) and magnetic snap. + * Used when inverting snap mode logic with key modifiers, or snap distance calculation. * @return Snapped frame offset from this region's position. */ frameoffset_t -RegionView::snap_frame_to_frame (frameoffset_t x) const +RegionView::snap_frame_to_frame (frameoffset_t x, bool ensure_snap) const { PublicEditor& editor = trackview.editor(); @@ -951,12 +953,12 @@ RegionView::snap_frame_to_frame (frameoffset_t x) const /* try a snap in either direction */ framepos_t frame = session_frame; - editor.snap_to (frame, RoundNearest); + editor.snap_to (frame, RoundNearest, false, ensure_snap); /* if we went off the beginning of the region, snap forwards */ if (frame < _region->position ()) { frame = session_frame; - editor.snap_to (frame, RoundUpAlways); + editor.snap_to (frame, RoundUpAlways, false, ensure_snap); } /* back to region relative */ diff --git a/gtk2_ardour/region_view.h b/gtk2_ardour/region_view.h index 92006c556c..f17e37a72d 100644 --- a/gtk2_ardour/region_view.h +++ b/gtk2_ardour/region_view.h @@ -121,8 +121,8 @@ class RegionView : public TimeAxisViewItem } }; - ARDOUR::frameoffset_t snap_frame_to_frame (ARDOUR::frameoffset_t) const; - + ARDOUR::frameoffset_t snap_frame_to_frame (ARDOUR::frameoffset_t, bool ensure_snap = false) const; + protected: /** Allows derived types to specify their visibility requirements |