2 Copyright (C) 2009 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include "gtk2ardour-config.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/basename.h"
28 #include "pbd/stateful_diff_command.h"
30 #include "gtkmm2ext/utils.h"
32 #include "ardour/audioregion.h"
33 #include "ardour/dB.h"
34 #include "ardour/midi_region.h"
35 #include "ardour/operations.h"
36 #include "ardour/region_factory.h"
37 #include "ardour/session.h"
42 #include "audio_region_view.h"
43 #include "midi_region_view.h"
44 #include "ardour_ui.h"
45 #include "gui_thread.h"
46 #include "control_point.h"
48 #include "region_gain_line.h"
49 #include "editor_drag.h"
50 #include "audio_time_axis.h"
51 #include "midi_time_axis.h"
52 #include "canvas-note.h"
53 #include "selection.h"
54 #include "midi_selection.h"
55 #include "automation_time_axis.h"
57 #include "editor_cursors.h"
58 #include "mouse_cursors.h"
59 #include "verbose_cursor.h"
62 using namespace ARDOUR;
65 using namespace Gtkmm2ext;
66 using namespace Editing;
67 using namespace ArdourCanvas;
69 using Gtkmm2ext::Keyboard;
71 double ControlPointDrag::_zero_gain_fraction = -1.0;
73 DragManager::DragManager (Editor* e)
76 , _current_pointer_frame (0)
80 DragManager::~DragManager ()
85 /** Call abort for each active drag */
91 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
96 if (!_drags.empty ()) {
97 _editor->set_follow_playhead (_old_follow_playhead, false);
106 DragManager::add (Drag* d)
108 d->set_manager (this);
109 _drags.push_back (d);
113 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
115 d->set_manager (this);
116 _drags.push_back (d);
121 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
123 /* Prevent follow playhead during the drag to be nice to the user */
124 _old_follow_playhead = _editor->follow_playhead ();
125 _editor->set_follow_playhead (false);
127 _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
129 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
130 (*i)->start_grab (e, c);
134 /** Call end_grab for each active drag.
135 * @return true if any drag reported movement having occurred.
138 DragManager::end_grab (GdkEvent* e)
143 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
144 bool const t = (*i)->end_grab (e);
155 _editor->set_follow_playhead (_old_follow_playhead, false);
161 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
165 _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
167 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
168 bool const t = (*i)->motion_handler (e, from_autoscroll);
179 DragManager::have_item (ArdourCanvas::Item* i) const
181 list<Drag*>::const_iterator j = _drags.begin ();
182 while (j != _drags.end() && (*j)->item () != i) {
186 return j != _drags.end ();
189 Drag::Drag (Editor* e, ArdourCanvas::Item* i)
192 , _pointer_frame_offset (0)
193 , _move_threshold_passed (false)
194 , _raw_grab_frame (0)
196 , _last_pointer_frame (0)
202 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time)
208 _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, time);
210 _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
215 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
217 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
219 if (Keyboard::is_button2_event (&event->button)) {
220 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
221 _y_constrained = true;
222 _x_constrained = false;
224 _y_constrained = false;
225 _x_constrained = true;
228 _x_constrained = false;
229 _y_constrained = false;
232 _raw_grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
233 setup_pointer_frame_offset ();
234 _grab_frame = adjusted_frame (_raw_grab_frame, event);
235 _last_pointer_frame = _grab_frame;
236 _last_pointer_x = _grab_x;
237 _last_pointer_y = _grab_y;
240 _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
243 _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
248 if (_editor->session() && _editor->session()->transport_rolling()) {
251 _was_rolling = false;
254 switch (_editor->snap_type()) {
255 case SnapToRegionStart:
256 case SnapToRegionEnd:
257 case SnapToRegionSync:
258 case SnapToRegionBoundary:
259 _editor->build_region_boundary_cache ();
266 /** Call to end a drag `successfully'. Ungrabs item and calls
267 * subclass' finished() method.
269 * @param event GDK event, or 0.
270 * @return true if some movement occurred, otherwise false.
273 Drag::end_grab (GdkEvent* event)
275 _editor->stop_canvas_autoscroll ();
277 _item->ungrab (event ? event->button.time : 0);
279 finished (event, _move_threshold_passed);
281 _editor->verbose_cursor()->hide ();
283 return _move_threshold_passed;
287 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
291 if (f > _pointer_frame_offset) {
292 pos = f - _pointer_frame_offset;
296 _editor->snap_to_with_modifier (pos, event);
303 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
305 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
309 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
311 /* check to see if we have moved in any way that matters since the last motion event */
312 if (_move_threshold_passed &&
313 (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
314 (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
318 pair<framecnt_t, int> const threshold = move_threshold ();
320 bool const old_move_threshold_passed = _move_threshold_passed;
322 if (!from_autoscroll && !_move_threshold_passed) {
324 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
325 bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);
327 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
330 if (active (_editor->mouse_mode) && _move_threshold_passed) {
332 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
333 if (!from_autoscroll) {
334 bool const moving_left = _drags->current_pointer_x() < _last_pointer_x;
335 bool const moving_up = _drags->current_pointer_y() < _last_pointer_y;
336 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), moving_left, moving_up);
339 motion (event, _move_threshold_passed != old_move_threshold_passed);
341 _last_pointer_x = _drags->current_pointer_x ();
342 _last_pointer_y = _drags->current_pointer_y ();
343 _last_pointer_frame = adjusted_current_frame (event);
351 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
359 aborted (_move_threshold_passed);
361 _editor->stop_canvas_autoscroll ();
362 _editor->verbose_cursor()->hide ();
366 Drag::show_verbose_cursor_time (framepos_t frame)
368 _editor->verbose_cursor()->set_time (
370 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
371 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
374 _editor->verbose_cursor()->show ();
378 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double xoffset)
380 _editor->verbose_cursor()->show (xoffset);
382 _editor->verbose_cursor()->set_duration (
384 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
385 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
390 Drag::show_verbose_cursor_text (string const & text)
392 _editor->verbose_cursor()->show ();
394 _editor->verbose_cursor()->set (
396 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
397 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
401 boost::shared_ptr<Region>
402 Drag::add_midi_region (MidiTimeAxisView* view)
404 if (_editor->session()) {
405 const TempoMap& map (_editor->session()->tempo_map());
406 framecnt_t pos = grab_frame();
407 const Meter& m = map.meter_at (pos);
408 /* not that the frame rate used here can be affected by pull up/down which
411 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
412 return view->add_region (grab_frame(), len, true);
415 return boost::shared_ptr<Region>();
418 struct EditorOrderTimeAxisViewSorter {
419 bool operator() (TimeAxisView* a, TimeAxisView* b) {
420 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
421 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
423 return ra->route()->order_key (N_ ("editor")) < rb->route()->order_key (N_ ("editor"));
427 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
431 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
433 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
434 as some of the regions we are dragging may be on such tracks.
437 TrackViewList track_views = _editor->track_views;
438 track_views.sort (EditorOrderTimeAxisViewSorter ());
440 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
441 _time_axis_views.push_back (*i);
443 TimeAxisView::Children children_list = (*i)->get_child_list ();
444 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
445 _time_axis_views.push_back (j->get());
449 /* the list of views can be empty at this point if this is a region list-insert drag
452 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
453 _views.push_back (DraggingView (*i, this));
456 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
460 RegionDrag::region_going_away (RegionView* v)
462 list<DraggingView>::iterator i = _views.begin ();
463 while (i != _views.end() && i->view != v) {
467 if (i != _views.end()) {
472 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
473 * or -1 if it is not found.
476 RegionDrag::find_time_axis_view (TimeAxisView* t) const
479 int const N = _time_axis_views.size ();
480 while (i < N && _time_axis_views[i] != t) {
491 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
492 : RegionDrag (e, i, p, v),
501 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
503 Drag::start_grab (event, cursor);
505 show_verbose_cursor_time (_last_frame_position);
507 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
508 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
509 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
513 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
515 /* compute the amount of pointer motion in frames, and where
516 the region would be if we moved it by that much.
518 *pending_region_position = adjusted_current_frame (event);
520 framepos_t sync_frame;
521 framecnt_t sync_offset;
524 sync_offset = _primary->region()->sync_offset (sync_dir);
526 /* we don't handle a sync point that lies before zero.
528 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
530 sync_frame = *pending_region_position + (sync_dir*sync_offset);
532 _editor->snap_to_with_modifier (sync_frame, event);
534 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
537 *pending_region_position = _last_frame_position;
540 if (*pending_region_position > max_framepos - _primary->region()->length()) {
541 *pending_region_position = _last_frame_position;
546 /* in locked edit mode, reverse the usual meaning of _x_constrained */
547 bool const x_move_allowed = Config->get_edit_mode() == Lock ? _x_constrained : !_x_constrained;
549 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
551 /* x movement since last time (in pixels) */
552 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
554 /* total x movement */
555 framecnt_t total_dx = *pending_region_position;
556 if (regions_came_from_canvas()) {
557 total_dx = total_dx - grab_frame ();
560 /* check that no regions have gone off the start of the session */
561 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
562 if ((i->view->region()->position() + total_dx) < 0) {
564 *pending_region_position = _last_frame_position;
569 _last_frame_position = *pending_region_position;
576 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer) const
578 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
579 int const n = i->time_axis_view + delta_track;
580 if (n < 0 || n >= int (_time_axis_views.size())) {
581 /* off the top or bottom track */
585 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
586 if (to == 0 || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
587 /* not a track, or the wrong type */
591 double const l = i->layer + delta_layer;
593 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
594 mode to allow the user to place a region below another on layer 0.
596 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
597 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
598 If it has, the layers will be munged later anyway, so it's ok.
604 /* all regions being dragged are ok with this change */
609 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
611 assert (!_views.empty ());
613 /* Find the TimeAxisView that the pointer is now over */
614 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
616 if (first_move && tv.first->view()->layer_display() == Stacked) {
617 tv.first->view()->set_layer_display (Expanded);
620 /* Bail early if we're not over a track */
621 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv.first);
622 if (!rtv || !rtv->is_track()) {
623 _editor->verbose_cursor()->hide ();
627 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
629 /* Here's the current pointer position in terms of time axis view and layer */
630 int const current_pointer_time_axis_view = find_time_axis_view (tv.first);
631 double const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
633 /* Work out the change in x */
634 framepos_t pending_region_position;
635 double const x_delta = compute_x_delta (event, &pending_region_position);
637 /* Work out the change in y */
638 int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
639 double delta_layer = current_pointer_layer - _last_pointer_layer;
641 if (!y_movement_allowed (delta_time_axis_view, delta_layer)) {
642 /* this y movement is not allowed, so do no y movement this time */
643 delta_time_axis_view = 0;
647 if (x_delta == 0 && delta_time_axis_view == 0 && delta_layer == 0 && !first_move) {
648 /* haven't reached next snap point, and we're not switching
649 trackviews nor layers. nothing to do.
654 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
656 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
658 RegionView* rv = i->view;
660 if (rv->region()->locked()) {
668 /* Absolutely no idea why this is necessary, but it is; without
669 it, the region view disappears after the reparent.
671 _editor->update_canvas_now ();
673 /* Reparent to a non scrolling group so that we can keep the
674 region selection above all time axis views.
675 Reparenting means that we will have to move the region view
676 later, as the two parent groups have different coordinates.
679 rv->get_canvas_group()->reparent (*(_editor->_region_motion_group));
681 rv->fake_set_opaque (true);
684 /* If we have moved tracks, we'll fudge the layer delta so that the
685 region gets moved back onto layer 0 on its new track; this avoids
686 confusion when dragging regions from non-zero layers onto different
689 double this_delta_layer = delta_layer;
690 if (delta_time_axis_view != 0) {
691 this_delta_layer = - i->layer;
694 /* The TimeAxisView that this region is now on */
695 TimeAxisView* tv = _time_axis_views[i->time_axis_view + delta_time_axis_view];
697 /* Ensure it is moved from stacked -> expanded if appropriate */
698 if (tv->view()->layer_display() == Stacked) {
699 tv->view()->set_layer_display (Expanded);
702 /* We're only allowed to go -ve in layer on Expanded views */
703 if (tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
704 this_delta_layer = - i->layer;
708 rv->set_height (tv->view()->child_height ());
710 /* Update show/hidden status as the region view may have come from a hidden track,
711 or have moved to one.
714 rv->get_canvas_group()->hide ();
716 rv->get_canvas_group()->show ();
719 /* Update the DraggingView */
720 i->time_axis_view += delta_time_axis_view;
721 i->layer += this_delta_layer;
724 _editor->mouse_brush_insert_region (rv, pending_region_position);
729 /* Get the y coordinate of the top of the track that this region is now on */
730 tv->canvas_display()->i2w (x, y);
731 y += _editor->get_trackview_group_vertical_offset();
733 /* And adjust for the layer that it should be on */
734 StreamView* cv = tv->view ();
735 switch (cv->layer_display ()) {
739 y += (cv->layers() - i->layer - 1) * cv->child_height ();
742 y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
746 /* Now move the region view */
747 rv->move (x_delta, y - rv->get_canvas_group()->property_y());
750 } /* foreach region */
752 _total_x_delta += x_delta;
755 _editor->cursor_group->raise_to_top();
758 if (x_delta != 0 && !_brushing) {
759 show_verbose_cursor_time (_last_frame_position);
762 _last_pointer_time_axis_view += delta_time_axis_view;
763 _last_pointer_layer += delta_layer;
767 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
769 if (_copy && first_move) {
771 /* duplicate the regionview(s) and region(s) */
773 list<DraggingView> new_regionviews;
775 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
777 RegionView* rv = i->view;
778 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
779 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
781 const boost::shared_ptr<const Region> original = rv->region();
782 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
783 region_copy->set_position (original->position());
787 boost::shared_ptr<AudioRegion> audioregion_copy
788 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
790 nrv = new AudioRegionView (*arv, audioregion_copy);
792 boost::shared_ptr<MidiRegion> midiregion_copy
793 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
794 nrv = new MidiRegionView (*mrv, midiregion_copy);
799 nrv->get_canvas_group()->show ();
800 new_regionviews.push_back (DraggingView (nrv, this));
802 /* swap _primary to the copy */
804 if (rv == _primary) {
808 /* ..and deselect the one we copied */
810 rv->set_selected (false);
813 if (!new_regionviews.empty()) {
815 /* reflect the fact that we are dragging the copies */
817 _views = new_regionviews;
819 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
822 sync the canvas to what we think is its current state
823 without it, the canvas seems to
824 "forget" to update properly after the upcoming reparent()
825 ..only if the mouse is in rapid motion at the time of the grab.
826 something to do with regionview creation taking so long?
828 _editor->update_canvas_now();
832 RegionMotionDrag::motion (event, first_move);
836 RegionMotionDrag::finished (GdkEvent *, bool)
838 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
843 if ((*i)->view()->layer_display() == Expanded) {
844 (*i)->view()->set_layer_display (Stacked);
850 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
852 RegionMotionDrag::finished (ev, movement_occurred);
854 if (!movement_occurred) {
859 /* reverse this here so that we have the correct logic to finalize
863 if (Config->get_edit_mode() == Lock) {
864 _x_constrained = !_x_constrained;
867 assert (!_views.empty ());
869 /* We might have hidden region views so that they weren't visible during the drag
870 (when they have been reparented). Now everything can be shown again, as region
871 views are back in their track parent groups.
873 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
874 i->view->get_canvas_group()->show ();
877 bool const changed_position = (_last_frame_position != _primary->region()->position());
878 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
879 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
881 _editor->update_canvas_now ();
901 if (_editor->session() && Config->get_always_play_range()) {
902 _editor->session()->request_locate (_editor->get_selection().regions.start());
907 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
909 RegionSelection new_views;
910 PlaylistSet modified_playlists;
911 list<RegionView*> views_to_delete;
914 /* all changes were made during motion event handlers */
916 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
920 _editor->commit_reversible_command ();
924 if (_x_constrained) {
925 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
927 _editor->begin_reversible_command (Operations::region_copy);
930 /* insert the regions into their new playlists */
931 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
933 if (i->view->region()->locked()) {
939 if (changed_position && !_x_constrained) {
940 where = i->view->region()->position() - drag_delta;
942 where = i->view->region()->position();
945 RegionView* new_view = insert_region_into_playlist (
946 i->view->region(), dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]), i->layer, where, modified_playlists
953 new_views.push_back (new_view);
955 /* we don't need the copied RegionView any more */
956 views_to_delete.push_back (i->view);
959 /* Delete views that are no longer needed; we can't do this directly in the iteration over _views
960 because when views are deleted they are automagically removed from _views, which messes
963 for (list<RegionView*>::iterator i = views_to_delete.begin(); i != views_to_delete.end(); ++i) {
967 /* If we've created new regions either by copying or moving
968 to a new track, we want to replace the old selection with the new ones
971 if (new_views.size() > 0) {
972 _editor->selection->set (new_views);
975 /* write commands for the accumulated diffs for all our modified playlists */
976 add_stateful_diff_commands_for_playlists (modified_playlists);
978 _editor->commit_reversible_command ();
982 RegionMoveDrag::finished_no_copy (
983 bool const changed_position,
984 bool const changed_tracks,
985 framecnt_t const drag_delta
988 RegionSelection new_views;
989 PlaylistSet modified_playlists;
990 PlaylistSet frozen_playlists;
991 set<RouteTimeAxisView*> views_to_update;
994 /* all changes were made during motion event handlers */
995 _editor->commit_reversible_command ();
999 if (_x_constrained) {
1000 _editor->begin_reversible_command (_("fixed time region drag"));
1002 _editor->begin_reversible_command (Operations::region_drag);
1005 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1007 RegionView* rv = i->view;
1009 RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1010 double const dest_layer = i->layer;
1012 if (rv->region()->locked()) {
1017 views_to_update.insert (dest_rtv);
1021 if (changed_position && !_x_constrained) {
1022 where = rv->region()->position() - drag_delta;
1024 where = rv->region()->position();
1027 if (changed_tracks) {
1029 /* insert into new playlist */
1031 RegionView* new_view = insert_region_into_playlist (
1032 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
1035 if (new_view == 0) {
1040 new_views.push_back (new_view);
1042 /* remove from old playlist */
1044 /* the region that used to be in the old playlist is not
1045 moved to the new one - we use a copy of it. as a result,
1046 any existing editor for the region should no longer be
1049 rv->hide_region_editor();
1050 rv->fake_set_opaque (false);
1052 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1056 rv->region()->clear_changes ();
1059 motion on the same track. plonk the previously reparented region
1060 back to its original canvas group (its streamview).
1061 No need to do anything for copies as they are fake regions which will be deleted.
1064 rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
1065 rv->get_canvas_group()->property_y() = i->initial_y;
1068 /* just change the model */
1070 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1072 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1073 playlist->set_layer (rv->region(), dest_layer);
1076 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1078 pair<PlaylistSet::iterator, bool> r = frozen_playlists.insert (playlist);
1081 playlist->freeze ();
1084 /* this movement may result in a crossfade being modified, so we need to get undo
1085 data from the playlist as well as the region.
1088 r = modified_playlists.insert (playlist);
1090 playlist->clear_changes ();
1093 rv->region()->set_position (where);
1095 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1098 if (changed_tracks) {
1100 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1101 was selected in all of them, then removing it from a playlist will have removed all
1102 trace of it from _views (i.e. there were N regions selected, we removed 1,
1103 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1104 corresponding regionview, and _views is now empty).
1106 This could have invalidated any and all iterators into _views.
1108 The heuristic we use here is: if the region selection is empty, break out of the loop
1109 here. if the region selection is not empty, then restart the loop because we know that
1110 we must have removed at least the region(view) we've just been working on as well as any
1111 that we processed on previous iterations.
1113 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1114 we can just iterate.
1118 if (_views.empty()) {
1129 /* If we've created new regions either by copying or moving
1130 to a new track, we want to replace the old selection with the new ones
1133 if (new_views.size() > 0) {
1134 _editor->selection->set (new_views);
1137 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1141 /* write commands for the accumulated diffs for all our modified playlists */
1142 add_stateful_diff_commands_for_playlists (modified_playlists);
1144 _editor->commit_reversible_command ();
1146 /* We have futzed with the layering of canvas items on our streamviews.
1147 If any region changed layer, this will have resulted in the stream
1148 views being asked to set up their region views, and all will be well.
1149 If not, we might now have badly-ordered region views. Ask the StreamViews
1150 involved to sort themselves out, just in case.
1153 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1154 (*i)->view()->playlist_layered ((*i)->track ());
1158 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1159 * @param region Region to remove.
1160 * @param playlist playlist To remove from.
1161 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1162 * that clear_changes () is only called once per playlist.
1165 RegionMoveDrag::remove_region_from_playlist (
1166 boost::shared_ptr<Region> region,
1167 boost::shared_ptr<Playlist> playlist,
1168 PlaylistSet& modified_playlists
1171 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1174 playlist->clear_changes ();
1177 playlist->remove_region (region);
1181 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1182 * clearing the playlist's diff history first if necessary.
1183 * @param region Region to insert.
1184 * @param dest_rtv Destination RouteTimeAxisView.
1185 * @param dest_layer Destination layer.
1186 * @param where Destination position.
1187 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1188 * that clear_changes () is only called once per playlist.
1189 * @return New RegionView, or 0 if no insert was performed.
1192 RegionMoveDrag::insert_region_into_playlist (
1193 boost::shared_ptr<Region> region,
1194 RouteTimeAxisView* dest_rtv,
1197 PlaylistSet& modified_playlists
1200 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1201 if (!dest_playlist) {
1205 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1206 _new_region_view = 0;
1207 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1209 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1210 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1212 dest_playlist->clear_changes ();
1215 dest_playlist->add_region (region, where);
1217 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1218 dest_playlist->set_layer (region, dest_layer);
1223 assert (_new_region_view);
1225 return _new_region_view;
1229 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1231 _new_region_view = rv;
1235 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1237 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1238 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1240 _editor->session()->add_command (c);
1249 RegionMoveDrag::aborted (bool movement_occurred)
1253 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1260 RegionMotionDrag::aborted (movement_occurred);
1265 RegionMotionDrag::aborted (bool)
1267 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1268 if ((*i)->view()->layer_display() == Expanded) {
1269 (*i)->view()->set_layer_display (Stacked);
1273 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1274 RegionView* rv = i->view;
1275 TimeAxisView* tv = &(rv->get_time_axis_view ());
1276 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1278 rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
1279 rv->get_canvas_group()->property_y() = 0;
1281 rv->fake_set_opaque (false);
1282 rv->move (-_total_x_delta, 0);
1283 rv->set_height (rtv->view()->child_height ());
1286 _editor->update_canvas_now ();
1289 /** @param b true to brush, otherwise false.
1290 * @param c true to make copies of the regions being moved, otherwise false.
1292 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1293 : RegionMotionDrag (e, i, p, v, b),
1296 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1299 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1300 if (rtv && rtv->is_track()) {
1301 speed = rtv->track()->speed ();
1304 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1308 RegionMoveDrag::setup_pointer_frame_offset ()
1310 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1313 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1314 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1316 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1318 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1319 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1321 _primary = v->view()->create_region_view (r, false, false);
1323 _primary->get_canvas_group()->show ();
1324 _primary->set_position (pos, 0);
1325 _views.push_back (DraggingView (_primary, this));
1327 _last_frame_position = pos;
1329 _item = _primary->get_canvas_group ();
1333 RegionInsertDrag::finished (GdkEvent *, bool)
1335 _editor->update_canvas_now ();
1337 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[_views.front().time_axis_view]);
1339 _primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
1340 _primary->get_canvas_group()->property_y() = 0;
1342 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1344 _editor->begin_reversible_command (Operations::insert_region);
1345 playlist->clear_changes ();
1346 playlist->add_region (_primary->region (), _last_frame_position);
1347 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1348 _editor->commit_reversible_command ();
1356 RegionInsertDrag::aborted (bool)
1363 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1364 : RegionMoveDrag (e, i, p, v, false, false)
1366 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1369 struct RegionSelectionByPosition {
1370 bool operator() (RegionView*a, RegionView* b) {
1371 return a->region()->position () < b->region()->position();
1376 RegionSpliceDrag::motion (GdkEvent* event, bool)
1378 /* Which trackview is this ? */
1380 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
1381 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1383 /* The region motion is only processed if the pointer is over
1387 if (!tv || !tv->is_track()) {
1388 /* To make sure we hide the verbose canvas cursor when the mouse is
1389 not held over and audiotrack.
1391 _editor->verbose_cursor()->hide ();
1397 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1403 RegionSelection copy (_editor->selection->regions);
1405 RegionSelectionByPosition cmp;
1408 framepos_t const pf = adjusted_current_frame (event);
1410 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1412 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1418 boost::shared_ptr<Playlist> playlist;
1420 if ((playlist = atv->playlist()) == 0) {
1424 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1429 if (pf < (*i)->region()->last_frame() + 1) {
1433 if (pf > (*i)->region()->first_frame()) {
1439 playlist->shuffle ((*i)->region(), dir);
1444 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1446 RegionMoveDrag::finished (event, movement_occurred);
1450 RegionSpliceDrag::aborted (bool)
1455 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
1457 _view (dynamic_cast<MidiTimeAxisView*> (v))
1459 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
1465 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
1468 _region = add_midi_region (_view);
1469 _view->playlist()->freeze ();
1472 framepos_t const f = adjusted_current_frame (event);
1473 if (f < grab_frame()) {
1474 _region->set_position (f);
1477 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
1478 so that if this region is duplicated, its duplicate starts on
1479 a snap point rather than 1 frame after a snap point. Otherwise things get
1480 a bit confusing as if a region starts 1 frame after a snap point, one cannot
1481 place snapped notes at the start of the region.
1484 framecnt_t const len = abs (f - grab_frame () - 1);
1485 _region->set_length (len < 1 ? 1 : len);
1491 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
1493 if (!movement_occurred) {
1494 add_midi_region (_view);
1496 _view->playlist()->thaw ();
1500 _editor->commit_reversible_command ();
1505 RegionCreateDrag::aborted (bool)
1508 _view->playlist()->thaw ();
1514 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
1518 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
1522 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
1524 Gdk::Cursor* cursor;
1525 ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item);
1526 float x_fraction = cnote->mouse_x_fraction ();
1528 if (x_fraction > 0.0 && x_fraction < 0.25) {
1529 cursor = _editor->cursors()->left_side_trim;
1531 cursor = _editor->cursors()->right_side_trim;
1534 Drag::start_grab (event, cursor);
1536 region = &cnote->region_view();
1538 double const region_start = region->get_position_pixels();
1539 double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;
1541 if (grab_x() <= middle_point) {
1542 cursor = _editor->cursors()->left_side_trim;
1545 cursor = _editor->cursors()->right_side_trim;
1549 _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, *cursor, event->motion.time);
1551 if (event->motion.state & Keyboard::PrimaryModifier) {
1557 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1559 if (ms.size() > 1) {
1560 /* has to be relative, may make no sense otherwise */
1564 /* select this note; if it is already selected, preserve the existing selection,
1565 otherwise make this note the only one selected.
1567 region->note_selected (cnote, cnote->selected ());
1569 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
1570 MidiRegionSelection::iterator next;
1573 (*r)->begin_resizing (at_front);
1579 NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
1581 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1582 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1583 (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1588 NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
1590 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1591 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1592 (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1597 NoteResizeDrag::aborted (bool)
1599 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1600 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1601 (*r)->abort_resizing ();
1605 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1606 : RegionDrag (e, i, p, v)
1608 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
1612 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
1615 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1616 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1618 if (tv && tv->is_track()) {
1619 speed = tv->track()->speed();
1622 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
1623 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
1624 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
1626 framepos_t const pf = adjusted_current_frame (event);
1628 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1629 /* Move the contents of the region around without changing the region bounds */
1630 _operation = ContentsTrim;
1631 Drag::start_grab (event, _editor->cursors()->trimmer);
1633 /* These will get overridden for a point trim.*/
1634 if (pf < (region_start + region_length/2)) {
1635 /* closer to front */
1636 _operation = StartTrim;
1637 Drag::start_grab (event, _editor->cursors()->left_side_trim);
1640 _operation = EndTrim;
1641 Drag::start_grab (event, _editor->cursors()->right_side_trim);
1645 switch (_operation) {
1647 show_verbose_cursor_time (region_start);
1648 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1649 i->view->trim_front_starting ();
1653 show_verbose_cursor_time (region_end);
1656 show_verbose_cursor_time (pf);
1660 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1661 i->view->region()->suspend_property_changes ();
1666 TrimDrag::motion (GdkEvent* event, bool first_move)
1668 RegionView* rv = _primary;
1671 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1672 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1673 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
1675 if (tv && tv->is_track()) {
1676 speed = tv->track()->speed();
1679 framecnt_t const dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
1685 switch (_operation) {
1687 trim_type = "Region start trim";
1690 trim_type = "Region end trim";
1693 trim_type = "Region content trim";
1697 _editor->begin_reversible_command (trim_type);
1699 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1700 RegionView* rv = i->view;
1701 rv->fake_set_opaque (false);
1702 rv->enable_display (false);
1703 rv->region()->playlist()->clear_owned_changes ();
1705 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
1708 arv->temporarily_hide_envelope ();
1712 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
1713 insert_result = _editor->motion_frozen_playlists.insert (pl);
1715 if (insert_result.second) {
1721 bool non_overlap_trim = false;
1723 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1724 non_overlap_trim = true;
1727 switch (_operation) {
1729 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1730 i->view->trim_front (i->initial_position + dt, non_overlap_trim);
1735 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1736 i->view->trim_end (i->initial_end + dt, non_overlap_trim);
1742 bool swap_direction = false;
1744 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1745 swap_direction = true;
1748 framecnt_t frame_delta = 0;
1750 bool left_direction = false;
1751 if (last_pointer_frame() > adjusted_current_frame(event)) {
1752 left_direction = true;
1755 if (left_direction) {
1756 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
1758 frame_delta = (adjusted_current_frame(event) - last_pointer_frame());
1761 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1762 i->view->trim_contents (frame_delta, left_direction, swap_direction);
1768 switch (_operation) {
1770 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
1773 show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed));
1776 show_verbose_cursor_time (adjusted_current_frame (event));
1783 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
1785 if (movement_occurred) {
1786 motion (event, false);
1788 /* This must happen before the region's StatefulDiffCommand is created, as it may
1789 `correct' (ahem) the region's _start from being negative to being zero. It
1790 needs to be zero in the undo record.
1792 if (_operation == StartTrim) {
1793 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1794 i->view->trim_front_ending ();
1798 if (!_editor->selection->selected (_primary)) {
1799 _primary->thaw_after_trim ();
1802 set<boost::shared_ptr<Playlist> > diffed_playlists;
1804 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1805 i->view->thaw_after_trim ();
1806 i->view->enable_display (true);
1807 i->view->fake_set_opaque (true);
1809 /* Trimming one region may affect others on the playlist, so we need
1810 to get undo Commands from the whole playlist rather than just the
1811 region. Use diffed_playlists to make sure we don't diff a given
1812 playlist more than once.
1814 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
1815 if (diffed_playlists.find (p) == diffed_playlists.end()) {
1816 vector<Command*> cmds;
1818 _editor->session()->add_commands (cmds);
1819 diffed_playlists.insert (p);
1823 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
1827 _editor->motion_frozen_playlists.clear ();
1828 _editor->commit_reversible_command();
1831 /* no mouse movement */
1832 _editor->point_trim (event, adjusted_current_frame (event));
1835 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1836 if (_operation == StartTrim) {
1837 i->view->trim_front_ending ();
1840 i->view->region()->resume_property_changes ();
1845 TrimDrag::aborted (bool movement_occurred)
1847 /* Our motion method is changing model state, so use the Undo system
1848 to cancel. Perhaps not ideal, as this will leave an Undo point
1849 behind which may be slightly odd from the user's point of view.
1854 if (movement_occurred) {
1858 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1859 i->view->region()->resume_property_changes ();
1864 TrimDrag::setup_pointer_frame_offset ()
1866 list<DraggingView>::iterator i = _views.begin ();
1867 while (i != _views.end() && i->view != _primary) {
1871 if (i == _views.end()) {
1875 switch (_operation) {
1877 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
1880 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
1887 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1891 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
1892 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
1897 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1899 Drag::start_grab (event, cursor);
1900 show_verbose_cursor_time (adjusted_current_frame(event));
1904 MeterMarkerDrag::setup_pointer_frame_offset ()
1906 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
1910 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
1914 // create a dummy marker for visual representation of moving the
1915 // section, because whether its a copy or not, we're going to
1916 // leave or lose the original marker (leave if its a copy; lose if its
1917 // not, because we'll remove it from the map).
1919 MeterSection section (_marker->meter());
1921 if (!section.movable()) {
1926 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
1928 _marker = new MeterMarker (
1930 *_editor->meter_group,
1931 ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
1933 *new MeterSection (_marker->meter())
1936 /* use the new marker for the grab */
1937 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
1940 TempoMap& map (_editor->session()->tempo_map());
1941 /* get current state */
1942 before_state = &map.get_state();
1943 /* remove the section while we drag it */
1944 map.remove_meter (section, true);
1948 framepos_t const pf = adjusted_current_frame (event);
1949 _marker->set_position (pf);
1950 show_verbose_cursor_time (pf);
1954 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
1956 if (!movement_occurred) {
1960 motion (event, false);
1962 Timecode::BBT_Time when;
1964 TempoMap& map (_editor->session()->tempo_map());
1965 map.bbt_time (last_pointer_frame(), when);
1967 if (_copy == true) {
1968 _editor->begin_reversible_command (_("copy meter mark"));
1969 XMLNode &before = map.get_state();
1970 map.add_meter (_marker->meter(), when);
1971 XMLNode &after = map.get_state();
1972 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1973 _editor->commit_reversible_command ();
1976 _editor->begin_reversible_command (_("move meter mark"));
1978 /* we removed it before, so add it back now */
1980 map.add_meter (_marker->meter(), when);
1981 XMLNode &after = map.get_state();
1982 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
1983 _editor->commit_reversible_command ();
1986 // delete the dummy marker we used for visual representation while moving.
1987 // a new visual marker will show up automatically.
1992 MeterMarkerDrag::aborted (bool moved)
1994 _marker->set_position (_marker->meter().frame ());
1997 TempoMap& map (_editor->session()->tempo_map());
1998 /* we removed it before, so add it back now */
1999 map.add_meter (_marker->meter(), _marker->meter().frame());
2000 // delete the dummy marker we used for visual representation while moving.
2001 // a new visual marker will show up automatically.
2006 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
2010 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
2012 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
2017 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2019 Drag::start_grab (event, cursor);
2020 show_verbose_cursor_time (adjusted_current_frame (event));
2024 TempoMarkerDrag::setup_pointer_frame_offset ()
2026 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
2030 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
2034 // create a dummy marker for visual representation of moving the
2035 // section, because whether its a copy or not, we're going to
2036 // leave or lose the original marker (leave if its a copy; lose if its
2037 // not, because we'll remove it from the map).
2039 // create a dummy marker for visual representation of moving the copy.
2040 // The actual copying is not done before we reach the finish callback.
2043 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
2045 TempoSection section (_marker->tempo());
2047 _marker = new TempoMarker (
2049 *_editor->tempo_group,
2050 ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
2052 *new TempoSection (_marker->tempo())
2055 /* use the new marker for the grab */
2056 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
2059 TempoMap& map (_editor->session()->tempo_map());
2060 /* get current state */
2061 before_state = &map.get_state();
2062 /* remove the section while we drag it */
2063 map.remove_tempo (section, true);
2067 framepos_t const pf = adjusted_current_frame (event);
2068 _marker->set_position (pf);
2069 show_verbose_cursor_time (pf);
2073 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2075 if (!movement_occurred) {
2079 motion (event, false);
2081 TempoMap& map (_editor->session()->tempo_map());
2082 framepos_t beat_time = map.round_to_beat (last_pointer_frame(), 0);
2083 Timecode::BBT_Time when;
2085 map.bbt_time (beat_time, when);
2087 if (_copy == true) {
2088 _editor->begin_reversible_command (_("copy tempo mark"));
2089 XMLNode &before = map.get_state();
2090 map.add_tempo (_marker->tempo(), when);
2091 XMLNode &after = map.get_state();
2092 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2093 _editor->commit_reversible_command ();
2096 _editor->begin_reversible_command (_("move tempo mark"));
2097 /* we removed it before, so add it back now */
2098 map.add_tempo (_marker->tempo(), when);
2099 XMLNode &after = map.get_state();
2100 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
2101 _editor->commit_reversible_command ();
2104 // delete the dummy marker we used for visual representation while moving.
2105 // a new visual marker will show up automatically.
2110 TempoMarkerDrag::aborted (bool moved)
2112 _marker->set_position (_marker->tempo().frame());
2114 TempoMap& map (_editor->session()->tempo_map());
2115 /* we removed it before, so add it back now */
2116 map.add_tempo (_marker->tempo(), _marker->tempo().start());
2117 // delete the dummy marker we used for visual representation while moving.
2118 // a new visual marker will show up automatically.
2123 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
2127 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
2130 /** Do all the things we do when dragging the playhead to make it look as though
2131 * we have located, without actually doing the locate (because that would cause
2132 * the diskstream buffers to be refilled, which is too slow).
2135 CursorDrag::fake_locate (framepos_t t)
2137 _editor->playhead_cursor->set_position (t);
2139 Session* s = _editor->session ();
2140 if (s->timecode_transmission_suspended ()) {
2141 framepos_t const f = _editor->playhead_cursor->current_frame;
2142 s->send_mmc_locate (f);
2143 s->send_full_time_code (f);
2146 show_verbose_cursor_time (t);
2147 _editor->UpdateAllTransportClocks (t);
2151 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
2153 Drag::start_grab (event, c);
2155 _grab_zoom = _editor->frames_per_unit;
2157 framepos_t where = _editor->event_frame (event, 0, 0);
2158 _editor->snap_to_with_modifier (where, event);
2160 _editor->_dragging_playhead = true;
2162 Session* s = _editor->session ();
2165 if (_was_rolling && _stop) {
2169 if (s->is_auditioning()) {
2170 s->cancel_audition ();
2173 s->request_suspend_timecode_transmission ();
2174 while (!s->timecode_transmission_suspended ()) {
2175 /* twiddle our thumbs */
2179 fake_locate (where);
2183 CursorDrag::motion (GdkEvent* event, bool)
2185 framepos_t const adjusted_frame = adjusted_current_frame (event);
2186 if (adjusted_frame != last_pointer_frame()) {
2187 fake_locate (adjusted_frame);
2189 _editor->update_canvas_now ();
2195 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
2197 _editor->_dragging_playhead = false;
2199 if (!movement_occurred && _stop) {
2203 motion (event, false);
2205 Session* s = _editor->session ();
2207 s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
2208 _editor->_pending_locate_request = true;
2209 s->request_resume_timecode_transmission ();
2214 CursorDrag::aborted (bool)
2216 if (_editor->_dragging_playhead) {
2217 _editor->session()->request_resume_timecode_transmission ();
2218 _editor->_dragging_playhead = false;
2221 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
2224 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2225 : RegionDrag (e, i, p, v)
2227 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
2231 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2233 Drag::start_grab (event, cursor);
2235 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2236 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2238 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
2240 arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
2244 FadeInDrag::setup_pointer_frame_offset ()
2246 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2247 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2248 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
2252 FadeInDrag::motion (GdkEvent* event, bool)
2254 framecnt_t fade_length;
2256 framepos_t const pos = adjusted_current_frame (event);
2258 boost::shared_ptr<Region> region = _primary->region ();
2260 if (pos < (region->position() + 64)) {
2261 fade_length = 64; // this should be a minimum defined somewhere
2262 } else if (pos > region->last_frame()) {
2263 fade_length = region->length();
2265 fade_length = pos - region->position();
2268 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2270 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2276 tmp->reset_fade_in_shape_width (fade_length);
2277 tmp->show_fade_line((framecnt_t) fade_length);
2280 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
2284 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
2286 if (!movement_occurred) {
2290 framecnt_t fade_length;
2292 framepos_t const pos = adjusted_current_frame (event);
2294 boost::shared_ptr<Region> region = _primary->region ();
2296 if (pos < (region->position() + 64)) {
2297 fade_length = 64; // this should be a minimum defined somewhere
2298 } else if (pos > region->last_frame()) {
2299 fade_length = region->length();
2301 fade_length = pos - region->position();
2304 _editor->begin_reversible_command (_("change fade in length"));
2306 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2308 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2314 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
2315 XMLNode &before = alist->get_state();
2317 tmp->audio_region()->set_fade_in_length (fade_length);
2318 tmp->audio_region()->set_fade_in_active (true);
2319 tmp->hide_fade_line();
2321 XMLNode &after = alist->get_state();
2322 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2325 _editor->commit_reversible_command ();
2329 FadeInDrag::aborted (bool)
2331 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2332 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2338 tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
2339 tmp->hide_fade_line();
2343 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2344 : RegionDrag (e, i, p, v)
2346 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
2350 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2352 Drag::start_grab (event, cursor);
2354 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2355 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2357 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
2359 arv->show_fade_line(r->length() - r->fade_out()->back()->when);
2363 FadeOutDrag::setup_pointer_frame_offset ()
2365 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2366 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2367 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
2371 FadeOutDrag::motion (GdkEvent* event, bool)
2373 framecnt_t fade_length;
2375 framepos_t const pos = adjusted_current_frame (event);
2377 boost::shared_ptr<Region> region = _primary->region ();
2379 if (pos > (region->last_frame() - 64)) {
2380 fade_length = 64; // this should really be a minimum fade defined somewhere
2382 else if (pos < region->position()) {
2383 fade_length = region->length();
2386 fade_length = region->last_frame() - pos;
2389 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2391 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2397 tmp->reset_fade_out_shape_width (fade_length);
2398 tmp->show_fade_line(region->length() - fade_length);
2401 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
2405 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
2407 if (!movement_occurred) {
2411 framecnt_t fade_length;
2413 framepos_t const pos = adjusted_current_frame (event);
2415 boost::shared_ptr<Region> region = _primary->region ();
2417 if (pos > (region->last_frame() - 64)) {
2418 fade_length = 64; // this should really be a minimum fade defined somewhere
2420 else if (pos < region->position()) {
2421 fade_length = region->length();
2424 fade_length = region->last_frame() - pos;
2427 _editor->begin_reversible_command (_("change fade out length"));
2429 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2431 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2437 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
2438 XMLNode &before = alist->get_state();
2440 tmp->audio_region()->set_fade_out_length (fade_length);
2441 tmp->audio_region()->set_fade_out_active (true);
2442 tmp->hide_fade_line();
2444 XMLNode &after = alist->get_state();
2445 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2448 _editor->commit_reversible_command ();
2452 FadeOutDrag::aborted (bool)
2454 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2455 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2461 tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
2462 tmp->hide_fade_line();
2466 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
2469 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
2471 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
2474 _points.push_back (Gnome::Art::Point (0, 0));
2475 _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
2478 MarkerDrag::~MarkerDrag ()
2480 for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
2486 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2488 Drag::start_grab (event, cursor);
2492 Location *location = _editor->find_location_from_marker (_marker, is_start);
2493 _editor->_dragging_edit_point = true;
2495 update_item (location);
2497 // _drag_line->show();
2498 // _line->raise_to_top();
2501 show_verbose_cursor_time (location->start());
2503 show_verbose_cursor_time (location->end());
2506 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2509 case Selection::Toggle:
2510 _editor->selection->toggle (_marker);
2512 case Selection::Set:
2513 if (!_editor->selection->selected (_marker)) {
2514 _editor->selection->set (_marker);
2517 case Selection::Extend:
2519 Locations::LocationList ll;
2520 list<Marker*> to_add;
2522 _editor->selection->markers.range (s, e);
2523 s = min (_marker->position(), s);
2524 e = max (_marker->position(), e);
2527 if (e < max_framepos) {
2530 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
2531 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
2532 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
2535 to_add.push_back (lm->start);
2538 to_add.push_back (lm->end);
2542 if (!to_add.empty()) {
2543 _editor->selection->add (to_add);
2547 case Selection::Add:
2548 _editor->selection->add (_marker);
2552 /* Set up copies for us to manipulate during the drag */
2554 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
2555 Location* l = _editor->find_location_from_marker (*i, is_start);
2556 _copied_locations.push_back (new Location (*l));
2561 MarkerDrag::setup_pointer_frame_offset ()
2564 Location *location = _editor->find_location_from_marker (_marker, is_start);
2565 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
2569 MarkerDrag::motion (GdkEvent* event, bool)
2571 framecnt_t f_delta = 0;
2573 bool move_both = false;
2575 Location *real_location;
2576 Location *copy_location = 0;
2578 framepos_t const newframe = adjusted_current_frame (event);
2580 framepos_t next = newframe;
2582 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2586 MarkerSelection::iterator i;
2587 list<Location*>::iterator x;
2589 /* find the marker we're dragging, and compute the delta */
2591 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2592 x != _copied_locations.end() && i != _editor->selection->markers.end();
2598 if (marker == _marker) {
2600 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2605 if (real_location->is_mark()) {
2606 f_delta = newframe - copy_location->start();
2610 switch (marker->type()) {
2611 case Marker::SessionStart:
2612 case Marker::RangeStart:
2613 case Marker::LoopStart:
2614 case Marker::PunchIn:
2615 f_delta = newframe - copy_location->start();
2618 case Marker::SessionEnd:
2619 case Marker::RangeEnd:
2620 case Marker::LoopEnd:
2621 case Marker::PunchOut:
2622 f_delta = newframe - copy_location->end();
2625 /* what kind of marker is this ? */
2633 if (i == _editor->selection->markers.end()) {
2634 /* hmm, impossible - we didn't find the dragged marker */
2638 /* now move them all */
2640 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2641 x != _copied_locations.end() && i != _editor->selection->markers.end();
2647 /* call this to find out if its the start or end */
2649 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2653 if (real_location->locked()) {
2657 if (copy_location->is_mark()) {
2661 copy_location->set_start (copy_location->start() + f_delta);
2665 framepos_t new_start = copy_location->start() + f_delta;
2666 framepos_t new_end = copy_location->end() + f_delta;
2668 if (is_start) { // start-of-range marker
2671 copy_location->set_start (new_start);
2672 copy_location->set_end (new_end);
2673 } else if (new_start < copy_location->end()) {
2674 copy_location->set_start (new_start);
2675 } else if (newframe > 0) {
2676 _editor->snap_to (next, 1, true);
2677 copy_location->set_end (next);
2678 copy_location->set_start (newframe);
2681 } else { // end marker
2684 copy_location->set_end (new_end);
2685 copy_location->set_start (new_start);
2686 } else if (new_end > copy_location->start()) {
2687 copy_location->set_end (new_end);
2688 } else if (newframe > 0) {
2689 _editor->snap_to (next, -1, true);
2690 copy_location->set_start (next);
2691 copy_location->set_end (newframe);
2696 update_item (copy_location);
2698 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
2701 lm->set_position (copy_location->start(), copy_location->end());
2705 assert (!_copied_locations.empty());
2707 show_verbose_cursor_time (newframe);
2710 _editor->update_canvas_now ();
2715 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2717 if (!movement_occurred) {
2719 /* just a click, do nothing but finish
2720 off the selection process
2723 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2726 case Selection::Set:
2727 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
2728 _editor->selection->set (_marker);
2732 case Selection::Toggle:
2733 case Selection::Extend:
2734 case Selection::Add:
2741 _editor->_dragging_edit_point = false;
2743 _editor->begin_reversible_command ( _("move marker") );
2744 XMLNode &before = _editor->session()->locations()->get_state();
2746 MarkerSelection::iterator i;
2747 list<Location*>::iterator x;
2750 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2751 x != _copied_locations.end() && i != _editor->selection->markers.end();
2754 Location * location = _editor->find_location_from_marker (*i, is_start);
2758 if (location->locked()) {
2762 if (location->is_mark()) {
2763 location->set_start ((*x)->start());
2765 location->set ((*x)->start(), (*x)->end());
2770 XMLNode &after = _editor->session()->locations()->get_state();
2771 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
2772 _editor->commit_reversible_command ();
2776 MarkerDrag::aborted (bool)
2782 MarkerDrag::update_item (Location*)
2787 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
2789 _cumulative_x_drag (0),
2790 _cumulative_y_drag (0)
2792 if (_zero_gain_fraction < 0.0) {
2793 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
2796 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
2798 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
2804 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2806 Drag::start_grab (event, _editor->cursors()->fader);
2808 // start the grab at the center of the control point so
2809 // the point doesn't 'jump' to the mouse after the first drag
2810 _fixed_grab_x = _point->get_x();
2811 _fixed_grab_y = _point->get_y();
2813 float const fraction = 1 - (_point->get_y() / _point->line().height());
2815 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
2817 _editor->verbose_cursor()->set (_point->line().get_verbose_cursor_string (fraction),
2818 event->button.x + 10, event->button.y + 10);
2820 _editor->verbose_cursor()->show ();
2822 if (!_point->can_slide ()) {
2823 _x_constrained = true;
2828 ControlPointDrag::motion (GdkEvent* event, bool)
2830 double dx = _drags->current_pointer_x() - last_pointer_x();
2831 double dy = _drags->current_pointer_y() - last_pointer_y();
2833 if (event->button.state & Keyboard::SecondaryModifier) {
2838 /* coordinate in pixels relative to the start of the region (for region-based automation)
2839 or track (for track-based automation) */
2840 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
2841 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2843 // calculate zero crossing point. back off by .01 to stay on the
2844 // positive side of zero
2845 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
2847 // make sure we hit zero when passing through
2848 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
2852 if (_x_constrained) {
2855 if (_y_constrained) {
2859 _cumulative_x_drag = cx - _fixed_grab_x;
2860 _cumulative_y_drag = cy - _fixed_grab_y;
2864 cy = min ((double) _point->line().height(), cy);
2866 framepos_t cx_frames = _editor->unit_to_frame (cx);
2868 if (!_x_constrained) {
2869 _editor->snap_to_with_modifier (cx_frames, event);
2872 cx_frames = min (cx_frames, _point->line().maximum_time());
2874 float const fraction = 1.0 - (cy / _point->line().height());
2876 bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
2878 _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
2880 _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
2884 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
2886 if (!movement_occurred) {
2890 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2891 _editor->reset_point_selection ();
2895 motion (event, false);
2898 _point->line().end_drag ();
2899 _editor->session()->commit_reversible_command ();
2903 ControlPointDrag::aborted (bool)
2905 _point->line().reset ();
2909 ControlPointDrag::active (Editing::MouseMode m)
2911 if (m == Editing::MouseGain) {
2912 /* always active in mouse gain */
2916 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
2917 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
2920 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
2923 _cumulative_y_drag (0)
2925 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
2929 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2931 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
2934 _item = &_line->grab_item ();
2936 /* need to get x coordinate in terms of parent (TimeAxisItemView)
2937 origin, and ditto for y.
2940 double cx = event->button.x;
2941 double cy = event->button.y;
2943 _line->parent_group().w2i (cx, cy);
2945 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit);
2950 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
2951 /* no adjacent points */
2955 Drag::start_grab (event, _editor->cursors()->fader);
2957 /* store grab start in parent frame */
2962 double fraction = 1.0 - (cy / _line->height());
2964 _line->start_drag_line (before, after, fraction);
2966 _editor->verbose_cursor()->set (_line->get_verbose_cursor_string (fraction),
2967 event->button.x + 10, event->button.y + 10);
2969 _editor->verbose_cursor()->show ();
2973 LineDrag::motion (GdkEvent* event, bool)
2975 double dy = _drags->current_pointer_y() - last_pointer_y();
2977 if (event->button.state & Keyboard::SecondaryModifier) {
2981 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2983 _cumulative_y_drag = cy - _fixed_grab_y;
2986 cy = min ((double) _line->height(), cy);
2988 double const fraction = 1.0 - (cy / _line->height());
2989 bool const push = !Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
2991 /* we are ignoring x position for this drag, so we can just pass in anything */
2992 _line->drag_motion (0, fraction, true, push);
2994 _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
2998 LineDrag::finished (GdkEvent* event, bool)
3000 motion (event, false);
3002 _editor->session()->commit_reversible_command ();
3006 LineDrag::aborted (bool)
3011 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
3014 _cumulative_x_drag (0)
3016 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
3020 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
3022 Drag::start_grab (event);
3024 _line = reinterpret_cast<Line*> (_item);
3027 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
3029 double cx = event->button.x;
3030 double cy = event->button.y;
3032 _item->property_parent().get_value()->w2i(cx, cy);
3034 /* store grab start in parent frame */
3035 _region_view_grab_x = cx;
3037 _before = *(float*) _item->get_data ("position");
3039 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3041 _max_x = _editor->frame_to_pixel(_arv->get_duration());
3045 FeatureLineDrag::motion (GdkEvent*, bool)
3047 double dx = _drags->current_pointer_x() - last_pointer_x();
3049 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
3051 _cumulative_x_drag += dx;
3053 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
3062 ArdourCanvas::Points points;
3064 double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
3066 _line->get_bounds(x1, y2, x2, y2);
3068 points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
3069 points.push_back(Gnome::Art::Point(cx, y2 - y1));
3071 _line->property_points() = points;
3073 float *pos = new float;
3076 _line->set_data ("position", pos);
3082 FeatureLineDrag::finished (GdkEvent*, bool)
3084 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3085 _arv->update_transient(_before, _before);
3089 FeatureLineDrag::aborted (bool)
3094 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
3096 , _vertical_only (false)
3098 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
3102 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3104 Drag::start_grab (event);
3105 show_verbose_cursor_time (adjusted_current_frame (event));
3109 RubberbandSelectDrag::motion (GdkEvent* event, bool)
3116 framepos_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
3118 framepos_t grab = grab_frame ();
3119 if (Config->get_rubberbanding_snaps_to_grid ()) {
3120 _editor->snap_to_with_modifier (grab, event);
3123 /* base start and end on initial click position */
3133 if (_drags->current_pointer_y() < grab_y()) {
3134 y1 = _drags->current_pointer_y();
3137 y2 = _drags->current_pointer_y();
3142 if (start != end || y1 != y2) {
3144 double x1 = _editor->frame_to_pixel (start);
3145 double x2 = _editor->frame_to_pixel (end);
3147 _editor->rubberband_rect->property_x1() = x1;
3148 if (_vertical_only) {
3149 /* fixed 10 pixel width */
3150 _editor->rubberband_rect->property_x2() = x1 + 10;
3152 _editor->rubberband_rect->property_x2() = x2;
3155 _editor->rubberband_rect->property_y1() = y1;
3156 _editor->rubberband_rect->property_y2() = y2;
3158 _editor->rubberband_rect->show();
3159 _editor->rubberband_rect->raise_to_top();
3161 show_verbose_cursor_time (pf);
3163 do_select_things (event, true);
3168 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
3173 if (grab_frame() < last_pointer_frame()) {
3175 x2 = last_pointer_frame ();
3178 x1 = last_pointer_frame ();
3184 if (_drags->current_pointer_y() < grab_y()) {
3185 y1 = _drags->current_pointer_y();
3188 y2 = _drags->current_pointer_y();
3192 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
3196 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
3198 if (movement_occurred) {
3200 motion (event, false);
3201 do_select_things (event, false);
3207 bool do_deselect = true;
3208 MidiTimeAxisView* mtv;
3210 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
3212 if (_editor->selection->empty()) {
3213 /* nothing selected */
3214 add_midi_region (mtv);
3215 do_deselect = false;
3225 _editor->rubberband_rect->hide();
3229 RubberbandSelectDrag::aborted (bool)
3231 _editor->rubberband_rect->hide ();
3234 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
3235 : RegionDrag (e, i, p, v)
3237 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
3241 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3243 Drag::start_grab (event, cursor);
3245 show_verbose_cursor_time (adjusted_current_frame (event));
3249 TimeFXDrag::motion (GdkEvent* event, bool)
3251 RegionView* rv = _primary;
3252 StreamView* cv = rv->get_time_axis_view().view ();
3254 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
3255 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
3256 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
3258 framepos_t const pf = adjusted_current_frame (event);
3260 if (pf > rv->region()->position()) {
3261 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
3264 show_verbose_cursor_time (pf);
3268 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3270 _primary->get_time_axis_view().hide_timestretch ();
3272 if (!movement_occurred) {
3276 if (last_pointer_frame() < _primary->region()->position()) {
3277 /* backwards drag of the left edge - not usable */
3281 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
3283 float percentage = (double) newlen / (double) _primary->region()->length();
3285 #ifndef USE_RUBBERBAND
3286 // Soundtouch uses percentage / 100 instead of normal (/ 1)
3287 if (_primary->region()->data_type() == DataType::AUDIO) {
3288 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
3292 if (!_editor->get_selection().regions.empty()) {
3293 /* primary will already be included in the selection, and edit
3294 group shared editing will propagate selection across
3295 equivalent regions, so just use the current region
3299 if (_editor->time_stretch (_editor->get_selection().regions, percentage) == -1) {
3300 error << _("An error occurred while executing time stretch operation") << endmsg;
3306 TimeFXDrag::aborted (bool)
3308 _primary->get_time_axis_view().hide_timestretch ();
3311 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
3314 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
3318 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3320 Drag::start_grab (event);
3324 ScrubDrag::motion (GdkEvent* /*event*/, bool)
3326 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
3330 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3332 if (movement_occurred && _editor->session()) {
3333 /* make sure we stop */
3334 _editor->session()->request_transport_speed (0.0);
3339 ScrubDrag::aborted (bool)
3344 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3348 , _original_pointer_time_axis (-1)
3349 , _last_pointer_time_axis (-1)
3350 , _time_selection_at_start (!_editor->get_selection().time.empty())
3352 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
3356 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
3358 if (_editor->session() == 0) {
3362 Gdk::Cursor* cursor = 0;
3364 switch (_operation) {
3365 case CreateSelection:
3366 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3371 cursor = _editor->cursors()->selector;
3372 Drag::start_grab (event, cursor);
3375 case SelectionStartTrim:
3376 if (_editor->clicked_axisview) {
3377 _editor->clicked_axisview->order_selection_trims (_item, true);
3379 Drag::start_grab (event, _editor->cursors()->left_side_trim);
3382 case SelectionEndTrim:
3383 if (_editor->clicked_axisview) {
3384 _editor->clicked_axisview->order_selection_trims (_item, false);
3386 Drag::start_grab (event, _editor->cursors()->right_side_trim);
3390 Drag::start_grab (event, cursor);
3394 if (_operation == SelectionMove) {
3395 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
3397 show_verbose_cursor_time (adjusted_current_frame (event));
3400 _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
3404 SelectionDrag::setup_pointer_frame_offset ()
3406 switch (_operation) {
3407 case CreateSelection:
3408 _pointer_frame_offset = 0;
3411 case SelectionStartTrim:
3413 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
3416 case SelectionEndTrim:
3417 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
3423 SelectionDrag::motion (GdkEvent* event, bool first_move)
3425 framepos_t start = 0;
3429 pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
3430 if (pending_time_axis.first == 0) {
3434 framepos_t const pending_position = adjusted_current_frame (event);
3436 /* only alter selection if things have changed */
3438 if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
3442 switch (_operation) {
3443 case CreateSelection:
3445 framepos_t grab = grab_frame ();
3448 _editor->snap_to (grab);
3451 if (pending_position < grab_frame()) {
3452 start = pending_position;
3455 end = pending_position;
3459 /* first drag: Either add to the selection
3460 or create a new selection
3466 /* adding to the selection */
3467 _editor->set_selected_track_as_side_effect (Selection::Add);
3468 //_editor->selection->add (_editor->clicked_axisview);
3469 _editor->clicked_selection = _editor->selection->add (start, end);
3474 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3475 //_editor->selection->set (_editor->clicked_axisview);
3476 _editor->set_selected_track_as_side_effect (Selection::Set);
3479 _editor->clicked_selection = _editor->selection->set (start, end);
3483 /* select the track that we're in */
3484 if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
3485 // _editor->set_selected_track_as_side_effect (Selection::Add);
3486 _editor->selection->add (pending_time_axis.first);
3487 _added_time_axes.push_back (pending_time_axis.first);
3490 /* deselect any tracks that this drag no longer includes, being careful to only deselect
3491 tracks that we selected in the first place.
3494 int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
3495 int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
3497 list<TimeAxisView*>::iterator i = _added_time_axes.begin();
3498 while (i != _added_time_axes.end()) {
3500 list<TimeAxisView*>::iterator tmp = i;
3503 if ((*i)->order() < min_order || (*i)->order() > max_order) {
3504 _editor->selection->remove (*i);
3505 _added_time_axes.remove (*i);
3514 case SelectionStartTrim:
3516 start = _editor->selection->time[_editor->clicked_selection].start;
3517 end = _editor->selection->time[_editor->clicked_selection].end;
3519 if (pending_position > end) {
3522 start = pending_position;
3526 case SelectionEndTrim:
3528 start = _editor->selection->time[_editor->clicked_selection].start;
3529 end = _editor->selection->time[_editor->clicked_selection].end;
3531 if (pending_position < start) {
3534 end = pending_position;
3541 start = _editor->selection->time[_editor->clicked_selection].start;
3542 end = _editor->selection->time[_editor->clicked_selection].end;
3544 length = end - start;
3546 start = pending_position;
3547 _editor->snap_to (start);
3549 end = start + length;
3554 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3555 _editor->start_canvas_autoscroll (1, 0);
3559 _editor->selection->replace (_editor->clicked_selection, start, end);
3562 if (_operation == SelectionMove) {
3563 show_verbose_cursor_time(start);
3565 show_verbose_cursor_time(pending_position);
3570 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
3572 Session* s = _editor->session();
3574 if (movement_occurred) {
3575 motion (event, false);
3576 /* XXX this is not object-oriented programming at all. ick */
3577 if (_editor->selection->time.consolidate()) {
3578 _editor->selection->TimeChanged ();
3581 /* XXX what if its a music time selection? */
3583 if ((s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
3584 s->request_play_range (&_editor->selection->time, true);
3586 if (Config->get_always_play_range()) {
3587 if (_editor->doing_range_stuff()) {
3588 s->request_locate (_editor->get_selection().time.start());
3595 /* just a click, no pointer movement.
3598 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
3599 if (!_time_selection_at_start) {
3600 if (_editor->clicked_regionview) {
3601 if (_editor->get_selection().selected (_editor->clicked_regionview)) {
3602 /* range select the entire current
3605 _editor->select_range (_editor->get_selection().regions.start(),
3606 _editor->get_selection().regions.end_frame());
3608 /* range select this (unselected)
3611 _editor->select_range (_editor->clicked_regionview->region()->position(),
3612 _editor->clicked_regionview->region()->last_frame());
3616 _editor->selection->clear_time();
3620 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3621 _editor->selection->set (_editor->clicked_axisview);
3624 if (s && s->get_play_range () && s->transport_rolling()) {
3625 s->request_stop (false, false);
3628 if (Config->get_always_play_range()) {
3629 if (_editor->doing_range_stuff()) {
3630 s->request_locate (_editor->get_selection().time.start());
3635 _editor->stop_canvas_autoscroll ();
3639 SelectionDrag::aborted (bool)
3644 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3649 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
3651 _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
3652 physical_screen_height (_editor->get_window()));
3653 _drag_rect->hide ();
3655 _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3656 _drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3660 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3662 if (_editor->session() == 0) {
3666 Gdk::Cursor* cursor = 0;
3668 if (!_editor->temp_location) {
3669 _editor->temp_location = new Location (*_editor->session());
3672 switch (_operation) {
3673 case CreateRangeMarker:
3674 case CreateTransportMarker:
3675 case CreateCDMarker:
3677 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3682 cursor = _editor->cursors()->selector;
3686 Drag::start_grab (event, cursor);
3688 show_verbose_cursor_time (adjusted_current_frame (event));
3692 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
3694 framepos_t start = 0;
3696 ArdourCanvas::SimpleRect *crect;
3698 switch (_operation) {
3699 case CreateRangeMarker:
3700 crect = _editor->range_bar_drag_rect;
3702 case CreateTransportMarker:
3703 crect = _editor->transport_bar_drag_rect;
3705 case CreateCDMarker:
3706 crect = _editor->cd_marker_bar_drag_rect;
3709 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
3714 framepos_t const pf = adjusted_current_frame (event);
3716 if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
3717 framepos_t grab = grab_frame ();
3718 _editor->snap_to (grab);
3720 if (pf < grab_frame()) {
3728 /* first drag: Either add to the selection
3729 or create a new selection.
3734 _editor->temp_location->set (start, end);
3738 update_item (_editor->temp_location);
3740 //_drag_rect->raise_to_top();
3745 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3746 _editor->start_canvas_autoscroll (1, 0);
3750 _editor->temp_location->set (start, end);
3752 double x1 = _editor->frame_to_pixel (start);
3753 double x2 = _editor->frame_to_pixel (end);
3754 crect->property_x1() = x1;
3755 crect->property_x2() = x2;
3757 update_item (_editor->temp_location);
3760 show_verbose_cursor_time (pf);
3765 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
3767 Location * newloc = 0;
3771 if (movement_occurred) {
3772 motion (event, false);
3775 switch (_operation) {
3776 case CreateRangeMarker:
3777 case CreateCDMarker:
3779 _editor->begin_reversible_command (_("new range marker"));
3780 XMLNode &before = _editor->session()->locations()->get_state();
3781 _editor->session()->locations()->next_available_name(rangename,"unnamed");
3782 if (_operation == CreateCDMarker) {
3783 flags = Location::IsRangeMarker | Location::IsCDMarker;
3784 _editor->cd_marker_bar_drag_rect->hide();
3787 flags = Location::IsRangeMarker;
3788 _editor->range_bar_drag_rect->hide();
3790 newloc = new Location (
3791 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
3794 _editor->session()->locations()->add (newloc, true);
3795 XMLNode &after = _editor->session()->locations()->get_state();
3796 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3797 _editor->commit_reversible_command ();
3801 case CreateTransportMarker:
3802 // popup menu to pick loop or punch
3803 _editor->new_transport_marker_context_menu (&event->button, _item);
3807 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
3809 if (Keyboard::no_modifier_keys_pressed (&event->button) && _operation != CreateCDMarker) {
3814 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
3816 if (end == max_framepos) {
3817 end = _editor->session()->current_end_frame ();
3820 if (start == max_framepos) {
3821 start = _editor->session()->current_start_frame ();
3824 switch (_editor->mouse_mode) {
3826 /* find the two markers on either side and then make the selection from it */
3827 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
3831 /* find the two markers on either side of the click and make the range out of it */
3832 _editor->selection->set (start, end);
3841 _editor->stop_canvas_autoscroll ();
3845 RangeMarkerBarDrag::aborted (bool)
3851 RangeMarkerBarDrag::update_item (Location* location)
3853 double const x1 = _editor->frame_to_pixel (location->start());
3854 double const x2 = _editor->frame_to_pixel (location->end());
3856 _drag_rect->property_x1() = x1;
3857 _drag_rect->property_x2() = x2;
3860 MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i)
3864 DEBUG_TRACE (DEBUG::Drags, "New MouseZoomDrag\n");
3868 MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3870 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
3871 Drag::start_grab (event, _editor->cursors()->zoom_out);
3874 Drag::start_grab (event, _editor->cursors()->zoom_in);
3878 show_verbose_cursor_time (adjusted_current_frame (event));
3882 MouseZoomDrag::motion (GdkEvent* event, bool first_move)
3887 framepos_t const pf = adjusted_current_frame (event);
3889 framepos_t grab = grab_frame ();
3890 _editor->snap_to_with_modifier (grab, event);
3892 /* base start and end on initial click position */
3904 _editor->zoom_rect->show();
3905 _editor->zoom_rect->raise_to_top();
3908 _editor->reposition_zoom_rect(start, end);
3910 show_verbose_cursor_time (pf);
3915 MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
3917 if (movement_occurred) {
3918 motion (event, false);
3920 if (grab_frame() < last_pointer_frame()) {
3921 _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame());
3923 _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame());
3926 if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
3927 _editor->tav_zoom_step (_zoom_out);
3929 _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
3933 _editor->zoom_rect->hide();
3937 MouseZoomDrag::aborted (bool)
3939 _editor->zoom_rect->hide ();
3942 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
3944 , _cumulative_dx (0)
3945 , _cumulative_dy (0)
3947 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
3949 _primary = dynamic_cast<CanvasNoteEvent*> (_item);
3950 _region = &_primary->region_view ();
3951 _note_height = _region->midi_stream_view()->note_height ();
3955 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3957 Drag::start_grab (event);
3959 if (!(_was_selected = _primary->selected())) {
3961 /* tertiary-click means extend selection - we'll do that on button release,
3962 so don't add it here, because otherwise we make it hard to figure
3963 out the "extend-to" range.
3966 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
3969 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
3972 _region->note_selected (_primary, true);
3974 _region->unique_select (_primary);
3980 /** @return Current total drag x change in frames */
3982 NoteDrag::total_dx () const
3985 frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
3987 /* primary note time */
3988 frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
3990 /* new time of the primary note in session frames */
3991 frameoffset_t st = n + dx;
3993 framepos_t const rp = _region->region()->position ();
3995 /* prevent the note being dragged earlier than the region's position */
3998 /* snap and return corresponding delta */
3999 return _region->snap_frame_to_frame (st - rp) + rp - n;
4002 /** @return Current total drag y change in note number */
4004 NoteDrag::total_dy () const
4006 return ((int8_t) (grab_y() / _note_height)) - ((int8_t) (_drags->current_pointer_y() / _note_height));
4010 NoteDrag::motion (GdkEvent *, bool)
4012 /* Total change in x and y since the start of the drag */
4013 frameoffset_t const dx = total_dx ();
4014 int8_t const dy = total_dy ();
4016 /* Now work out what we have to do to the note canvas items to set this new drag delta */
4017 double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx;
4018 double const tdy = -dy * _note_height - _cumulative_dy;
4021 _cumulative_dx += tdx;
4022 _cumulative_dy += tdy;
4024 int8_t note_delta = total_dy();
4026 _region->move_selection (tdx, tdy, note_delta);
4028 /* the new note value may be the same as the old one, but we
4029 * don't know what that means because the selection may have
4030 * involved more than one note and we might be doing something
4031 * odd with them. so show the note value anyway, always.
4035 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
4037 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
4038 (int) floor (new_note));
4040 show_verbose_cursor_text (buf);
4045 NoteDrag::finished (GdkEvent* ev, bool moved)
4048 /* no motion - select note */
4050 if (_editor->current_mouse_mode() == Editing::MouseObject ||
4051 _editor->current_mouse_mode() == Editing::MouseDraw) {
4053 if (_was_selected) {
4054 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
4056 _region->note_deselected (_primary);
4059 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
4060 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
4062 if (!extend && !add && _region->selection_size() > 1) {
4063 _region->unique_select (_primary);
4064 } else if (extend) {
4065 _region->note_selected (_primary, true, true);
4067 /* it was added during button press */
4072 _region->note_dropped (_primary, total_dx(), total_dy());
4077 NoteDrag::aborted (bool)
4082 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
4083 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
4084 : Drag (editor, atv->base_item ())
4086 , _nothing_to_drag (false)
4088 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
4090 setup (atv->lines ());
4093 /** Make an AutomationRangeDrag for region gain lines */
4094 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AudioRegionView* rv, list<AudioRange> const & r)
4095 : Drag (editor, rv->get_canvas_group ())
4097 , _nothing_to_drag (false)
4099 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
4101 list<boost::shared_ptr<AutomationLine> > lines;
4102 lines.push_back (rv->get_gain_line ());
4106 /** @param lines AutomationLines to drag.
4107 * @param offset Offset from the session start to the points in the AutomationLines.
4110 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
4112 /* find the lines that overlap the ranges being dragged */
4113 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
4114 while (i != lines.end ()) {
4115 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
4118 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
4120 /* check this range against all the AudioRanges that we are using */
4121 list<AudioRange>::const_iterator k = _ranges.begin ();
4122 while (k != _ranges.end()) {
4123 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
4129 /* add it to our list if it overlaps at all */
4130 if (k != _ranges.end()) {
4135 _lines.push_back (n);
4141 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
4145 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4147 Drag::start_grab (event, cursor);
4149 /* Get line states before we start changing things */
4150 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4151 i->state = &i->line->get_state ();
4154 if (_ranges.empty()) {
4156 /* No selected time ranges: drag all points */
4157 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4158 uint32_t const N = i->line->npoints ();
4159 for (uint32_t j = 0; j < N; ++j) {
4160 i->points.push_back (i->line->nth (j));
4166 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
4168 framecnt_t const half = (i->start + i->end) / 2;
4170 /* find the line that this audio range starts in */
4171 list<Line>::iterator j = _lines.begin();
4172 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
4176 if (j != _lines.end()) {
4177 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4179 /* j is the line that this audio range starts in; fade into it;
4180 64 samples length plucked out of thin air.
4183 framepos_t a = i->start + 64;
4188 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
4189 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
4191 the_list->add (p, the_list->eval (p));
4192 the_list->add (q, the_list->eval (q));
4195 /* same thing for the end */
4198 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
4202 if (j != _lines.end()) {
4203 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4205 /* j is the line that this audio range starts in; fade out of it;
4206 64 samples length plucked out of thin air.
4209 framepos_t b = i->end - 64;
4214 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
4215 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
4217 the_list->add (p, the_list->eval (p));
4218 the_list->add (q, the_list->eval (q));
4222 _nothing_to_drag = true;
4224 /* Find all the points that should be dragged and put them in the relevant
4225 points lists in the Line structs.
4228 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4230 uint32_t const N = i->line->npoints ();
4231 for (uint32_t j = 0; j < N; ++j) {
4233 /* here's a control point on this line */
4234 ControlPoint* p = i->line->nth (j);
4235 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
4237 /* see if it's inside a range */
4238 list<AudioRange>::const_iterator k = _ranges.begin ();
4239 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
4243 if (k != _ranges.end()) {
4244 /* dragging this point */
4245 _nothing_to_drag = false;
4246 i->points.push_back (p);
4252 if (_nothing_to_drag) {
4256 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4257 i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state);
4262 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
4264 if (_nothing_to_drag) {
4268 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4269 float const f = 1 - (_drags->current_pointer_y() / i->line->height());
4271 /* we are ignoring x position for this drag, so we can just pass in anything */
4272 i->line->drag_motion (0, f, true, false);
4277 AutomationRangeDrag::finished (GdkEvent* event, bool)
4279 if (_nothing_to_drag) {
4283 motion (event, false);
4284 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4285 i->line->end_drag ();
4288 _editor->session()->commit_reversible_command ();
4292 AutomationRangeDrag::aborted (bool)
4294 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4299 DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
4302 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
4303 layer = v->region()->layer ();
4304 initial_y = v->get_canvas_group()->property_y ();
4305 initial_playlist = v->region()->playlist ();
4306 initial_position = v->region()->position ();
4307 initial_end = v->region()->position () + v->region()->length ();
4310 PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
4314 , _cumulative_dx (0)
4316 DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
4320 PatchChangeDrag::motion (GdkEvent* ev, bool)
4322 framepos_t f = adjusted_current_frame (ev);
4323 boost::shared_ptr<Region> r = _region_view->region ();
4324 f = max (f, r->position ());
4325 f = min (f, r->last_frame ());
4327 framecnt_t const dxf = f - grab_frame();
4328 double const dxu = _editor->frame_to_unit (dxf);
4329 _patch_change->move (dxu - _cumulative_dx, 0);
4330 _cumulative_dx = dxu;
4334 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
4336 if (!movement_occurred) {
4340 boost::shared_ptr<Region> r (_region_view->region ());
4342 framepos_t f = adjusted_current_frame (ev);
4343 f = max (f, r->position ());
4344 f = min (f, r->last_frame ());
4346 _region_view->move_patch_change (
4348 _region_view->region_frames_to_region_beats (f - r->position() - r->start())
4353 PatchChangeDrag::aborted (bool)
4355 _patch_change->move (-_cumulative_dx, 0);
4359 PatchChangeDrag::setup_pointer_frame_offset ()
4361 boost::shared_ptr<Region> region = _region_view->region ();
4362 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
4365 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
4366 : RubberbandSelectDrag (e, rv->get_canvas_frame ())
4373 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
4375 framepos_t const p = _region_view->region()->position ();
4376 double const y = _region_view->midi_view()->y_position ();
4378 x1 = max ((framepos_t) 0, x1 - p);
4379 x2 = max ((framepos_t) 0, x2 - p);
4380 y1 = max (0.0, y1 - y);
4381 y2 = max (0.0, y2 - y);
4383 _region_view->update_drag_selection (
4384 _editor->frame_to_pixel (x1),
4385 _editor->frame_to_pixel (x2),
4388 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
4393 MidiRubberbandSelectDrag::deselect_things ()
4398 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
4399 : RubberbandSelectDrag (e, rv->get_canvas_frame ())
4402 _vertical_only = true;
4406 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
4408 double const y = _region_view->midi_view()->y_position ();
4410 y1 = max (0.0, y1 - y);
4411 y2 = max (0.0, y2 - y);
4413 _region_view->update_vertical_drag_selection (
4416 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
4421 MidiVerticalSelectDrag::deselect_things ()
4426 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4427 : RubberbandSelectDrag (e, i)
4433 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
4435 if (drag_in_progress) {
4436 /* We just want to select things at the end of the drag, not during it */
4440 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
4442 _editor->begin_reversible_command (_("rubberband selection"));
4443 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
4444 _editor->commit_reversible_command ();
4448 EditorRubberbandSelectDrag::deselect_things ()
4450 if (!getenv("ARDOUR_SAE")) {
4451 _editor->selection->clear_tracks();
4453 _editor->selection->clear_regions();
4454 _editor->selection->clear_points ();
4455 _editor->selection->clear_lines ();
4458 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
4466 NoteCreateDrag::~NoteCreateDrag ()
4472 NoteCreateDrag::grid_frames (framepos_t t) const
4475 Evoral::MusicalTime grid_beats = _editor->get_grid_type_as_beats (success, t);
4480 return _region_view->region_beats_to_region_frames (grid_beats);
4484 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4486 Drag::start_grab (event, cursor);
4488 _drag_rect = new ArdourCanvas::SimpleRect (*_region_view->get_canvas_group ());
4490 framepos_t pf = _drags->current_pointer_frame ();
4491 framecnt_t const g = grid_frames (pf);
4493 /* Hack so that we always snap to the note that we are over, instead of snapping
4494 to the next one if we're more than halfway through the one we're over.
4496 if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
4500 _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
4502 MidiStreamView* sv = _region_view->midi_stream_view ();
4503 double const x = _editor->frame_to_pixel (_note[0]);
4504 double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
4506 _drag_rect->property_x1() = x;
4507 _drag_rect->property_y1() = y;
4508 _drag_rect->property_x2() = x;
4509 _drag_rect->property_y2() = y + floor (_region_view->midi_stream_view()->note_height ());
4511 _drag_rect->property_outline_what() = 0xff;
4512 _drag_rect->property_outline_color_rgba() = 0xffffff99;
4513 _drag_rect->property_fill_color_rgba() = 0xffffff66;
4517 NoteCreateDrag::motion (GdkEvent* event, bool)
4519 _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
4520 double const x = _editor->frame_to_pixel (_note[1]);
4521 if (_note[1] > _note[0]) {
4522 _drag_rect->property_x2() = x;
4524 _drag_rect->property_x1() = x;
4529 NoteCreateDrag::finished (GdkEvent*, bool had_movement)
4531 if (!had_movement) {
4535 framepos_t const start = min (_note[0], _note[1]);
4536 framecnt_t length = abs (_note[0] - _note[1]);
4538 framecnt_t const g = grid_frames (start);
4539 double const one_tick = 1 / Timecode::BBT_Time::ticks_per_beat;
4541 if (_editor->snap_mode() == SnapNormal && length < g) {
4542 length = g - one_tick;
4545 double const length_beats = max (one_tick, _region_view->region_frames_to_region_beats (length));
4547 _region_view->create_note_at (start, _drag_rect->property_y1(), length_beats, false);
4551 NoteCreateDrag::y_to_region (double y) const
4554 _region_view->get_canvas_group()->w2i (x, y);
4559 NoteCreateDrag::aborted (bool)
4566 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
4574 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
4576 Drag::start_grab (event, cursor);
4580 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
4586 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
4589 distance = _drags->current_pointer_x() - grab_x();
4590 len = ar->fade_in()->back()->when;
4592 distance = grab_x() - _drags->current_pointer_x();
4593 len = ar->fade_out()->back()->when;
4596 /* how long should it be ? */
4598 new_length = len + _editor->unit_to_frame (distance);
4600 /* now check with the region that this is legal */
4602 new_length = ar->verify_xfade_bounds (new_length, start);
4605 arv->redraw_start_xfade_to (ar, new_length);
4607 arv->redraw_end_xfade_to (ar, new_length);
4612 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
4618 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
4621 distance = _drags->current_pointer_x() - grab_x();
4622 len = ar->fade_in()->back()->when;
4624 distance = grab_x() - _drags->current_pointer_x();
4625 len = ar->fade_out()->back()->when;
4628 new_length = ar->verify_xfade_bounds (len + _editor->unit_to_frame (distance), start);
4630 _editor->begin_reversible_command ("xfade trim");
4631 ar->playlist()->clear_owned_changes ();
4634 ar->set_fade_in_length (new_length);
4636 ar->set_fade_out_length (new_length);
4639 /* Adjusting the xfade may affect other regions in the playlist, so we need
4640 to get undo Commands from the whole playlist rather than just the
4644 vector<Command*> cmds;
4645 ar->playlist()->rdiff (cmds);
4646 _editor->session()->add_commands (cmds);
4651 CrossfadeEdgeDrag::aborted (bool)
4654 arv->redraw_start_xfade ();
4656 arv->redraw_end_xfade ();