Major rework of DnD.
Allow dragging multiple regions from multiple tracks across any number of hidden tracks and automation lanes. fixes #6176 and #6172
This commit is contained in:
parent
089c334d0c
commit
b637c2223f
|
@ -640,46 +640,52 @@ 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
|
||||
{
|
||||
if (delta == 0) {
|
||||
return start;
|
||||
}
|
||||
|
||||
const int dt = delta > 0 ? +1 : -1;
|
||||
int current = start;
|
||||
int target = start + delta - skip;
|
||||
|
||||
assert (current < 0 || current >= _time_axis_views.size() || !_time_axis_views[current]->hidden());
|
||||
assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
|
||||
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
if (current >= _time_axis_views.size() && target >= 0 && target < _time_axis_views.size()) {
|
||||
printf("MOVE OUT OF THE ZONE cur: %d d: %d s: %d\n", start, delta, skip);
|
||||
}
|
||||
#endif
|
||||
|
||||
while (current >= 0 && current != target) {
|
||||
current += dt;
|
||||
if (current < 0 || current >= _time_axis_views.size()) {
|
||||
continue;
|
||||
}
|
||||
if (_time_axis_views[current]->hidden()) {
|
||||
target += dt;
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
|
||||
int n = i->time_axis_view + delta_track;
|
||||
/* TODO: for i > 0, account for hidden tracks
|
||||
*
|
||||
* example. top-to-bottom:
|
||||
* 2 Audio Tracks [one region each] 1 Bus 1 Audio Track.
|
||||
* Hide the Bus. (looks like 3 audio tracks)
|
||||
*
|
||||
* select both region, grab the upper and move down 1 track
|
||||
* -> delta_track = 1;
|
||||
*
|
||||
* real_delta_track=1 (for region on track1)
|
||||
* real_delta_track=2 (for region on track2, skip the hidden bus)
|
||||
* -> fail
|
||||
*
|
||||
* assuming it worked..
|
||||
* Audio Track Audio Track[with region] hidden-Bus Audio Track[with region]
|
||||
*
|
||||
* selected both regions, grab the lower region and
|
||||
* move up one track -> given delta == -2 (skip the bus)
|
||||
*
|
||||
* real_delta_track=-1
|
||||
* real_delta_track=-2
|
||||
*
|
||||
* It gets worse if both regions have to ignore differen numbers of hidden tracks
|
||||
* in between.
|
||||
*
|
||||
*
|
||||
* Proposed solution: if i == 0, count number of hidden tracks crossed
|
||||
* subtract that number for i > 0 AND also subtract hidden tracks crossed
|
||||
*/
|
||||
int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
printf("Y MOVEMENT CHECK: from %d to %d skip: %d\n", i->time_axis_view, i->time_axis_view + delta_track, skip_invisible);
|
||||
#endif
|
||||
assert (n < 0 || n >= _time_axis_views.size() || !_time_axis_views[n]->hidden());
|
||||
|
||||
if (i->time_axis_view < 0) {
|
||||
/* already in the drop zone */
|
||||
if (delta_track >= 0) {
|
||||
|
@ -700,9 +706,6 @@ RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const
|
|||
} else if (n >= int (_time_axis_views.size())) {
|
||||
/* downward motion into drop zone. That's fine. */
|
||||
continue;
|
||||
} else {
|
||||
/* target is not in the drop zone */
|
||||
all_in_drop_zone = false;
|
||||
}
|
||||
|
||||
RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
|
||||
|
@ -725,7 +728,7 @@ 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;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -749,6 +752,9 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
printf("--------- LAST AXIS: %d\n", _last_pointer_time_axis_view);
|
||||
#endif
|
||||
/* 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 */
|
||||
|
@ -786,6 +792,9 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
|
|||
* This is necessary because steps may be skipped if
|
||||
* the bottom-most track is not a valid target,
|
||||
*/
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
printf("MOVE OUT OF THE ZONE...\n");
|
||||
#endif
|
||||
delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size ();
|
||||
} else {
|
||||
delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
|
||||
|
@ -813,6 +822,22 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
|
|||
* 4) proportionally scale layer to layers available on target
|
||||
*/
|
||||
delta_layer = current_pointer_layer - _last_pointer_layer;
|
||||
|
||||
} else if (current_pointer_y() >= 0 && _last_pointer_time_axis_view >= 0) {
|
||||
/* Moving into the drop-zone..
|
||||
*
|
||||
* TODO allow moving further down in drop-zone:
|
||||
* e.g. 2 Tracks, select a region on both of them.
|
||||
*
|
||||
* A) grab the upper, drag 2 down, both regions are in the dropzone: all fine (works)
|
||||
*
|
||||
* B) grab the lower, drag 1 down, region (and mouse) are in dropzone, The End.
|
||||
* upper region is only down one track and cannot be moved into the zone.
|
||||
*/
|
||||
delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
printf("INTO THE ZONE DELTA: %d\n", delta_time_axis_view);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Work out the change in x */
|
||||
|
@ -820,11 +845,37 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
|
|||
double const x_delta = compute_x_delta (event, &pending_region_position);
|
||||
_last_frame_position = pending_region_position;
|
||||
|
||||
/* calculate hidden tracks in current delta */
|
||||
int delta_skip = 0;
|
||||
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)
|
||||
-_time_axis_views.size() - delta_time_axis_view;
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
printf("NOW WHAT?? last: %d delta %d || skip %d\n", _last_pointer_time_axis_view, delta_time_axis_view, delta_skip);
|
||||
#endif
|
||||
} 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)
|
||||
- _last_pointer_time_axis_view
|
||||
- delta_time_axis_view;
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
printf("Drag from %d to %d || skip %d\n",
|
||||
_last_pointer_time_axis_view,
|
||||
_last_pointer_time_axis_view + delta_time_axis_view,
|
||||
delta_skip);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 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;
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
printf(" ** NOT ALLOWED\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
|
||||
|
@ -839,13 +890,14 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
|
|||
PlaylistDropzoneMap playlist_dropzone_map;
|
||||
int biggest_drop_zone_offset = 0;
|
||||
|
||||
/* find drop-zone y-position */
|
||||
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 (std::vector<TimeAxisView*>::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;
|
||||
}
|
||||
}
|
||||
|
||||
for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
|
||||
|
@ -886,40 +938,69 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move)
|
|||
this_delta_layer = - i->layer;
|
||||
}
|
||||
|
||||
if (tv) {
|
||||
int this_delta_time_axis_view = delta_time_axis_view;
|
||||
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.
|
||||
|
||||
the final real time axis view has index _time_axis_views.size() - 1.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
int index_from_bottom = i->time_axis_view - delta_time_axis_view;
|
||||
int const bottom = _time_axis_views.size() - 1;
|
||||
|
||||
if (index_from_bottom >= 0) {
|
||||
track_index = bottom - index_from_bottom;
|
||||
} else {
|
||||
track_index = index_from_bottom;
|
||||
}
|
||||
}
|
||||
int track_index = i->time_axis_view + this_delta_time_axis_view;
|
||||
assert(track_index >= 0);
|
||||
|
||||
if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
|
||||
// Velociraptor was here
|
||||
goto dropzone;
|
||||
i->time_axis_view = track_index;
|
||||
#ifdef DEBUG_DROPZONEDRAG
|
||||
printf("IN THE ZONE\n");
|
||||
#endif
|
||||
assert(i->time_axis_view >= _time_axis_views.size());
|
||||
if (current_pointer_y() >= 0) {
|
||||
|
||||
int dzoffset;
|
||||
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()) {
|
||||
|
||||
/* TODO
|
||||
* retain the ordering top -> bottom in the drop-zone
|
||||
* this can be done by sorting the regions according to
|
||||
* i->time_axis_view Y, prior to iterating over DraggingView
|
||||
*/
|
||||
|
||||
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<ArdourCanvas::Rect> 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));
|
||||
dzoffset = -n;
|
||||
|
||||
} else {
|
||||
ip = pdz->second;
|
||||
dzoffset = ip.first;
|
||||
}
|
||||
|
||||
/* values are zero or negative, hence the use of min() */
|
||||
biggest_drop_zone_offset = min (biggest_drop_zone_offset, dzoffset);
|
||||
y_delta = ip.second - rv->get_canvas_group()->canvas_origin().y;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/* The TimeAxisView that this region is now over */
|
||||
TimeAxisView* current_tv = _time_axis_views[track_index];
|
||||
|
||||
|
@ -979,54 +1060,6 @@ 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<ArdourCanvas::Rect> 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 */
|
||||
|
@ -1932,9 +1965,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 {
|
||||
|
@ -4939,7 +4972,6 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
|
|||
|
||||
} else { /* operation == CreateRangeMarker || CreateSkipMarker */
|
||||
|
||||
|
||||
framepos_t start;
|
||||
framepos_t end;
|
||||
|
||||
|
|
|
@ -292,6 +292,7 @@ protected:
|
|||
/** a list of the non-hidden TimeAxisViews sorted by editor order key */
|
||||
std::vector<TimeAxisView*> _time_axis_views;
|
||||
int find_time_axis_view (TimeAxisView *) const;
|
||||
int apply_track_delta (const int start, const int delta, const int skip) const;
|
||||
|
||||
int _visible_y_low;
|
||||
int _visible_y_high;
|
||||
|
@ -326,7 +327,7 @@ public:
|
|||
protected:
|
||||
|
||||
double compute_x_delta (GdkEvent const *, ARDOUR::framepos_t *);
|
||||
virtual bool y_movement_allowed (int, double) const;
|
||||
virtual bool y_movement_allowed (int, double, int skip_invisible = 0) const;
|
||||
|
||||
bool _brushing;
|
||||
ARDOUR::framepos_t _last_frame_position; ///< last position of the thing being dragged
|
||||
|
@ -436,7 +437,7 @@ public:
|
|||
void finished (GdkEvent *, bool);
|
||||
void aborted (bool);
|
||||
protected:
|
||||
bool y_movement_allowed (int delta_track, double delta_layer) const;
|
||||
bool y_movement_allowed (int delta_track, double delta_layer, int skip_invisible = 0) const;
|
||||
|
||||
private:
|
||||
TimeAxisView *prev_tav; // where regions were most recently dragged from
|
||||
|
|
Loading…
Reference in New Issue
Block a user