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"
27 #include "pbd/memento_command.h"
28 #include "pbd/basename.h"
29 #include "pbd/stateful_diff_command.h"
31 #include "gtkmm2ext/utils.h"
33 #include "ardour/audioengine.h"
34 #include "ardour/audioregion.h"
35 #include "ardour/audio_track.h"
36 #include "ardour/dB.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_track.h"
39 #include "ardour/operations.h"
40 #include "ardour/region_factory.h"
41 #include "ardour/session.h"
43 #include "canvas/canvas.h"
44 #include "canvas/scroll_group.h"
49 #include "audio_region_view.h"
50 #include "automation_region_view.h"
51 #include "midi_region_view.h"
52 #include "ardour_ui.h"
53 #include "gui_thread.h"
54 #include "control_point.h"
55 #include "region_gain_line.h"
56 #include "editor_drag.h"
57 #include "audio_time_axis.h"
58 #include "midi_time_axis.h"
59 #include "selection.h"
60 #include "midi_selection.h"
61 #include "automation_time_axis.h"
63 #include "editor_cursors.h"
64 #include "mouse_cursors.h"
65 #include "note_base.h"
66 #include "patch_change.h"
67 #include "verbose_cursor.h"
70 using namespace ARDOUR;
73 using namespace Gtkmm2ext;
74 using namespace Editing;
75 using namespace ArdourCanvas;
77 using Gtkmm2ext::Keyboard;
79 double ControlPointDrag::_zero_gain_fraction = -1.0;
81 DragManager::DragManager (Editor* e)
84 , _current_pointer_frame (0)
88 DragManager::~DragManager ()
93 /** Call abort for each active drag */
99 cerr << "Aborting drag\n";
101 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
106 if (!_drags.empty ()) {
107 _editor->set_follow_playhead (_old_follow_playhead, false);
111 _editor->abort_reversible_command();
117 DragManager::add (Drag* d)
119 d->set_manager (this);
120 _drags.push_back (d);
124 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
126 d->set_manager (this);
127 _drags.push_back (d);
132 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
134 /* Prevent follow playhead during the drag to be nice to the user */
135 _old_follow_playhead = _editor->follow_playhead ();
136 _editor->set_follow_playhead (false);
138 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
140 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
141 (*i)->start_grab (e, c);
145 /** Call end_grab for each active drag.
146 * @return true if any drag reported movement having occurred.
149 DragManager::end_grab (GdkEvent* e)
154 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
155 bool const t = (*i)->end_grab (e);
166 _editor->set_follow_playhead (_old_follow_playhead, false);
172 DragManager::mark_double_click ()
174 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
175 (*i)->set_double_click (true);
180 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
184 /* calling this implies that we expect the event to have canvas
187 * Can we guarantee that this is true?
190 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
192 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
193 bool const t = (*i)->motion_handler (e, from_autoscroll);
194 /* run all handlers; return true if at least one of them
195 returns true (indicating that the event has been handled).
207 DragManager::have_item (ArdourCanvas::Item* i) const
209 list<Drag*>::const_iterator j = _drags.begin ();
210 while (j != _drags.end() && (*j)->item () != i) {
214 return j != _drags.end ();
217 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
220 , _pointer_frame_offset (0)
221 , _trackview_only (trackview_only)
222 , _move_threshold_passed (false)
223 , _starting_point_passed (false)
224 , _initially_vertical (false)
225 , _was_double_click (false)
226 , _raw_grab_frame (0)
228 , _last_pointer_frame (0)
234 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
240 _cursor_ctx = CursorContext::create (*_editor, cursor);
242 _cursor_ctx->change (cursor);
249 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
251 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
253 if (Keyboard::is_button2_event (&event->button)) {
254 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
255 _y_constrained = true;
256 _x_constrained = false;
258 _y_constrained = false;
259 _x_constrained = true;
262 _x_constrained = false;
263 _y_constrained = false;
266 _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
267 setup_pointer_frame_offset ();
268 _grab_frame = adjusted_frame (_raw_grab_frame, event);
269 _last_pointer_frame = _grab_frame;
270 _last_pointer_x = _grab_x;
272 if (_trackview_only) {
273 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
276 _last_pointer_y = _grab_y;
280 if (!_editor->cursors()->is_invalid (cursor)) {
281 /* CAIROCANVAS need a variant here that passes *cursor */
282 _cursor_ctx = CursorContext::create (*_editor, cursor);
285 if (_editor->session() && _editor->session()->transport_rolling()) {
288 _was_rolling = false;
291 switch (_editor->snap_type()) {
292 case SnapToRegionStart:
293 case SnapToRegionEnd:
294 case SnapToRegionSync:
295 case SnapToRegionBoundary:
296 _editor->build_region_boundary_cache ();
303 /** Call to end a drag `successfully'. Ungrabs item and calls
304 * subclass' finished() method.
306 * @param event GDK event, or 0.
307 * @return true if some movement occurred, otherwise false.
310 Drag::end_grab (GdkEvent* event)
312 _editor->stop_canvas_autoscroll ();
316 finished (event, _move_threshold_passed);
318 _editor->verbose_cursor()->hide ();
321 return _move_threshold_passed;
325 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
329 if (f > _pointer_frame_offset) {
330 pos = f - _pointer_frame_offset;
334 _editor->snap_to_with_modifier (pos, event);
341 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
343 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
347 Drag::current_pointer_x() const
349 return _drags->current_pointer_x ();
353 Drag::current_pointer_y () const
355 if (!_trackview_only) {
356 return _drags->current_pointer_y ();
359 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
363 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
365 /* check to see if we have moved in any way that matters since the last motion event */
366 if (_move_threshold_passed &&
367 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
368 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
372 pair<framecnt_t, int> const threshold = move_threshold ();
374 bool const old_move_threshold_passed = _move_threshold_passed;
376 if (!_move_threshold_passed) {
378 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
379 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
381 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
384 if (active (_editor->mouse_mode) && _move_threshold_passed) {
386 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
388 if (old_move_threshold_passed != _move_threshold_passed) {
392 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
393 _initially_vertical = true;
395 _initially_vertical = false;
399 if (!from_autoscroll) {
400 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
403 if (!_editor->autoscroll_active() || from_autoscroll) {
406 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
408 motion (event, first_move && !_starting_point_passed);
410 if (first_move && !_starting_point_passed) {
411 _starting_point_passed = true;
414 _last_pointer_x = _drags->current_pointer_x ();
415 _last_pointer_y = current_pointer_y ();
416 _last_pointer_frame = adjusted_current_frame (event);
426 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
434 aborted (_move_threshold_passed);
436 _editor->stop_canvas_autoscroll ();
437 _editor->verbose_cursor()->hide ();
441 Drag::show_verbose_cursor_time (framepos_t frame)
443 _editor->verbose_cursor()->set_time (frame);
444 _editor->verbose_cursor()->show ();
448 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
450 _editor->verbose_cursor()->set_duration (start, end);
451 _editor->verbose_cursor()->show ();
455 Drag::show_verbose_cursor_text (string const & text)
457 _editor->verbose_cursor()->set (text);
458 _editor->verbose_cursor()->show ();
461 boost::shared_ptr<Region>
462 Drag::add_midi_region (MidiTimeAxisView* view)
464 if (_editor->session()) {
465 const TempoMap& map (_editor->session()->tempo_map());
466 framecnt_t pos = grab_frame();
467 const Meter& m = map.meter_at (pos);
468 /* not that the frame rate used here can be affected by pull up/down which
471 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
472 return view->add_region (grab_frame(), len, true);
475 return boost::shared_ptr<Region>();
478 struct EditorOrderTimeAxisViewSorter {
479 bool operator() (TimeAxisView* a, TimeAxisView* b) {
480 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
481 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
483 return ra->route()->order_key () < rb->route()->order_key ();
487 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
491 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
493 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
494 as some of the regions we are dragging may be on such tracks.
497 TrackViewList track_views = _editor->track_views;
498 track_views.sort (EditorOrderTimeAxisViewSorter ());
500 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
501 _time_axis_views.push_back (*i);
503 TimeAxisView::Children children_list = (*i)->get_child_list ();
504 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
505 _time_axis_views.push_back (j->get());
509 /* the list of views can be empty at this point if this is a region list-insert drag
512 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
513 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
516 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
520 RegionDrag::region_going_away (RegionView* v)
522 list<DraggingView>::iterator i = _views.begin ();
523 while (i != _views.end() && i->view != v) {
527 if (i != _views.end()) {
532 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
533 * or -1 if it is not found.
536 RegionDrag::find_time_axis_view (TimeAxisView* t) const
539 int const N = _time_axis_views.size ();
540 while (i < N && _time_axis_views[i] != t) {
544 if (_time_axis_views[i] != t) {
551 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
552 : RegionDrag (e, i, p, v)
555 , _last_pointer_time_axis_view (0)
556 , _last_pointer_layer (0)
557 , _single_axis (false)
559 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
563 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
565 Drag::start_grab (event, cursor);
567 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
571 show_verbose_cursor_time (_last_frame_position);
573 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
575 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
576 assert(_last_pointer_time_axis_view >= 0);
577 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
582 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
584 /* compute the amount of pointer motion in frames, and where
585 the region would be if we moved it by that much.
587 *pending_region_position = adjusted_current_frame (event);
589 framepos_t sync_frame;
590 framecnt_t sync_offset;
593 sync_offset = _primary->region()->sync_offset (sync_dir);
595 /* we don't handle a sync point that lies before zero.
597 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
599 sync_frame = *pending_region_position + (sync_dir*sync_offset);
601 _editor->snap_to_with_modifier (sync_frame, event);
603 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
606 *pending_region_position = _last_frame_position;
609 if (*pending_region_position > max_framepos - _primary->region()->length()) {
610 *pending_region_position = _last_frame_position;
615 /* in locked edit mode, reverse the usual meaning of _x_constrained */
616 bool const x_move_allowed = Config->get_edit_mode() == Lock ? _x_constrained : !_x_constrained;
618 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
620 /* x movement since last time (in pixels) */
621 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
623 /* total x movement */
624 framecnt_t total_dx = *pending_region_position;
625 if (regions_came_from_canvas()) {
626 total_dx = total_dx - grab_frame ();
629 /* check that no regions have gone off the start of the session */
630 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
631 if ((i->view->region()->position() + total_dx) < 0) {
633 *pending_region_position = _last_frame_position;
644 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
650 const int dt = delta > 0 ? +1 : -1;
652 int target = start + delta - skip;
654 assert (current < 0 || current >= _time_axis_views.size() || !_time_axis_views[current]->hidden());
655 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
657 #ifdef DEBUG_DROPZONEDRAG
658 if (current >= _time_axis_views.size() && target >= 0 && target < _time_axis_views.size()) {
659 printf("MOVE OUT OF THE ZONE cur: %d d: %d s: %d\n", start, delta, skip);
661 printf("CALC DISTANCE cur: %d d: %d s: %d\n", start, delta, skip);
665 while (current >= 0 && current != target) {
667 if (current < 0 && dt < 0) {
668 #ifdef DEBUG_DROPZONEDRAG
669 printf("BREAK AT BOTTOM\n");
673 if (current >= _time_axis_views.size() && dt > 0) {
674 #ifdef DEBUG_DROPZONEDRAG
675 printf("BREAK AT TOP\n");
679 if (current < 0 || current >= _time_axis_views.size()) {
683 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
684 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
688 if (distance_only && current == start + delta) {
689 #ifdef DEBUG_DROPZONEDRAG
690 printf("BREAK AFTER DISTANCE\n");
699 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
701 if (_y_constrained) {
705 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
706 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
707 #ifdef DEBUG_DROPZONEDRAG
708 printf("Y MOVEMENT CHECK: from %d to %d skip: %d\n",
709 i->time_axis_view, i->time_axis_view + delta_track,
712 assert (n < 0 || n >= _time_axis_views.size() || !_time_axis_views[n]->hidden());
714 if (i->time_axis_view < 0 || i->time_axis_view >= _time_axis_views.size()) {
715 /* already in the drop zone */
716 if (delta_track >= 0) {
717 /* downward motion - might be OK if others are still not in the dropzone,
718 so check at the end of the loop if that is the case.
723 /* upward motion - set n to the track we would end up in if motion
724 is successful, and check validity below. */
725 n = _time_axis_views.size() + delta_track;
731 } else if (n >= int (_time_axis_views.size())) {
732 /* downward motion into drop zone. That's fine. */
736 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
737 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
738 /* not a track, or the wrong type */
742 double const l = i->layer + delta_layer;
744 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
745 mode to allow the user to place a region below another on layer 0.
747 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
748 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
749 If it has, the layers will be munged later anyway, so it's ok.
755 /* all regions being dragged are ok with this change */
759 struct DraggingViewSorter {
760 bool operator() (const DraggingView& a, const DraggingView& b) {
761 return a.time_axis_view < b.time_axis_view;
766 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
768 double delta_layer = 0;
769 int delta_time_axis_view = 0;
770 int current_pointer_time_axis_view = -1;
772 assert (!_views.empty ());
776 if (initially_vertical()) {
777 _y_constrained = false;
778 _x_constrained = true;
780 _y_constrained = true;
781 _x_constrained = false;
786 #ifdef DEBUG_DROPZONEDRAG
787 printf("--------- LAST AXIS: %d\n", _last_pointer_time_axis_view);
789 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
791 /* Find the TimeAxisView that the pointer is now over */
793 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (current_pointer_y ());
794 TimeAxisView* tv = r.first;
796 if (!tv && current_pointer_y() < 0) {
797 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
801 if (tv && tv->view()) {
802 double layer = r.second;
804 if (first_move && tv->view()->layer_display() == Stacked) {
805 tv->view()->set_layer_display (Expanded);
808 /* Here's the current pointer position in terms of time axis view and layer */
809 current_pointer_time_axis_view = find_time_axis_view (tv);
810 assert(current_pointer_time_axis_view >= 0);
811 #ifdef DEBUG_DROPZONEDRAG
812 printf(" On AXIS: %d\n", current_pointer_time_axis_view);
815 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
817 /* Work out the change in y */
819 if (_last_pointer_time_axis_view < 0) {
820 /* Was in the drop-zone, now over a track.
821 * Hence it must be an upward move (from the bottom)
823 * track_index is still -1, so delta must be set to
824 * move up the correct number of tracks from the bottom.
826 * This is necessary because steps may be skipped if
827 * the bottom-most track is not a valid target,
829 #ifdef DEBUG_DROPZONEDRAG
830 printf("MOVE OUT OF THE ZONE...\n");
832 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size ();
834 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
837 /* TODO needs adjustment per DraggingView,
839 * e.g. select one region on the top-layer of a track
840 * and one region which is at the bottom-layer of another track
843 * Indicated drop-zones and layering is wrong.
844 * and may infer additional layers on the target-track
845 * (depending how many layers the original track had).
847 * Or select two regions (different layers) on a same track,
848 * move across a non-layer track.. -> layering info is lost.
849 * on drop either of the regions may be on top.
851 * Proposed solution: screw it :) well,
852 * don't use delta_layer, use an absolute value
853 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
854 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
855 * 3) iterate over all DraggingView, find the one that is over the track with most layers
856 * 4) proportionally scale layer to layers available on target
858 delta_layer = current_pointer_layer - _last_pointer_layer;
861 /* for automation lanes, there is a TimeAxisView but no ->view() */
862 else if (!tv && current_pointer_y() >= 0 && _last_pointer_time_axis_view >= 0) {
863 /* Moving into the drop-zone..
865 * TODO allow moving further down in drop-zone:
866 * e.g. 2 Tracks, select a region on both of them.
868 * A) grab the upper, drag 2 down, both regions are in the dropzone: all fine (works)
870 * B) grab the lower, drag 1 down, region (and mouse) are in dropzone, The End.
871 * upper region is only down one track and cannot be moved into the zone.
874 * keep track of how many regions are in the DZ (private var),
875 * also count from how many tracks the dragged-regions come from (first move)
877 * if not all regions are in the DZ, keep going.
879 * Using 'default height' H for all dropzone regions will make things
880 * a lot simpler: (number_of_DZ_entries * H + Pointer_YPOS - DZ_YPOS) / H.
881 * (because at this point in time PlaylistDropzoneMap is not yet populated)
883 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
884 #ifdef DEBUG_DROPZONEDRAG
885 printf("INTO THE ZONE DELTA: %d\n", delta_time_axis_view);
889 /* Work out the change in x */
890 framepos_t pending_region_position;
891 double const x_delta = compute_x_delta (event, &pending_region_position);
892 _last_frame_position = pending_region_position;
894 /* calculate hidden tracks in current delta */
896 if (_last_pointer_time_axis_view < 0) {
897 // Moving out of the zone, check for hidden tracks at the bottom.
898 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
899 -_time_axis_views.size() - delta_time_axis_view;
900 #ifdef DEBUG_DROPZONEDRAG
901 printf("NOW WHAT?? last: %d delta %d || skip %d\n", _last_pointer_time_axis_view, delta_time_axis_view, delta_skip);
904 // calculate hidden tracks that are skipped by the pointer movement
905 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
906 - _last_pointer_time_axis_view
907 - delta_time_axis_view;
908 #ifdef DEBUG_DROPZONEDRAG
909 printf("Drag from %d to %d || skip %d\n",
910 _last_pointer_time_axis_view,
911 _last_pointer_time_axis_view + delta_time_axis_view,
916 /* Verify change in y */
917 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
918 /* this y movement is not allowed, so do no y movement this time */
919 delta_time_axis_view = 0;
922 #ifdef DEBUG_DROPZONEDRAG
923 printf(" ** NOT ALLOWED\n");
927 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
928 /* haven't reached next snap point, and we're not switching
929 trackviews nor layers. nothing to do.
934 typedef pair<int,double> NewTrackIndexAndPosition;
935 typedef map<boost::shared_ptr<Playlist>,NewTrackIndexAndPosition> PlaylistDropzoneMap;
936 PlaylistDropzoneMap playlist_dropzone_map;
937 int biggest_drop_zone_offset = 0;
939 /* find drop-zone y-position */
940 Coord last_track_bottom_edge;
941 last_track_bottom_edge = 0;
942 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
943 if (!(*t)->hidden()) {
944 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
950 /* sort views by time_axis.
951 * This retains track order in the dropzone, regardless
952 * of actual selection order
954 _views.sort (DraggingViewSorter());
957 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
959 RegionView* rv = i->view;
964 if (rv->region()->locked() || rv->region()->video_locked()) {
971 /* reparent the regionview into a group above all
975 ArdourCanvas::Item* rvg = rv->get_canvas_group();
976 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
977 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
978 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
979 /* move the item so that it continues to appear at the
980 same location now that its parent has changed.
982 rvg->move (rv_canvas_offset - dmg_canvas_offset);
985 /* If we have moved tracks, we'll fudge the layer delta so that the
986 region gets moved back onto layer 0 on its new track; this avoids
987 confusion when dragging regions from non-zero layers onto different
990 double this_delta_layer = delta_layer;
991 if (delta_time_axis_view != 0) {
992 this_delta_layer = - i->layer;
995 int this_delta_time_axis_view = delta_time_axis_view;
996 this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
998 int track_index = i->time_axis_view + this_delta_time_axis_view;
999 assert(track_index >= 0);
1001 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1002 i->time_axis_view = track_index;
1003 #ifdef DEBUG_DROPZONEDRAG
1004 printf("IN THE ZONE\n");
1006 assert(i->time_axis_view >= _time_axis_views.size());
1007 if (current_pointer_y() >= 0) {
1010 NewTrackIndexAndPosition ip;
1011 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1013 /* store index of each new playlist as a negative count, starting at -1 */
1015 if (pdz == playlist_dropzone_map.end()) {
1018 * retain the ordering top -> bottom in the drop-zone
1019 * this can be done by sorting the regions according to
1020 * i->time_axis_view Y, prior to iterating over DraggingView
1023 int n = playlist_dropzone_map.size() + 1;
1025 /* compute where this new track (which doesn't exist yet) will live
1029 ip.first = -n; /* in time axis units, negative to signify "in drop zone " */
1030 ip.second = last_track_bottom_edge; /* where to place the top edge of the regionview */
1032 /* How high is this region view ? */
1034 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1035 ArdourCanvas::Rect bbox;
1038 bbox = obbox.get ();
1041 last_track_bottom_edge += bbox.height();
1043 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), ip));
1048 dzoffset = ip.first;
1051 /* values are zero or negative, hence the use of min() */
1052 biggest_drop_zone_offset = min (biggest_drop_zone_offset, dzoffset);
1053 y_delta = ip.second - rv->get_canvas_group()->canvas_origin().y;
1058 /* The TimeAxisView that this region is now over */
1059 TimeAxisView* current_tv = _time_axis_views[track_index];
1061 /* Ensure it is moved from stacked -> expanded if appropriate */
1062 if (current_tv->view()->layer_display() == Stacked) {
1063 current_tv->view()->set_layer_display (Expanded);
1066 /* We're only allowed to go -ve in layer on Expanded views */
1067 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1068 this_delta_layer = - i->layer;
1072 rv->set_height (current_tv->view()->child_height ());
1074 /* Update show/hidden status as the region view may have come from a hidden track,
1075 or have moved to one.
1077 if (current_tv->hidden ()) {
1078 rv->get_canvas_group()->hide ();
1080 rv->get_canvas_group()->show ();
1083 /* Update the DraggingView */
1084 i->time_axis_view = track_index;
1085 i->layer += this_delta_layer;
1088 _editor->mouse_brush_insert_region (rv, pending_region_position);
1092 /* Get the y coordinate of the top of the track that this region is now over */
1093 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1095 /* And adjust for the layer that it should be on */
1096 StreamView* cv = current_tv->view ();
1097 switch (cv->layer_display ()) {
1101 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1104 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1108 /* need to get the parent of the regionview
1109 * canvas group and get its position in
1110 * equivalent coordinate space as the trackview
1111 * we are now dragging over.
1114 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1119 /* Now move the region view */
1120 rv->move (x_delta, y_delta);
1122 } /* foreach region */
1124 _total_x_delta += x_delta;
1126 if (x_delta != 0 && !_brushing) {
1127 show_verbose_cursor_time (_last_frame_position);
1132 /* the pointer is currently over a time axis view */
1134 if (_last_pointer_time_axis_view < 0) {
1136 /* last motion event was not over a time axis view */
1138 if (delta_time_axis_view < 0) {
1139 /* was in the drop zone, moving up */
1140 assert(current_pointer_time_axis_view >= 0);
1141 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1143 /* was in the drop zone, moving down ... not possible */
1148 /* last motion event was also over a time axis view */
1150 _last_pointer_time_axis_view += delta_time_axis_view;
1151 assert(_last_pointer_time_axis_view >= 0);
1156 /* the pointer is not over a time axis view */
1158 _last_pointer_time_axis_view = biggest_drop_zone_offset;
1161 _last_pointer_layer += delta_layer;
1165 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1167 if (_copy && first_move) {
1169 if (_x_constrained) {
1170 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1172 _editor->begin_reversible_command (Operations::region_copy);
1175 /* duplicate the regionview(s) and region(s) */
1177 list<DraggingView> new_regionviews;
1179 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1181 RegionView* rv = i->view;
1182 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1183 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1185 const boost::shared_ptr<const Region> original = rv->region();
1186 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
1187 region_copy->set_position (original->position());
1188 /* need to set this so that the drop zone code can work. This doesn't
1189 actually put the region into the playlist, but just sets a weak pointer
1192 region_copy->set_playlist (original->playlist());
1196 boost::shared_ptr<AudioRegion> audioregion_copy
1197 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1199 nrv = new AudioRegionView (*arv, audioregion_copy);
1201 boost::shared_ptr<MidiRegion> midiregion_copy
1202 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1203 nrv = new MidiRegionView (*mrv, midiregion_copy);
1208 nrv->get_canvas_group()->show ();
1209 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1211 /* swap _primary to the copy */
1213 if (rv == _primary) {
1217 /* ..and deselect the one we copied */
1219 rv->set_selected (false);
1222 if (!new_regionviews.empty()) {
1224 /* reflect the fact that we are dragging the copies */
1226 _views = new_regionviews;
1228 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1231 } else if (!_copy && first_move) {
1233 if (_x_constrained) {
1234 _editor->begin_reversible_command (_("fixed time region drag"));
1236 _editor->begin_reversible_command (Operations::region_drag);
1240 RegionMotionDrag::motion (event, first_move);
1244 RegionMotionDrag::finished (GdkEvent *, bool)
1246 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1247 if (!(*i)->view()) {
1251 if ((*i)->view()->layer_display() == Expanded) {
1252 (*i)->view()->set_layer_display (Stacked);
1258 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1260 RegionMotionDrag::finished (ev, movement_occurred);
1262 if (!movement_occurred) {
1266 if (was_double_click() && !_views.empty()) {
1267 DraggingView dv = _views.front();
1268 dv.view->show_region_editor ();
1275 /* reverse this here so that we have the correct logic to finalize
1279 if (Config->get_edit_mode() == Lock) {
1280 _x_constrained = !_x_constrained;
1283 assert (!_views.empty ());
1285 /* We might have hidden region views so that they weren't visible during the drag
1286 (when they have been reparented). Now everything can be shown again, as region
1287 views are back in their track parent groups.
1289 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1290 i->view->get_canvas_group()->show ();
1293 bool const changed_position = (_last_frame_position != _primary->region()->position());
1294 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1295 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1315 _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
1319 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1321 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1326 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1327 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1328 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), region->n_channels(), ARDOUR::Normal, 0, 1, region->name());
1329 RouteTimeAxisView* rtav = _editor->axis_view_from_route (audio_tracks.front());
1331 rtav->set_height (original->current_height());
1335 ChanCount one_midi_port (DataType::MIDI, 1);
1336 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1337 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port, boost::shared_ptr<ARDOUR::PluginInfo>(), ARDOUR::Normal, 0, 1, region->name());
1338 RouteTimeAxisView* rtav = _editor->axis_view_from_route (midi_tracks.front());
1340 rtav->set_height (original->current_height());
1345 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1351 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
1353 RegionSelection new_views;
1354 PlaylistSet modified_playlists;
1355 RouteTimeAxisView* new_time_axis_view = 0;
1358 /* all changes were made during motion event handlers */
1360 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1364 _editor->commit_reversible_command ();
1368 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1369 PlaylistMapping playlist_mapping;
1371 /* insert the regions into their new playlists */
1372 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1374 RouteTimeAxisView* dest_rtv = 0;
1376 if (i->view->region()->locked() || i->view->region()->video_locked()) {
1382 if (changed_position && !_x_constrained) {
1383 where = i->view->region()->position() - drag_delta;
1385 where = i->view->region()->position();
1388 if (i->time_axis_view < 0 || i->time_axis_view >= _time_axis_views.size()) {
1389 /* dragged to drop zone */
1391 PlaylistMapping::iterator pm;
1393 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1394 /* first region from this original playlist: create a new track */
1395 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1396 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1397 dest_rtv = new_time_axis_view;
1399 /* we already created a new track for regions from this playlist, use it */
1400 dest_rtv = pm->second;
1403 /* destination time axis view is the one we dragged to */
1404 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1407 if (dest_rtv != 0) {
1408 RegionView* new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, modified_playlists);
1409 if (new_view != 0) {
1410 new_views.push_back (new_view);
1414 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1415 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1418 list<DraggingView>::const_iterator next = i;
1424 /* If we've created new regions either by copying or moving
1425 to a new track, we want to replace the old selection with the new ones
1428 if (new_views.size() > 0) {
1429 _editor->selection->set (new_views);
1432 /* write commands for the accumulated diffs for all our modified playlists */
1433 add_stateful_diff_commands_for_playlists (modified_playlists);
1435 _editor->commit_reversible_command ();
1439 RegionMoveDrag::finished_no_copy (
1440 bool const changed_position,
1441 bool const changed_tracks,
1442 framecnt_t const drag_delta
1445 RegionSelection new_views;
1446 PlaylistSet modified_playlists;
1447 PlaylistSet frozen_playlists;
1448 set<RouteTimeAxisView*> views_to_update;
1449 RouteTimeAxisView* new_time_axis_view = 0;
1452 /* all changes were made during motion event handlers */
1453 _editor->commit_reversible_command ();
1457 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1458 PlaylistMapping playlist_mapping;
1460 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1462 RegionView* rv = i->view;
1463 RouteTimeAxisView* dest_rtv = 0;
1465 if (rv->region()->locked() || rv->region()->video_locked()) {
1470 if (i->time_axis_view < 0 || i->time_axis_view >= _time_axis_views.size()) {
1471 /* dragged to drop zone */
1473 PlaylistMapping::iterator pm;
1475 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1476 /* first region from this original playlist: create a new track */
1477 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1478 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1479 dest_rtv = new_time_axis_view;
1481 /* we already created a new track for regions from this playlist, use it */
1482 dest_rtv = pm->second;
1486 /* destination time axis view is the one we dragged to */
1487 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1492 double const dest_layer = i->layer;
1494 views_to_update.insert (dest_rtv);
1498 if (changed_position && !_x_constrained) {
1499 where = rv->region()->position() - drag_delta;
1501 where = rv->region()->position();
1504 if (changed_tracks) {
1506 /* insert into new playlist */
1508 RegionView* new_view = insert_region_into_playlist (
1509 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
1512 if (new_view == 0) {
1517 new_views.push_back (new_view);
1519 /* remove from old playlist */
1521 /* the region that used to be in the old playlist is not
1522 moved to the new one - we use a copy of it. as a result,
1523 any existing editor for the region should no longer be
1526 rv->hide_region_editor();
1529 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1533 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1535 /* this movement may result in a crossfade being modified, or a layering change,
1536 so we need to get undo data from the playlist as well as the region.
1539 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1541 playlist->clear_changes ();
1544 rv->region()->clear_changes ();
1547 motion on the same track. plonk the previously reparented region
1548 back to its original canvas group (its streamview).
1549 No need to do anything for copies as they are fake regions which will be deleted.
1552 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1553 rv->get_canvas_group()->set_y_position (i->initial_y);
1556 /* just change the model */
1557 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1558 playlist->set_layer (rv->region(), dest_layer);
1561 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1563 r = frozen_playlists.insert (playlist);
1566 playlist->freeze ();
1569 rv->region()->set_position (where);
1571 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1574 if (changed_tracks) {
1576 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1577 was selected in all of them, then removing it from a playlist will have removed all
1578 trace of it from _views (i.e. there were N regions selected, we removed 1,
1579 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1580 corresponding regionview, and _views is now empty).
1582 This could have invalidated any and all iterators into _views.
1584 The heuristic we use here is: if the region selection is empty, break out of the loop
1585 here. if the region selection is not empty, then restart the loop because we know that
1586 we must have removed at least the region(view) we've just been working on as well as any
1587 that we processed on previous iterations.
1589 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1590 we can just iterate.
1594 if (_views.empty()) {
1605 /* If we've created new regions either by copying or moving
1606 to a new track, we want to replace the old selection with the new ones
1609 if (new_views.size() > 0) {
1610 _editor->selection->set (new_views);
1613 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1617 /* write commands for the accumulated diffs for all our modified playlists */
1618 add_stateful_diff_commands_for_playlists (modified_playlists);
1620 _editor->commit_reversible_command ();
1622 /* We have futzed with the layering of canvas items on our streamviews.
1623 If any region changed layer, this will have resulted in the stream
1624 views being asked to set up their region views, and all will be well.
1625 If not, we might now have badly-ordered region views. Ask the StreamViews
1626 involved to sort themselves out, just in case.
1629 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1630 (*i)->view()->playlist_layered ((*i)->track ());
1634 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1635 * @param region Region to remove.
1636 * @param playlist playlist To remove from.
1637 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1638 * that clear_changes () is only called once per playlist.
1641 RegionMoveDrag::remove_region_from_playlist (
1642 boost::shared_ptr<Region> region,
1643 boost::shared_ptr<Playlist> playlist,
1644 PlaylistSet& modified_playlists
1647 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1650 playlist->clear_changes ();
1653 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1657 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1658 * clearing the playlist's diff history first if necessary.
1659 * @param region Region to insert.
1660 * @param dest_rtv Destination RouteTimeAxisView.
1661 * @param dest_layer Destination layer.
1662 * @param where Destination position.
1663 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1664 * that clear_changes () is only called once per playlist.
1665 * @return New RegionView, or 0 if no insert was performed.
1668 RegionMoveDrag::insert_region_into_playlist (
1669 boost::shared_ptr<Region> region,
1670 RouteTimeAxisView* dest_rtv,
1673 PlaylistSet& modified_playlists
1676 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1677 if (!dest_playlist) {
1681 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1682 _new_region_view = 0;
1683 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1685 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1686 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1688 dest_playlist->clear_changes ();
1691 dest_playlist->add_region (region, where);
1693 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1694 dest_playlist->set_layer (region, dest_layer);
1699 assert (_new_region_view);
1701 return _new_region_view;
1705 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1707 _new_region_view = rv;
1711 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1713 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1714 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1716 _editor->session()->add_command (c);
1725 RegionMoveDrag::aborted (bool movement_occurred)
1729 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1730 list<DraggingView>::const_iterator next = i;
1739 RegionMotionDrag::aborted (movement_occurred);
1744 RegionMotionDrag::aborted (bool)
1746 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1748 StreamView* sview = (*i)->view();
1751 if (sview->layer_display() == Expanded) {
1752 sview->set_layer_display (Stacked);
1757 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1758 RegionView* rv = i->view;
1759 TimeAxisView* tv = &(rv->get_time_axis_view ());
1760 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1762 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1763 rv->get_canvas_group()->set_y_position (0);
1765 rv->move (-_total_x_delta, 0);
1766 rv->set_height (rtv->view()->child_height ());
1770 /** @param b true to brush, otherwise false.
1771 * @param c true to make copies of the regions being moved, otherwise false.
1773 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1774 : RegionMotionDrag (e, i, p, v, b)
1777 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1780 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1781 if (rtv && rtv->is_track()) {
1782 speed = rtv->track()->speed ();
1785 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1789 RegionMoveDrag::setup_pointer_frame_offset ()
1791 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1794 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1795 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1797 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1799 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1800 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1802 _primary = v->view()->create_region_view (r, false, false);
1804 _primary->get_canvas_group()->show ();
1805 _primary->set_position (pos, 0);
1806 _views.push_back (DraggingView (_primary, this, v));
1808 _last_frame_position = pos;
1810 _item = _primary->get_canvas_group ();
1814 RegionInsertDrag::finished (GdkEvent *, bool)
1816 int pos = _views.front().time_axis_view;
1817 assert(pos >= 0 && pos < _time_axis_views.size());
1819 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1821 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1822 _primary->get_canvas_group()->set_y_position (0);
1824 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1826 _editor->begin_reversible_command (Operations::insert_region);
1827 playlist->clear_changes ();
1828 playlist->add_region (_primary->region (), _last_frame_position);
1830 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
1831 if (Config->get_edit_mode() == Ripple) {
1832 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
1835 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1836 _editor->commit_reversible_command ();
1844 RegionInsertDrag::aborted (bool)
1851 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1852 : RegionMoveDrag (e, i, p, v, false, false)
1854 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1857 struct RegionSelectionByPosition {
1858 bool operator() (RegionView*a, RegionView* b) {
1859 return a->region()->position () < b->region()->position();
1864 RegionSpliceDrag::motion (GdkEvent* event, bool)
1866 /* Which trackview is this ? */
1868 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
1869 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1871 /* The region motion is only processed if the pointer is over
1875 if (!tv || !tv->is_track()) {
1876 /* To make sure we hide the verbose canvas cursor when the mouse is
1877 not held over an audio track.
1879 _editor->verbose_cursor()->hide ();
1882 _editor->verbose_cursor()->show ();
1887 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1893 RegionSelection copy;
1894 _editor->selection->regions.by_position(copy);
1896 framepos_t const pf = adjusted_current_frame (event);
1898 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1900 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1906 boost::shared_ptr<Playlist> playlist;
1908 if ((playlist = atv->playlist()) == 0) {
1912 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1917 if (pf < (*i)->region()->last_frame() + 1) {
1921 if (pf > (*i)->region()->first_frame()) {
1927 playlist->shuffle ((*i)->region(), dir);
1932 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1934 RegionMoveDrag::finished (event, movement_occurred);
1938 RegionSpliceDrag::aborted (bool)
1948 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
1951 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
1953 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
1954 RegionSelection to_ripple;
1955 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
1956 if ((*i)->position() >= where) {
1957 to_ripple.push_back (rtv->view()->find_view(*i));
1961 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
1962 if (!exclude.contains (*i)) {
1963 // the selection has already been added to _views
1965 if (drag_in_progress) {
1966 // do the same things that RegionMotionDrag::motion does when
1967 // first_move is true, for the region views that we're adding
1968 // to _views this time
1971 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
1972 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
1973 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1974 rvg->reparent (_editor->_drag_motion_group);
1976 // we only need to move in the y direction
1977 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
1982 _views.push_back (DraggingView (*i, this, tav));
1988 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
1991 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
1992 // we added all the regions after the selection
1994 std::list<DraggingView>::iterator to_erase = i++;
1995 if (!_editor->selection->regions.contains (to_erase->view)) {
1996 // restore the non-selected regions to their original playlist & positions,
1997 // and then ripple them back by the length of the regions that were dragged away
1998 // do the same things as RegionMotionDrag::aborted
2000 RegionView *rv = to_erase->view;
2001 TimeAxisView* tv = &(rv->get_time_axis_view ());
2002 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2005 // plonk them back onto their own track
2006 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2007 rv->get_canvas_group()->set_y_position (0);
2011 // move the underlying region to match the view
2012 rv->region()->set_position (rv->region()->position() + amount);
2014 // restore the view to match the underlying region's original position
2015 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2018 rv->set_height (rtv->view()->child_height ());
2019 _views.erase (to_erase);
2025 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2027 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2029 return allow_moves_across_tracks;
2037 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2038 : RegionMoveDrag (e, i, p, v, false, false)
2040 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2041 // compute length of selection
2042 RegionSelection selected_regions = _editor->selection->regions;
2043 selection_length = selected_regions.end_frame() - selected_regions.start();
2045 // we'll only allow dragging to another track in ripple mode if all the regions
2046 // being dragged start off on the same track
2047 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2050 exclude = new RegionList;
2051 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2052 exclude->push_back((*i)->region());
2055 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2056 RegionSelection copy;
2057 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2059 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2060 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2062 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2063 // find ripple start point on each applicable playlist
2064 RegionView *first_selected_on_this_track = NULL;
2065 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2066 if ((*i)->region()->playlist() == (*pi)) {
2067 // region is on this playlist - it's the first, because they're sorted
2068 first_selected_on_this_track = *i;
2072 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2073 add_all_after_to_views (
2074 &first_selected_on_this_track->get_time_axis_view(),
2075 first_selected_on_this_track->region()->position(),
2076 selected_regions, false);
2079 if (allow_moves_across_tracks) {
2080 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2088 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2090 /* Which trackview is this ? */
2092 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2093 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2095 /* The region motion is only processed if the pointer is over
2099 if (!tv || !tv->is_track()) {
2100 /* To make sure we hide the verbose canvas cursor when the mouse is
2101 not held over an audiotrack.
2103 _editor->verbose_cursor()->hide ();
2107 framepos_t where = adjusted_current_frame (event);
2108 assert (where >= 0);
2110 double delta = compute_x_delta (event, &after);
2112 framecnt_t amount = _editor->pixel_to_sample (delta);
2114 if (allow_moves_across_tracks) {
2115 // all the originally selected regions were on the same track
2117 framecnt_t adjust = 0;
2118 if (prev_tav && tv != prev_tav) {
2119 // dragged onto a different track
2120 // remove the unselected regions from _views, restore them to their original positions
2121 // and add the regions after the drop point on the new playlist to _views instead.
2122 // undo the effect of rippling the previous playlist, and include the effect of removing
2123 // the dragged region(s) from this track
2125 remove_unselected_from_views (prev_amount, false);
2126 // ripple previous playlist according to the regions that have been removed onto the new playlist
2127 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2130 // move just the selected regions
2131 RegionMoveDrag::motion(event, first_move);
2133 // ensure that the ripple operation on the new playlist inserts selection_length time
2134 adjust = selection_length;
2135 // ripple the new current playlist
2136 tv->playlist()->ripple (where, amount+adjust, exclude);
2138 // add regions after point where drag entered this track to subsequent ripples
2139 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2142 // motion on same track
2143 RegionMoveDrag::motion(event, first_move);
2147 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2148 prev_position = where;
2150 // selection encompasses multiple tracks - just drag
2151 // cross-track drags are forbidden
2152 RegionMoveDrag::motion(event, first_move);
2155 if (!_x_constrained) {
2156 prev_amount += amount;
2159 _last_frame_position = after;
2163 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2165 if (!movement_occurred) {
2169 if (was_double_click() && !_views.empty()) {
2170 DraggingView dv = _views.front();
2171 dv.view->show_region_editor ();
2178 _editor->begin_reversible_command(_("Ripple drag"));
2180 // remove the regions being rippled from the dragging view, updating them to
2181 // their new positions
2182 remove_unselected_from_views (prev_amount, true);
2184 if (allow_moves_across_tracks) {
2186 // if regions were dragged across tracks, we've rippled any later
2187 // regions on the track the regions were dragged off, so we need
2188 // to add the original track to the undo record
2189 orig_tav->playlist()->clear_changes();
2190 vector<Command*> cmds;
2191 orig_tav->playlist()->rdiff (cmds);
2192 _editor->session()->add_commands (cmds);
2194 if (prev_tav && prev_tav != orig_tav) {
2195 prev_tav->playlist()->clear_changes();
2196 vector<Command*> cmds;
2197 prev_tav->playlist()->rdiff (cmds);
2198 _editor->session()->add_commands (cmds);
2201 // selection spanned multiple tracks - all will need adding to undo record
2203 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2204 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2206 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2207 (*pi)->clear_changes();
2208 vector<Command*> cmds;
2209 (*pi)->rdiff (cmds);
2210 _editor->session()->add_commands (cmds);
2214 // other modified playlists are added to undo by RegionMoveDrag::finished()
2215 RegionMoveDrag::finished (event, movement_occurred);
2216 _editor->commit_reversible_command();
2220 RegionRippleDrag::aborted (bool movement_occurred)
2222 RegionMoveDrag::aborted (movement_occurred);
2227 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2229 _view (dynamic_cast<MidiTimeAxisView*> (v))
2231 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2237 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2240 _region = add_midi_region (_view);
2241 _view->playlist()->freeze ();
2244 framepos_t const f = adjusted_current_frame (event);
2245 if (f < grab_frame()) {
2246 _region->set_position (f);
2249 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2250 so that if this region is duplicated, its duplicate starts on
2251 a snap point rather than 1 frame after a snap point. Otherwise things get
2252 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2253 place snapped notes at the start of the region.
2256 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2257 _region->set_length (len < 1 ? 1 : len);
2263 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
2265 if (!movement_occurred) {
2266 add_midi_region (_view);
2268 _view->playlist()->thaw ();
2273 RegionCreateDrag::aborted (bool)
2276 _view->playlist()->thaw ();
2282 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2286 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2290 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2292 Gdk::Cursor* cursor;
2293 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2295 float x_fraction = cnote->mouse_x_fraction ();
2297 if (x_fraction > 0.0 && x_fraction < 0.25) {
2298 cursor = _editor->cursors()->left_side_trim;
2301 cursor = _editor->cursors()->right_side_trim;
2305 Drag::start_grab (event, cursor);
2307 region = &cnote->region_view();
2311 if (event->motion.state & Keyboard::PrimaryModifier) {
2317 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2319 if (ms.size() > 1) {
2320 /* has to be relative, may make no sense otherwise */
2324 /* select this note; if it is already selected, preserve the existing selection,
2325 otherwise make this note the only one selected.
2327 region->note_selected (cnote, cnote->selected ());
2329 _editor->begin_reversible_command (_("resize notes"));
2331 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2332 MidiRegionSelection::iterator next;
2335 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2337 mrv->begin_resizing (at_front);
2344 NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
2346 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2347 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2348 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2350 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2352 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative);
2358 NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
2360 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2361 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2362 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2364 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2366 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative);
2370 _editor->commit_reversible_command ();
2374 NoteResizeDrag::aborted (bool)
2376 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2377 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2378 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2380 mrv->abort_resizing ();
2385 AVDraggingView::AVDraggingView (RegionView* v)
2388 initial_position = v->region()->position ();
2391 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2394 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2397 TrackViewList empty;
2399 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2400 std::list<RegionView*> views = rs.by_layer();
2402 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2403 RegionView* rv = (*i);
2404 if (!rv->region()->video_locked()) {
2407 _views.push_back (AVDraggingView (rv));
2412 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2414 Drag::start_grab (event);
2415 if (_editor->session() == 0) {
2419 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2420 _max_backwards_drag = (
2421 ARDOUR_UI::instance()->video_timeline->get_duration()
2422 + ARDOUR_UI::instance()->video_timeline->get_offset()
2423 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2426 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2427 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2428 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2431 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2434 Timecode::Time timecode;
2435 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2436 snprintf (buf, sizeof (buf), "Video Start:\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, (_startdrag_video_offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2437 show_verbose_cursor_text (buf);
2441 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2443 if (_editor->session() == 0) {
2446 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2450 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2451 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2453 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2454 dt = - _max_backwards_drag;
2457 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2458 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2460 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2461 RegionView* rv = i->view;
2462 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2465 rv->region()->clear_changes ();
2466 rv->region()->suspend_property_changes();
2468 rv->region()->set_position(i->initial_position + dt);
2469 rv->region_changed(ARDOUR::Properties::position);
2472 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2473 Timecode::Time timecode;
2474 Timecode::Time timediff;
2476 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2477 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2478 snprintf (buf, sizeof (buf),
2479 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2480 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2481 , _("Video Start:"),
2482 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2484 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2486 show_verbose_cursor_text (buf);
2490 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2492 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2496 if (!movement_occurred || ! _editor->session()) {
2500 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2502 _editor->begin_reversible_command (_("Move Video"));
2504 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2505 ARDOUR_UI::instance()->video_timeline->save_undo();
2506 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2507 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2509 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2510 i->view->drag_end();
2511 i->view->region()->resume_property_changes ();
2513 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2516 _editor->session()->maybe_update_session_range(
2517 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2518 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2522 _editor->commit_reversible_command ();
2526 VideoTimeLineDrag::aborted (bool)
2528 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2531 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2532 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2534 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2535 i->view->region()->resume_property_changes ();
2536 i->view->region()->set_position(i->initial_position);
2540 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2541 : RegionDrag (e, i, p, v)
2542 , _preserve_fade_anchor (preserve_fade_anchor)
2543 , _jump_position_when_done (false)
2545 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2549 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2552 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2553 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2555 if (tv && tv->is_track()) {
2556 speed = tv->track()->speed();
2559 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2560 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2561 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2563 framepos_t const pf = adjusted_current_frame (event);
2565 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2566 /* Move the contents of the region around without changing the region bounds */
2567 _operation = ContentsTrim;
2568 Drag::start_grab (event, _editor->cursors()->trimmer);
2570 /* These will get overridden for a point trim.*/
2571 if (pf < (region_start + region_length/2)) {
2572 /* closer to front */
2573 _operation = StartTrim;
2575 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2576 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2578 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2582 _operation = EndTrim;
2583 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2584 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2586 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2591 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2592 _jump_position_when_done = true;
2595 switch (_operation) {
2597 show_verbose_cursor_time (region_start);
2598 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2599 i->view->trim_front_starting ();
2603 show_verbose_cursor_time (region_end);
2606 show_verbose_cursor_time (pf);
2610 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2611 i->view->region()->suspend_property_changes ();
2616 TrimDrag::motion (GdkEvent* event, bool first_move)
2618 RegionView* rv = _primary;
2621 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2622 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2623 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2624 frameoffset_t frame_delta = 0;
2626 if (tv && tv->is_track()) {
2627 speed = tv->track()->speed();
2630 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
2636 switch (_operation) {
2638 trim_type = "Region start trim";
2641 trim_type = "Region end trim";
2644 trim_type = "Region content trim";
2651 _editor->begin_reversible_command (trim_type);
2653 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2654 RegionView* rv = i->view;
2655 rv->enable_display (false);
2656 rv->region()->playlist()->clear_owned_changes ();
2658 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2661 arv->temporarily_hide_envelope ();
2665 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2666 insert_result = _editor->motion_frozen_playlists.insert (pl);
2668 if (insert_result.second) {
2674 bool non_overlap_trim = false;
2676 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2677 non_overlap_trim = true;
2680 /* contstrain trim to fade length */
2681 if (_preserve_fade_anchor) {
2682 switch (_operation) {
2684 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2685 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2687 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2688 if (ar->locked()) continue;
2689 framecnt_t len = ar->fade_in()->back()->when;
2690 if (len < dt) dt = min(dt, len);
2694 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2695 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2697 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2698 if (ar->locked()) continue;
2699 framecnt_t len = ar->fade_out()->back()->when;
2700 if (len < -dt) dt = max(dt, -len);
2709 switch (_operation) {
2711 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2712 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim);
2713 if (changed && _preserve_fade_anchor) {
2714 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2716 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2717 framecnt_t len = ar->fade_in()->back()->when;
2718 framecnt_t diff = ar->first_frame() - i->initial_position;
2719 framepos_t new_length = len - diff;
2720 i->anchored_fade_length = min (ar->length(), new_length);
2721 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
2722 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
2729 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2730 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim);
2731 if (changed && _preserve_fade_anchor) {
2732 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2734 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2735 framecnt_t len = ar->fade_out()->back()->when;
2736 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
2737 framepos_t new_length = len + diff;
2738 i->anchored_fade_length = min (ar->length(), new_length);
2739 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
2740 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
2748 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
2750 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2751 i->view->move_contents (frame_delta);
2757 switch (_operation) {
2759 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
2762 show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed));
2765 // show_verbose_cursor_time (frame_delta);
2772 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
2774 if (movement_occurred) {
2775 motion (event, false);
2777 if (_operation == StartTrim) {
2778 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2780 /* This must happen before the region's StatefulDiffCommand is created, as it may
2781 `correct' (ahem) the region's _start from being negative to being zero. It
2782 needs to be zero in the undo record.
2784 i->view->trim_front_ending ();
2786 if (_preserve_fade_anchor) {
2787 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2789 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2790 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
2791 ar->set_fade_in_length(i->anchored_fade_length);
2792 ar->set_fade_in_active(true);
2795 if (_jump_position_when_done) {
2796 i->view->region()->set_position (i->initial_position);
2799 } else if (_operation == EndTrim) {
2800 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2801 if (_preserve_fade_anchor) {
2802 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2804 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2805 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
2806 ar->set_fade_out_length(i->anchored_fade_length);
2807 ar->set_fade_out_active(true);
2810 if (_jump_position_when_done) {
2811 i->view->region()->set_position (i->initial_end - i->view->region()->length());
2816 if (!_views.empty()) {
2817 if (_operation == StartTrim) {
2818 _editor->maybe_locate_with_edit_preroll(
2819 _views.begin()->view->region()->position());
2821 if (_operation == EndTrim) {
2822 _editor->maybe_locate_with_edit_preroll(
2823 _views.begin()->view->region()->position() +
2824 _views.begin()->view->region()->length());
2828 if (!_editor->selection->selected (_primary)) {
2829 _primary->thaw_after_trim ();
2832 set<boost::shared_ptr<Playlist> > diffed_playlists;
2834 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2835 i->view->thaw_after_trim ();
2836 i->view->enable_display (true);
2838 /* Trimming one region may affect others on the playlist, so we need
2839 to get undo Commands from the whole playlist rather than just the
2840 region. Use diffed_playlists to make sure we don't diff a given
2841 playlist more than once.
2843 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
2844 if (diffed_playlists.find (p) == diffed_playlists.end()) {
2845 vector<Command*> cmds;
2847 _editor->session()->add_commands (cmds);
2848 diffed_playlists.insert (p);
2853 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
2857 _editor->motion_frozen_playlists.clear ();
2858 _editor->commit_reversible_command();
2861 /* no mouse movement */
2862 _editor->point_trim (event, adjusted_current_frame (event));
2865 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2866 if (_operation == StartTrim) {
2867 i->view->trim_front_ending ();
2870 i->view->region()->resume_property_changes ();
2875 TrimDrag::aborted (bool movement_occurred)
2877 /* Our motion method is changing model state, so use the Undo system
2878 to cancel. Perhaps not ideal, as this will leave an Undo point
2879 behind which may be slightly odd from the user's point of view.
2884 if (movement_occurred) {
2888 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2889 i->view->region()->resume_property_changes ();
2894 TrimDrag::setup_pointer_frame_offset ()
2896 list<DraggingView>::iterator i = _views.begin ();
2897 while (i != _views.end() && i->view != _primary) {
2901 if (i == _views.end()) {
2905 switch (_operation) {
2907 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
2910 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
2917 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
2921 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
2922 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
2927 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2929 Drag::start_grab (event, cursor);
2930 show_verbose_cursor_time (adjusted_current_frame(event));
2934 MeterMarkerDrag::setup_pointer_frame_offset ()
2936 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
2940 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
2942 if (!_marker->meter().movable()) {
2948 // create a dummy marker for visual representation of moving the
2949 // section, because whether its a copy or not, we're going to
2950 // leave or lose the original marker (leave if its a copy; lose if its
2951 // not, because we'll remove it from the map).
2953 MeterSection section (_marker->meter());
2955 if (!section.movable()) {
2960 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
2962 _marker = new MeterMarker (
2964 *_editor->meter_group,
2965 ARDOUR_UI::config()->color ("meter marker"),
2967 *new MeterSection (_marker->meter())
2970 /* use the new marker for the grab */
2971 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
2974 TempoMap& map (_editor->session()->tempo_map());
2975 /* get current state */
2976 before_state = &map.get_state();
2977 /* remove the section while we drag it */
2978 map.remove_meter (section, true);
2982 framepos_t const pf = adjusted_current_frame (event);
2984 _marker->set_position (pf);
2985 show_verbose_cursor_time (pf);
2989 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2991 if (!movement_occurred) {
2992 if (was_double_click()) {
2993 _editor->edit_meter_marker (*_marker);
2998 if (!_marker->meter().movable()) {
3002 motion (event, false);
3004 Timecode::BBT_Time when;
3006 TempoMap& map (_editor->session()->tempo_map());
3007 map.bbt_time (last_pointer_frame(), when);
3009 if (_copy == true) {
3010 _editor->begin_reversible_command (_("copy meter mark"));
3011 XMLNode &before = map.get_state();
3012 map.add_meter (_marker->meter(), when);
3013 XMLNode &after = map.get_state();
3014 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
3015 _editor->commit_reversible_command ();
3018 _editor->begin_reversible_command (_("move meter mark"));
3020 /* we removed it before, so add it back now */
3022 map.add_meter (_marker->meter(), when);
3023 XMLNode &after = map.get_state();
3024 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3025 _editor->commit_reversible_command ();
3028 // delete the dummy marker we used for visual representation while moving.
3029 // a new visual marker will show up automatically.
3034 MeterMarkerDrag::aborted (bool moved)
3036 _marker->set_position (_marker->meter().frame ());
3039 TempoMap& map (_editor->session()->tempo_map());
3040 /* we removed it before, so add it back now */
3041 map.add_meter (_marker->meter(), _marker->meter().frame());
3042 // delete the dummy marker we used for visual representation while moving.
3043 // a new visual marker will show up automatically.
3048 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3052 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3054 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3059 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3061 Drag::start_grab (event, cursor);
3062 show_verbose_cursor_time (adjusted_current_frame (event));
3066 TempoMarkerDrag::setup_pointer_frame_offset ()
3068 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
3072 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3074 if (!_marker->tempo().movable()) {
3080 // create a dummy marker for visual representation of moving the
3081 // section, because whether its a copy or not, we're going to
3082 // leave or lose the original marker (leave if its a copy; lose if its
3083 // not, because we'll remove it from the map).
3085 // create a dummy marker for visual representation of moving the copy.
3086 // The actual copying is not done before we reach the finish callback.
3089 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
3091 TempoSection section (_marker->tempo());
3093 _marker = new TempoMarker (
3095 *_editor->tempo_group,
3096 ARDOUR_UI::config()->color ("tempo marker"),
3098 *new TempoSection (_marker->tempo())
3101 /* use the new marker for the grab */
3102 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3105 TempoMap& map (_editor->session()->tempo_map());
3106 /* get current state */
3107 before_state = &map.get_state();
3108 /* remove the section while we drag it */
3109 map.remove_tempo (section, true);
3113 framepos_t const pf = adjusted_current_frame (event);
3114 _marker->set_position (pf);
3115 show_verbose_cursor_time (pf);
3119 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3121 if (!movement_occurred) {
3122 if (was_double_click()) {
3123 _editor->edit_tempo_marker (*_marker);
3128 if (!_marker->tempo().movable()) {
3132 motion (event, false);
3134 TempoMap& map (_editor->session()->tempo_map());
3135 framepos_t beat_time = map.round_to_beat (last_pointer_frame(), RoundNearest);
3136 Timecode::BBT_Time when;
3138 map.bbt_time (beat_time, when);
3140 if (_copy == true) {
3141 _editor->begin_reversible_command (_("copy tempo mark"));
3142 XMLNode &before = map.get_state();
3143 map.add_tempo (_marker->tempo(), when);
3144 XMLNode &after = map.get_state();
3145 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
3146 _editor->commit_reversible_command ();
3149 _editor->begin_reversible_command (_("move tempo mark"));
3150 /* we removed it before, so add it back now */
3151 map.add_tempo (_marker->tempo(), when);
3152 XMLNode &after = map.get_state();
3153 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3154 _editor->commit_reversible_command ();
3157 // delete the dummy marker we used for visual representation while moving.
3158 // a new visual marker will show up automatically.
3163 TempoMarkerDrag::aborted (bool moved)
3165 _marker->set_position (_marker->tempo().frame());
3167 TempoMap& map (_editor->session()->tempo_map());
3168 /* we removed it before, so add it back now */
3169 map.add_tempo (_marker->tempo(), _marker->tempo().start());
3170 // delete the dummy marker we used for visual representation while moving.
3171 // a new visual marker will show up automatically.
3176 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3177 : Drag (e, &c.track_canvas_item(), false)
3181 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3184 /** Do all the things we do when dragging the playhead to make it look as though
3185 * we have located, without actually doing the locate (because that would cause
3186 * the diskstream buffers to be refilled, which is too slow).
3189 CursorDrag::fake_locate (framepos_t t)
3191 _editor->playhead_cursor->set_position (t);
3193 Session* s = _editor->session ();
3194 if (s->timecode_transmission_suspended ()) {
3195 framepos_t const f = _editor->playhead_cursor->current_frame ();
3196 /* This is asynchronous so it will be sent "now"
3198 s->send_mmc_locate (f);
3199 /* These are synchronous and will be sent during the next
3202 s->queue_full_time_code ();
3203 s->queue_song_position_pointer ();
3206 show_verbose_cursor_time (t);
3207 _editor->UpdateAllTransportClocks (t);
3211 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3213 Drag::start_grab (event, c);
3215 _grab_zoom = _editor->samples_per_pixel;
3217 framepos_t where = _editor->canvas_event_sample (event);
3219 _editor->snap_to_with_modifier (where, event);
3221 _editor->_dragging_playhead = true;
3223 Session* s = _editor->session ();
3225 /* grab the track canvas item as well */
3227 _cursor.track_canvas_item().grab();
3230 if (_was_rolling && _stop) {
3234 if (s->is_auditioning()) {
3235 s->cancel_audition ();
3239 if (AudioEngine::instance()->connected()) {
3241 /* do this only if we're the engine is connected
3242 * because otherwise this request will never be
3243 * serviced and we'll busy wait forever. likewise,
3244 * notice if we are disconnected while waiting for the
3245 * request to be serviced.
3248 s->request_suspend_timecode_transmission ();
3249 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3250 /* twiddle our thumbs */
3255 fake_locate (where);
3259 CursorDrag::motion (GdkEvent* event, bool)
3261 framepos_t const adjusted_frame = adjusted_current_frame (event);
3262 if (adjusted_frame != last_pointer_frame()) {
3263 fake_locate (adjusted_frame);
3268 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3270 _editor->_dragging_playhead = false;
3272 _cursor.track_canvas_item().ungrab();
3274 if (!movement_occurred && _stop) {
3278 motion (event, false);
3280 Session* s = _editor->session ();
3282 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3283 _editor->_pending_locate_request = true;
3284 s->request_resume_timecode_transmission ();
3289 CursorDrag::aborted (bool)
3291 _cursor.track_canvas_item().ungrab();
3293 if (_editor->_dragging_playhead) {
3294 _editor->session()->request_resume_timecode_transmission ();
3295 _editor->_dragging_playhead = false;
3298 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3301 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3302 : RegionDrag (e, i, p, v)
3304 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3308 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3310 Drag::start_grab (event, cursor);
3312 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3313 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3315 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3319 FadeInDrag::setup_pointer_frame_offset ()
3321 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3322 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3323 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3327 FadeInDrag::motion (GdkEvent* event, bool)
3329 framecnt_t fade_length;
3330 framepos_t const pos = adjusted_current_frame (event);
3331 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3333 if (pos < (region->position() + 64)) {
3334 fade_length = 64; // this should be a minimum defined somewhere
3335 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3336 fade_length = region->length() - region->fade_out()->back()->when - 1;
3338 fade_length = pos - region->position();
3341 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3343 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3349 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3352 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3356 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3358 if (!movement_occurred) {
3362 framecnt_t fade_length;
3364 framepos_t const pos = adjusted_current_frame (event);
3366 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3368 if (pos < (region->position() + 64)) {
3369 fade_length = 64; // this should be a minimum defined somewhere
3370 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3371 fade_length = region->length() - region->fade_out()->back()->when - 1;
3373 fade_length = pos - region->position();
3376 _editor->begin_reversible_command (_("change fade in length"));
3378 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3380 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3386 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3387 XMLNode &before = alist->get_state();
3389 tmp->audio_region()->set_fade_in_length (fade_length);
3390 tmp->audio_region()->set_fade_in_active (true);
3392 XMLNode &after = alist->get_state();
3393 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3396 _editor->commit_reversible_command ();
3400 FadeInDrag::aborted (bool)
3402 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3403 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3409 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3413 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3414 : RegionDrag (e, i, p, v)
3416 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3420 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3422 Drag::start_grab (event, cursor);
3424 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3425 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3427 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3431 FadeOutDrag::setup_pointer_frame_offset ()
3433 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3434 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3435 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3439 FadeOutDrag::motion (GdkEvent* event, bool)
3441 framecnt_t fade_length;
3443 framepos_t const pos = adjusted_current_frame (event);
3445 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3447 if (pos > (region->last_frame() - 64)) {
3448 fade_length = 64; // this should really be a minimum fade defined somewhere
3449 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3450 fade_length = region->length() - region->fade_in()->back()->when - 1;
3452 fade_length = region->last_frame() - pos;
3455 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3457 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3463 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3466 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3470 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3472 if (!movement_occurred) {
3476 framecnt_t fade_length;
3478 framepos_t const pos = adjusted_current_frame (event);
3480 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3482 if (pos > (region->last_frame() - 64)) {
3483 fade_length = 64; // this should really be a minimum fade defined somewhere
3484 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3485 fade_length = region->length() - region->fade_in()->back()->when - 1;
3487 fade_length = region->last_frame() - pos;
3490 _editor->begin_reversible_command (_("change fade out length"));
3492 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3494 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3500 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3501 XMLNode &before = alist->get_state();
3503 tmp->audio_region()->set_fade_out_length (fade_length);
3504 tmp->audio_region()->set_fade_out_active (true);
3506 XMLNode &after = alist->get_state();
3507 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3510 _editor->commit_reversible_command ();
3514 FadeOutDrag::aborted (bool)
3516 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3517 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3523 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3527 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3530 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3532 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
3535 _points.push_back (ArdourCanvas::Duple (0, 0));
3536 _points.push_back (ArdourCanvas::Duple (0, physical_screen_height (_editor->get_window())));
3539 MarkerDrag::~MarkerDrag ()
3541 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
3546 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, Marker* m)
3548 location = new Location (*l);
3549 markers.push_back (m);
3554 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3556 Drag::start_grab (event, cursor);
3560 Location *location = _editor->find_location_from_marker (_marker, is_start);
3561 _editor->_dragging_edit_point = true;
3563 update_item (location);
3565 // _drag_line->show();
3566 // _line->raise_to_top();
3569 show_verbose_cursor_time (location->start());
3571 show_verbose_cursor_time (location->end());
3574 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3577 case Selection::Toggle:
3578 /* we toggle on the button release */
3580 case Selection::Set:
3581 if (!_editor->selection->selected (_marker)) {
3582 _editor->selection->set (_marker);
3585 case Selection::Extend:
3587 Locations::LocationList ll;
3588 list<Marker*> to_add;
3590 _editor->selection->markers.range (s, e);
3591 s = min (_marker->position(), s);
3592 e = max (_marker->position(), e);
3595 if (e < max_framepos) {
3598 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
3599 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
3600 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
3603 to_add.push_back (lm->start);
3606 to_add.push_back (lm->end);
3610 if (!to_add.empty()) {
3611 _editor->selection->add (to_add);
3615 case Selection::Add:
3616 _editor->selection->add (_marker);
3620 /* Set up copies for us to manipulate during the drag
3623 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
3625 Location* l = _editor->find_location_from_marker (*i, is_start);
3632 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
3634 /* range: check that the other end of the range isn't
3637 CopiedLocationInfo::iterator x;
3638 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3639 if (*(*x).location == *l) {
3643 if (x == _copied_locations.end()) {
3644 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
3646 (*x).markers.push_back (*i);
3647 (*x).move_both = true;
3655 MarkerDrag::setup_pointer_frame_offset ()
3658 Location *location = _editor->find_location_from_marker (_marker, is_start);
3659 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
3663 MarkerDrag::motion (GdkEvent* event, bool)
3665 framecnt_t f_delta = 0;
3667 bool move_both = false;
3668 Location *real_location;
3669 Location *copy_location = 0;
3671 framepos_t const newframe = adjusted_current_frame (event);
3672 framepos_t next = newframe;
3674 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
3678 CopiedLocationInfo::iterator x;
3680 /* find the marker we're dragging, and compute the delta */
3682 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3684 copy_location = (*x).location;
3686 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
3688 /* this marker is represented by this
3689 * CopiedLocationMarkerInfo
3692 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
3697 if (real_location->is_mark()) {
3698 f_delta = newframe - copy_location->start();
3702 switch (_marker->type()) {
3703 case Marker::SessionStart:
3704 case Marker::RangeStart:
3705 case Marker::LoopStart:
3706 case Marker::PunchIn:
3707 f_delta = newframe - copy_location->start();
3710 case Marker::SessionEnd:
3711 case Marker::RangeEnd:
3712 case Marker::LoopEnd:
3713 case Marker::PunchOut:
3714 f_delta = newframe - copy_location->end();
3717 /* what kind of marker is this ? */
3726 if (x == _copied_locations.end()) {
3727 /* hmm, impossible - we didn't find the dragged marker */
3731 /* now move them all */
3733 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3735 copy_location = x->location;
3737 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
3741 if (real_location->locked()) {
3745 if (copy_location->is_mark()) {
3749 copy_location->set_start (copy_location->start() + f_delta);
3753 framepos_t new_start = copy_location->start() + f_delta;
3754 framepos_t new_end = copy_location->end() + f_delta;
3756 if (is_start) { // start-of-range marker
3758 if (move_both || (*x).move_both) {
3759 copy_location->set_start (new_start);
3760 copy_location->set_end (new_end);
3761 } else if (new_start < copy_location->end()) {
3762 copy_location->set_start (new_start);
3763 } else if (newframe > 0) {
3764 _editor->snap_to (next, RoundUpAlways, true);
3765 copy_location->set_end (next);
3766 copy_location->set_start (newframe);
3769 } else { // end marker
3771 if (move_both || (*x).move_both) {
3772 copy_location->set_end (new_end);
3773 copy_location->set_start (new_start);
3774 } else if (new_end > copy_location->start()) {
3775 copy_location->set_end (new_end);
3776 } else if (newframe > 0) {
3777 _editor->snap_to (next, RoundDownAlways, true);
3778 copy_location->set_start (next);
3779 copy_location->set_end (newframe);
3784 update_item (copy_location);
3786 /* now lookup the actual GUI items used to display this
3787 * location and move them to wherever the copy of the location
3788 * is now. This means that the logic in ARDOUR::Location is
3789 * still enforced, even though we are not (yet) modifying
3790 * the real Location itself.
3793 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
3796 lm->set_position (copy_location->start(), copy_location->end());
3801 assert (!_copied_locations.empty());
3803 show_verbose_cursor_time (newframe);
3807 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3809 if (!movement_occurred) {
3811 if (was_double_click()) {
3812 _editor->rename_marker (_marker);
3816 /* just a click, do nothing but finish
3817 off the selection process
3820 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3823 case Selection::Set:
3824 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
3825 _editor->selection->set (_marker);
3829 case Selection::Toggle:
3830 /* we toggle on the button release, click only */
3831 _editor->selection->toggle (_marker);
3834 case Selection::Extend:
3835 case Selection::Add:
3842 _editor->_dragging_edit_point = false;
3844 _editor->begin_reversible_command ( _("move marker") );
3845 XMLNode &before = _editor->session()->locations()->get_state();
3847 MarkerSelection::iterator i;
3848 CopiedLocationInfo::iterator x;
3851 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
3852 x != _copied_locations.end() && i != _editor->selection->markers.end();
3855 Location * location = _editor->find_location_from_marker (*i, is_start);
3859 if (location->locked()) {
3863 if (location->is_mark()) {
3864 location->set_start (((*x).location)->start());
3866 location->set (((*x).location)->start(), ((*x).location)->end());
3871 XMLNode &after = _editor->session()->locations()->get_state();
3872 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3873 _editor->commit_reversible_command ();
3877 MarkerDrag::aborted (bool movement_occured)
3879 if (!movement_occured) {
3883 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3885 /* move all markers to their original location */
3888 for (vector<Marker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
3891 Location * location = _editor->find_location_from_marker (*m, is_start);
3894 (*m)->set_position (is_start ? location->start() : location->end());
3901 MarkerDrag::update_item (Location*)
3906 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
3908 _cumulative_x_drag (0),
3909 _cumulative_y_drag (0)
3911 if (_zero_gain_fraction < 0.0) {
3912 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
3915 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
3917 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
3923 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
3925 Drag::start_grab (event, _editor->cursors()->fader);
3927 // start the grab at the center of the control point so
3928 // the point doesn't 'jump' to the mouse after the first drag
3929 _fixed_grab_x = _point->get_x();
3930 _fixed_grab_y = _point->get_y();
3932 float const fraction = 1 - (_point->get_y() / _point->line().height());
3934 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
3936 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
3938 _pushing = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
3940 if (!_point->can_slide ()) {
3941 _x_constrained = true;
3946 ControlPointDrag::motion (GdkEvent* event, bool)
3948 double dx = _drags->current_pointer_x() - last_pointer_x();
3949 double dy = current_pointer_y() - last_pointer_y();
3951 if (event->button.state & Keyboard::SecondaryModifier) {
3956 /* coordinate in pixels relative to the start of the region (for region-based automation)
3957 or track (for track-based automation) */
3958 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
3959 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
3961 // calculate zero crossing point. back off by .01 to stay on the
3962 // positive side of zero
3963 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
3965 // make sure we hit zero when passing through
3966 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
3970 if (_x_constrained) {
3973 if (_y_constrained) {
3977 _cumulative_x_drag = cx - _fixed_grab_x;
3978 _cumulative_y_drag = cy - _fixed_grab_y;
3982 cy = min ((double) _point->line().height(), cy);
3984 framepos_t cx_frames = _editor->pixel_to_sample (cx);
3986 if (!_x_constrained) {
3987 _editor->snap_to_with_modifier (cx_frames, event);
3990 cx_frames = min (cx_frames, _point->line().maximum_time());
3992 float const fraction = 1.0 - (cy / _point->line().height());
3994 _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
3996 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4000 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4002 if (!movement_occurred) {
4006 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
4007 _editor->reset_point_selection ();
4011 motion (event, false);
4014 _point->line().end_drag (_pushing, _final_index);
4015 _editor->commit_reversible_command ();
4019 ControlPointDrag::aborted (bool)
4021 _point->line().reset ();
4025 ControlPointDrag::active (Editing::MouseMode m)
4027 if (m == Editing::MouseDraw) {
4028 /* always active in mouse draw */
4032 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4033 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4036 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4039 _cumulative_y_drag (0)
4041 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4045 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4047 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4050 _item = &_line->grab_item ();
4052 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4053 origin, and ditto for y.
4056 double cx = event->button.x;
4057 double cy = event->button.y;
4059 _line->parent_group().canvas_to_item (cx, cy);
4061 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->samples_per_pixel);
4066 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
4067 /* no adjacent points */
4071 Drag::start_grab (event, _editor->cursors()->fader);
4073 /* store grab start in parent frame */
4078 double fraction = 1.0 - (cy / _line->height());
4080 _line->start_drag_line (before, after, fraction);
4082 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4086 LineDrag::motion (GdkEvent* event, bool)
4088 double dy = current_pointer_y() - last_pointer_y();
4090 if (event->button.state & Keyboard::SecondaryModifier) {
4094 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4096 _cumulative_y_drag = cy - _fixed_grab_y;
4099 cy = min ((double) _line->height(), cy);
4101 double const fraction = 1.0 - (cy / _line->height());
4104 /* we are ignoring x position for this drag, so we can just pass in anything */
4105 _line->drag_motion (0, fraction, true, false, ignored);
4107 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4111 LineDrag::finished (GdkEvent* event, bool movement_occured)
4113 if (movement_occured) {
4114 motion (event, false);
4115 _line->end_drag (false, 0);
4117 /* add a new control point on the line */
4119 AutomationTimeAxisView* atv;
4121 _line->end_drag (false, 0);
4123 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4124 framepos_t where = _editor->window_event_sample (event, 0, 0);
4125 atv->add_automation_event (event, where, event->button.y, false);
4129 _editor->commit_reversible_command ();
4133 LineDrag::aborted (bool)
4138 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4141 _cumulative_x_drag (0)
4143 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4147 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4149 Drag::start_grab (event);
4151 _line = reinterpret_cast<Line*> (_item);
4154 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4156 double cx = event->button.x;
4157 double cy = event->button.y;
4159 _item->parent()->canvas_to_item (cx, cy);
4161 /* store grab start in parent frame */
4162 _region_view_grab_x = cx;
4164 _before = *(float*) _item->get_data ("position");
4166 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4168 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4172 FeatureLineDrag::motion (GdkEvent*, bool)
4174 double dx = _drags->current_pointer_x() - last_pointer_x();
4176 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4178 _cumulative_x_drag += dx;
4180 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4189 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4191 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4193 float *pos = new float;
4196 _line->set_data ("position", pos);
4202 FeatureLineDrag::finished (GdkEvent*, bool)
4204 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4205 _arv->update_transient(_before, _before);
4209 FeatureLineDrag::aborted (bool)
4214 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4216 , _vertical_only (false)
4218 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4222 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4224 Drag::start_grab (event);
4225 show_verbose_cursor_time (adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid()));
4229 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4236 framepos_t const pf = adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid());
4238 framepos_t grab = grab_frame ();
4239 if (ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) {
4240 _editor->snap_to_with_modifier (grab, event);
4242 grab = raw_grab_frame ();
4245 /* base start and end on initial click position */
4255 if (current_pointer_y() < grab_y()) {
4256 y1 = current_pointer_y();
4259 y2 = current_pointer_y();
4263 if (start != end || y1 != y2) {
4265 double x1 = _editor->sample_to_pixel (start);
4266 double x2 = _editor->sample_to_pixel (end);
4267 const double min_dimension = 2.0;
4269 if (_vertical_only) {
4270 /* fixed 10 pixel width */
4274 x2 = min (x1 - min_dimension, x2);
4276 x2 = max (x1 + min_dimension, x2);
4281 y2 = min (y1 - min_dimension, y2);
4283 y2 = max (y1 + min_dimension, y2);
4286 /* translate rect into item space and set */
4288 ArdourCanvas::Rect r (x1, y1, x2, y2);
4290 /* this drag is a _trackview_only == true drag, so the y1 and
4291 * y2 (computed using current_pointer_y() and grab_y()) will be
4292 * relative to the top of the trackview group). The
4293 * rubberband rect has the same parent/scroll offset as the
4294 * the trackview group, so we can use the "r" rect directly
4295 * to set the shape of the rubberband.
4298 _editor->rubberband_rect->set (r);
4299 _editor->rubberband_rect->show();
4300 _editor->rubberband_rect->raise_to_top();
4302 show_verbose_cursor_time (pf);
4304 do_select_things (event, true);
4309 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4313 framepos_t grab = grab_frame ();
4314 framepos_t lpf = last_pointer_frame ();
4316 if (!ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) {
4317 grab = raw_grab_frame ();
4318 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4332 if (current_pointer_y() < grab_y()) {
4333 y1 = current_pointer_y();
4336 y2 = current_pointer_y();
4340 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4344 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4346 if (movement_occurred) {
4348 motion (event, false);
4349 do_select_things (event, false);
4355 bool do_deselect = true;
4356 MidiTimeAxisView* mtv;
4358 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4360 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4361 /* nothing selected */
4362 add_midi_region (mtv);
4363 do_deselect = false;
4367 /* do not deselect if Primary or Tertiary (toggle-select or
4368 * extend-select are pressed.
4371 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4372 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4379 _editor->rubberband_rect->hide();
4383 RubberbandSelectDrag::aborted (bool)
4385 _editor->rubberband_rect->hide ();
4388 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4389 : RegionDrag (e, i, p, v)
4391 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4395 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4397 Drag::start_grab (event, cursor);
4399 show_verbose_cursor_time (adjusted_current_frame (event));
4403 TimeFXDrag::motion (GdkEvent* event, bool)
4405 RegionView* rv = _primary;
4406 StreamView* cv = rv->get_time_axis_view().view ();
4408 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4409 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4410 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4412 framepos_t const pf = adjusted_current_frame (event);
4414 if (pf > rv->region()->position()) {
4415 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4418 show_verbose_cursor_time (pf);
4422 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4424 _primary->get_time_axis_view().hide_timestretch ();
4426 if (!movement_occurred) {
4430 if (last_pointer_frame() < _primary->region()->position()) {
4431 /* backwards drag of the left edge - not usable */
4435 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
4437 float percentage = (double) newlen / (double) _primary->region()->length();
4439 #ifndef USE_RUBBERBAND
4440 // Soundtouch uses percentage / 100 instead of normal (/ 1)
4441 if (_primary->region()->data_type() == DataType::AUDIO) {
4442 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4446 if (!_editor->get_selection().regions.empty()) {
4447 /* primary will already be included in the selection, and edit
4448 group shared editing will propagate selection across
4449 equivalent regions, so just use the current region
4453 if (_editor->time_stretch (_editor->get_selection().regions, percentage) == -1) {
4454 error << _("An error occurred while executing time stretch operation") << endmsg;
4460 TimeFXDrag::aborted (bool)
4462 _primary->get_time_axis_view().hide_timestretch ();
4465 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
4468 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
4472 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4474 Drag::start_grab (event);
4478 ScrubDrag::motion (GdkEvent* /*event*/, bool)
4480 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
4484 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4486 if (movement_occurred && _editor->session()) {
4487 /* make sure we stop */
4488 _editor->session()->request_transport_speed (0.0);
4493 ScrubDrag::aborted (bool)
4498 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4502 , _time_selection_at_start (!_editor->get_selection().time.empty())
4504 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
4506 if (_time_selection_at_start) {
4507 start_at_start = _editor->get_selection().time.start();
4508 end_at_start = _editor->get_selection().time.end_frame();
4513 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
4515 if (_editor->session() == 0) {
4519 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4521 switch (_operation) {
4522 case CreateSelection:
4523 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4528 cursor = _editor->cursors()->selector;
4529 Drag::start_grab (event, cursor);
4532 case SelectionStartTrim:
4533 if (_editor->clicked_axisview) {
4534 _editor->clicked_axisview->order_selection_trims (_item, true);
4536 Drag::start_grab (event, _editor->cursors()->left_side_trim);
4539 case SelectionEndTrim:
4540 if (_editor->clicked_axisview) {
4541 _editor->clicked_axisview->order_selection_trims (_item, false);
4543 Drag::start_grab (event, _editor->cursors()->right_side_trim);
4547 Drag::start_grab (event, cursor);
4550 case SelectionExtend:
4551 Drag::start_grab (event, cursor);
4555 if (_operation == SelectionMove) {
4556 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
4558 show_verbose_cursor_time (adjusted_current_frame (event));
4563 SelectionDrag::setup_pointer_frame_offset ()
4565 switch (_operation) {
4566 case CreateSelection:
4567 _pointer_frame_offset = 0;
4570 case SelectionStartTrim:
4572 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
4575 case SelectionEndTrim:
4576 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
4579 case SelectionExtend:
4585 SelectionDrag::motion (GdkEvent* event, bool first_move)
4587 framepos_t start = 0;
4589 framecnt_t length = 0;
4590 framecnt_t distance = 0;
4592 framepos_t const pending_position = adjusted_current_frame (event);
4594 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
4598 switch (_operation) {
4599 case CreateSelection:
4601 framepos_t grab = grab_frame ();
4604 grab = adjusted_current_frame (event, false);
4605 if (grab < pending_position) {
4606 _editor->snap_to (grab, RoundDownMaybe);
4608 _editor->snap_to (grab, RoundUpMaybe);
4612 if (pending_position < grab) {
4613 start = pending_position;
4616 end = pending_position;
4620 /* first drag: Either add to the selection
4621 or create a new selection
4628 /* adding to the selection */
4629 _editor->set_selected_track_as_side_effect (Selection::Add);
4630 _editor->clicked_selection = _editor->selection->add (start, end);
4637 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
4638 _editor->set_selected_track_as_side_effect (Selection::Set);
4641 _editor->clicked_selection = _editor->selection->set (start, end);
4645 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
4646 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
4647 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
4649 _editor->selection->add (atest);
4653 /* select all tracks within the rectangle that we've marked out so far */
4654 TrackViewList new_selection;
4655 TrackViewList& all_tracks (_editor->track_views);
4657 ArdourCanvas::Coord const top = grab_y();
4658 ArdourCanvas::Coord const bottom = current_pointer_y();
4660 if (top >= 0 && bottom >= 0) {
4662 //first, find the tracks that are covered in the y range selection
4663 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
4664 if ((*i)->covered_by_y_range (top, bottom)) {
4665 new_selection.push_back (*i);
4669 //now find any tracks that are GROUPED with the tracks we selected
4670 TrackViewList grouped_add = new_selection;
4671 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
4672 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
4673 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::select.property_id) ) {
4674 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
4675 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
4676 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
4677 grouped_add.push_back (*j);
4682 //now compare our list with the current selection, and add or remove as necessary
4683 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
4684 TrackViewList tracks_to_add;
4685 TrackViewList tracks_to_remove;
4686 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
4687 if ( !_editor->selection->tracks.contains ( *i ) )
4688 tracks_to_add.push_back ( *i );
4689 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
4690 if ( !grouped_add.contains ( *i ) )
4691 tracks_to_remove.push_back ( *i );
4692 _editor->selection->add(tracks_to_add);
4693 _editor->selection->remove(tracks_to_remove);
4699 case SelectionStartTrim:
4701 start = _editor->selection->time[_editor->clicked_selection].start;
4702 end = _editor->selection->time[_editor->clicked_selection].end;
4704 if (pending_position > end) {
4707 start = pending_position;
4711 case SelectionEndTrim:
4713 start = _editor->selection->time[_editor->clicked_selection].start;
4714 end = _editor->selection->time[_editor->clicked_selection].end;
4716 if (pending_position < start) {
4719 end = pending_position;
4726 start = _editor->selection->time[_editor->clicked_selection].start;
4727 end = _editor->selection->time[_editor->clicked_selection].end;
4729 length = end - start;
4730 distance = pending_position - start;
4731 start = pending_position;
4732 _editor->snap_to (start);
4734 end = start + length;
4738 case SelectionExtend:
4743 switch (_operation) {
4745 if (_time_selection_at_start) {
4746 _editor->selection->move_time (distance);
4750 _editor->selection->replace (_editor->clicked_selection, start, end);
4754 if (_operation == SelectionMove) {
4755 show_verbose_cursor_time(start);
4757 show_verbose_cursor_time(pending_position);
4762 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
4764 Session* s = _editor->session();
4766 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
4767 if (movement_occurred) {
4768 motion (event, false);
4769 /* XXX this is not object-oriented programming at all. ick */
4770 if (_editor->selection->time.consolidate()) {
4771 _editor->selection->TimeChanged ();
4774 /* XXX what if its a music time selection? */
4776 if ( s->get_play_range() && s->transport_rolling() ) {
4777 s->request_play_range (&_editor->selection->time, true);
4779 if (ARDOUR_UI::config()->get_follow_edits() && !s->transport_rolling()) {
4780 if (_operation == SelectionEndTrim)
4781 _editor->maybe_locate_with_edit_preroll( _editor->get_selection().time.end_frame());
4783 s->request_locate (_editor->get_selection().time.start());
4789 /* just a click, no pointer movement.
4792 if (_operation == SelectionExtend) {
4793 if (_time_selection_at_start) {
4794 framepos_t pos = adjusted_current_frame (event, false);
4795 framepos_t start = min (pos, start_at_start);
4796 framepos_t end = max (pos, end_at_start);
4797 _editor->selection->set (start, end);
4800 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4801 if (_editor->clicked_selection) {
4802 _editor->selection->remove (_editor->clicked_selection);
4805 if (!_editor->clicked_selection) {
4806 _editor->selection->clear_time();
4811 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
4812 _editor->selection->set (_editor->clicked_axisview);
4815 if (s && s->get_play_range () && s->transport_rolling()) {
4816 s->request_stop (false, false);
4821 _editor->stop_canvas_autoscroll ();
4822 _editor->clicked_selection = 0;
4823 _editor->commit_reversible_selection_op ();
4827 SelectionDrag::aborted (bool)
4832 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4833 : Drag (e, i, false),
4837 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
4839 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
4840 ArdourCanvas::Rect (0.0, 0.0, 0.0,
4841 physical_screen_height (_editor->get_window())));
4842 _drag_rect->hide ();
4844 _drag_rect->set_fill_color (ARDOUR_UI::config()->color ("range drag rect"));
4845 _drag_rect->set_outline_color (ARDOUR_UI::config()->color ("range drag rect"));
4848 RangeMarkerBarDrag::~RangeMarkerBarDrag()
4850 /* normal canvas items will be cleaned up when their parent group is deleted. But
4851 this item is created as the child of a long-lived parent group, and so we
4852 need to explicitly delete it.
4858 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4860 if (_editor->session() == 0) {
4864 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4866 if (!_editor->temp_location) {
4867 _editor->temp_location = new Location (*_editor->session());
4870 switch (_operation) {
4871 case CreateSkipMarker:
4872 case CreateRangeMarker:
4873 case CreateTransportMarker:
4874 case CreateCDMarker:
4876 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
4881 cursor = _editor->cursors()->selector;
4885 Drag::start_grab (event, cursor);
4887 show_verbose_cursor_time (adjusted_current_frame (event));
4891 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
4893 framepos_t start = 0;
4895 ArdourCanvas::Rectangle *crect;
4897 switch (_operation) {
4898 case CreateSkipMarker:
4899 crect = _editor->range_bar_drag_rect;
4901 case CreateRangeMarker:
4902 crect = _editor->range_bar_drag_rect;
4904 case CreateTransportMarker:
4905 crect = _editor->transport_bar_drag_rect;
4907 case CreateCDMarker:
4908 crect = _editor->cd_marker_bar_drag_rect;
4911 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
4916 framepos_t const pf = adjusted_current_frame (event);
4918 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
4919 framepos_t grab = grab_frame ();
4920 _editor->snap_to (grab);
4922 if (pf < grab_frame()) {
4930 /* first drag: Either add to the selection
4931 or create a new selection.
4936 _editor->temp_location->set (start, end);
4940 update_item (_editor->temp_location);
4942 //_drag_rect->raise_to_top();
4948 _editor->temp_location->set (start, end);
4950 double x1 = _editor->sample_to_pixel (start);
4951 double x2 = _editor->sample_to_pixel (end);
4955 update_item (_editor->temp_location);
4958 show_verbose_cursor_time (pf);
4963 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
4965 Location * newloc = 0;
4969 if (movement_occurred) {
4970 motion (event, false);
4973 switch (_operation) {
4974 case CreateSkipMarker:
4975 case CreateRangeMarker:
4976 case CreateCDMarker:
4978 XMLNode &before = _editor->session()->locations()->get_state();
4979 if (_operation == CreateSkipMarker) {
4980 _editor->begin_reversible_command (_("new skip marker"));
4981 _editor->session()->locations()->next_available_name(rangename,_("skip"));
4982 flags = Location::IsRangeMarker | Location::IsSkip;
4983 _editor->range_bar_drag_rect->hide();
4984 } else if (_operation == CreateCDMarker) {
4985 _editor->session()->locations()->next_available_name(rangename, _("CD"));
4986 _editor->begin_reversible_command (_("new CD marker"));
4987 flags = Location::IsRangeMarker | Location::IsCDMarker;
4988 _editor->cd_marker_bar_drag_rect->hide();
4990 _editor->begin_reversible_command (_("new skip marker"));
4991 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
4992 flags = Location::IsRangeMarker;
4993 _editor->range_bar_drag_rect->hide();
4995 newloc = new Location (
4996 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
4999 _editor->session()->locations()->add (newloc, true);
5000 XMLNode &after = _editor->session()->locations()->get_state();
5001 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5002 _editor->commit_reversible_command ();
5006 case CreateTransportMarker:
5007 // popup menu to pick loop or punch
5008 _editor->new_transport_marker_context_menu (&event->button, _item);
5014 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5016 if (_operation == CreateTransportMarker) {
5018 /* didn't drag, so just locate */
5020 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5022 } else if (_operation == CreateCDMarker) {
5024 /* didn't drag, but mark is already created so do
5027 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5032 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5034 if (end == max_framepos) {
5035 end = _editor->session()->current_end_frame ();
5038 if (start == max_framepos) {
5039 start = _editor->session()->current_start_frame ();
5042 switch (_editor->mouse_mode) {
5044 /* find the two markers on either side and then make the selection from it */
5045 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5049 /* find the two markers on either side of the click and make the range out of it */
5050 _editor->selection->set (start, end);
5059 _editor->stop_canvas_autoscroll ();
5063 RangeMarkerBarDrag::aborted (bool movement_occured)
5065 if (movement_occured) {
5066 _drag_rect->hide ();
5071 RangeMarkerBarDrag::update_item (Location* location)
5073 double const x1 = _editor->sample_to_pixel (location->start());
5074 double const x2 = _editor->sample_to_pixel (location->end());
5076 _drag_rect->set_x0 (x1);
5077 _drag_rect->set_x1 (x2);
5080 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5082 , _cumulative_dx (0)
5083 , _cumulative_dy (0)
5085 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5087 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5089 _region = &_primary->region_view ();
5090 _note_height = _region->midi_stream_view()->note_height ();
5094 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5096 Drag::start_grab (event);
5098 if (!(_was_selected = _primary->selected())) {
5100 /* tertiary-click means extend selection - we'll do that on button release,
5101 so don't add it here, because otherwise we make it hard to figure
5102 out the "extend-to" range.
5105 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5108 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5111 _region->note_selected (_primary, true);
5113 _region->unique_select (_primary);
5116 _editor->begin_reversible_selection_op(X_("Select Note Press"));
5117 _editor->commit_reversible_selection_op();
5122 /** @return Current total drag x change in frames */
5124 NoteDrag::total_dx () const
5127 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5129 /* primary note time */
5130 frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
5132 /* new time of the primary note in session frames */
5133 frameoffset_t st = n + dx;
5135 framepos_t const rp = _region->region()->position ();
5137 /* prevent the note being dragged earlier than the region's position */
5140 /* snap and return corresponding delta */
5141 return _region->snap_frame_to_frame (st - rp) + rp - n;
5144 /** @return Current total drag y change in note number */
5146 NoteDrag::total_dy () const
5148 MidiStreamView* msv = _region->midi_stream_view ();
5149 double const y = _region->midi_view()->y_position ();
5150 /* new current note */
5151 uint8_t n = msv->y_to_note (current_pointer_y () - y);
5153 n = max (msv->lowest_note(), n);
5154 n = min (msv->highest_note(), n);
5155 /* and work out delta */
5156 return n - msv->y_to_note (grab_y() - y);
5160 NoteDrag::motion (GdkEvent *, bool)
5162 /* Total change in x and y since the start of the drag */
5163 frameoffset_t const dx = total_dx ();
5164 int8_t const dy = total_dy ();
5166 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5167 double const tdx = _editor->sample_to_pixel (dx) - _cumulative_dx;
5168 double const tdy = -dy * _note_height - _cumulative_dy;
5171 _cumulative_dx += tdx;
5172 _cumulative_dy += tdy;
5174 int8_t note_delta = total_dy();
5176 _region->move_selection (tdx, tdy, note_delta);
5178 /* the new note value may be the same as the old one, but we
5179 * don't know what that means because the selection may have
5180 * involved more than one note and we might be doing something
5181 * odd with them. so show the note value anyway, always.
5185 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5187 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
5188 (int) floor ((double)new_note));
5190 show_verbose_cursor_text (buf);
5195 NoteDrag::finished (GdkEvent* ev, bool moved)
5198 /* no motion - select note */
5200 if (_editor->current_mouse_mode() == Editing::MouseObject ||
5201 _editor->current_mouse_mode() == Editing::MouseDraw) {
5203 bool changed = false;
5205 if (_was_selected) {
5206 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5208 _region->note_deselected (_primary);
5212 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5213 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5215 if (!extend && !add && _region->selection_size() > 1) {
5216 _region->unique_select (_primary);
5218 } else if (extend) {
5219 _region->note_selected (_primary, true, true);
5222 /* it was added during button press */
5227 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5228 _editor->commit_reversible_selection_op();
5232 _region->note_dropped (_primary, total_dx(), total_dy());
5237 NoteDrag::aborted (bool)
5242 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5243 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5244 : Drag (editor, atv->base_item ())
5246 , _y_origin (atv->y_position())
5247 , _nothing_to_drag (false)
5249 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5250 setup (atv->lines ());
5253 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5254 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5255 : Drag (editor, rv->get_canvas_group ())
5257 , _y_origin (rv->get_time_axis_view().y_position())
5258 , _nothing_to_drag (false)
5261 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5263 list<boost::shared_ptr<AutomationLine> > lines;
5265 AudioRegionView* audio_view;
5266 AutomationRegionView* automation_view;
5267 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5268 lines.push_back (audio_view->get_gain_line ());
5269 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5270 lines.push_back (automation_view->line ());
5273 error << _("Automation range drag created for invalid region type") << endmsg;
5279 /** @param lines AutomationLines to drag.
5280 * @param offset Offset from the session start to the points in the AutomationLines.
5283 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5285 /* find the lines that overlap the ranges being dragged */
5286 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5287 while (i != lines.end ()) {
5288 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5291 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5293 /* check this range against all the AudioRanges that we are using */
5294 list<AudioRange>::const_iterator k = _ranges.begin ();
5295 while (k != _ranges.end()) {
5296 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5302 /* add it to our list if it overlaps at all */
5303 if (k != _ranges.end()) {
5308 _lines.push_back (n);
5314 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5318 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5320 return 1.0 - ((global_y - _y_origin) / line->height());
5324 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5326 const double v = list->eval(x);
5327 return _integral ? rint(v) : v;
5331 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5333 Drag::start_grab (event, cursor);
5335 /* Get line states before we start changing things */
5336 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5337 i->state = &i->line->get_state ();
5338 i->original_fraction = y_fraction (i->line, current_pointer_y());
5341 if (_ranges.empty()) {
5343 /* No selected time ranges: drag all points */
5344 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5345 uint32_t const N = i->line->npoints ();
5346 for (uint32_t j = 0; j < N; ++j) {
5347 i->points.push_back (i->line->nth (j));
5353 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5355 framecnt_t const half = (i->start + i->end) / 2;
5357 /* find the line that this audio range starts in */
5358 list<Line>::iterator j = _lines.begin();
5359 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5363 if (j != _lines.end()) {
5364 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5366 /* j is the line that this audio range starts in; fade into it;
5367 64 samples length plucked out of thin air.
5370 framepos_t a = i->start + 64;
5375 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5376 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5378 the_list->editor_add (p, value (the_list, p));
5379 the_list->editor_add (q, value (the_list, q));
5382 /* same thing for the end */
5385 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5389 if (j != _lines.end()) {
5390 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5392 /* j is the line that this audio range starts in; fade out of it;
5393 64 samples length plucked out of thin air.
5396 framepos_t b = i->end - 64;
5401 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
5402 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
5404 the_list->editor_add (p, value (the_list, p));
5405 the_list->editor_add (q, value (the_list, q));
5409 _nothing_to_drag = true;
5411 /* Find all the points that should be dragged and put them in the relevant
5412 points lists in the Line structs.
5415 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5417 uint32_t const N = i->line->npoints ();
5418 for (uint32_t j = 0; j < N; ++j) {
5420 /* here's a control point on this line */
5421 ControlPoint* p = i->line->nth (j);
5422 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
5424 /* see if it's inside a range */
5425 list<AudioRange>::const_iterator k = _ranges.begin ();
5426 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
5430 if (k != _ranges.end()) {
5431 /* dragging this point */
5432 _nothing_to_drag = false;
5433 i->points.push_back (p);
5439 if (_nothing_to_drag) {
5443 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5444 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
5449 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
5451 if (_nothing_to_drag) {
5455 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
5456 float const f = y_fraction (l->line, current_pointer_y());
5457 /* we are ignoring x position for this drag, so we can just pass in anything */
5459 l->line->drag_motion (0, f, true, false, ignored);
5460 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, f));
5465 AutomationRangeDrag::finished (GdkEvent* event, bool)
5467 if (_nothing_to_drag) {
5471 motion (event, false);
5472 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5473 i->line->end_drag (false, 0);
5476 _editor->commit_reversible_command ();
5480 AutomationRangeDrag::aborted (bool)
5482 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5487 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
5489 , initial_time_axis_view (itav)
5491 /* note that time_axis_view may be null if the regionview was created
5492 * as part of a copy operation.
5494 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
5495 layer = v->region()->layer ();
5496 initial_y = v->get_canvas_group()->position().y;
5497 initial_playlist = v->region()->playlist ();
5498 initial_position = v->region()->position ();
5499 initial_end = v->region()->position () + v->region()->length ();
5502 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
5503 : Drag (e, i->canvas_item ())
5506 , _cumulative_dx (0)
5508 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
5509 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
5514 PatchChangeDrag::motion (GdkEvent* ev, bool)
5516 framepos_t f = adjusted_current_frame (ev);
5517 boost::shared_ptr<Region> r = _region_view->region ();
5518 f = max (f, r->position ());
5519 f = min (f, r->last_frame ());
5521 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
5522 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
5523 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
5524 _cumulative_dx = dxu;
5528 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
5530 if (!movement_occurred) {
5534 boost::shared_ptr<Region> r (_region_view->region ());
5535 framepos_t f = adjusted_current_frame (ev);
5536 f = max (f, r->position ());
5537 f = min (f, r->last_frame ());
5539 _region_view->move_patch_change (
5541 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
5546 PatchChangeDrag::aborted (bool)
5548 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
5552 PatchChangeDrag::setup_pointer_frame_offset ()
5554 boost::shared_ptr<Region> region = _region_view->region ();
5555 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
5558 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
5559 : RubberbandSelectDrag (e, rv->get_canvas_group ())
5566 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
5568 _region_view->update_drag_selection (
5570 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
5574 MidiRubberbandSelectDrag::deselect_things ()
5579 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
5580 : RubberbandSelectDrag (e, rv->get_canvas_group ())
5583 _vertical_only = true;
5587 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
5589 double const y = _region_view->midi_view()->y_position ();
5591 y1 = max (0.0, y1 - y);
5592 y2 = max (0.0, y2 - y);
5594 _region_view->update_vertical_drag_selection (
5597 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
5602 MidiVerticalSelectDrag::deselect_things ()
5607 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5608 : RubberbandSelectDrag (e, i)
5614 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
5616 if (drag_in_progress) {
5617 /* We just want to select things at the end of the drag, not during it */
5621 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
5623 _editor->begin_reversible_selection_op (X_("rubberband selection"));
5625 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
5627 _editor->commit_reversible_selection_op ();
5631 EditorRubberbandSelectDrag::deselect_things ()
5633 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
5635 _editor->selection->clear_tracks();
5636 _editor->selection->clear_regions();
5637 _editor->selection->clear_points ();
5638 _editor->selection->clear_lines ();
5639 _editor->selection->clear_midi_notes ();
5641 _editor->commit_reversible_selection_op();
5644 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
5649 _note[0] = _note[1] = 0;
5652 NoteCreateDrag::~NoteCreateDrag ()
5658 NoteCreateDrag::grid_frames (framepos_t t) const
5661 Evoral::Beats grid_beats = _editor->get_grid_type_as_beats (success, t);
5663 grid_beats = Evoral::Beats(1);
5666 return _region_view->region_beats_to_region_frames (grid_beats);
5670 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5672 Drag::start_grab (event, cursor);
5674 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
5676 framepos_t pf = _drags->current_pointer_frame ();
5677 framecnt_t const g = grid_frames (pf);
5679 /* Hack so that we always snap to the note that we are over, instead of snapping
5680 to the next one if we're more than halfway through the one we're over.
5682 if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
5686 _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
5687 _note[1] = _note[0];
5689 MidiStreamView* sv = _region_view->midi_stream_view ();
5690 double const x = _editor->sample_to_pixel (_note[0]);
5691 double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
5693 _drag_rect->set (ArdourCanvas::Rect (x, y, x, y + floor (_region_view->midi_stream_view()->note_height ())));
5694 _drag_rect->set_outline_all ();
5695 _drag_rect->set_outline_color (0xffffff99);
5696 _drag_rect->set_fill_color (0xffffff66);
5700 NoteCreateDrag::motion (GdkEvent* event, bool)
5702 _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
5703 double const x0 = _editor->sample_to_pixel (_note[0]);
5704 double const x1 = _editor->sample_to_pixel (_note[1]);
5705 _drag_rect->set_x0 (std::min(x0, x1));
5706 _drag_rect->set_x1 (std::max(x0, x1));
5710 NoteCreateDrag::finished (GdkEvent*, bool had_movement)
5712 if (!had_movement) {
5716 framepos_t const start = min (_note[0], _note[1]);
5717 framecnt_t length = (framecnt_t) fabs ((double)(_note[0] - _note[1]));
5719 framecnt_t const g = grid_frames (start);
5720 Evoral::Beats const one_tick = Evoral::Beats::ticks(1);
5722 if (_editor->snap_mode() == SnapNormal && length < g) {
5726 Evoral::Beats length_beats = max (
5727 one_tick, _region_view->region_frames_to_region_beats (length) - one_tick);
5729 _region_view->create_note_at (start, _drag_rect->y0(), length_beats, false);
5733 NoteCreateDrag::y_to_region (double y) const
5736 _region_view->get_canvas_group()->canvas_to_item (x, y);
5741 NoteCreateDrag::aborted (bool)
5746 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
5751 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
5755 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
5757 Drag::start_grab (event, cursor);
5761 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
5767 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
5770 distance = _drags->current_pointer_x() - grab_x();
5771 len = ar->fade_in()->back()->when;
5773 distance = grab_x() - _drags->current_pointer_x();
5774 len = ar->fade_out()->back()->when;
5777 /* how long should it be ? */
5779 new_length = len + _editor->pixel_to_sample (distance);
5781 /* now check with the region that this is legal */
5783 new_length = ar->verify_xfade_bounds (new_length, start);
5786 arv->reset_fade_in_shape_width (ar, new_length);
5788 arv->reset_fade_out_shape_width (ar, new_length);
5793 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
5799 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
5802 distance = _drags->current_pointer_x() - grab_x();
5803 len = ar->fade_in()->back()->when;
5805 distance = grab_x() - _drags->current_pointer_x();
5806 len = ar->fade_out()->back()->when;
5809 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
5811 _editor->begin_reversible_command ("xfade trim");
5812 ar->playlist()->clear_owned_changes ();
5815 ar->set_fade_in_length (new_length);
5817 ar->set_fade_out_length (new_length);
5820 /* Adjusting the xfade may affect other regions in the playlist, so we need
5821 to get undo Commands from the whole playlist rather than just the
5825 vector<Command*> cmds;
5826 ar->playlist()->rdiff (cmds);
5827 _editor->session()->add_commands (cmds);
5828 _editor->commit_reversible_command ();
5833 CrossfadeEdgeDrag::aborted (bool)
5836 // arv->redraw_start_xfade ();
5838 // arv->redraw_end_xfade ();
5842 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
5843 : Drag (e, item, true)
5844 , line (new EditorCursor (*e))
5846 line->set_position (pos);
5850 RegionCutDrag::~RegionCutDrag ()
5856 RegionCutDrag::motion (GdkEvent*, bool)
5858 framepos_t where = _drags->current_pointer_frame();
5859 _editor->snap_to (where);
5861 line->set_position (where);
5865 RegionCutDrag::finished (GdkEvent*, bool)
5867 _editor->get_track_canvas()->canvas()->re_enter();
5869 framepos_t pos = _drags->current_pointer_frame();
5873 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
5879 _editor->split_regions_at (pos, rs);
5883 RegionCutDrag::aborted (bool)