X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;ds=sidebyside;f=gtk2_ardour%2Feditor_drag.cc;h=014b4679e8e4499cfe6b078fa44ff56d2ca5867c;hb=88477ace25dbf1c65b568be3bfb08dfd67623c13;hp=f8b08d2a26cd1e59bbd21100322d817e13681ce1;hpb=3f8583f8d6b71abca7b070f2fdcbd9fb29c29d38;p=ardour.git diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index f8b08d2a26..014b4679e8 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -182,7 +182,7 @@ DragManager::motion_handler (GdkEvent* e, bool from_autoscroll) bool r = false; /* calling this implies that we expect the event to have canvas - * coordinates + * coordinates * * Can we guarantee that this is true? */ @@ -226,6 +226,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) { } @@ -250,20 +251,12 @@ 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 */ + _x_constrained = false; + _y_constrained = false; _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,10 +336,20 @@ 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 { - return _drags->current_pointer_x (); + return _drags->current_pointer_x (); } double @@ -355,10 +358,18 @@ Drag::current_pointer_y () const if (!_trackview_only) { return _drags->current_pointer_y (); } - + 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) { @@ -390,27 +401,44 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll) /* just changed */ if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) { + if ((event->motion.state & Gdk::BUTTON2_MASK) && Config->get_edit_mode() != Lock) { + _x_constrained = true; + _y_constrained = false; + } _initially_vertical = true; } else { + if ((event->motion.state & Gdk::BUTTON2_MASK) && Config->get_edit_mode() != Lock) { + _x_constrained = false; + _y_constrained = true; + } _initially_vertical = false; } + + if (Config->get_edit_mode() == Lock) { + if (event->button.state & Gdk::BUTTON2_MASK) { + _x_constrained = false; + } else { + _x_constrained = true; + } + _y_constrained = false; + } } - + if (!from_autoscroll) { _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false); } - + if (!_editor->autoscroll_active() || from_autoscroll) { - + bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll; motion (event, first_move && !_starting_point_passed); - + if (first_move && !_starting_point_passed) { _starting_point_passed = true; } - + _last_pointer_x = _drags->current_pointer_x (); _last_pointer_y = current_pointer_y (); _last_pointer_frame = adjusted_current_frame (event); @@ -476,17 +504,18 @@ Drag::add_midi_region (MidiTimeAxisView* view) } struct EditorOrderTimeAxisViewSorter { - bool operator() (TimeAxisView* a, TimeAxisView* b) { - RouteTimeAxisView* ra = dynamic_cast (a); - RouteTimeAxisView* rb = dynamic_cast (b); - assert (ra && rb); - return ra->route()->order_key () < rb->route()->order_key (); - } + bool operator() (TimeAxisView* a, TimeAxisView* b) { + RouteTimeAxisView* ra = dynamic_cast (a); + RouteTimeAxisView* rb = dynamic_cast (b); + assert (ra && rb); + return ra->route()->order_key () < rb->route()->order_key (); + } }; RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list const & v) - : Drag (e, i), - _primary (p) + : Drag (e, i) + , _primary (p) + , _ntracks (0) { _editor->visible_order_range (&_visible_y_low, &_visible_y_high); @@ -499,7 +528,7 @@ RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, listget_child_list (); for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) { _time_axis_views.push_back (j->get()); @@ -541,7 +570,7 @@ RegionDrag::find_time_axis_view (TimeAxisView* t) const ++i; } - if (i == N) { + if (_time_axis_views[i] != t) { return -1; } @@ -554,7 +583,9 @@ 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) { DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n"); } @@ -563,16 +594,14 @@ void RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { Drag::start_grab (event, cursor); + setup_snap_delta (_last_frame_position); - if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) { - _single_axis = true; - } - show_verbose_cursor_time (_last_frame_position); pair const tv = _editor->trackview_by_y_position (current_pointer_y ()); if (tv.first) { _last_pointer_time_axis_view = find_time_axis_view (tv.first); + assert(_last_pointer_time_axis_view >= 0); _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second; } } @@ -583,7 +612,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; @@ -595,11 +624,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; @@ -611,8 +640,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) { @@ -639,47 +667,76 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r return dx; } +int +RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const +{ + if (delta == 0) { + return start; + } + + const int tavsize = _time_axis_views.size(); + const int dt = delta > 0 ? +1 : -1; + int current = start; + int target = start + delta - skip; + + assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden()); + assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0)); + + while (current >= 0 && current != target) { + current += dt; + if (current < 0 && dt < 0) { + break; + } + if (current >= tavsize && dt > 0) { + break; + } + if (current < 0 || current >= tavsize) { + continue; + } + + RouteTimeAxisView const * rtav = dynamic_cast (_time_axis_views[current]); + if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) { + target += dt; + } + + if (distance_only && current == start + delta) { + break; + } + } + return target; +} + bool -RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const +RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const { if (_y_constrained) { return false; } - bool all_in_drop_zone = true; - + const int tavsize = _time_axis_views.size(); for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { - int n = i->time_axis_view + delta_track; - if (i->time_axis_view < 0) { + int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible); + assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden()); + + if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) { /* already in the drop zone */ - if (delta_track > 0) { - /* downward motion - might be OK if others are still not in the dropzone, - so check at the end of the loop if that is the case. - */ + if (delta_track >= 0) { + /* downward motion - OK if others are still not in the dropzone */ continue; - } - /* upward motion, and this view is currently in the drop zone. That's fine, so - we have to set all_in_drop_zone correctly to avoid blocking upward motion. - */ - all_in_drop_zone = false; - continue; - } else { - - all_in_drop_zone = false; - - if (n < 0) { - /* off the top */ - return false; } + } - if (n >= int (_time_axis_views.size())) { + if (n < 0) { + /* off the top */ + return false; + } else if (n >= tavsize) { /* downward motion into drop zone. That's fine. */ continue; } - + RouteTimeAxisView const * to = dynamic_cast (_time_axis_views[n]); - if (to == 0 || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) { + if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) { /* not a track, or the wrong type */ return false; } @@ -698,43 +755,48 @@ RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const } /* all regions being dragged are ok with this change */ - return !all_in_drop_zone; + return true; } +struct DraggingViewSorter { + bool operator() (const DraggingView& a, const DraggingView& b) { + return a.time_axis_view < b.time_axis_view; + } +}; + void RegionMotionDrag::motion (GdkEvent* event, bool first_move) { double delta_layer = 0; int delta_time_axis_view = 0; - int current_pointer_time_axis_view; + int current_pointer_time_axis_view = -1; 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 */ - - pair const r = _editor->trackview_by_y_position (current_pointer_y ()); + const double cur_y = current_pointer_y (); + pair const r = _editor->trackview_by_y_position (cur_y); TimeAxisView* tv = r.first; - if (!tv && current_pointer_y() < 0) { + if (!tv && cur_y < 0) { /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */ return; } - + + /* find drop-zone y-position */ + Coord last_track_bottom_edge; + last_track_bottom_edge = 0; + for (std::vector::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) { + if (!(*t)->hidden()) { + last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height(); + break; + } + } + if (tv && tv->view()) { + /* the mouse is over a track */ double layer = r.second; if (first_move && tv->view()->layer_display() == Stacked) { @@ -743,31 +805,142 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) /* Here's the current pointer position in terms of time axis view and layer */ current_pointer_time_axis_view = find_time_axis_view (tv); + assert(current_pointer_time_axis_view >= 0); double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer; - + /* Work out the change in y */ - if (_last_pointer_time_axis_view < 0) { - /* we can only move up */ - delta_time_axis_view = -1; + RouteTimeAxisView* rtv = dynamic_cast (tv); + if (!rtv || !rtv->is_track()) { + /* ignore busses early on. we can't move any regions on them */ + } else if (_last_pointer_time_axis_view < 0) { + /* Was in the drop-zone, now over a track. + * Hence it must be an upward move (from the bottom) + * + * track_index is still -1, so delta must be set to + * move up the correct number of tracks from the bottom. + * + * This is necessary because steps may be skipped if + * the bottom-most track is not a valid target and/or + * if there are hidden tracks at the bottom. + * Hence the initial offset (_ddropzone) as well as the + * last valid pointer position (_pdropzone) need to be + * taken into account. + */ + delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone; } else { delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view; } + /* TODO needs adjustment per DraggingView, + * + * e.g. select one region on the top-layer of a track + * and one region which is at the bottom-layer of another track + * drag both. + * + * Indicated drop-zones and layering is wrong. + * and may infer additional layers on the target-track + * (depending how many layers the original track had). + * + * Or select two regions (different layers) on a same track, + * move across a non-layer track.. -> layering info is lost. + * on drop either of the regions may be on top. + * + * Proposed solution: screw it :) well, + * don't use delta_layer, use an absolute value + * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source] + * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track. + * 3) iterate over all DraggingView, find the one that is over the track with most layers + * 4) proportionally scale layer to layers available on target + */ delta_layer = current_pointer_layer - _last_pointer_layer; - } + + } + /* for automation lanes, there is a TimeAxisView but no ->view() + * if (!tv) -> dropzone + */ + else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) { + /* Moving into the drop-zone.. */ + delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view; + /* delta_time_axis_view may not be sufficient to move into the DZ + * the mouse may enter it, but it may not be a valid move due to + * constraints. + * + * -> remember the delta needed to move into the dropzone + */ + _ddropzone = delta_time_axis_view; + /* ..but subtract hidden tracks (or routes) at the bottom. + * we silently move mover them + */ + _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true) + - _time_axis_views.size(); + } + else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) { + /* move around inside the zone. + * This allows to move further down until all regions are in the zone. + */ + const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y; + assert(ptr_y >= last_track_bottom_edge); + assert(_ddropzone > 0); + + /* calculate mouse position in 'tracks' below last track. */ + const double dzi_h = TimeAxisView::preset_height (HeightNormal); + uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h); + + if (dzpos > _pdropzone && _ndropzone < _ntracks) { + // move further down + delta_time_axis_view = dzpos - _pdropzone; + } else if (dzpos < _pdropzone && _ndropzone > 0) { + // move up inside the DZ + delta_time_axis_view = dzpos - _pdropzone; + } + } /* Work out the change in x */ framepos_t pending_region_position; double const x_delta = compute_x_delta (event, &pending_region_position); _last_frame_position = pending_region_position; + /* calculate hidden tracks in current y-axis delta */ + int delta_skip = 0; + if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) { + /* The mouse is more than one track below the dropzone. + * distance calculation is not needed (and would not work, either + * because the dropzone is "packed"). + * + * Except when [partially] moving regions out of dropzone in a large step. + * (the mouse may or may not remain in the DZ) + * Hidden tracks at the bottom of the TAV need to be skipped. + * + * This also handles the case if the mouse entered the DZ + * in a large step (exessive delta), either due to fast-movement, + * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above) + */ + if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) { + const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone; + assert(dt <= 0); + delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true) + -_time_axis_views.size() - dt; + } + } + else if (_last_pointer_time_axis_view < 0) { + /* Moving out of the zone. Check for hidden tracks at the bottom. */ + delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true) + -_time_axis_views.size() - delta_time_axis_view; + } else { + /* calculate hidden tracks that are skipped by the pointer movement */ + delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true) + - _last_pointer_time_axis_view + - delta_time_axis_view; + } + /* Verify change in y */ - if (!y_movement_allowed (delta_time_axis_view, delta_layer)) { + if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) { /* this y movement is not allowed, so do no y movement this time */ delta_time_axis_view = 0; delta_layer = 0; + delta_skip = 0; } if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) { @@ -777,20 +950,39 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) return; } - typedef pair NewTrackIndexAndPosition; - typedef map,NewTrackIndexAndPosition> PlaylistDropzoneMap; + typedef map, double> PlaylistDropzoneMap; PlaylistDropzoneMap playlist_dropzone_map; - int biggest_drop_zone_offset = 0; + _ndropzone = 0; // number of elements currently in the dropzone + + if (first_move) { + /* sort views by time_axis. + * This retains track order in the dropzone, regardless + * of actual selection order + */ + _views.sort (DraggingViewSorter()); + + /* count number of distinct tracks of all regions + * being dragged, used for dropzone. + */ + int prev_track = -1; + for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { + if (i->time_axis_view != prev_track) { + prev_track = i->time_axis_view; + ++_ntracks; + } + } +#ifndef NDEBUG + int spread = + _views.back().time_axis_view - + _views.front().time_axis_view; + + spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true) + - _views.back().time_axis_view; + + printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread); +#endif + } - Coord last_track_bottom_edge; - - if (_time_axis_views.empty()) { - last_track_bottom_edge = 0; - } else { - TimeAxisView* last = _time_axis_views.back(); - last_track_bottom_edge = last->canvas_display()->canvas_origin ().y + last->effective_height(); - } - for (list::iterator i = _views.begin(); i != _views.end(); ++i) { RegionView* rv = i->view; @@ -803,20 +995,20 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) } if (first_move) { - rv->drag_start (); + rv->drag_start (); /* reparent the regionview into a group above all * others */ - + ArdourCanvas::Item* rvg = rv->get_canvas_group(); - Duple rv_canvas_offset = rvg->parent()->canvas_origin (); - Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin (); - rv->get_canvas_group()->reparent (_editor->_drag_motion_group); + Duple rv_canvas_offset = rvg->parent()->canvas_origin (); + Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin (); + rv->get_canvas_group()->reparent (_editor->_drag_motion_group); /* move the item so that it continues to appear at the same location now that its parent has changed. - */ - rvg->move (rv_canvas_offset - dmg_canvas_offset); + */ + rvg->move (rv_canvas_offset - dmg_canvas_offset); } /* If we have moved tracks, we'll fudge the layer delta so that the @@ -829,39 +1021,54 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) this_delta_layer = - i->layer; } - if (tv) { + int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view; - int track_index; - - if (i->time_axis_view >= 0) { - track_index = i->time_axis_view + delta_time_axis_view; - } else { - /* delta time axis view will be at least -1, because we can only - be moving up if some dragged views have i->time_axis_view negative. + int track_index = i->time_axis_view + this_delta_time_axis_view; + assert(track_index >= 0); - the final real time axis view has index _time_axis_views.size() - 1. + if (track_index < 0 || track_index >= (int) _time_axis_views.size()) { + /* Track is in the Dropzone */ - so for DTAV = -1, the first drop zone track (i->tav == -1) should - move to the final TAV. - - the second drop zone track should move to i->tav == -1 (i.e. becomes - the first drop zone track). and so on. - */ + i->time_axis_view = track_index; + assert(i->time_axis_view >= (int) _time_axis_views.size()); + if (cur_y >= 0) { + + double yposition = 0; + PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist()); + rv->set_height (TimeAxisView::preset_height (HeightNormal)); + ++_ndropzone; + + /* store index of each new playlist as a negative count, starting at -1 */ + + if (pdz == playlist_dropzone_map.end()) { + /* compute where this new track (which doesn't exist yet) will live + on the y-axis. + */ + yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */ + + /* How high is this region view ? */ + + boost::optional obbox = rv->get_canvas_group()->bounding_box (); + ArdourCanvas::Rect bbox; + + if (obbox) { + bbox = obbox.get (); + } + + last_track_bottom_edge += bbox.height(); - int index_from_bottom = i->time_axis_view - delta_time_axis_view; - int const bottom = _time_axis_views.size() - 1; + playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition)); - if (index_from_bottom >= 0) { - track_index = bottom - index_from_bottom; } else { - track_index = index_from_bottom; + yposition = pdz->second; } - } - if (track_index < 0 || track_index >= (int) _time_axis_views.size()) { - goto dropzone; + /* values are zero or negative, hence the use of min() */ + y_delta = yposition - rv->get_canvas_group()->canvas_origin().y; } + } else { + /* The TimeAxisView that this region is now over */ TimeAxisView* current_tv = _time_axis_views[track_index]; @@ -869,15 +1076,15 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) if (current_tv->view()->layer_display() == Stacked) { current_tv->view()->set_layer_display (Expanded); } - + /* We're only allowed to go -ve in layer on Expanded views */ if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) { this_delta_layer = - i->layer; } - + /* Set height */ rv->set_height (current_tv->view()->child_height ()); - + /* Update show/hidden status as the region view may have come from a hidden track, or have moved to one. */ @@ -898,7 +1105,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) /* Get the y coordinate of the top of the track that this region is now over */ track_origin = current_tv->canvas_display()->item_to_canvas (track_origin); - + /* And adjust for the layer that it should be on */ StreamView* cv = current_tv->view (); switch (cv->layer_display ()) { @@ -919,61 +1126,13 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) */ y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y; - - } - - - } else { - - dropzone: - - if (current_pointer_y() >= 0) { - - NewTrackIndexAndPosition ip; - PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist()); - /* store index of each new playlist as a negative count, starting at -1 */ - - if (pdz == playlist_dropzone_map.end()) { - - int n = playlist_dropzone_map.size() + 1; - - /* compute where this new track (which doesn't exist yet) will live - on the y-axis. - */ - - ip.first = -n; /* in time axis units, negative to signify "in drop zone " */ - ip.second = last_track_bottom_edge; /* where to place the top edge of the regionview */ - - /* How high is this region view ? */ - - boost::optional obbox = rv->get_canvas_group()->bounding_box (); - ArdourCanvas::Rect bbox; - - if (obbox) { - bbox = obbox.get (); - } - - last_track_bottom_edge += bbox.height(); - - playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), ip)); - i->time_axis_view = -n; - - } else { - ip = pdz->second; - i->time_axis_view = ip.first; - } - - /* values are zero or negative, hence the use of min() */ - biggest_drop_zone_offset = min (biggest_drop_zone_offset, i->time_axis_view); - - y_delta = ip.second - rv->get_canvas_group()->canvas_origin().y; } } - + /* Now move the region view */ rv->move (x_delta, y_delta); - + } /* foreach region */ _total_x_delta += x_delta; @@ -982,33 +1141,64 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) show_verbose_cursor_time (_last_frame_position); } + /* keep track of pointer movement */ if (tv) { - /* the pointer is currently over a time axis view */ - - if (_last_pointer_time_axis_view < 0) { - - /* last motion event was not over a time axis view */ + if (_last_pointer_time_axis_view < 0) { + /* last motion event was not over a time axis view + * or last y-movement out of the dropzone was not valid + */ + int dtz = 0; if (delta_time_axis_view < 0) { - /* was in the drop zone, moving up */ - _last_pointer_time_axis_view = current_pointer_time_axis_view; - } else { - /* was in the drop zone, moving down ... not possible */ + /* in the drop zone, moving up */ + + /* _pdropzone is the last known pointer y-axis position inside the DZ. + * We do not use negative _last_pointer_time_axis_view because + * the dropzone is "packed" (the actual track offset is ignored) + * + * As opposed to the actual number + * of elements in the dropzone (_ndropzone) + * _pdropzone is not constrained. This is necessary + * to allow moving multiple regions with y-distance + * into the DZ. + * + * There can be 0 elements in the dropzone, + * even though the drag-pointer is inside the DZ. + * + * example: + * [ Audio-track, Midi-track, Audio-track, DZ ] + * move regions from both audio tracks at the same time into the + * DZ by grabbing the region in the bottom track. + */ + assert(current_pointer_time_axis_view >= 0); + dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view); + _pdropzone -= dtz; } + /* only move out of the zone if the movement is OK */ + if (_pdropzone == 0 && delta_time_axis_view != 0) { + assert(delta_time_axis_view < 0); + _last_pointer_time_axis_view = current_pointer_time_axis_view; + /* if all logic and maths are correct, there is no need to assign the 'current' pointer. + * the current position can be calculated as follows: + */ + // a well placed oofus attack can still throw this off. + // likley auto-scroll related, printf() debugging may tell, commented out for now. + //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view); + } } else { - /* last motion event was also over a time axis view */ - - _last_pointer_time_axis_view = current_pointer_time_axis_view; + _last_pointer_time_axis_view += delta_time_axis_view; + assert(_last_pointer_time_axis_view >= 0); } } else { /* the pointer is not over a time axis view */ - - _last_pointer_time_axis_view = biggest_drop_zone_offset; + assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view))); + _pdropzone += delta_time_axis_view - delta_skip; + _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter. } _last_pointer_layer += delta_layer; @@ -1043,7 +1233,7 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move) to it. */ region_copy->set_playlist (original->playlist()); - + RegionView* nrv; if (arv) { boost::shared_ptr audioregion_copy @@ -1111,28 +1301,20 @@ void RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred) { RegionMotionDrag::finished (ev, movement_occurred); - + if (!movement_occurred) { - + /* just a click */ if (was_double_click() && !_views.empty()) { DraggingView dv = _views.front(); dv.view->show_region_editor (); - + } 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 @@ -1142,7 +1324,7 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred) for (list::iterator i = _views.begin(); i != _views.end(); ++i) { i->view->get_canvas_group()->show (); } - + bool const changed_position = (_last_frame_position != _primary->region()->position()); bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view()); framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position; @@ -1170,15 +1352,19 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred) RouteTimeAxisView* RegionMoveDrag::create_destination_time_axis (boost::shared_ptr region, TimeAxisView* original) -{ +{ /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the new track. */ - + try { if (boost::dynamic_pointer_cast (region)) { list > audio_tracks; - audio_tracks = _editor->session()->new_audio_track (region->n_channels(), region->n_channels(), ARDOUR::Normal, 0, 1, region->name()); + uint32_t output_chan = region->n_channels(); + if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) { + output_chan = _editor->session()->master_out()->n_inputs().n_audio(); + } + audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, ARDOUR::Normal, 0, 1, region->name()); RouteTimeAxisView* rtav = _editor->axis_view_from_route (audio_tracks.front()); if (rtav) { rtav->set_height (original->current_height()); @@ -1193,7 +1379,7 @@ RegionMoveDrag::create_destination_time_axis (boost::shared_ptr region, rtav->set_height (original->current_height()); } return rtav; - } + } } catch (...) { error << _("Could not create new track after region placed in the drop zone") << endmsg; return 0; @@ -1205,7 +1391,7 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed { RegionSelection new_views; PlaylistSet modified_playlists; - RouteTimeAxisView* new_time_axis_view = 0; + RouteTimeAxisView* new_time_axis_view = 0; if (_brushing) { /* all changes were made during motion event handlers */ @@ -1238,11 +1424,11 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed where = i->view->region()->position(); } - if (i->time_axis_view < 0) { + if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) { /* dragged to drop zone */ PlaylistMapping::iterator pm; - + if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) { /* first region from this original playlist: create a new track */ new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view); @@ -1255,15 +1441,15 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed } else { /* destination time axis view is the one we dragged to */ dest_rtv = dynamic_cast (_time_axis_views[i->time_axis_view]); - } - + } + if (dest_rtv != 0) { RegionView* new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, modified_playlists); if (new_view != 0) { new_views.push_back (new_view); } } - + /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator since deletion will automagically remove it from _views, thus invalidating i as an iterator. */ @@ -1319,12 +1505,12 @@ RegionMoveDrag::finished_no_copy ( ++i; continue; } - - if (i->time_axis_view < 0) { + + if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) { /* dragged to drop zone */ PlaylistMapping::iterator pm; - + if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) { /* first region from this original playlist: create a new track */ new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view); @@ -1334,16 +1520,16 @@ RegionMoveDrag::finished_no_copy ( /* we already created a new track for regions from this playlist, use it */ dest_rtv = pm->second; } - + } else { /* destination time axis view is the one we dragged to */ dest_rtv = dynamic_cast (_time_axis_views[i->time_axis_view]); - } + } assert (dest_rtv); double const dest_layer = i->layer; - + views_to_update.insert (dest_rtv); framepos_t where; @@ -1606,7 +1792,7 @@ RegionMotionDrag::aborted (bool) } } } - + for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { RegionView* rv = i->view; TimeAxisView* tv = &(rv->get_time_axis_view ()); @@ -1666,7 +1852,10 @@ RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr r, Rout void RegionInsertDrag::finished (GdkEvent *, bool) { - RouteTimeAxisView* dest_rtv = dynamic_cast (_time_axis_views[_views.front().time_axis_view]); + int pos = _views.front().time_axis_view; + assert(pos >= 0 && pos < (int)_time_axis_views.size()); + + RouteTimeAxisView* dest_rtv = dynamic_cast (_time_axis_views[pos]); _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item()); _primary->get_canvas_group()->set_y_position (0); @@ -1705,9 +1894,9 @@ RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView } struct RegionSelectionByPosition { - bool operator() (RegionView*a, RegionView* b) { - return a->region()->position () < b->region()->position(); - } + bool operator() (RegionView*a, RegionView* b) { + return a->region()->position () < b->region()->position(); + } }; void @@ -1839,7 +2028,7 @@ RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regi { for (std::list::iterator i = _views.begin(); i != _views.end(); ) { - // we added all the regions after the selection + // we added all the regions after the selection std::list::iterator to_erase = i++; if (!_editor->selection->regions.contains (to_erase->view)) { @@ -1872,9 +2061,9 @@ RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regi } bool -RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer) const +RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const { - if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer)) { + if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) { if (delta_track) { return allow_moves_across_tracks; } else { @@ -1966,7 +2155,7 @@ RegionRippleDrag::motion (GdkEvent* event, bool first_move) framecnt_t adjust = 0; if (prev_tav && tv != prev_tav) { - // dragged onto a different track + // dragged onto a different track // remove the unselected regions from _views, restore them to their original positions // and add the regions after the drop point on the new playlist to _views instead. // undo the effect of rippling the previous playlist, and include the effect of removing @@ -1978,9 +2167,9 @@ RegionRippleDrag::motion (GdkEvent* event, bool first_move) prev_amount = 0; // move just the selected regions - RegionMoveDrag::motion(event, first_move); + RegionMoveDrag::motion(event, first_move); - // ensure that the ripple operation on the new playlist inserts selection_length time + // ensure that the ripple operation on the new playlist inserts selection_length time adjust = selection_length; // ripple the new current playlist tv->playlist()->ripple (where, amount+adjust, exclude); @@ -1990,7 +2179,7 @@ RegionRippleDrag::motion (GdkEvent* event, bool first_move) } else { // motion on same track - RegionMoveDrag::motion(event, first_move); + RegionMoveDrag::motion(event, first_move); } prev_tav = tv; @@ -1999,7 +2188,7 @@ RegionRippleDrag::motion (GdkEvent* event, bool first_move) } else { // selection encompasses multiple tracks - just drag // cross-track drags are forbidden - RegionMoveDrag::motion(event, first_move); + RegionMoveDrag::motion(event, first_move); } if (!_x_constrained) { @@ -2013,21 +2202,21 @@ void RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred) { if (!movement_occurred) { - + /* just a click */ if (was_double_click() && !_views.empty()) { DraggingView dv = _views.front(); dv.view->show_region_editor (); - + } return; } _editor->begin_reversible_command(_("Ripple drag")); - - // remove the regions being rippled from the dragging view, updating them to + + // remove the regions being rippled from the dragging view, updating them to // their new positions remove_unselected_from_views (prev_amount, true); @@ -2132,6 +2321,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"); } @@ -2156,9 +2346,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; @@ -2191,7 +2385,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) { @@ -2199,21 +2393,62 @@ NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/) assert (nb); MidiRegionView* mrv = dynamic_cast(*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 (_item->get_data ("notebase")); assert (nb); MidiRegionView* mrv = dynamic_cast(*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); } } @@ -2411,8 +2646,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); @@ -2421,8 +2657,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); @@ -2430,17 +2665,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: @@ -2450,7 +2686,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); @@ -2476,8 +2712,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) { @@ -2523,7 +2759,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; } @@ -2609,7 +2845,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); @@ -2617,7 +2853,6 @@ TrimDrag::motion (GdkEvent* event, bool first_move) } } - void TrimDrag::finished (GdkEvent* event, bool movement_occurred) { @@ -2674,7 +2909,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred) _views.begin()->view->region()->length()); } } - + if (!_editor->selection->selected (_primary)) { _primary->thaw_after_trim (); } else { @@ -2682,7 +2917,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred) set > diffed_playlists; for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { - i->view->thaw_after_trim (); + i->view->thaw_after_trim (); i->view->enable_display (true); /* Trimming one region may affect others on the playlist, so we need @@ -2796,19 +3031,19 @@ MeterMarkerDrag::motion (GdkEvent* event, bool first_move) if (first_move) { // create a dummy marker for visual representation of moving the - // section, because whether its a copy or not, we're going to + // section, because whether its a copy or not, we're going to // leave or lose the original marker (leave if its a copy; lose if its // not, because we'll remove it from the map). - + MeterSection section (_marker->meter()); if (!section.movable()) { return; } - + char name[64]; snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ()); - + _marker = new MeterMarker ( *_editor, *_editor->meter_group, @@ -2816,7 +3051,7 @@ MeterMarkerDrag::motion (GdkEvent* event, bool first_move) name, *new MeterSection (_marker->meter()) ); - + /* use the new marker for the grab */ swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME); @@ -2855,7 +3090,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred) TempoMap& map (_editor->session()->tempo_map()); map.bbt_time (last_pointer_frame(), when); - + if (_copy == true) { _editor->begin_reversible_command (_("copy meter mark")); XMLNode &before = map.get_state(); @@ -2868,7 +3103,7 @@ MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred) _editor->begin_reversible_command (_("move meter mark")); /* we removed it before, so add it back now */ - + map.add_meter (_marker->meter(), when); XMLNode &after = map.get_state(); _editor->session()->add_command(new MementoCommand(map, before_state, &after)); @@ -2928,10 +3163,10 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move) if (first_move) { // create a dummy marker for visual representation of moving the - // section, because whether its a copy or not, we're going to + // section, because whether its a copy or not, we're going to // leave or lose the original marker (leave if its a copy; lose if its // not, because we'll remove it from the map). - + // create a dummy marker for visual representation of moving the copy. // The actual copying is not done before we reach the finish callback. @@ -3061,10 +3296,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); @@ -3087,7 +3323,7 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c) if (AudioEngine::instance()->connected()) { - + /* do this only if we're the engine is connected * because otherwise this request will never be * serviced and we'll busy wait forever. likewise, @@ -3102,15 +3338,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)); } } @@ -3161,6 +3398,7 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) AudioRegionView* arv = dynamic_cast (_primary); boost::shared_ptr const r = arv->audio_region (); + setup_snap_delta (r->position ()); show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32); } @@ -3177,7 +3415,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 region = boost::dynamic_pointer_cast (_primary->region ()); if (pos < (region->position() + 64)) { @@ -3210,8 +3452,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 region = boost::dynamic_pointer_cast (_primary->region ()); @@ -3273,6 +3516,7 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) AudioRegionView* arv = dynamic_cast (_primary); boost::shared_ptr 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()); } @@ -3290,7 +3534,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 region = boost::dynamic_pointer_cast (_primary->region ()); @@ -3325,7 +3571,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 region = boost::dynamic_pointer_cast (_primary->region ()); @@ -3467,7 +3715,7 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) break; } - /* Set up copies for us to manipulate during the drag + /* Set up copies for us to manipulate during the drag */ for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) { @@ -3497,7 +3745,7 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) (*x).move_both = true; } } - + } } @@ -3521,7 +3769,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; } @@ -3536,7 +3784,7 @@ MarkerDrag::motion (GdkEvent* event, bool) if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) { /* this marker is represented by this - * CopiedLocationMarkerInfo + * CopiedLocationMarkerInfo */ if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) { @@ -3599,12 +3847,12 @@ MarkerDrag::motion (GdkEvent* event, bool) copy_location->set_start (copy_location->start() + f_delta); } else { - + framepos_t new_start = copy_location->start() + f_delta; framepos_t new_end = copy_location->end() + f_delta; - + if (is_start) { // start-of-range marker - + if (move_both || (*x).move_both) { copy_location->set_start (new_start); copy_location->set_end (new_end); @@ -3632,14 +3880,14 @@ MarkerDrag::motion (GdkEvent* event, bool) } update_item (copy_location); - + /* now lookup the actual GUI items used to display this * location and move them to wherever the copy of the location * is now. This means that the logic in ARDOUR::Location is - * still enforced, even though we are not (yet) modifying + * still enforced, even though we are not (yet) modifying * the real Location itself. */ - + Editor::LocationMarkers* lm = _editor->find_location_markers (real_location); if (lm) { @@ -3657,7 +3905,7 @@ void MarkerDrag::finished (GdkEvent* event, bool movement_occurred) { if (!movement_occurred) { - + if (was_double_click()) { _editor->rename_marker (_marker); return; @@ -3733,7 +3981,7 @@ MarkerDrag::aborted (bool movement_occured) for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) { /* move all markers to their original location */ - + for (vector::iterator m = x->markers.begin(); m != x->markers.end(); ++m) { @@ -3779,13 +4027,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; @@ -3798,7 +4049,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; } @@ -3831,12 +4082,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()); @@ -3852,8 +4104,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 (); } @@ -3884,9 +4135,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"); } @@ -3937,7 +4188,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; } @@ -4125,7 +4376,7 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool) } else { x2 = max (x1 + min_dimension, x2); } - } + } if (y2 < y1) { y2 = min (y1 - min_dimension, y2); @@ -4135,7 +4386,7 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool) /* translate rect into item space and set */ - ArdourCanvas::Rect r (x1, y1, x2, y2); + ArdourCanvas::Rect r (x1, y1, x2, y2); /* this drag is a _trackview_only == true drag, so the y1 and * y2 (computed using current_pointer_y() and grab_y()) will be @@ -4167,7 +4418,7 @@ RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress) grab = raw_grab_frame (); lpf = _editor->pixel_to_sample_from_event (last_pointer_x()); } - + if (grab < lpf) { x1 = grab; x2 = lpf; @@ -4178,7 +4429,7 @@ RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress) double y1; double y2; - + if (current_pointer_y() < grab_y()) { y1 = current_pointer_y(); y2 = grab_y(); @@ -4212,14 +4463,14 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred) add_midi_region (mtv); do_deselect = false; } - } + } /* do not deselect if Primary or Tertiary (toggle-select or * extend-select are pressed. */ - if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) && - !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) && + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) && + !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) && do_deselect) { deselect_things (); } @@ -4246,7 +4497,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 @@ -4258,14 +4514,15 @@ TimeFXDrag::motion (GdkEvent* event, bool) pair 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 @@ -4349,11 +4606,10 @@ SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o) : Drag (e, i) , _operation (o) , _add (false) - , _original_pointer_time_axis (-1) , _time_selection_at_start (!_editor->get_selection().time.empty()) { DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n"); - + if (_time_selection_at_start) { start_at_start = _editor->get_selection().time.start(); end_at_start = _editor->get_selection().time.end_frame(); @@ -4408,8 +4664,6 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*) } else { show_verbose_cursor_time (adjusted_current_frame (event)); } - - _original_pointer_time_axis = _editor->trackview_by_y_position (current_pointer_y ()).first->order (); } void @@ -4482,7 +4736,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) _editor->set_selected_track_as_side_effect (Selection::Add); _editor->clicked_selection = _editor->selection->add (start, end); _add = false; - + } else { /* new selection */ @@ -4494,15 +4748,15 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) _editor->clicked_selection = _editor->selection->set (start, end); } } - - //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff, - // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this + + //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff, + // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this AutomationTimeAxisView *atest = dynamic_cast(_editor->clicked_axisview); if (atest) { _editor->selection->add (atest); - break; + break; } - + /* select all tracks within the rectangle that we've marked out so far */ TrackViewList new_selection; TrackViewList& all_tracks (_editor->track_views); @@ -4573,7 +4827,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) } break; - + case SelectionMove: start = _editor->selection->time[_editor->clicked_selection].start; @@ -4594,7 +4848,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) if (start != end) { switch (_operation) { - case SelectionMove: + case SelectionMove: if (_time_selection_at_start) { _editor->selection->move_time (distance); } @@ -4616,7 +4870,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred) { Session* s = _editor->session(); - _editor->begin_reversible_selection_op (_("Change Time Selection")); + _editor->begin_reversible_selection_op (X_("Change Time Selection")); if (movement_occurred) { motion (event, false); /* XXX this is not object-oriented programming at all. ick */ @@ -4664,7 +4918,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred) if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) { _editor->selection->set (_editor->clicked_axisview); } - + if (s && s->get_play_range () && s->transport_rolling()) { s->request_stop (false, false); } @@ -4689,7 +4943,7 @@ RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operat { DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n"); - _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group, + _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, physical_screen_height (_editor->get_window()))); _drag_rect->hide (); @@ -4700,7 +4954,7 @@ RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operat RangeMarkerBarDrag::~RangeMarkerBarDrag() { - /* normal canvas items will be cleaned up when their parent group is deleted. But + /* normal canvas items will be cleaned up when their parent group is deleted. But this item is created as the child of a long-lived parent group, and so we need to explicitly delete it. */ @@ -4726,7 +4980,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; @@ -4878,7 +5132,6 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred) * nothing */ } else { /* operation == CreateRangeMarker || CreateSkipMarker */ - framepos_t start; framepos_t end; @@ -4948,6 +5201,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())) { @@ -4967,7 +5221,7 @@ NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *) _region->unique_select (_primary); } - _editor->begin_reversible_selection_op(_("Select Note Press")); + _editor->begin_reversible_selection_op(X_("Select Note Press")); _editor->commit_reversible_selection_op(); } } @@ -4975,7 +5229,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()); @@ -4984,15 +5238,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 */ @@ -5011,10 +5282,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 */ @@ -5037,7 +5308,7 @@ NoteDrag::motion (GdkEvent *, bool) char buf[12]; uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127); - + snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(), (int) floor ((double)new_note)); @@ -5078,12 +5349,12 @@ NoteDrag::finished (GdkEvent* ev, bool moved) } if (changed) { - _editor->begin_reversible_selection_op(_("Select Note Release")); + _editor->begin_reversible_selection_op(X_("Select Note Release")); _editor->commit_reversible_selection_op(); } } } else { - _region->note_dropped (_primary, total_dx(), total_dy()); + _region->note_dropped (_primary, total_dx (ev->button.state), total_dy()); } } @@ -5444,7 +5715,7 @@ MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, fram y1 = max (0.0, y1 - y); y2 = max (0.0, y2 - y); - + _region_view->update_vertical_drag_selection ( y1, y2, @@ -5471,10 +5742,10 @@ EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, fram /* We just want to select things at the end of the drag, not during it */ return; } - + Selection::Operation op = ArdourKeyboard::selection_type (button_state); - _editor->begin_reversible_selection_op (_("rubberband selection")); + _editor->begin_reversible_selection_op (X_("rubberband selection")); _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false); @@ -5484,7 +5755,7 @@ EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, fram void EditorRubberbandSelectDrag::deselect_things () { - _editor->begin_reversible_selection_op (_("Clear Selection (rubberband)")); + _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)")); _editor->selection->clear_tracks(); _editor->selection->clear_regions(); @@ -5524,7 +5795,7 @@ void NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { Drag::start_grab (event, cursor); - + _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ()); framepos_t pf = _drags->current_pointer_frame (); @@ -5566,13 +5837,13 @@ NoteCreateDrag::finished (GdkEvent*, bool had_movement) if (!had_movement) { return; } - + framepos_t const start = min (_note[0], _note[1]); framecnt_t length = (framecnt_t) fabs ((double)(_note[0] - _note[1])); framecnt_t const g = grid_frames (start); Evoral::Beats const one_tick = Evoral::Beats::ticks(1); - + if (_editor->snap_mode() == SnapNormal && length < g) { length = g; } @@ -5594,7 +5865,7 @@ NoteCreateDrag::y_to_region (double y) const void NoteCreateDrag::aborted (bool) { - + } CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn) @@ -5661,9 +5932,9 @@ CrossfadeEdgeDrag::finished (GdkEvent*, bool) } new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start); - + _editor->begin_reversible_command ("xfade trim"); - ar->playlist()->clear_owned_changes (); + ar->playlist()->clear_owned_changes (); if (start) { ar->set_fade_in_length (new_length); @@ -5721,7 +5992,7 @@ RegionCutDrag::finished (GdkEvent*, bool) _editor->get_track_canvas()->canvas()->re_enter(); framepos_t pos = _drags->current_pointer_frame(); - + line->hide (); RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);