X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Feditor_drag.cc;h=0877a8b4bece9185ba597f640c58833e598969c0;hb=46c83693284ece4a732d26e62113ea4ac584d539;hp=34e169b133c2de77e7e962609454c5fdbf80661c;hpb=ade1c4923ca58a56c5160826253091f67f03fd2f;p=ardour.git diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index 34e169b133..0877a8b4be 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -96,8 +96,6 @@ DragManager::abort () { _ending = true; - cerr << "Aborting drag\n"; - for (list::const_iterator i = _drags.begin(); i != _drags.end(); ++i) { (*i)->abort (); delete *i; @@ -108,6 +106,7 @@ DragManager::abort () } _drags.clear (); + _editor->abort_reversible_command(); _ending = false; } @@ -181,7 +180,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? */ @@ -217,12 +216,17 @@ 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) + , _initially_vertical (false) , _was_double_click (false) , _raw_grab_frame (0) , _grab_frame (0) , _last_pointer_frame (0) + , _snap_delta (0) { } @@ -233,32 +237,23 @@ Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*t _item->ungrab (); _item = new_item; - if (cursor == 0) { - _item->grab (); + if (!_cursor_ctx) { + _cursor_ctx = CursorContext::create (*_editor, cursor); } else { - _item->grab (); + _cursor_ctx->change (cursor); } + + _item->grab (); } 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; @@ -270,12 +265,11 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor) _last_pointer_y = _grab_y; - if (cursor == 0) { - _item->grab (); - } else { + _item->grab (); + + if (!_editor->cursors()->is_invalid (cursor)) { /* CAIROCANVAS need a variant here that passes *cursor */ - _item->grab (); - _cursor_ctx = CursorContext::create(*_editor, cursor); + _cursor_ctx = CursorContext::create (*_editor, cursor); } if (_editor->session() && _editor->session()->transport_rolling()) { @@ -339,10 +333,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 _snap_delta; + } + + return 0; +} + double Drag::current_pointer_x() const { - return _drags->current_pointer_x (); + return _drags->current_pointer_x (); } double @@ -351,10 +355,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) { @@ -369,7 +381,7 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll) bool const old_move_threshold_passed = _move_threshold_passed; - if (!from_autoscroll && !_move_threshold_passed) { + if (!_move_threshold_passed) { bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first); bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second); @@ -380,13 +392,66 @@ Drag::motion_handler (GdkEvent* event, bool from_autoscroll) if (active (_editor->mouse_mode) && _move_threshold_passed) { if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) { + + if (old_move_threshold_passed != _move_threshold_passed) { + + /* just changed */ + + if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) { + _initially_vertical = true; + } 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) { _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false); } if (!_editor->autoscroll_active() || from_autoscroll) { - motion (event, _move_threshold_passed != old_move_threshold_passed); - + + + 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); @@ -452,17 +517,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); @@ -475,7 +541,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()); @@ -517,7 +583,7 @@ RegionDrag::find_time_axis_view (TimeAxisView* t) const ++i; } - if (i == N) { + if (_time_axis_views[i] != t) { return -1; } @@ -530,6 +596,9 @@ RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView , _total_x_delta (0) , _last_pointer_time_axis_view (0) , _last_pointer_layer (0) + , _ndropzone (0) + , _pdropzone (0) + , _ddropzone (0) { DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n"); } @@ -538,14 +607,21 @@ void RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { Drag::start_grab (event, cursor); + setup_snap_delta (_last_frame_position); 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; } + + if (_brushing) { + /* cross track dragging seems broken here. disabled for now. */ + _y_constrained = true; + } } double @@ -554,7 +630,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; @@ -566,11 +642,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; @@ -582,8 +658,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) { @@ -610,18 +685,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; + } + + const int tavsize = _time_axis_views.size(); for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { - int const n = i->time_axis_view + delta_track; - if (n < 0 || n >= int (_time_axis_views.size())) { - /* off the top or bottom track */ + 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 - OK if others are still not in the dropzone */ + continue; + } + + } + + 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; } @@ -643,21 +776,45 @@ RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const 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 = -1; assert (!_views.empty ()); /* 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 && 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) { @@ -665,25 +822,143 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) } /* Here's the current pointer position in terms of time axis view and layer */ - int const current_pointer_time_axis_view = find_time_axis_view (tv); + 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 */ - delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view; + 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) { @@ -693,30 +968,64 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) return; } - pair >::iterator,bool> insert_result; + typedef map, double> PlaylistDropzoneMap; + PlaylistDropzoneMap playlist_dropzone_map; + _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 + } for (list::iterator i = _views.begin(); i != _views.end(); ++i) { RegionView* rv = i->view; + double y_delta; + + y_delta = 0; if (rv->region()->locked() || rv->region()->video_locked()) { continue; } 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); } @@ -730,20 +1039,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 { - track_index = _time_axis_views.size() - 1 + delta_time_axis_view; - } - - if (track_index < 0 || track_index >= (int) _time_axis_views.size()) { - continue; + 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()) { + /* Track is in the Dropzone */ + + 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(); + + playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition)); + + } else { + yposition = pdz->second; + } + + /* 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]; @@ -751,15 +1094,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. */ @@ -780,7 +1123,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 ()) { @@ -799,31 +1142,15 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) * equivalent coordinate space as the trackview * we are now dragging over. */ - - /* Now move the region view */ - rv->move (x_delta, track_origin.y - rv->get_canvas_group()->canvas_origin().y); - } - } else { - /* Only move the region into the empty dropzone at the bottom if the pointer - * is down there. - */ + y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y; - if (current_pointer_y() >= 0) { - - Coord last_track_bottom_edge; - if (!_time_axis_views.empty()) { - TimeAxisView* last = _time_axis_views.back(); - last_track_bottom_edge = last->canvas_display()->canvas_origin ().y + last->effective_height(); - } else { - last_track_bottom_edge = 0; - } - - rv->move (x_delta, last_track_bottom_edge - rv->get_canvas_group()->canvas_origin().y); - i->time_axis_view = -1; } } - + + /* Now move the region view */ + rv->move (x_delta, y_delta); + } /* foreach region */ _total_x_delta += x_delta; @@ -832,7 +1159,66 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) show_verbose_cursor_time (_last_frame_position); } - _last_pointer_time_axis_view += delta_time_axis_view; + /* 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 + * or last y-movement out of the dropzone was not valid + */ + int dtz = 0; + if (delta_time_axis_view < 0) { + /* 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 += delta_time_axis_view; + assert(_last_pointer_time_axis_view >= 0); + } + + } else { + + /* the pointer is not over a time axis view */ + 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; } @@ -840,13 +1226,13 @@ void RegionMoveDrag::motion (GdkEvent* event, bool first_move) { if (_copy && first_move) { - - if (_x_constrained) { + if (_x_constrained && !_brushing) { _editor->begin_reversible_command (Operations::fixed_time_region_copy); - } else { + } else if (!_brushing) { _editor->begin_reversible_command (Operations::region_copy); + } else if (_brushing) { + _editor->begin_reversible_command (Operations::drag_region_brush); } - /* duplicate the regionview(s) and region(s) */ list new_regionviews; @@ -860,6 +1246,11 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move) const boost::shared_ptr original = rv->region(); boost::shared_ptr region_copy = RegionFactory::create (original, true); region_copy->set_position (original->position()); + /* need to set this so that the drop zone code can work. This doesn't + actually put the region into the playlist, but just sets a weak pointer + to it. + */ + region_copy->set_playlist (original->playlist()); RegionView* nrv; if (arv) { @@ -899,14 +1290,14 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move) } } else if (!_copy && first_move) { - - if (_x_constrained) { + if (_x_constrained && !_brushing) { _editor->begin_reversible_command (_("fixed time region drag")); - } else { + } else if (!_brushing) { _editor->begin_reversible_command (Operations::region_drag); + } else if (_brushing) { + _editor->begin_reversible_command (Operations::drag_region_brush); } } - RegionMotionDrag::motion (event, first_move); } @@ -928,28 +1319,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 @@ -959,11 +1342,11 @@ 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; - + if (_copy) { finished_copy ( @@ -987,15 +1370,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()); @@ -1010,7 +1397,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; @@ -1022,7 +1409,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 */ @@ -1035,6 +1422,9 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed return; } + typedef map, RouteTimeAxisView*> PlaylistMapping; + PlaylistMapping playlist_mapping; + /* insert the regions into their new playlists */ for (list::const_iterator i = _views.begin(); i != _views.end();) { @@ -1051,23 +1441,33 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed } else { where = i->view->region()->position(); } - - if (i->time_axis_view < 0) { - if (!new_time_axis_view) { + + 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); + playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view)); + dest_rtv = new_time_axis_view; + } else { + /* we already created a new track for regions from this playlist, use it */ + dest_rtv = pm->second; } - dest_rtv = new_time_axis_view; } 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. */ @@ -1105,11 +1505,8 @@ RegionMoveDrag::finished_no_copy ( set views_to_update; RouteTimeAxisView* new_time_axis_view = 0; - if (_brushing) { - /* all changes were made during motion event handlers */ - _editor->commit_reversible_command (); - return; - } + typedef map, RouteTimeAxisView*> PlaylistMapping; + PlaylistMapping playlist_mapping; for (list::const_iterator i = _views.begin(); i != _views.end(); ) { @@ -1120,20 +1517,31 @@ RegionMoveDrag::finished_no_copy ( ++i; continue; } - - if (i->time_axis_view < 0) { - if (!new_time_axis_view) { - new_time_axis_view = create_destination_time_axis (rv->region(), i->initial_time_axis_view); + + 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); + playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view)); + dest_rtv = new_time_axis_view; + } else { + /* we already created a new track for regions from this playlist, use it */ + dest_rtv = pm->second; } - dest_rtv = new_time_axis_view; + } 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; @@ -1210,7 +1618,6 @@ RegionMoveDrag::finished_no_copy ( } rv->region()->set_position (where); - _editor->session()->add_command (new StatefulDiffCommand (rv->region())); } @@ -1259,7 +1666,7 @@ RegionMoveDrag::finished_no_copy ( /* write commands for the accumulated diffs for all our modified playlists */ add_stateful_diff_commands_for_playlists (modified_playlists); - + /* applies to _brushing */ _editor->commit_reversible_command (); /* We have futzed with the layering of canvas items on our streamviews. @@ -1369,8 +1776,11 @@ RegionMoveDrag::aborted (bool movement_occurred) { if (_copy) { - for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { + for (list::const_iterator i = _views.begin(); i != _views.end();) { + list::const_iterator next = i; + ++next; delete i->view; + i = next; } _views.clear (); @@ -1393,7 +1803,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 ()); @@ -1411,8 +1821,8 @@ RegionMotionDrag::aborted (bool) * @param c true to make copies of the regions being moved, otherwise false. */ RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list const & v, bool b, bool c) - : RegionMotionDrag (e, i, p, v, b), - _copy (c) + : RegionMotionDrag (e, i, p, v, b) + , _copy (c) { DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n"); @@ -1453,7 +1863,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); @@ -1492,9 +1905,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 @@ -1626,7 +2039,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)) { @@ -1659,9 +2072,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 { @@ -1753,7 +2166,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 @@ -1765,9 +2178,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); @@ -1777,7 +2190,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; @@ -1786,7 +2199,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) { @@ -1800,21 +2213,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); @@ -1919,6 +2332,9 @@ RegionCreateDrag::aborted (bool) NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i) : Drag (e, i) , region (0) + , relative (false) + , at_front (true) + , _snap_delta (0) { DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n"); } @@ -1943,64 +2359,114 @@ 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; } - MidiRegionSelection& ms (_editor->get_selection().midi_regions); - if (ms.size() > 1) { /* has to be relative, may make no sense otherwise */ relative = true; } - /* select this note; if it is already selected, preserve the existing selection, otherwise make this note the only one selected. */ region->note_selected (cnote, cnote->selected ()); - - _editor->begin_reversible_command (_("resize notes")); - - for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) { - MidiRegionSelection::iterator next; - next = r; - ++next; - MidiRegionView* mrv = dynamic_cast(*r); - if (mrv) { - mrv->begin_resizing (at_front); - } - r = next; - } } void -NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/) +NoteResizeDrag::motion (GdkEvent* event, bool first_move) { MidiRegionSelection& ms (_editor->get_selection().midi_regions); + if (first_move) { + _editor->begin_reversible_command (_("resize notes")); + + for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) { + MidiRegionSelection::iterator next; + next = r; + ++next; + MidiRegionView* mrv = dynamic_cast(*r); + if (mrv) { + mrv->begin_resizing (at_front); + } + r = next; + } + } + 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); 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) { + if (!movement_occurred) { + return; + } + 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); } } @@ -2198,8 +2664,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); @@ -2208,8 +2675,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); @@ -2217,17 +2683,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: @@ -2237,7 +2704,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); @@ -2263,8 +2730,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) { @@ -2310,7 +2777,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; } @@ -2396,7 +2863,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); @@ -2404,7 +2871,6 @@ TrimDrag::motion (GdkEvent* event, bool first_move) } } - void TrimDrag::finished (GdkEvent* event, bool movement_occurred) { @@ -2461,7 +2927,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred) _views.begin()->view->region()->length()); } } - + if (!_editor->selection->selected (_primary)) { _primary->thaw_after_trim (); } else { @@ -2469,7 +2935,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 @@ -2583,19 +3049,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, @@ -2603,7 +3069,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); @@ -2642,7 +3108,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(); @@ -2655,7 +3121,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)); @@ -2715,10 +3181,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. @@ -2848,10 +3314,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); @@ -2874,7 +3341,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, @@ -2889,15 +3356,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)); } } @@ -2948,6 +3416,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); } @@ -2965,14 +3434,16 @@ 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 = _primary->region (); + boost::shared_ptr region = boost::dynamic_pointer_cast (_primary->region ()); if (pos < (region->position() + 64)) { fade_length = 64; // this should be a minimum defined somewhere - } else if (pos > region->last_frame()) { - fade_length = region->length(); + } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) { + fade_length = region->length() - region->fade_out()->back()->when - 1; } else { fade_length = pos - region->position(); } @@ -2999,20 +3470,21 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred) } framecnt_t fade_length; + 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); - framepos_t const pos = adjusted_current_frame (event); - - boost::shared_ptr region = _primary->region (); + boost::shared_ptr region = boost::dynamic_pointer_cast (_primary->region ()); if (pos < (region->position() + 64)) { fade_length = 64; // this should be a minimum defined somewhere - } else if (pos > region->last_frame()) { - fade_length = region->length(); + } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) { + fade_length = region->length() - region->fade_out()->back()->when - 1; } else { fade_length = pos - region->position(); } - _editor->begin_reversible_command (_("change fade in length")); + bool in_command = false; for (list::iterator i = _views.begin(); i != _views.end(); ++i) { @@ -3028,11 +3500,17 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred) tmp->audio_region()->set_fade_in_length (fade_length); tmp->audio_region()->set_fade_in_active (true); + if (!in_command) { + _editor->begin_reversible_command (_("change fade in length")); + in_command = true; + } XMLNode &after = alist->get_state(); _editor->session()->add_command(new MementoCommand(*alist.get(), &before, &after)); } - _editor->commit_reversible_command (); + if (in_command) { + _editor->commit_reversible_command (); + } } void @@ -3062,6 +3540,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()); } @@ -3079,17 +3558,17 @@ 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 = _primary->region (); + boost::shared_ptr region = boost::dynamic_pointer_cast (_primary->region ()); if (pos > (region->last_frame() - 64)) { fade_length = 64; // this should really be a minimum fade defined somewhere - } - else if (pos < region->position()) { - fade_length = region->length(); - } - else { + } else if (pos <= region->position() + region->fade_in()->back()->when) { + fade_length = region->length() - region->fade_in()->back()->when - 1; + } else { fade_length = region->last_frame() - pos; } @@ -3116,21 +3595,21 @@ 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 = _primary->region (); + boost::shared_ptr region = boost::dynamic_pointer_cast (_primary->region ()); if (pos > (region->last_frame() - 64)) { fade_length = 64; // this should really be a minimum fade defined somewhere - } - else if (pos < region->position()) { - fade_length = region->length(); - } - else { + } else if (pos <= region->position() + region->fade_in()->back()->when) { + fade_length = region->length() - region->fade_in()->back()->when - 1; + } else { fade_length = region->last_frame() - pos; } - _editor->begin_reversible_command (_("change fade out length")); + bool in_command = false; for (list::iterator i = _views.begin(); i != _views.end(); ++i) { @@ -3146,11 +3625,17 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred) tmp->audio_region()->set_fade_out_length (fade_length); tmp->audio_region()->set_fade_out_active (true); + if (!in_command) { + _editor->begin_reversible_command (_("change fade out length")); + in_command = false; + } XMLNode &after = alist->get_state(); _editor->session()->add_command(new MementoCommand(*alist.get(), &before, &after)); } - _editor->commit_reversible_command (); + if (in_command) { + _editor->commit_reversible_command (); + } } void @@ -3260,7 +3745,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) { @@ -3290,7 +3775,7 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) (*x).move_both = true; } } - + } } @@ -3314,7 +3799,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; } @@ -3329,7 +3814,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) { @@ -3377,8 +3862,6 @@ MarkerDrag::motion (GdkEvent* event, bool) copy_location = x->location; - /* call this to find out if its the start or end */ - if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) { continue; } @@ -3394,12 +3877,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); @@ -3427,14 +3910,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) { @@ -3452,7 +3935,7 @@ void MarkerDrag::finished (GdkEvent* event, bool movement_occurred) { if (!movement_occurred) { - + if (was_double_click()) { _editor->rename_marker (_marker); return; @@ -3486,8 +3969,8 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred) _editor->_dragging_edit_point = false; - _editor->begin_reversible_command ( _("move marker") ); XMLNode &before = _editor->session()->locations()->get_state(); + bool in_command = false; MarkerSelection::iterator i; CopiedLocationInfo::iterator x; @@ -3502,9 +3985,12 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred) if (location) { if (location->locked()) { - return; + continue; + } + if (!in_command) { + _editor->begin_reversible_command ( _("move marker") ); + in_command = true; } - if (location->is_mark()) { location->set_start (((*x).location)->start()); } else { @@ -3513,15 +3999,35 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred) } } - XMLNode &after = _editor->session()->locations()->get_state(); - _editor->session()->add_command(new MementoCommand(*(_editor->session()->locations()), &before, &after)); - _editor->commit_reversible_command (); + if (in_command) { + XMLNode &after = _editor->session()->locations()->get_state(); + _editor->session()->add_command(new MementoCommand(*(_editor->session()->locations()), &before, &after)); + _editor->commit_reversible_command (); + } } void -MarkerDrag::aborted (bool) +MarkerDrag::aborted (bool movement_occured) { - /* XXX: TODO */ + if (!movement_occured) { + return; + } + + 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) { + + bool is_start; + Location * location = _editor->find_location_from_marker (*m, is_start); + + if (location) { + (*m)->set_position (is_start ? location->start() : location->end()); + } + } + } } void @@ -3556,13 +4062,13 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) _fixed_grab_x = _point->get_x(); _fixed_grab_y = _point->get_y(); - float const fraction = 1 - (_point->get_y() / _point->line().height()); - - _point->line().start_drag_single (_point, _fixed_grab_x, fraction); + framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x); + setup_snap_delta (pos); + float const fraction = 1 - (_point->get_y() / _point->line().height()); 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; @@ -3570,12 +4076,12 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) } void -ControlPointDrag::motion (GdkEvent* event, bool) +ControlPointDrag::motion (GdkEvent* event, bool first_motion) { 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; } @@ -3589,11 +4095,6 @@ ControlPointDrag::motion (GdkEvent* event, bool) // positive side of zero double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01; - // make sure we hit zero when passing through - if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) { - cy = zero_gain_y; - } - if (_x_constrained) { cx = _fixed_grab_x; } @@ -3604,20 +4105,31 @@ ControlPointDrag::motion (GdkEvent* event, bool) _cumulative_x_drag = cx - _fixed_grab_x; _cumulative_y_drag = cy - _fixed_grab_y; + // make sure we hit zero when passing through + if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) { + cy = zero_gain_y; + } + cx = max (0.0, cx); 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()); + if (first_motion) { + _editor->begin_reversible_command (_("automation event move")); + _point->line().start_drag_single (_point, _fixed_grab_x, fraction); + } + _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index); show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction)); @@ -3629,17 +4141,15 @@ 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 (); } } else { motion (event, false); + _point->line().end_drag (_pushing, _final_index); + _editor->commit_reversible_command (); } - - _point->line().end_drag (_pushing, _final_index); - _editor->commit_reversible_command (); } void @@ -3661,9 +4171,11 @@ 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) + , _before (0) + , _after (0) { DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n"); } @@ -3687,10 +4199,7 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->samples_per_pixel); - uint32_t before; - uint32_t after; - - if (!_line->control_points_adjacent (frame_within_region, before, after)) { + if (!_line->control_points_adjacent (frame_within_region, _before, _after)) { /* no adjacent points */ return; } @@ -3704,17 +4213,15 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) double fraction = 1.0 - (cy / _line->height()); - _line->start_drag_line (before, after, fraction); - show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction)); } void -LineDrag::motion (GdkEvent* event, bool) +LineDrag::motion (GdkEvent* event, bool first_move) { 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; } @@ -3728,6 +4235,11 @@ LineDrag::motion (GdkEvent* event, bool) double const fraction = 1.0 - (cy / _line->height()); uint32_t ignored; + if (first_move) { + _editor->begin_reversible_command (_("automation range move")); + _line->start_drag_line (_before, _after, fraction); + } + /* we are ignoring x position for this drag, so we can just pass in anything */ _line->drag_motion (0, fraction, true, false, ignored); @@ -3740,20 +4252,17 @@ LineDrag::finished (GdkEvent* event, bool movement_occured) if (movement_occured) { motion (event, false); _line->end_drag (false, 0); + _editor->commit_reversible_command (); } else { /* add a new control point on the line */ AutomationTimeAxisView* atv; - _line->end_drag (false, 0); - if ((atv = dynamic_cast(_editor->clicked_axisview)) != 0) { framepos_t where = _editor->window_event_sample (event, 0, 0); atv->add_automation_event (event, where, event->button.y, false); } } - - _editor->commit_reversible_command (); } void @@ -3849,7 +4358,7 @@ void RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *) { Drag::start_grab (event); - show_verbose_cursor_time (adjusted_current_frame (event)); + show_verbose_cursor_time (adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid())); } void @@ -3860,11 +4369,13 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool) double y1; double y2; - framepos_t const pf = adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()); + framepos_t const pf = adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid()); framepos_t grab = grab_frame (); if (ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) { _editor->snap_to_with_modifier (grab, event); + } else { + grab = raw_grab_frame (); } /* base start and end on initial click position */ @@ -3900,7 +4411,7 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool) } else { x2 = max (x1 + min_dimension, x2); } - } + } if (y2 < y1) { y2 = min (y1 - min_dimension, y2); @@ -3910,7 +4421,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 @@ -3935,18 +4446,25 @@ RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress) { framepos_t x1; framepos_t x2; - - if (grab_frame() < last_pointer_frame()) { - x1 = grab_frame (); - x2 = last_pointer_frame (); + framepos_t grab = grab_frame (); + framepos_t lpf = last_pointer_frame (); + + if (!ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) { + grab = raw_grab_frame (); + lpf = _editor->pixel_to_sample_from_event (last_pointer_x()); + } + + if (grab < lpf) { + x1 = grab; + x2 = lpf; } else { - x2 = grab_frame (); - x1 = last_pointer_frame (); + x2 = grab; + x1 = lpf; } double y1; double y2; - + if (current_pointer_y() < grab_y()) { y1 = current_pointer_y(); y2 = grab_y(); @@ -3980,14 +4498,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 (); } @@ -4014,7 +4532,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 @@ -4026,14 +4549,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 @@ -4117,11 +4641,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(); @@ -4135,7 +4658,7 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*) return; } - Gdk::Cursor* cursor = 0; + Gdk::Cursor* cursor = MouseCursors::invalid_cursor(); switch (_operation) { case CreateSelection: @@ -4176,8 +4699,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 @@ -4250,7 +4771,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 */ @@ -4262,15 +4783,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); @@ -4341,7 +4862,7 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) } break; - + case SelectionMove: start = _editor->selection->time[_editor->clicked_selection].start; @@ -4362,7 +4883,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); } @@ -4384,7 +4905,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 */ @@ -4394,7 +4915,7 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred) /* XXX what if its a music time selection? */ if (s) { - if ( s->get_play_range() && s->transport_rolling() ) { + if (s->get_play_range() && s->transport_rolling()) { s->request_play_range (&_editor->selection->time, true); } else { if (ARDOUR_UI::config()->get_follow_edits() && !s->transport_rolling()) { @@ -4404,8 +4925,14 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred) s->request_locate (_editor->get_selection().time.start()); } } - } + if (_editor->get_selection().time.length() != 0) { + s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame()); + } else { + s->clear_range_selection (); + } + } + } else { /* just a click, no pointer movement. */ @@ -4432,7 +4959,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); } @@ -4457,7 +4984,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 (); @@ -4466,6 +4993,15 @@ RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operat _drag_rect->set_outline_color (ARDOUR_UI::config()->color ("range drag rect")); } +RangeMarkerBarDrag::~RangeMarkerBarDrag() +{ + /* 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. + */ + delete _drag_rect; +} + void RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *) { @@ -4473,7 +5009,7 @@ RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *) return; } - Gdk::Cursor* cursor = 0; + Gdk::Cursor* cursor = MouseCursors::invalid_cursor(); if (!_editor->temp_location) { _editor->temp_location = new Location (*_editor->session()); @@ -4485,7 +5021,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; @@ -4637,7 +5173,6 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred) * nothing */ } else { /* operation == CreateRangeMarker || CreateSkipMarker */ - framepos_t start; framepos_t end; @@ -4673,9 +5208,11 @@ RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred) } void -RangeMarkerBarDrag::aborted (bool) +RangeMarkerBarDrag::aborted (bool movement_occured) { - /* XXX: TODO */ + if (movement_occured) { + _drag_rect->hide (); + } } void @@ -4705,6 +5242,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())) { @@ -4724,7 +5262,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(); } } @@ -4732,7 +5270,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()); @@ -4741,15 +5279,38 @@ 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; + } + } + } + + frameoffset_t ret; + if (snap) { + ret = _region->snap_frame_to_frame (st - rp) + rp - n - snap_delta (state); + } else { + ret = st - n - snap_delta (state); + } + return ret; } /** @return Current total drag y change in note number */ @@ -4768,10 +5329,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 */ @@ -4794,7 +5355,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)); @@ -4835,12 +5396,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()); } } @@ -4986,8 +5547,8 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ()); double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ()); - the_list->editor_add (p, value (the_list, p)); - the_list->editor_add (q, value (the_list, q)); + the_list->editor_add (p, value (the_list, p), false); + the_list->editor_add (q, value (the_list, q), false); } /* same thing for the end */ @@ -5012,8 +5573,8 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ()); double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ()); - the_list->editor_add (p, value (the_list, p)); - the_list->editor_add (q, value (the_list, q)); + the_list->editor_add (p, value (the_list, p), false); + the_list->editor_add (q, value (the_list, q), false); } } @@ -5050,19 +5611,22 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) if (_nothing_to_drag) { return; } - - for (list::iterator i = _lines.begin(); i != _lines.end(); ++i) { - i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state); - } } void -AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/) +AutomationRangeDrag::motion (GdkEvent*, bool first_move) { if (_nothing_to_drag) { return; } + if (first_move) { + _editor->begin_reversible_command (_("automation range move")); + for (list::iterator i = _lines.begin(); i != _lines.end(); ++i) { + i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state); + } + } + for (list::iterator l = _lines.begin(); l != _lines.end(); ++l) { float const f = y_fraction (l->line, current_pointer_y()); /* we are ignoring x position for this drag, so we can just pass in anything */ @@ -5073,9 +5637,9 @@ AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/) } void -AutomationRangeDrag::finished (GdkEvent* event, bool) +AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred) { - if (_nothing_to_drag) { + if (_nothing_to_drag || !motion_occurred) { return; } @@ -5201,7 +5765,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, @@ -5228,10 +5792,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); @@ -5241,7 +5805,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(); @@ -5281,7 +5845,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 (); @@ -5323,13 +5887,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; } @@ -5351,7 +5915,7 @@ NoteCreateDrag::y_to_region (double y) const void NoteCreateDrag::aborted (bool) { - + } CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn) @@ -5418,9 +5982,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); @@ -5444,9 +6008,9 @@ void CrossfadeEdgeDrag::aborted (bool) { if (start) { - arv->redraw_start_xfade (); + // arv->redraw_start_xfade (); } else { - arv->redraw_end_xfade (); + // arv->redraw_end_xfade (); } } @@ -5478,7 +6042,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);