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 "ui_config.h"
68 #include "verbose_cursor.h"
71 using namespace ARDOUR;
74 using namespace Gtkmm2ext;
75 using namespace Editing;
76 using namespace ArdourCanvas;
78 using Gtkmm2ext::Keyboard;
80 double ControlPointDrag::_zero_gain_fraction = -1.0;
82 DragManager::DragManager (Editor* e)
85 , _current_pointer_x (0.0)
86 , _current_pointer_y (0.0)
87 , _current_pointer_frame (0)
88 , _old_follow_playhead (false)
92 DragManager::~DragManager ()
97 /** Call abort for each active drag */
103 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
108 if (!_drags.empty ()) {
109 _editor->set_follow_playhead (_old_follow_playhead, false);
113 _editor->abort_reversible_command();
119 DragManager::add (Drag* d)
121 d->set_manager (this);
122 _drags.push_back (d);
126 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
128 d->set_manager (this);
129 _drags.push_back (d);
134 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
136 /* Prevent follow playhead during the drag to be nice to the user */
137 _old_follow_playhead = _editor->follow_playhead ();
138 _editor->set_follow_playhead (false);
140 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
142 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
143 (*i)->start_grab (e, c);
147 /** Call end_grab for each active drag.
148 * @return true if any drag reported movement having occurred.
151 DragManager::end_grab (GdkEvent* e)
156 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
157 bool const t = (*i)->end_grab (e);
168 _editor->set_follow_playhead (_old_follow_playhead, false);
174 DragManager::mark_double_click ()
176 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
177 (*i)->set_double_click (true);
182 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
186 /* calling this implies that we expect the event to have canvas
189 * Can we guarantee that this is true?
192 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
194 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
195 bool const t = (*i)->motion_handler (e, from_autoscroll);
196 /* run all handlers; return true if at least one of them
197 returns true (indicating that the event has been handled).
209 DragManager::have_item (ArdourCanvas::Item* i) const
211 list<Drag*>::const_iterator j = _drags.begin ();
212 while (j != _drags.end() && (*j)->item () != i) {
216 return j != _drags.end ();
219 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
223 , _pointer_frame_offset (0)
224 , _x_constrained (false)
225 , _y_constrained (false)
226 , _was_rolling (false)
227 , _trackview_only (trackview_only)
228 , _move_threshold_passed (false)
229 , _starting_point_passed (false)
230 , _initially_vertical (false)
231 , _was_double_click (false)
234 , _last_pointer_x (0.0)
235 , _last_pointer_y (0.0)
236 , _raw_grab_frame (0)
238 , _last_pointer_frame (0)
245 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
251 _cursor_ctx = CursorContext::create (*_editor, cursor);
253 _cursor_ctx->change (cursor);
260 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
263 /* we set up x/y dragging constraints on first move */
265 _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::snap_delta (guint state) const
349 if (ArdourKeyboard::indicates_snap_delta (state)) {
357 Drag::current_pointer_x() const
359 return _drags->current_pointer_x ();
363 Drag::current_pointer_y () const
365 if (!_trackview_only) {
366 return _drags->current_pointer_y ();
369 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
373 Drag::setup_snap_delta (framepos_t pos)
375 framepos_t temp = pos;
376 _editor->snap_to (temp, ARDOUR::RoundNearest, false, true);
377 _snap_delta = temp - pos;
381 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
383 /* check to see if we have moved in any way that matters since the last motion event */
384 if (_move_threshold_passed &&
385 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
386 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
390 pair<framecnt_t, int> const threshold = move_threshold ();
392 bool const old_move_threshold_passed = _move_threshold_passed;
394 if (!_move_threshold_passed) {
396 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
397 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
399 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
402 if (active (_editor->mouse_mode) && _move_threshold_passed) {
404 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
406 if (old_move_threshold_passed != _move_threshold_passed) {
410 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
411 _initially_vertical = true;
413 _initially_vertical = false;
415 /** check constraints for this drag.
416 * Note that the current convention is to use "contains" for
417 * key modifiers during motion and "equals" when initiating a drag.
418 * In this case we haven't moved yet, so "equals" applies here.
420 if (Config->get_edit_mode() != Lock) {
421 if (event->motion.state & Gdk::BUTTON2_MASK) {
422 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
423 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::constraint_modifier ())) {
424 _x_constrained = false;
425 _y_constrained = true;
427 _x_constrained = true;
428 _y_constrained = false;
430 } else if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::constraint_modifier ())) {
431 // if dragging normally, the motion is constrained to the first direction of movement.
432 if (_initially_vertical) {
433 _x_constrained = true;
434 _y_constrained = false;
436 _x_constrained = false;
437 _y_constrained = true;
441 if (event->button.state & Gdk::BUTTON2_MASK) {
442 _x_constrained = false;
444 _x_constrained = true;
446 _y_constrained = false;
450 if (!from_autoscroll) {
451 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
454 if (!_editor->autoscroll_active() || from_autoscroll) {
457 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
459 motion (event, first_move && !_starting_point_passed);
461 if (first_move && !_starting_point_passed) {
462 _starting_point_passed = true;
465 _last_pointer_x = _drags->current_pointer_x ();
466 _last_pointer_y = current_pointer_y ();
467 _last_pointer_frame = adjusted_current_frame (event, false);
477 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
485 aborted (_move_threshold_passed);
487 _editor->stop_canvas_autoscroll ();
488 _editor->verbose_cursor()->hide ();
492 Drag::show_verbose_cursor_time (framepos_t frame)
494 _editor->verbose_cursor()->set_time (frame);
495 _editor->verbose_cursor()->show ();
499 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
501 _editor->verbose_cursor()->set_duration (start, end);
502 _editor->verbose_cursor()->show ();
506 Drag::show_verbose_cursor_text (string const & text)
508 _editor->verbose_cursor()->set (text);
509 _editor->verbose_cursor()->show ();
512 boost::shared_ptr<Region>
513 Drag::add_midi_region (MidiTimeAxisView* view, bool commit)
515 if (_editor->session()) {
516 const TempoMap& map (_editor->session()->tempo_map());
517 framecnt_t pos = grab_frame();
518 /* not that the frame rate used here can be affected by pull up/down which
521 framecnt_t len = map.frame_at_beat (map.beat_at_frame (pos) + 1.0) - pos;
522 return view->add_region (grab_frame(), len, commit);
525 return boost::shared_ptr<Region>();
528 struct PresentationInfoTimeAxisViewSorter {
529 bool operator() (TimeAxisView* a, TimeAxisView* b) {
530 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
531 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
532 /* anything not a route goes at the end */
540 /* XXXX pointer comparison. Should use
541 presentation_info in a time axis view
545 return ra->route()->presentation_info () < rb->route()->presentation_info();
549 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
554 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
556 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
557 as some of the regions we are dragging may be on such tracks.
560 TrackViewList track_views = _editor->track_views;
561 track_views.sort (PresentationInfoTimeAxisViewSorter ());
563 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
564 _time_axis_views.push_back (*i);
566 TimeAxisView::Children children_list = (*i)->get_child_list ();
567 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
568 _time_axis_views.push_back (j->get());
572 /* the list of views can be empty at this point if this is a region list-insert drag
575 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
576 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
579 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
583 RegionDrag::region_going_away (RegionView* v)
585 list<DraggingView>::iterator i = _views.begin ();
586 while (i != _views.end() && i->view != v) {
590 if (i != _views.end()) {
595 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
596 * or -1 if it is not found.
599 RegionDrag::find_time_axis_view (TimeAxisView* t) const
602 int const N = _time_axis_views.size ();
603 while (i < N && _time_axis_views[i] != t) {
607 if (_time_axis_views[i] != t) {
614 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
615 : RegionDrag (e, i, p, v)
617 , _ignore_video_lock (false)
619 , _last_pointer_time_axis_view (0)
620 , _last_pointer_layer (0)
625 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
629 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
631 Drag::start_grab (event, cursor);
632 setup_snap_delta (_last_frame_position);
634 show_verbose_cursor_time (_last_frame_position);
636 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
638 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
639 assert(_last_pointer_time_axis_view >= 0);
640 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
643 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
644 _ignore_video_lock = true;
648 /* cross track dragging seems broken here. disabled for now. */
649 _y_constrained = true;
654 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
656 /* compute the amount of pointer motion in frames, and where
657 the region would be if we moved it by that much.
659 *pending_region_position = adjusted_frame (_drags->current_pointer_frame (), event, false);
661 framepos_t sync_frame;
662 framecnt_t sync_offset;
665 sync_offset = _primary->region()->sync_offset (sync_dir);
667 /* we don't handle a sync point that lies before zero.
669 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
671 framecnt_t const sd = snap_delta (event->button.state);
672 sync_frame = *pending_region_position + (sync_dir * sync_offset) + sd;
674 _editor->snap_to_with_modifier (sync_frame, event);
676 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - sd;
679 *pending_region_position = _last_frame_position;
682 if (*pending_region_position > max_framepos - _primary->region()->length()) {
683 *pending_region_position = _last_frame_position;
688 bool const x_move_allowed = !_x_constrained;
690 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
692 /* x movement since last time (in pixels) */
693 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
695 /* total x movement */
696 framecnt_t total_dx = *pending_region_position;
697 if (regions_came_from_canvas()) {
698 total_dx = total_dx - grab_frame ();
701 /* check that no regions have gone off the start of the session */
702 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
703 if ((i->view->region()->position() + total_dx) < 0) {
705 *pending_region_position = _last_frame_position;
716 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
722 const int tavsize = _time_axis_views.size();
723 const int dt = delta > 0 ? +1 : -1;
725 int target = start + delta - skip;
727 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
728 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
730 while (current >= 0 && current != target) {
732 if (current < 0 && dt < 0) {
735 if (current >= tavsize && dt > 0) {
738 if (current < 0 || current >= tavsize) {
742 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
743 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
747 if (distance_only && current == start + delta) {
755 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
757 if (_y_constrained) {
761 const int tavsize = _time_axis_views.size();
762 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
763 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
764 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
766 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
767 /* already in the drop zone */
768 if (delta_track >= 0) {
769 /* downward motion - OK if others are still not in the dropzone */
778 } else if (n >= tavsize) {
779 /* downward motion into drop zone. That's fine. */
783 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
784 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
785 /* not a track, or the wrong type */
789 double const l = i->layer + delta_layer;
791 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
792 mode to allow the user to place a region below another on layer 0.
794 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
795 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
796 If it has, the layers will be munged later anyway, so it's ok.
802 /* all regions being dragged are ok with this change */
806 struct DraggingViewSorter {
807 bool operator() (const DraggingView& a, const DraggingView& b) {
808 return a.time_axis_view < b.time_axis_view;
813 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
815 double delta_layer = 0;
816 int delta_time_axis_view = 0;
817 int current_pointer_time_axis_view = -1;
819 assert (!_views.empty ());
821 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
823 /* Find the TimeAxisView that the pointer is now over */
824 const double cur_y = current_pointer_y ();
825 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
826 TimeAxisView* tv = r.first;
828 if (!tv && cur_y < 0) {
829 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
833 /* find drop-zone y-position */
834 Coord last_track_bottom_edge;
835 last_track_bottom_edge = 0;
836 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
837 if (!(*t)->hidden()) {
838 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
843 if (tv && tv->view()) {
844 /* the mouse is over a track */
845 double layer = r.second;
847 if (first_move && tv->view()->layer_display() == Stacked) {
848 tv->view()->set_layer_display (Expanded);
851 /* Here's the current pointer position in terms of time axis view and layer */
852 current_pointer_time_axis_view = find_time_axis_view (tv);
853 assert(current_pointer_time_axis_view >= 0);
855 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
857 /* Work out the change in y */
859 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
860 if (!rtv || !rtv->is_track()) {
861 /* ignore non-tracks early on. we can't move any regions on them */
862 } else if (_last_pointer_time_axis_view < 0) {
863 /* Was in the drop-zone, now over a track.
864 * Hence it must be an upward move (from the bottom)
866 * track_index is still -1, so delta must be set to
867 * move up the correct number of tracks from the bottom.
869 * This is necessary because steps may be skipped if
870 * the bottom-most track is not a valid target and/or
871 * if there are hidden tracks at the bottom.
872 * Hence the initial offset (_ddropzone) as well as the
873 * last valid pointer position (_pdropzone) need to be
874 * taken into account.
876 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
878 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
881 /* TODO needs adjustment per DraggingView,
883 * e.g. select one region on the top-layer of a track
884 * and one region which is at the bottom-layer of another track
887 * Indicated drop-zones and layering is wrong.
888 * and may infer additional layers on the target-track
889 * (depending how many layers the original track had).
891 * Or select two regions (different layers) on a same track,
892 * move across a non-layer track.. -> layering info is lost.
893 * on drop either of the regions may be on top.
895 * Proposed solution: screw it :) well,
896 * don't use delta_layer, use an absolute value
897 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
898 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
899 * 3) iterate over all DraggingView, find the one that is over the track with most layers
900 * 4) proportionally scale layer to layers available on target
902 delta_layer = current_pointer_layer - _last_pointer_layer;
905 /* for automation lanes, there is a TimeAxisView but no ->view()
906 * if (!tv) -> dropzone
908 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
909 /* Moving into the drop-zone.. */
910 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
911 /* delta_time_axis_view may not be sufficient to move into the DZ
912 * the mouse may enter it, but it may not be a valid move due to
915 * -> remember the delta needed to move into the dropzone
917 _ddropzone = delta_time_axis_view;
918 /* ..but subtract hidden tracks (or routes) at the bottom.
919 * we silently move mover them
921 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
922 - _time_axis_views.size();
924 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
925 /* move around inside the zone.
926 * This allows to move further down until all regions are in the zone.
928 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
929 assert(ptr_y >= last_track_bottom_edge);
930 assert(_ddropzone > 0);
932 /* calculate mouse position in 'tracks' below last track. */
933 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
934 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
936 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
938 delta_time_axis_view = dzpos - _pdropzone;
939 } else if (dzpos < _pdropzone && _ndropzone > 0) {
940 // move up inside the DZ
941 delta_time_axis_view = dzpos - _pdropzone;
945 /* Work out the change in x */
946 framepos_t pending_region_position;
947 double const x_delta = compute_x_delta (event, &pending_region_position);
948 _last_frame_position = pending_region_position;
950 /* calculate hidden tracks in current y-axis delta */
952 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
953 /* The mouse is more than one track below the dropzone.
954 * distance calculation is not needed (and would not work, either
955 * because the dropzone is "packed").
957 * Except when [partially] moving regions out of dropzone in a large step.
958 * (the mouse may or may not remain in the DZ)
959 * Hidden tracks at the bottom of the TAV need to be skipped.
961 * This also handles the case if the mouse entered the DZ
962 * in a large step (exessive delta), either due to fast-movement,
963 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
965 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
966 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
968 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
969 -_time_axis_views.size() - dt;
972 else if (_last_pointer_time_axis_view < 0) {
973 /* Moving out of the zone. Check for hidden tracks at the bottom. */
974 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
975 -_time_axis_views.size() - delta_time_axis_view;
977 /* calculate hidden tracks that are skipped by the pointer movement */
978 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
979 - _last_pointer_time_axis_view
980 - delta_time_axis_view;
983 /* Verify change in y */
984 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
985 /* this y movement is not allowed, so do no y movement this time */
986 delta_time_axis_view = 0;
991 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
992 /* haven't reached next snap point, and we're not switching
993 trackviews nor layers. nothing to do.
998 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
999 PlaylistDropzoneMap playlist_dropzone_map;
1000 _ndropzone = 0; // number of elements currently in the dropzone
1003 /* sort views by time_axis.
1004 * This retains track order in the dropzone, regardless
1005 * of actual selection order
1007 _views.sort (DraggingViewSorter());
1009 /* count number of distinct tracks of all regions
1010 * being dragged, used for dropzone.
1012 int prev_track = -1;
1013 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1014 if (i->time_axis_view != prev_track) {
1015 prev_track = i->time_axis_view;
1021 _views.back().time_axis_view -
1022 _views.front().time_axis_view;
1024 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1025 - _views.back().time_axis_view;
1027 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1031 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1033 RegionView* rv = i->view;
1038 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1045 /* reparent the regionview into a group above all
1049 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1050 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1051 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1052 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1053 /* move the item so that it continues to appear at the
1054 same location now that its parent has changed.
1056 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1059 /* If we have moved tracks, we'll fudge the layer delta so that the
1060 region gets moved back onto layer 0 on its new track; this avoids
1061 confusion when dragging regions from non-zero layers onto different
1064 double this_delta_layer = delta_layer;
1065 if (delta_time_axis_view != 0) {
1066 this_delta_layer = - i->layer;
1069 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1071 int track_index = i->time_axis_view + this_delta_time_axis_view;
1072 assert(track_index >= 0);
1074 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1075 /* Track is in the Dropzone */
1077 i->time_axis_view = track_index;
1078 assert(i->time_axis_view >= (int) _time_axis_views.size());
1081 double yposition = 0;
1082 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1083 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1086 /* store index of each new playlist as a negative count, starting at -1 */
1088 if (pdz == playlist_dropzone_map.end()) {
1089 /* compute where this new track (which doesn't exist yet) will live
1092 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1094 /* How high is this region view ? */
1096 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1097 ArdourCanvas::Rect bbox;
1100 bbox = obbox.get ();
1103 last_track_bottom_edge += bbox.height();
1105 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1108 yposition = pdz->second;
1111 /* values are zero or negative, hence the use of min() */
1112 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1117 /* The TimeAxisView that this region is now over */
1118 TimeAxisView* current_tv = _time_axis_views[track_index];
1120 /* Ensure it is moved from stacked -> expanded if appropriate */
1121 if (current_tv->view()->layer_display() == Stacked) {
1122 current_tv->view()->set_layer_display (Expanded);
1125 /* We're only allowed to go -ve in layer on Expanded views */
1126 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1127 this_delta_layer = - i->layer;
1131 rv->set_height (current_tv->view()->child_height ());
1133 /* Update show/hidden status as the region view may have come from a hidden track,
1134 or have moved to one.
1136 if (current_tv->hidden ()) {
1137 rv->get_canvas_group()->hide ();
1139 rv->get_canvas_group()->show ();
1142 /* Update the DraggingView */
1143 i->time_axis_view = track_index;
1144 i->layer += this_delta_layer;
1147 _editor->mouse_brush_insert_region (rv, pending_region_position);
1151 /* Get the y coordinate of the top of the track that this region is now over */
1152 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1154 /* And adjust for the layer that it should be on */
1155 StreamView* cv = current_tv->view ();
1156 switch (cv->layer_display ()) {
1160 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1163 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1167 /* need to get the parent of the regionview
1168 * canvas group and get its position in
1169 * equivalent coordinate space as the trackview
1170 * we are now dragging over.
1173 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1178 /* Now move the region view */
1179 rv->move (x_delta, y_delta);
1181 } /* foreach region */
1183 _total_x_delta += x_delta;
1185 if (x_delta != 0 && !_brushing) {
1186 show_verbose_cursor_time (_last_frame_position);
1189 /* keep track of pointer movement */
1191 /* the pointer is currently over a time axis view */
1193 if (_last_pointer_time_axis_view < 0) {
1194 /* last motion event was not over a time axis view
1195 * or last y-movement out of the dropzone was not valid
1198 if (delta_time_axis_view < 0) {
1199 /* in the drop zone, moving up */
1201 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1202 * We do not use negative _last_pointer_time_axis_view because
1203 * the dropzone is "packed" (the actual track offset is ignored)
1205 * As opposed to the actual number
1206 * of elements in the dropzone (_ndropzone)
1207 * _pdropzone is not constrained. This is necessary
1208 * to allow moving multiple regions with y-distance
1211 * There can be 0 elements in the dropzone,
1212 * even though the drag-pointer is inside the DZ.
1215 * [ Audio-track, Midi-track, Audio-track, DZ ]
1216 * move regions from both audio tracks at the same time into the
1217 * DZ by grabbing the region in the bottom track.
1219 assert(current_pointer_time_axis_view >= 0);
1220 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1224 /* only move out of the zone if the movement is OK */
1225 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1226 assert(delta_time_axis_view < 0);
1227 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1228 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1229 * the current position can be calculated as follows:
1231 // a well placed oofus attack can still throw this off.
1232 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1233 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1236 /* last motion event was also over a time axis view */
1237 _last_pointer_time_axis_view += delta_time_axis_view;
1238 assert(_last_pointer_time_axis_view >= 0);
1243 /* the pointer is not over a time axis view */
1244 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1245 _pdropzone += delta_time_axis_view - delta_skip;
1246 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1249 _last_pointer_layer += delta_layer;
1253 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1255 if (_copy && first_move) {
1256 if (_x_constrained && !_brushing) {
1257 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1258 } else if (!_brushing) {
1259 _editor->begin_reversible_command (Operations::region_copy);
1260 } else if (_brushing) {
1261 _editor->begin_reversible_command (Operations::drag_region_brush);
1263 /* duplicate the regionview(s) and region(s) */
1265 list<DraggingView> new_regionviews;
1267 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1269 RegionView* rv = i->view;
1270 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1271 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1273 const boost::shared_ptr<const Region> original = rv->region();
1274 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
1275 region_copy->set_position (original->position());
1276 /* need to set this so that the drop zone code can work. This doesn't
1277 actually put the region into the playlist, but just sets a weak pointer
1280 region_copy->set_playlist (original->playlist());
1284 boost::shared_ptr<AudioRegion> audioregion_copy
1285 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1287 nrv = new AudioRegionView (*arv, audioregion_copy);
1289 boost::shared_ptr<MidiRegion> midiregion_copy
1290 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1291 nrv = new MidiRegionView (*mrv, midiregion_copy);
1296 nrv->get_canvas_group()->show ();
1297 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1299 /* swap _primary to the copy */
1301 if (rv == _primary) {
1305 /* ..and deselect the one we copied */
1307 rv->set_selected (false);
1310 if (!new_regionviews.empty()) {
1312 /* reflect the fact that we are dragging the copies */
1314 _views = new_regionviews;
1316 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1319 } else if (!_copy && first_move) {
1320 if (_x_constrained && !_brushing) {
1321 _editor->begin_reversible_command (_("fixed time region drag"));
1322 } else if (!_brushing) {
1323 _editor->begin_reversible_command (Operations::region_drag);
1324 } else if (_brushing) {
1325 _editor->begin_reversible_command (Operations::drag_region_brush);
1328 RegionMotionDrag::motion (event, first_move);
1332 RegionMotionDrag::finished (GdkEvent *, bool)
1334 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1335 if (!(*i)->view()) {
1339 if ((*i)->view()->layer_display() == Expanded) {
1340 (*i)->view()->set_layer_display (Stacked);
1346 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1348 RegionMotionDrag::finished (ev, movement_occurred);
1350 if (!movement_occurred) {
1354 if (was_double_click() && !_views.empty()) {
1355 DraggingView dv = _views.front();
1356 dv.view->show_region_editor ();
1363 assert (!_views.empty ());
1365 /* We might have hidden region views so that they weren't visible during the drag
1366 (when they have been reparented). Now everything can be shown again, as region
1367 views are back in their track parent groups.
1369 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1370 i->view->get_canvas_group()->show ();
1373 bool const changed_position = (_last_frame_position != _primary->region()->position());
1374 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1375 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1395 _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
1399 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1401 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1406 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1407 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1408 uint32_t output_chan = region->n_channels();
1409 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1410 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1412 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1413 RouteTimeAxisView* rtav = _editor->axis_view_from_route (audio_tracks.front());
1415 rtav->set_height (original->current_height());
1419 ChanCount one_midi_port (DataType::MIDI, 1);
1420 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1421 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port, boost::shared_ptr<ARDOUR::PluginInfo>(),
1422 (ARDOUR::Plugin::PresetRecord*) 0,
1423 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1424 RouteTimeAxisView* rtav = _editor->axis_view_from_route (midi_tracks.front());
1426 rtav->set_height (original->current_height());
1431 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1437 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
1439 RegionSelection new_views;
1440 PlaylistSet modified_playlists;
1441 RouteTimeAxisView* new_time_axis_view = 0;
1444 /* all changes were made during motion event handlers */
1446 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1450 _editor->commit_reversible_command ();
1454 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1455 PlaylistMapping playlist_mapping;
1457 /* insert the regions into their new playlists */
1458 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1460 RouteTimeAxisView* dest_rtv = 0;
1462 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1468 if (changed_position && !_x_constrained) {
1469 where = i->view->region()->position() - drag_delta;
1471 where = i->view->region()->position();
1474 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1475 /* dragged to drop zone */
1477 PlaylistMapping::iterator pm;
1479 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1480 /* first region from this original playlist: create a new track */
1481 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1482 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1483 dest_rtv = new_time_axis_view;
1485 /* we already created a new track for regions from this playlist, use it */
1486 dest_rtv = pm->second;
1489 /* destination time axis view is the one we dragged to */
1490 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1493 if (dest_rtv != 0) {
1494 RegionView* new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, modified_playlists);
1495 if (new_view != 0) {
1496 new_views.push_back (new_view);
1500 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1501 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1504 list<DraggingView>::const_iterator next = i;
1510 /* If we've created new regions either by copying or moving
1511 to a new track, we want to replace the old selection with the new ones
1514 if (new_views.size() > 0) {
1515 _editor->selection->set (new_views);
1518 /* write commands for the accumulated diffs for all our modified playlists */
1519 add_stateful_diff_commands_for_playlists (modified_playlists);
1521 _editor->commit_reversible_command ();
1525 RegionMoveDrag::finished_no_copy (
1526 bool const changed_position,
1527 bool const changed_tracks,
1528 framecnt_t const drag_delta
1531 RegionSelection new_views;
1532 PlaylistSet modified_playlists;
1533 PlaylistSet frozen_playlists;
1534 set<RouteTimeAxisView*> views_to_update;
1535 RouteTimeAxisView* new_time_axis_view = 0;
1537 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1538 PlaylistMapping playlist_mapping;
1540 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1542 RegionView* rv = i->view;
1543 RouteTimeAxisView* dest_rtv = 0;
1545 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1550 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1551 /* dragged to drop zone */
1553 PlaylistMapping::iterator pm;
1555 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1556 /* first region from this original playlist: create a new track */
1557 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1558 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1559 dest_rtv = new_time_axis_view;
1561 /* we already created a new track for regions from this playlist, use it */
1562 dest_rtv = pm->second;
1566 /* destination time axis view is the one we dragged to */
1567 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1572 double const dest_layer = i->layer;
1574 views_to_update.insert (dest_rtv);
1578 if (changed_position && !_x_constrained) {
1579 where = rv->region()->position() - drag_delta;
1581 where = rv->region()->position();
1584 if (changed_tracks) {
1586 /* insert into new playlist */
1588 RegionView* new_view = insert_region_into_playlist (
1589 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
1592 if (new_view == 0) {
1597 new_views.push_back (new_view);
1599 /* remove from old playlist */
1601 /* the region that used to be in the old playlist is not
1602 moved to the new one - we use a copy of it. as a result,
1603 any existing editor for the region should no longer be
1606 rv->hide_region_editor();
1609 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1613 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1615 /* this movement may result in a crossfade being modified, or a layering change,
1616 so we need to get undo data from the playlist as well as the region.
1619 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1621 playlist->clear_changes ();
1624 rv->region()->clear_changes ();
1627 motion on the same track. plonk the previously reparented region
1628 back to its original canvas group (its streamview).
1629 No need to do anything for copies as they are fake regions which will be deleted.
1632 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1633 rv->get_canvas_group()->set_y_position (i->initial_y);
1636 /* just change the model */
1637 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1638 playlist->set_layer (rv->region(), dest_layer);
1641 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1643 r = frozen_playlists.insert (playlist);
1646 playlist->freeze ();
1649 rv->region()->set_position (where);
1650 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1653 if (changed_tracks) {
1655 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1656 was selected in all of them, then removing it from a playlist will have removed all
1657 trace of it from _views (i.e. there were N regions selected, we removed 1,
1658 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1659 corresponding regionview, and _views is now empty).
1661 This could have invalidated any and all iterators into _views.
1663 The heuristic we use here is: if the region selection is empty, break out of the loop
1664 here. if the region selection is not empty, then restart the loop because we know that
1665 we must have removed at least the region(view) we've just been working on as well as any
1666 that we processed on previous iterations.
1668 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1669 we can just iterate.
1673 if (_views.empty()) {
1684 /* If we've created new regions either by copying or moving
1685 to a new track, we want to replace the old selection with the new ones
1688 if (new_views.size() > 0) {
1689 _editor->selection->set (new_views);
1692 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1696 /* write commands for the accumulated diffs for all our modified playlists */
1697 add_stateful_diff_commands_for_playlists (modified_playlists);
1698 /* applies to _brushing */
1699 _editor->commit_reversible_command ();
1701 /* We have futzed with the layering of canvas items on our streamviews.
1702 If any region changed layer, this will have resulted in the stream
1703 views being asked to set up their region views, and all will be well.
1704 If not, we might now have badly-ordered region views. Ask the StreamViews
1705 involved to sort themselves out, just in case.
1708 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1709 (*i)->view()->playlist_layered ((*i)->track ());
1713 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1714 * @param region Region to remove.
1715 * @param playlist playlist To remove from.
1716 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1717 * that clear_changes () is only called once per playlist.
1720 RegionMoveDrag::remove_region_from_playlist (
1721 boost::shared_ptr<Region> region,
1722 boost::shared_ptr<Playlist> playlist,
1723 PlaylistSet& modified_playlists
1726 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1729 playlist->clear_changes ();
1732 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1736 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1737 * clearing the playlist's diff history first if necessary.
1738 * @param region Region to insert.
1739 * @param dest_rtv Destination RouteTimeAxisView.
1740 * @param dest_layer Destination layer.
1741 * @param where Destination position.
1742 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1743 * that clear_changes () is only called once per playlist.
1744 * @return New RegionView, or 0 if no insert was performed.
1747 RegionMoveDrag::insert_region_into_playlist (
1748 boost::shared_ptr<Region> region,
1749 RouteTimeAxisView* dest_rtv,
1752 PlaylistSet& modified_playlists
1755 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1756 if (!dest_playlist) {
1760 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1761 _new_region_view = 0;
1762 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1764 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1765 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1767 dest_playlist->clear_changes ();
1770 dest_playlist->add_region (region, where);
1772 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1773 dest_playlist->set_layer (region, dest_layer);
1778 assert (_new_region_view);
1780 return _new_region_view;
1784 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1786 _new_region_view = rv;
1790 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1792 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1793 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1795 _editor->session()->add_command (c);
1804 RegionMoveDrag::aborted (bool movement_occurred)
1808 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1809 list<DraggingView>::const_iterator next = i;
1818 RegionMotionDrag::aborted (movement_occurred);
1823 RegionMotionDrag::aborted (bool)
1825 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1827 StreamView* sview = (*i)->view();
1830 if (sview->layer_display() == Expanded) {
1831 sview->set_layer_display (Stacked);
1836 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1837 RegionView* rv = i->view;
1838 TimeAxisView* tv = &(rv->get_time_axis_view ());
1839 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1841 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1842 rv->get_canvas_group()->set_y_position (0);
1844 rv->move (-_total_x_delta, 0);
1845 rv->set_height (rtv->view()->child_height ());
1849 /** @param b true to brush, otherwise false.
1850 * @param c true to make copies of the regions being moved, otherwise false.
1852 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1853 : RegionMotionDrag (e, i, p, v, b)
1855 , _new_region_view (0)
1857 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1860 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1861 if (rtv && rtv->is_track()) {
1862 speed = rtv->track()->speed ();
1865 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1869 RegionMoveDrag::setup_pointer_frame_offset ()
1871 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1874 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1875 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1877 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1879 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1880 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1882 _primary = v->view()->create_region_view (r, false, false);
1884 _primary->get_canvas_group()->show ();
1885 _primary->set_position (pos, 0);
1886 _views.push_back (DraggingView (_primary, this, v));
1888 _last_frame_position = pos;
1890 _item = _primary->get_canvas_group ();
1894 RegionInsertDrag::finished (GdkEvent *, bool)
1896 int pos = _views.front().time_axis_view;
1897 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1899 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1901 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1902 _primary->get_canvas_group()->set_y_position (0);
1904 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1906 _editor->begin_reversible_command (Operations::insert_region);
1907 playlist->clear_changes ();
1908 playlist->add_region (_primary->region (), _last_frame_position);
1910 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
1911 if (Config->get_edit_mode() == Ripple) {
1912 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
1915 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1916 _editor->commit_reversible_command ();
1924 RegionInsertDrag::aborted (bool)
1931 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1932 : RegionMoveDrag (e, i, p, v, false, false)
1934 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1937 struct RegionSelectionByPosition {
1938 bool operator() (RegionView*a, RegionView* b) {
1939 return a->region()->position () < b->region()->position();
1944 RegionSpliceDrag::motion (GdkEvent* event, bool)
1946 /* Which trackview is this ? */
1948 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
1949 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1951 /* The region motion is only processed if the pointer is over
1955 if (!tv || !tv->is_track()) {
1956 /* To make sure we hide the verbose canvas cursor when the mouse is
1957 not held over an audio track.
1959 _editor->verbose_cursor()->hide ();
1962 _editor->verbose_cursor()->show ();
1967 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1973 RegionSelection copy;
1974 _editor->selection->regions.by_position(copy);
1976 framepos_t const pf = adjusted_current_frame (event);
1978 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1980 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1986 boost::shared_ptr<Playlist> playlist;
1988 if ((playlist = atv->playlist()) == 0) {
1992 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1997 if (pf < (*i)->region()->last_frame() + 1) {
2001 if (pf > (*i)->region()->first_frame()) {
2007 playlist->shuffle ((*i)->region(), dir);
2012 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2014 RegionMoveDrag::finished (event, movement_occurred);
2018 RegionSpliceDrag::aborted (bool)
2028 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2031 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
2033 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2034 RegionSelection to_ripple;
2035 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2036 if ((*i)->position() >= where) {
2037 to_ripple.push_back (rtv->view()->find_view(*i));
2041 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2042 if (!exclude.contains (*i)) {
2043 // the selection has already been added to _views
2045 if (drag_in_progress) {
2046 // do the same things that RegionMotionDrag::motion does when
2047 // first_move is true, for the region views that we're adding
2048 // to _views this time
2051 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2052 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2053 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2054 rvg->reparent (_editor->_drag_motion_group);
2056 // we only need to move in the y direction
2057 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2062 _views.push_back (DraggingView (*i, this, tav));
2068 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2071 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2072 // we added all the regions after the selection
2074 std::list<DraggingView>::iterator to_erase = i++;
2075 if (!_editor->selection->regions.contains (to_erase->view)) {
2076 // restore the non-selected regions to their original playlist & positions,
2077 // and then ripple them back by the length of the regions that were dragged away
2078 // do the same things as RegionMotionDrag::aborted
2080 RegionView *rv = to_erase->view;
2081 TimeAxisView* tv = &(rv->get_time_axis_view ());
2082 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2085 // plonk them back onto their own track
2086 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2087 rv->get_canvas_group()->set_y_position (0);
2091 // move the underlying region to match the view
2092 rv->region()->set_position (rv->region()->position() + amount);
2094 // restore the view to match the underlying region's original position
2095 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2098 rv->set_height (rtv->view()->child_height ());
2099 _views.erase (to_erase);
2105 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2107 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2109 return allow_moves_across_tracks;
2117 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2118 : RegionMoveDrag (e, i, p, v, false, false)
2120 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2121 // compute length of selection
2122 RegionSelection selected_regions = _editor->selection->regions;
2123 selection_length = selected_regions.end_frame() - selected_regions.start();
2125 // we'll only allow dragging to another track in ripple mode if all the regions
2126 // being dragged start off on the same track
2127 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2130 exclude = new RegionList;
2131 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2132 exclude->push_back((*i)->region());
2135 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2136 RegionSelection copy;
2137 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2139 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2140 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2142 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2143 // find ripple start point on each applicable playlist
2144 RegionView *first_selected_on_this_track = NULL;
2145 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2146 if ((*i)->region()->playlist() == (*pi)) {
2147 // region is on this playlist - it's the first, because they're sorted
2148 first_selected_on_this_track = *i;
2152 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2153 add_all_after_to_views (
2154 &first_selected_on_this_track->get_time_axis_view(),
2155 first_selected_on_this_track->region()->position(),
2156 selected_regions, false);
2159 if (allow_moves_across_tracks) {
2160 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2168 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2170 /* Which trackview is this ? */
2172 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2173 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2175 /* The region motion is only processed if the pointer is over
2179 if (!tv || !tv->is_track()) {
2180 /* To make sure we hide the verbose canvas cursor when the mouse is
2181 not held over an audiotrack.
2183 _editor->verbose_cursor()->hide ();
2187 framepos_t where = adjusted_current_frame (event);
2188 assert (where >= 0);
2190 double delta = compute_x_delta (event, &after);
2192 framecnt_t amount = _editor->pixel_to_sample (delta);
2194 if (allow_moves_across_tracks) {
2195 // all the originally selected regions were on the same track
2197 framecnt_t adjust = 0;
2198 if (prev_tav && tv != prev_tav) {
2199 // dragged onto a different track
2200 // remove the unselected regions from _views, restore them to their original positions
2201 // and add the regions after the drop point on the new playlist to _views instead.
2202 // undo the effect of rippling the previous playlist, and include the effect of removing
2203 // the dragged region(s) from this track
2205 remove_unselected_from_views (prev_amount, false);
2206 // ripple previous playlist according to the regions that have been removed onto the new playlist
2207 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2210 // move just the selected regions
2211 RegionMoveDrag::motion(event, first_move);
2213 // ensure that the ripple operation on the new playlist inserts selection_length time
2214 adjust = selection_length;
2215 // ripple the new current playlist
2216 tv->playlist()->ripple (where, amount+adjust, exclude);
2218 // add regions after point where drag entered this track to subsequent ripples
2219 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2222 // motion on same track
2223 RegionMoveDrag::motion(event, first_move);
2227 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2228 prev_position = where;
2230 // selection encompasses multiple tracks - just drag
2231 // cross-track drags are forbidden
2232 RegionMoveDrag::motion(event, first_move);
2235 if (!_x_constrained) {
2236 prev_amount += amount;
2239 _last_frame_position = after;
2243 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2245 if (!movement_occurred) {
2249 if (was_double_click() && !_views.empty()) {
2250 DraggingView dv = _views.front();
2251 dv.view->show_region_editor ();
2258 _editor->begin_reversible_command(_("Ripple drag"));
2260 // remove the regions being rippled from the dragging view, updating them to
2261 // their new positions
2262 remove_unselected_from_views (prev_amount, true);
2264 if (allow_moves_across_tracks) {
2266 // if regions were dragged across tracks, we've rippled any later
2267 // regions on the track the regions were dragged off, so we need
2268 // to add the original track to the undo record
2269 orig_tav->playlist()->clear_changes();
2270 vector<Command*> cmds;
2271 orig_tav->playlist()->rdiff (cmds);
2272 _editor->session()->add_commands (cmds);
2274 if (prev_tav && prev_tav != orig_tav) {
2275 prev_tav->playlist()->clear_changes();
2276 vector<Command*> cmds;
2277 prev_tav->playlist()->rdiff (cmds);
2278 _editor->session()->add_commands (cmds);
2281 // selection spanned multiple tracks - all will need adding to undo record
2283 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2284 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2286 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2287 (*pi)->clear_changes();
2288 vector<Command*> cmds;
2289 (*pi)->rdiff (cmds);
2290 _editor->session()->add_commands (cmds);
2294 // other modified playlists are added to undo by RegionMoveDrag::finished()
2295 RegionMoveDrag::finished (event, movement_occurred);
2296 _editor->commit_reversible_command();
2300 RegionRippleDrag::aborted (bool movement_occurred)
2302 RegionMoveDrag::aborted (movement_occurred);
2307 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2309 _view (dynamic_cast<MidiTimeAxisView*> (v))
2311 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2317 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2320 _editor->begin_reversible_command (_("create region"));
2321 _region = add_midi_region (_view, false);
2322 _view->playlist()->freeze ();
2325 framepos_t const f = adjusted_current_frame (event);
2326 if (f < grab_frame()) {
2327 _region->set_initial_position (f);
2330 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2331 so that if this region is duplicated, its duplicate starts on
2332 a snap point rather than 1 frame after a snap point. Otherwise things get
2333 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2334 place snapped notes at the start of the region.
2337 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2338 _region->set_length (len < 1 ? 1 : len);
2344 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
2346 if (!movement_occurred) {
2347 add_midi_region (_view, true);
2349 _view->playlist()->thaw ();
2350 _editor->commit_reversible_command();
2355 RegionCreateDrag::aborted (bool)
2358 _view->playlist()->thaw ();
2364 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2369 , _was_selected (false)
2372 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2376 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2378 Gdk::Cursor* cursor;
2379 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2381 float x_fraction = cnote->mouse_x_fraction ();
2383 if (x_fraction > 0.0 && x_fraction < 0.25) {
2384 cursor = _editor->cursors()->left_side_trim;
2387 cursor = _editor->cursors()->right_side_trim;
2391 Drag::start_grab (event, cursor);
2393 region = &cnote->region_view();
2396 temp = region->snap_to_pixel (cnote->x0 (), true);
2397 _snap_delta = temp - cnote->x0 ();
2401 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2406 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2407 if (ms.size() > 1) {
2408 /* has to be relative, may make no sense otherwise */
2412 if (!(_was_selected = cnote->selected())) {
2414 /* tertiary-click means extend selection - we'll do that on button release,
2415 so don't add it here, because otherwise we make it hard to figure
2416 out the "extend-to" range.
2419 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2422 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2425 region->note_selected (cnote, true);
2427 _editor->get_selection().clear_points();
2428 region->unique_select (cnote);
2435 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2437 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2439 _editor->begin_reversible_command (_("resize notes"));
2441 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2442 MidiRegionSelection::iterator next;
2445 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2447 mrv->begin_resizing (at_front);
2453 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2454 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2456 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2460 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2462 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2463 if (_editor->snap_mode () != SnapOff) {
2467 if (_editor->snap_mode () == SnapOff) {
2469 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2470 if (apply_snap_delta) {
2476 if (apply_snap_delta) {
2480 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2486 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2488 if (!movement_occurred) {
2489 /* no motion - select note */
2490 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2491 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2492 _editor->current_mouse_mode() == Editing::MouseDraw) {
2494 bool changed = false;
2496 if (_was_selected) {
2497 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2499 region->note_deselected (cnote);
2502 _editor->get_selection().clear_points();
2503 region->unique_select (cnote);
2507 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2508 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2510 if (!extend && !add && region->selection_size() > 1) {
2511 _editor->get_selection().clear_points();
2512 region->unique_select (cnote);
2514 } else if (extend) {
2515 region->note_selected (cnote, true, true);
2518 /* it was added during button press */
2524 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2525 _editor->commit_reversible_selection_op();
2532 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2533 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2534 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2536 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2539 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2541 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2542 if (_editor->snap_mode () != SnapOff) {
2546 if (_editor->snap_mode () == SnapOff) {
2548 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2549 if (apply_snap_delta) {
2555 if (apply_snap_delta) {
2559 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2563 _editor->commit_reversible_command ();
2567 NoteResizeDrag::aborted (bool)
2569 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2570 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2571 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2573 mrv->abort_resizing ();
2578 AVDraggingView::AVDraggingView (RegionView* v)
2581 initial_position = v->region()->position ();
2584 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2587 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2590 TrackViewList empty;
2592 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2593 std::list<RegionView*> views = rs.by_layer();
2596 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2597 RegionView* rv = (*i);
2598 if (!rv->region()->video_locked()) {
2601 if (rv->region()->locked()) {
2604 _views.push_back (AVDraggingView (rv));
2609 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2611 Drag::start_grab (event);
2612 if (_editor->session() == 0) {
2616 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2622 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2626 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2627 _max_backwards_drag = (
2628 ARDOUR_UI::instance()->video_timeline->get_duration()
2629 + ARDOUR_UI::instance()->video_timeline->get_offset()
2630 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2633 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2634 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2635 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2638 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2641 Timecode::Time timecode;
2642 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2643 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);
2644 show_verbose_cursor_text (buf);
2648 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2650 if (_editor->session() == 0) {
2653 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2657 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2661 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2662 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2664 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2665 dt = - _max_backwards_drag;
2668 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2669 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2671 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2672 RegionView* rv = i->view;
2673 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2676 rv->region()->clear_changes ();
2677 rv->region()->suspend_property_changes();
2679 rv->region()->set_position(i->initial_position + dt);
2680 rv->region_changed(ARDOUR::Properties::position);
2683 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2684 Timecode::Time timecode;
2685 Timecode::Time timediff;
2687 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2688 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2689 snprintf (buf, sizeof (buf),
2690 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2691 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2692 , _("Video Start:"),
2693 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2695 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2697 show_verbose_cursor_text (buf);
2701 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2703 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2710 if (!movement_occurred || ! _editor->session()) {
2714 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2716 _editor->begin_reversible_command (_("Move Video"));
2718 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2719 ARDOUR_UI::instance()->video_timeline->save_undo();
2720 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2721 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2723 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2724 i->view->drag_end();
2725 i->view->region()->resume_property_changes ();
2727 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2730 _editor->session()->maybe_update_session_range(
2731 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2732 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2736 _editor->commit_reversible_command ();
2740 VideoTimeLineDrag::aborted (bool)
2742 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2745 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2746 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2748 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2749 i->view->region()->resume_property_changes ();
2750 i->view->region()->set_position(i->initial_position);
2754 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2755 : RegionDrag (e, i, p, v)
2756 , _operation (StartTrim)
2757 , _preserve_fade_anchor (preserve_fade_anchor)
2758 , _jump_position_when_done (false)
2760 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2764 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2767 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2768 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2770 if (tv && tv->is_track()) {
2771 speed = tv->track()->speed();
2774 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2775 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2776 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2778 framepos_t const pf = adjusted_current_frame (event);
2779 setup_snap_delta (region_start);
2781 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2782 /* Move the contents of the region around without changing the region bounds */
2783 _operation = ContentsTrim;
2784 Drag::start_grab (event, _editor->cursors()->trimmer);
2786 /* These will get overridden for a point trim.*/
2787 if (pf < (region_start + region_length/2)) {
2788 /* closer to front */
2789 _operation = StartTrim;
2790 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2791 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2793 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2797 _operation = EndTrim;
2798 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2799 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2801 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2805 /* jump trim disabled for now
2806 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2807 _jump_position_when_done = true;
2811 switch (_operation) {
2813 show_verbose_cursor_time (region_start);
2816 show_verbose_cursor_duration (region_start, region_end);
2819 show_verbose_cursor_time (pf);
2823 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2824 i->view->region()->suspend_property_changes ();
2829 TrimDrag::motion (GdkEvent* event, bool first_move)
2831 RegionView* rv = _primary;
2834 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2835 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2836 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2837 frameoffset_t frame_delta = 0;
2839 if (tv && tv->is_track()) {
2840 speed = tv->track()->speed();
2842 framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
2843 framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state);
2849 switch (_operation) {
2851 trim_type = "Region start trim";
2854 trim_type = "Region end trim";
2857 trim_type = "Region content trim";
2864 _editor->begin_reversible_command (trim_type);
2866 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2867 RegionView* rv = i->view;
2868 rv->enable_display (false);
2869 rv->region()->playlist()->clear_owned_changes ();
2871 if (_operation == StartTrim) {
2872 rv->trim_front_starting ();
2875 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2878 arv->temporarily_hide_envelope ();
2882 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2883 insert_result = _editor->motion_frozen_playlists.insert (pl);
2885 if (insert_result.second) {
2891 bool non_overlap_trim = false;
2893 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
2894 non_overlap_trim = true;
2897 /* contstrain trim to fade length */
2898 if (_preserve_fade_anchor) {
2899 switch (_operation) {
2901 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2902 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2904 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2905 if (ar->locked()) continue;
2906 framecnt_t len = ar->fade_in()->back()->when;
2907 if (len < dt) dt = min(dt, len);
2911 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2912 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2914 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2915 if (ar->locked()) continue;
2916 framecnt_t len = ar->fade_out()->back()->when;
2917 if (len < -dt) dt = max(dt, -len);
2926 switch (_operation) {
2928 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2929 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim);
2930 if (changed && _preserve_fade_anchor) {
2931 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2933 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2934 framecnt_t len = ar->fade_in()->back()->when;
2935 framecnt_t diff = ar->first_frame() - i->initial_position;
2936 framepos_t new_length = len - diff;
2937 i->anchored_fade_length = min (ar->length(), new_length);
2938 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
2939 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
2946 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2947 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim);
2948 if (changed && _preserve_fade_anchor) {
2949 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2951 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2952 framecnt_t len = ar->fade_out()->back()->when;
2953 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
2954 framepos_t new_length = len + diff;
2955 i->anchored_fade_length = min (ar->length(), new_length);
2956 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
2957 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
2965 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
2967 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2968 i->view->move_contents (frame_delta);
2974 switch (_operation) {
2976 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
2979 show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
2982 // show_verbose_cursor_time (frame_delta);
2988 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
2990 if (movement_occurred) {
2991 motion (event, false);
2993 if (_operation == StartTrim) {
2994 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2996 /* This must happen before the region's StatefulDiffCommand is created, as it may
2997 `correct' (ahem) the region's _start from being negative to being zero. It
2998 needs to be zero in the undo record.
3000 i->view->trim_front_ending ();
3002 if (_preserve_fade_anchor) {
3003 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3005 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3006 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3007 ar->set_fade_in_length(i->anchored_fade_length);
3008 ar->set_fade_in_active(true);
3011 if (_jump_position_when_done) {
3012 i->view->region()->set_position (i->initial_position);
3015 } else if (_operation == EndTrim) {
3016 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3017 if (_preserve_fade_anchor) {
3018 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3020 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3021 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3022 ar->set_fade_out_length(i->anchored_fade_length);
3023 ar->set_fade_out_active(true);
3026 if (_jump_position_when_done) {
3027 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3032 if (!_views.empty()) {
3033 if (_operation == StartTrim) {
3034 _editor->maybe_locate_with_edit_preroll(
3035 _views.begin()->view->region()->position());
3037 if (_operation == EndTrim) {
3038 _editor->maybe_locate_with_edit_preroll(
3039 _views.begin()->view->region()->position() +
3040 _views.begin()->view->region()->length());
3044 if (!_editor->selection->selected (_primary)) {
3045 _primary->thaw_after_trim ();
3048 set<boost::shared_ptr<Playlist> > diffed_playlists;
3050 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3051 i->view->thaw_after_trim ();
3052 i->view->enable_display (true);
3054 /* Trimming one region may affect others on the playlist, so we need
3055 to get undo Commands from the whole playlist rather than just the
3056 region. Use diffed_playlists to make sure we don't diff a given
3057 playlist more than once.
3059 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
3060 if (diffed_playlists.find (p) == diffed_playlists.end()) {
3061 vector<Command*> cmds;
3063 _editor->session()->add_commands (cmds);
3064 diffed_playlists.insert (p);
3069 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3073 _editor->motion_frozen_playlists.clear ();
3074 _editor->commit_reversible_command();
3077 /* no mouse movement */
3078 if (adjusted_current_frame (event) != adjusted_frame (_drags->current_pointer_frame(), event, false)) {
3079 _editor->point_trim (event, adjusted_current_frame (event));
3083 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3084 i->view->region()->resume_property_changes ();
3089 TrimDrag::aborted (bool movement_occurred)
3091 /* Our motion method is changing model state, so use the Undo system
3092 to cancel. Perhaps not ideal, as this will leave an Undo point
3093 behind which may be slightly odd from the user's point of view.
3098 if (movement_occurred) {
3102 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3103 i->view->region()->resume_property_changes ();
3108 TrimDrag::setup_pointer_frame_offset ()
3110 list<DraggingView>::iterator i = _views.begin ();
3111 while (i != _views.end() && i->view != _primary) {
3115 if (i == _views.end()) {
3119 switch (_operation) {
3121 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
3124 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
3131 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3134 , _old_snap_type (e->snap_type())
3135 , _old_snap_mode (e->snap_mode())
3138 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3139 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3141 _real_section = &_marker->meter();
3146 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3148 Drag::start_grab (event, cursor);
3149 show_verbose_cursor_time (adjusted_current_frame(event));
3153 MeterMarkerDrag::setup_pointer_frame_offset ()
3155 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
3159 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3162 // create a dummy marker to catch events, then hide it.
3165 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3167 _marker = new MeterMarker (
3169 *_editor->meter_group,
3170 UIConfiguration::instance().color ("meter marker"),
3172 *new MeterSection (_marker->meter())
3175 /* use the new marker for the grab */
3176 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3179 TempoMap& map (_editor->session()->tempo_map());
3180 /* get current state */
3181 before_state = &map.get_state();
3184 _editor->begin_reversible_command (_("move meter mark"));
3186 _editor->begin_reversible_command (_("copy meter mark"));
3188 Timecode::BBT_Time bbt = _real_section->bbt();
3190 /* we can't add a meter where one currently exists */
3191 if (_real_section->frame() < adjusted_current_frame (event, false)) {
3196 const double beat = map.beat_at_bbt (bbt);
3197 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3198 , beat, bbt, map.frame_at_bbt (bbt), _real_section->position_lock_style());
3201 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3202 if (_real_section->position_lock_style() != AudioTime) {
3203 _editor->set_snap_to (SnapToBar);
3204 _editor->set_snap_mode (SnapNormal);
3208 framepos_t pf = adjusted_current_frame (event);
3210 if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3211 /* never snap to music for audio locked */
3212 pf = adjusted_current_frame (event, false);
3215 _editor->session()->tempo_map().gui_move_meter (_real_section, pf);
3217 /* fake marker meeds to stay under the mouse, unlike the real one. */
3218 _marker->set_position (adjusted_current_frame (event, false));
3220 show_verbose_cursor_time (_real_section->frame());
3224 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3226 if (!movement_occurred) {
3227 if (was_double_click()) {
3228 _editor->edit_meter_marker (*_marker);
3233 /* reinstate old snap setting */
3234 _editor->set_snap_to (_old_snap_type);
3235 _editor->set_snap_mode (_old_snap_mode);
3237 TempoMap& map (_editor->session()->tempo_map());
3239 XMLNode &after = map.get_state();
3240 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3241 _editor->commit_reversible_command ();
3243 // delete the dummy marker we used for visual representation while moving.
3244 // a new visual marker will show up automatically.
3249 MeterMarkerDrag::aborted (bool moved)
3251 _marker->set_position (_marker->meter().frame ());
3253 /* reinstate old snap setting */
3254 _editor->set_snap_to (_old_snap_type);
3255 _editor->set_snap_mode (_old_snap_mode);
3257 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3258 // delete the dummy marker we used for visual representation while moving.
3259 // a new visual marker will show up automatically.
3264 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3269 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3271 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3272 _real_section = &_marker->tempo();
3273 _movable = _real_section->movable();
3278 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3280 Drag::start_grab (event, cursor);
3281 if (!_real_section->active()) {
3282 show_verbose_cursor_text (_("inactive"));
3284 show_verbose_cursor_time (adjusted_current_frame (event));
3289 TempoMarkerDrag::setup_pointer_frame_offset ()
3291 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
3295 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3297 if (!_real_section->active()) {
3303 // mvc drag - create a dummy marker to catch events, hide it.
3306 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
3308 TempoSection section (_marker->tempo());
3310 _marker = new TempoMarker (
3312 *_editor->tempo_group,
3313 UIConfiguration::instance().color ("tempo marker"),
3315 *new TempoSection (_marker->tempo())
3318 /* use the new marker for the grab */
3319 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3322 TempoMap& map (_editor->session()->tempo_map());
3323 /* get current state */
3324 before_state = &map.get_state();
3327 _editor->begin_reversible_command (_("move tempo mark"));
3330 const framepos_t frame = adjusted_current_frame (event) + 1;
3332 _editor->begin_reversible_command (_("copy tempo mark"));
3334 if (_real_section->position_lock_style() == MusicTime) {
3335 _real_section = map.add_tempo (_marker->tempo(), map.pulse_at_frame (frame), 0, _real_section->type(), MusicTime);
3337 _real_section = map.add_tempo (_marker->tempo(), 0.0, frame, _real_section->type(), AudioTime);
3343 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::constraint_modifier ())) {
3344 /* use vertical movement to alter tempo .. should be log */
3345 double new_bpm = _real_section->beats_per_minute() + ((last_pointer_y() - current_pointer_y()) / 5.0);
3348 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type()));
3350 show_verbose_cursor_text (strs.str());
3352 } else if (_movable && !_real_section->locked_to_meter()) {
3353 TempoMap& map (_editor->session()->tempo_map());
3354 const bool was_music = _real_section->position_lock_style() == MusicTime;
3356 const framepos_t pf = adjusted_current_frame (event);
3358 if (!_editor->snap_musical()) {
3361 _real_section->set_position_lock_style (AudioTime);
3364 map.gui_move_tempo (_real_section, pf);
3367 _real_section->set_position_lock_style (MusicTime);
3373 _real_section->set_position_lock_style (MusicTime);
3376 map.gui_move_tempo (_real_section, pf);
3379 _real_section->set_position_lock_style (AudioTime);
3383 show_verbose_cursor_time (_real_section->frame());
3386 /* this has moved the bar lines themselves, so recalibrate the offset */
3387 setup_pointer_frame_offset();
3389 _marker->set_position (adjusted_current_frame (event, false));
3393 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3395 if (!_real_section->active()) {
3398 if (!movement_occurred) {
3399 if (was_double_click()) {
3400 _editor->edit_tempo_marker (*_marker);
3405 TempoMap& map (_editor->session()->tempo_map());
3407 XMLNode &after = map.get_state();
3408 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3409 _editor->commit_reversible_command ();
3411 // delete the dummy marker we used for visual representation while moving.
3412 // a new visual marker will show up automatically.
3417 TempoMarkerDrag::aborted (bool moved)
3419 _marker->set_position (_marker->tempo().frame());
3421 TempoMap& map (_editor->session()->tempo_map());
3422 map.set_state (*before_state, Stateful::current_state_version);
3423 // delete the dummy marker we used for visual representation while moving.
3424 // a new visual marker will show up automatically.
3429 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3435 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3440 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3442 Drag::start_grab (event, cursor);
3443 TempoMap& map (_editor->session()->tempo_map());
3444 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
3447 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).beats_per_minute() << "\n";
3448 sstr << "<" << fixed << setprecision(3) << _tempo->beats_per_minute();
3449 show_verbose_cursor_text (sstr.str());
3450 finished (event, false);
3454 BBTRulerDrag::setup_pointer_frame_offset ()
3456 TempoMap& map (_editor->session()->tempo_map());
3457 const double beat_at_frame = map.beat_at_frame (raw_grab_frame());
3458 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3461 if (divisions > 0) {
3462 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions);
3464 /* while it makes some sense for the user to determine the division to 'grab',
3465 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3466 and the result over steep tempo curves. Use sixteenths.
3468 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4);
3471 _pulse = map.pulse_at_beat (beat);
3473 _pointer_frame_offset = raw_grab_frame() - map.frame_at_pulse (_pulse);
3478 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3480 TempoMap& map (_editor->session()->tempo_map());
3483 /* get current state */
3484 before_state = &map.get_state();
3485 _editor->begin_reversible_command (_("dilate tempo"));
3490 if (_editor->snap_musical()) {
3491 pf = adjusted_current_frame (event, false);
3493 pf = adjusted_current_frame (event);
3496 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::constraint_modifier())) {
3497 /* adjust previous tempo to match pointer frame */
3498 _editor->session()->tempo_map().gui_dilate_tempo (_tempo, map.frame_at_pulse (_pulse), pf, _pulse);
3501 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).beats_per_minute() << "\n";
3502 sstr << "<" << fixed << setprecision(3) << _tempo->beats_per_minute();
3503 show_verbose_cursor_text (sstr.str());
3507 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3509 if (!movement_occurred) {
3513 TempoMap& map (_editor->session()->tempo_map());
3515 XMLNode &after = map.get_state();
3516 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3517 _editor->commit_reversible_command ();
3521 BBTRulerDrag::aborted (bool moved)
3524 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3529 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3530 : Drag (e, &c.track_canvas_item(), false)
3535 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3538 /** Do all the things we do when dragging the playhead to make it look as though
3539 * we have located, without actually doing the locate (because that would cause
3540 * the diskstream buffers to be refilled, which is too slow).
3543 CursorDrag::fake_locate (framepos_t t)
3545 if (_editor->session () == 0) {
3549 _editor->playhead_cursor->set_position (t);
3551 Session* s = _editor->session ();
3552 if (s->timecode_transmission_suspended ()) {
3553 framepos_t const f = _editor->playhead_cursor->current_frame ();
3554 /* This is asynchronous so it will be sent "now"
3556 s->send_mmc_locate (f);
3557 /* These are synchronous and will be sent during the next
3560 s->queue_full_time_code ();
3561 s->queue_song_position_pointer ();
3564 show_verbose_cursor_time (t);
3565 _editor->UpdateAllTransportClocks (t);
3569 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3571 Drag::start_grab (event, c);
3572 setup_snap_delta (_editor->playhead_cursor->current_frame ());
3574 _grab_zoom = _editor->samples_per_pixel;
3576 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3578 _editor->snap_to_with_modifier (where, event);
3580 _editor->_dragging_playhead = true;
3582 Session* s = _editor->session ();
3584 /* grab the track canvas item as well */
3586 _cursor.track_canvas_item().grab();
3589 if (_was_rolling && _stop) {
3593 if (s->is_auditioning()) {
3594 s->cancel_audition ();
3598 if (AudioEngine::instance()->connected()) {
3600 /* do this only if we're the engine is connected
3601 * because otherwise this request will never be
3602 * serviced and we'll busy wait forever. likewise,
3603 * notice if we are disconnected while waiting for the
3604 * request to be serviced.
3607 s->request_suspend_timecode_transmission ();
3608 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3609 /* twiddle our thumbs */
3614 fake_locate (where - snap_delta (event->button.state));
3618 CursorDrag::motion (GdkEvent* event, bool)
3620 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3621 _editor->snap_to_with_modifier (where, event);
3622 if (where != last_pointer_frame()) {
3623 fake_locate (where - snap_delta (event->button.state));
3628 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3630 _editor->_dragging_playhead = false;
3632 _cursor.track_canvas_item().ungrab();
3634 if (!movement_occurred && _stop) {
3638 motion (event, false);
3640 Session* s = _editor->session ();
3642 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3643 _editor->_pending_locate_request = true;
3644 s->request_resume_timecode_transmission ();
3649 CursorDrag::aborted (bool)
3651 _cursor.track_canvas_item().ungrab();
3653 if (_editor->_dragging_playhead) {
3654 _editor->session()->request_resume_timecode_transmission ();
3655 _editor->_dragging_playhead = false;
3658 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3661 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3662 : RegionDrag (e, i, p, v)
3664 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3668 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3670 Drag::start_grab (event, cursor);
3672 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3673 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3674 setup_snap_delta (r->position ());
3676 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3680 FadeInDrag::setup_pointer_frame_offset ()
3682 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3683 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3684 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3688 FadeInDrag::motion (GdkEvent* event, bool)
3690 framecnt_t fade_length;
3692 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3693 _editor->snap_to_with_modifier (pos, event);
3694 pos -= snap_delta (event->button.state);
3696 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3698 if (pos < (region->position() + 64)) {
3699 fade_length = 64; // this should be a minimum defined somewhere
3700 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3701 fade_length = region->length() - region->fade_out()->back()->when - 1;
3703 fade_length = pos - region->position();
3706 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3708 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3714 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3717 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3721 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3723 if (!movement_occurred) {
3727 framecnt_t fade_length;
3728 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3729 _editor->snap_to_with_modifier (pos, event);
3730 pos -= snap_delta (event->button.state);
3732 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3734 if (pos < (region->position() + 64)) {
3735 fade_length = 64; // this should be a minimum defined somewhere
3736 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3737 fade_length = region->length() - region->fade_out()->back()->when - 1;
3739 fade_length = pos - region->position();
3742 bool in_command = false;
3744 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3746 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3752 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3753 XMLNode &before = alist->get_state();
3755 tmp->audio_region()->set_fade_in_length (fade_length);
3756 tmp->audio_region()->set_fade_in_active (true);
3759 _editor->begin_reversible_command (_("change fade in length"));
3762 XMLNode &after = alist->get_state();
3763 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3767 _editor->commit_reversible_command ();
3772 FadeInDrag::aborted (bool)
3774 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3775 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3781 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3785 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3786 : RegionDrag (e, i, p, v)
3788 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3792 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3794 Drag::start_grab (event, cursor);
3796 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3797 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3798 setup_snap_delta (r->last_frame ());
3800 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3804 FadeOutDrag::setup_pointer_frame_offset ()
3806 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3807 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3808 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3812 FadeOutDrag::motion (GdkEvent* event, bool)
3814 framecnt_t fade_length;
3816 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3817 _editor->snap_to_with_modifier (pos, event);
3818 pos -= snap_delta (event->button.state);
3820 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3822 if (pos > (region->last_frame() - 64)) {
3823 fade_length = 64; // this should really be a minimum fade defined somewhere
3824 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3825 fade_length = region->length() - region->fade_in()->back()->when - 1;
3827 fade_length = region->last_frame() - pos;
3830 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3832 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3838 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3841 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3845 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3847 if (!movement_occurred) {
3851 framecnt_t fade_length;
3853 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3854 _editor->snap_to_with_modifier (pos, event);
3855 pos -= snap_delta (event->button.state);
3857 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3859 if (pos > (region->last_frame() - 64)) {
3860 fade_length = 64; // this should really be a minimum fade defined somewhere
3861 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3862 fade_length = region->length() - region->fade_in()->back()->when - 1;
3864 fade_length = region->last_frame() - pos;
3867 bool in_command = false;
3869 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3871 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3877 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3878 XMLNode &before = alist->get_state();
3880 tmp->audio_region()->set_fade_out_length (fade_length);
3881 tmp->audio_region()->set_fade_out_active (true);
3884 _editor->begin_reversible_command (_("change fade out length"));
3887 XMLNode &after = alist->get_state();
3888 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3892 _editor->commit_reversible_command ();
3897 FadeOutDrag::aborted (bool)
3899 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3900 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3906 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3910 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3912 , _selection_changed (false)
3914 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3915 Gtk::Window* toplevel = _editor->current_toplevel();
3916 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
3920 _points.push_back (ArdourCanvas::Duple (0, 0));
3922 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
3925 MarkerDrag::~MarkerDrag ()
3927 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
3932 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
3934 location = new Location (*l);
3935 markers.push_back (m);
3940 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3942 Drag::start_grab (event, cursor);
3946 Location *location = _editor->find_location_from_marker (_marker, is_start);
3947 _editor->_dragging_edit_point = true;
3949 update_item (location);
3951 // _drag_line->show();
3952 // _line->raise_to_top();
3955 show_verbose_cursor_time (location->start());
3957 show_verbose_cursor_time (location->end());
3959 setup_snap_delta (is_start ? location->start() : location->end());
3961 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3964 case Selection::Toggle:
3965 /* we toggle on the button release */
3967 case Selection::Set:
3968 if (!_editor->selection->selected (_marker)) {
3969 _editor->selection->set (_marker);
3970 _selection_changed = true;
3973 case Selection::Extend:
3975 Locations::LocationList ll;
3976 list<ArdourMarker*> to_add;
3978 _editor->selection->markers.range (s, e);
3979 s = min (_marker->position(), s);
3980 e = max (_marker->position(), e);
3983 if (e < max_framepos) {
3986 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
3987 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
3988 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
3991 to_add.push_back (lm->start);
3994 to_add.push_back (lm->end);
3998 if (!to_add.empty()) {
3999 _editor->selection->add (to_add);
4000 _selection_changed = true;
4004 case Selection::Add:
4005 _editor->selection->add (_marker);
4006 _selection_changed = true;
4011 /* Set up copies for us to manipulate during the drag
4014 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4016 Location* l = _editor->find_location_from_marker (*i, is_start);
4023 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4025 /* range: check that the other end of the range isn't
4028 CopiedLocationInfo::iterator x;
4029 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4030 if (*(*x).location == *l) {
4034 if (x == _copied_locations.end()) {
4035 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4037 (*x).markers.push_back (*i);
4038 (*x).move_both = true;
4046 MarkerDrag::setup_pointer_frame_offset ()
4049 Location *location = _editor->find_location_from_marker (_marker, is_start);
4050 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
4054 MarkerDrag::motion (GdkEvent* event, bool)
4056 framecnt_t f_delta = 0;
4058 bool move_both = false;
4059 Location *real_location;
4060 Location *copy_location = 0;
4061 framecnt_t const sd = snap_delta (event->button.state);
4063 framecnt_t const newframe = adjusted_frame (_drags->current_pointer_frame () + sd, event, true) - sd;
4064 framepos_t next = newframe;
4066 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4070 CopiedLocationInfo::iterator x;
4072 /* find the marker we're dragging, and compute the delta */
4074 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4076 copy_location = (*x).location;
4078 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4080 /* this marker is represented by this
4081 * CopiedLocationMarkerInfo
4084 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4089 if (real_location->is_mark()) {
4090 f_delta = newframe - copy_location->start();
4094 switch (_marker->type()) {
4095 case ArdourMarker::SessionStart:
4096 case ArdourMarker::RangeStart:
4097 case ArdourMarker::LoopStart:
4098 case ArdourMarker::PunchIn:
4099 f_delta = newframe - copy_location->start();
4102 case ArdourMarker::SessionEnd:
4103 case ArdourMarker::RangeEnd:
4104 case ArdourMarker::LoopEnd:
4105 case ArdourMarker::PunchOut:
4106 f_delta = newframe - copy_location->end();
4109 /* what kind of marker is this ? */
4118 if (x == _copied_locations.end()) {
4119 /* hmm, impossible - we didn't find the dragged marker */
4123 /* now move them all */
4125 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4127 copy_location = x->location;
4129 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4133 if (real_location->locked()) {
4137 if (copy_location->is_mark()) {
4141 copy_location->set_start (copy_location->start() + f_delta);
4145 framepos_t new_start = copy_location->start() + f_delta;
4146 framepos_t new_end = copy_location->end() + f_delta;
4148 if (is_start) { // start-of-range marker
4150 if (move_both || (*x).move_both) {
4151 copy_location->set_start (new_start);
4152 copy_location->set_end (new_end);
4153 } else if (new_start < copy_location->end()) {
4154 copy_location->set_start (new_start);
4155 } else if (newframe > 0) {
4156 //_editor->snap_to (next, RoundUpAlways, true);
4157 copy_location->set_end (next);
4158 copy_location->set_start (newframe);
4161 } else { // end marker
4163 if (move_both || (*x).move_both) {
4164 copy_location->set_end (new_end);
4165 copy_location->set_start (new_start);
4166 } else if (new_end > copy_location->start()) {
4167 copy_location->set_end (new_end);
4168 } else if (newframe > 0) {
4169 //_editor->snap_to (next, RoundDownAlways, true);
4170 copy_location->set_start (next);
4171 copy_location->set_end (newframe);
4176 update_item (copy_location);
4178 /* now lookup the actual GUI items used to display this
4179 * location and move them to wherever the copy of the location
4180 * is now. This means that the logic in ARDOUR::Location is
4181 * still enforced, even though we are not (yet) modifying
4182 * the real Location itself.
4185 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4188 lm->set_position (copy_location->start(), copy_location->end());
4193 assert (!_copied_locations.empty());
4195 show_verbose_cursor_time (newframe);
4199 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4201 if (!movement_occurred) {
4203 if (was_double_click()) {
4204 _editor->rename_marker (_marker);
4208 /* just a click, do nothing but finish
4209 off the selection process
4212 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4214 case Selection::Set:
4215 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4216 _editor->selection->set (_marker);
4217 _selection_changed = true;
4221 case Selection::Toggle:
4222 /* we toggle on the button release, click only */
4223 _editor->selection->toggle (_marker);
4224 _selection_changed = true;
4228 case Selection::Extend:
4229 case Selection::Add:
4233 if (_selection_changed) {
4234 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4235 _editor->commit_reversible_selection_op();
4241 _editor->_dragging_edit_point = false;
4243 XMLNode &before = _editor->session()->locations()->get_state();
4244 bool in_command = false;
4246 MarkerSelection::iterator i;
4247 CopiedLocationInfo::iterator x;
4250 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4251 x != _copied_locations.end() && i != _editor->selection->markers.end();
4254 Location * location = _editor->find_location_from_marker (*i, is_start);
4258 if (location->locked()) {
4262 _editor->begin_reversible_command ( _("move marker") );
4265 if (location->is_mark()) {
4266 location->set_start (((*x).location)->start());
4268 location->set (((*x).location)->start(), ((*x).location)->end());
4274 XMLNode &after = _editor->session()->locations()->get_state();
4275 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4276 _editor->commit_reversible_command ();
4281 MarkerDrag::aborted (bool movement_occurred)
4283 if (!movement_occurred) {
4287 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4289 /* move all markers to their original location */
4292 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4295 Location * location = _editor->find_location_from_marker (*m, is_start);
4298 (*m)->set_position (is_start ? location->start() : location->end());
4305 MarkerDrag::update_item (Location*)
4310 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4312 , _fixed_grab_x (0.0)
4313 , _fixed_grab_y (0.0)
4314 , _cumulative_x_drag (0.0)
4315 , _cumulative_y_drag (0.0)
4319 if (_zero_gain_fraction < 0.0) {
4320 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4323 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4325 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4331 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4333 Drag::start_grab (event, _editor->cursors()->fader);
4335 // start the grab at the center of the control point so
4336 // the point doesn't 'jump' to the mouse after the first drag
4337 _fixed_grab_x = _point->get_x();
4338 _fixed_grab_y = _point->get_y();
4340 framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
4341 setup_snap_delta (pos);
4343 float const fraction = 1 - (_point->get_y() / _point->line().height());
4344 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4346 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4348 if (!_point->can_slide ()) {
4349 _x_constrained = true;
4354 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4356 double dx = _drags->current_pointer_x() - last_pointer_x();
4357 double dy = current_pointer_y() - last_pointer_y();
4358 bool need_snap = true;
4360 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4366 /* coordinate in pixels relative to the start of the region (for region-based automation)
4367 or track (for track-based automation) */
4368 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4369 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4371 // calculate zero crossing point. back off by .01 to stay on the
4372 // positive side of zero
4373 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4375 if (_x_constrained) {
4378 if (_y_constrained) {
4382 _cumulative_x_drag = cx - _fixed_grab_x;
4383 _cumulative_y_drag = cy - _fixed_grab_y;
4387 cy = min ((double) _point->line().height(), cy);
4389 // make sure we hit zero when passing through
4390 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4394 framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state);
4396 if (!_x_constrained && need_snap) {
4397 _editor->snap_to_with_modifier (cx_frames, event);
4400 cx_frames -= snap_delta (event->button.state);
4401 cx_frames = min (cx_frames, _point->line().maximum_time());
4403 float const fraction = 1.0 - (cy / _point->line().height());
4406 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4407 _editor->begin_reversible_command (_("automation event move"));
4408 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4410 pair<double, float> result;
4411 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4413 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (result.second));
4417 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4419 if (!movement_occurred) {
4422 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4423 _editor->reset_point_selection ();
4427 _point->line().end_drag (_pushing, _final_index);
4428 _editor->commit_reversible_command ();
4433 ControlPointDrag::aborted (bool)
4435 _point->line().reset ();
4439 ControlPointDrag::active (Editing::MouseMode m)
4441 if (m == Editing::MouseDraw) {
4442 /* always active in mouse draw */
4446 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4447 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4450 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4453 , _fixed_grab_x (0.0)
4454 , _fixed_grab_y (0.0)
4455 , _cumulative_y_drag (0)
4459 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4463 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4465 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4468 _item = &_line->grab_item ();
4470 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4471 origin, and ditto for y.
4474 double mx = event->button.x;
4475 double my = event->button.y;
4477 _line->grab_item().canvas_to_item (mx, my);
4479 framecnt_t const frame_within_region = (framecnt_t) floor (mx * _editor->samples_per_pixel);
4481 if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
4482 /* no adjacent points */
4486 Drag::start_grab (event, _editor->cursors()->fader);
4488 /* store grab start in item frame */
4489 double const bx = _line->nth (_before)->get_x();
4490 double const ax = _line->nth (_after)->get_x();
4491 double const click_ratio = (ax - mx) / (ax - bx);
4493 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4498 double fraction = 1.0 - (cy / _line->height());
4500 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4504 LineDrag::motion (GdkEvent* event, bool first_move)
4506 double dy = current_pointer_y() - last_pointer_y();
4508 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4512 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4514 _cumulative_y_drag = cy - _fixed_grab_y;
4517 cy = min ((double) _line->height(), cy);
4519 double const fraction = 1.0 - (cy / _line->height());
4523 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4525 _editor->begin_reversible_command (_("automation range move"));
4526 _line->start_drag_line (_before, _after, initial_fraction);
4529 /* we are ignoring x position for this drag, so we can just pass in anything */
4530 pair<double, float> result;
4532 result = _line->drag_motion (0, fraction, true, false, ignored);
4533 show_verbose_cursor_text (_line->get_verbose_cursor_string (result.second));
4537 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4539 if (movement_occurred) {
4540 motion (event, false);
4541 _line->end_drag (false, 0);
4542 _editor->commit_reversible_command ();
4544 /* add a new control point on the line */
4546 AutomationTimeAxisView* atv;
4548 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4549 framepos_t where = grab_frame ();
4552 double cy = _fixed_grab_y;
4554 _line->grab_item().item_to_canvas (cx, cy);
4556 atv->add_automation_event (event, where, cy, false);
4557 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4558 AudioRegionView* arv;
4560 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4561 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4568 LineDrag::aborted (bool)
4573 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4577 _region_view_grab_x (0.0),
4578 _cumulative_x_drag (0),
4582 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4586 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4588 Drag::start_grab (event);
4590 _line = reinterpret_cast<Line*> (_item);
4593 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4595 double cx = event->button.x;
4596 double cy = event->button.y;
4598 _item->parent()->canvas_to_item (cx, cy);
4600 /* store grab start in parent frame */
4601 _region_view_grab_x = cx;
4603 _before = *(float*) _item->get_data ("position");
4605 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4607 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4611 FeatureLineDrag::motion (GdkEvent*, bool)
4613 double dx = _drags->current_pointer_x() - last_pointer_x();
4615 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4617 _cumulative_x_drag += dx;
4619 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4628 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4630 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4632 float *pos = new float;
4635 _line->set_data ("position", pos);
4641 FeatureLineDrag::finished (GdkEvent*, bool)
4643 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4644 _arv->update_transient(_before, _before);
4648 FeatureLineDrag::aborted (bool)
4653 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4655 , _vertical_only (false)
4657 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4661 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4663 Drag::start_grab (event);
4664 show_verbose_cursor_time (adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
4668 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4675 framepos_t const pf = adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
4677 framepos_t grab = grab_frame ();
4678 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4679 _editor->snap_to_with_modifier (grab, event);
4681 grab = raw_grab_frame ();
4684 /* base start and end on initial click position */
4694 if (current_pointer_y() < grab_y()) {
4695 y1 = current_pointer_y();
4698 y2 = current_pointer_y();
4702 if (start != end || y1 != y2) {
4704 double x1 = _editor->sample_to_pixel (start);
4705 double x2 = _editor->sample_to_pixel (end);
4706 const double min_dimension = 2.0;
4708 if (_vertical_only) {
4709 /* fixed 10 pixel width */
4713 x2 = min (x1 - min_dimension, x2);
4715 x2 = max (x1 + min_dimension, x2);
4720 y2 = min (y1 - min_dimension, y2);
4722 y2 = max (y1 + min_dimension, y2);
4725 /* translate rect into item space and set */
4727 ArdourCanvas::Rect r (x1, y1, x2, y2);
4729 /* this drag is a _trackview_only == true drag, so the y1 and
4730 * y2 (computed using current_pointer_y() and grab_y()) will be
4731 * relative to the top of the trackview group). The
4732 * rubberband rect has the same parent/scroll offset as the
4733 * the trackview group, so we can use the "r" rect directly
4734 * to set the shape of the rubberband.
4737 _editor->rubberband_rect->set (r);
4738 _editor->rubberband_rect->show();
4739 _editor->rubberband_rect->raise_to_top();
4741 show_verbose_cursor_time (pf);
4743 do_select_things (event, true);
4748 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4752 framepos_t grab = grab_frame ();
4753 framepos_t lpf = last_pointer_frame ();
4755 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4756 grab = raw_grab_frame ();
4757 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4771 if (current_pointer_y() < grab_y()) {
4772 y1 = current_pointer_y();
4775 y2 = current_pointer_y();
4779 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4783 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4785 if (movement_occurred) {
4787 motion (event, false);
4788 do_select_things (event, false);
4794 bool do_deselect = true;
4795 MidiTimeAxisView* mtv;
4797 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4799 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4800 /* nothing selected */
4801 add_midi_region (mtv, true);
4802 do_deselect = false;
4806 /* do not deselect if Primary or Tertiary (toggle-select or
4807 * extend-select are pressed.
4810 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4811 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4818 _editor->rubberband_rect->hide();
4822 RubberbandSelectDrag::aborted (bool)
4824 _editor->rubberband_rect->hide ();
4827 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4828 : RegionDrag (e, i, p, v)
4830 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4834 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4836 Drag::start_grab (event, cursor);
4838 _editor->get_selection().add (_primary);
4840 framepos_t where = _primary->region()->position();
4841 setup_snap_delta (where);
4843 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4847 TimeFXDrag::motion (GdkEvent* event, bool)
4849 RegionView* rv = _primary;
4850 StreamView* cv = rv->get_time_axis_view().view ();
4852 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4853 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4854 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4855 framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
4856 _editor->snap_to_with_modifier (pf, event);
4857 pf -= snap_delta (event->button.state);
4859 if (pf > rv->region()->position()) {
4860 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4863 show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4867 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
4869 /* this may have been a single click, no drag. We still want the dialog
4870 to show up in that case, so that the user can manually edit the
4871 parameters for the timestretch.
4874 float fraction = 1.0;
4876 if (movement_occurred) {
4878 motion (event, false);
4880 _primary->get_time_axis_view().hide_timestretch ();
4882 framepos_t adjusted_frame_pos = adjusted_current_frame (event);
4884 if (adjusted_frame_pos < _primary->region()->position()) {
4885 /* backwards drag of the left edge - not usable */
4889 framecnt_t newlen = adjusted_frame_pos - _primary->region()->position();
4891 fraction = (double) newlen / (double) _primary->region()->length();
4893 #ifndef USE_RUBBERBAND
4894 // Soundtouch uses fraction / 100 instead of normal (/ 1)
4895 if (_primary->region()->data_type() == DataType::AUDIO) {
4896 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4901 if (!_editor->get_selection().regions.empty()) {
4902 /* primary will already be included in the selection, and edit
4903 group shared editing will propagate selection across
4904 equivalent regions, so just use the current region
4908 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
4909 error << _("An error occurred while executing time stretch operation") << endmsg;
4915 TimeFXDrag::aborted (bool)
4917 _primary->get_time_axis_view().hide_timestretch ();
4920 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
4923 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
4927 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4929 Drag::start_grab (event);
4933 ScrubDrag::motion (GdkEvent* /*event*/, bool)
4935 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
4939 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4941 if (movement_occurred && _editor->session()) {
4942 /* make sure we stop */
4943 _editor->session()->request_transport_speed (0.0);
4948 ScrubDrag::aborted (bool)
4953 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4957 , _time_selection_at_start (!_editor->get_selection().time.empty())
4959 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
4961 if (_time_selection_at_start) {
4962 start_at_start = _editor->get_selection().time.start();
4963 end_at_start = _editor->get_selection().time.end_frame();
4968 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
4970 if (_editor->session() == 0) {
4974 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4976 switch (_operation) {
4977 case CreateSelection:
4978 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4983 cursor = _editor->cursors()->selector;
4984 Drag::start_grab (event, cursor);
4987 case SelectionStartTrim:
4988 if (_editor->clicked_axisview) {
4989 _editor->clicked_axisview->order_selection_trims (_item, true);
4991 Drag::start_grab (event, _editor->cursors()->left_side_trim);
4994 case SelectionEndTrim:
4995 if (_editor->clicked_axisview) {
4996 _editor->clicked_axisview->order_selection_trims (_item, false);
4998 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5002 Drag::start_grab (event, cursor);
5005 case SelectionExtend:
5006 Drag::start_grab (event, cursor);
5010 if (_operation == SelectionMove) {
5011 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5013 show_verbose_cursor_time (adjusted_current_frame (event));
5018 SelectionDrag::setup_pointer_frame_offset ()
5020 switch (_operation) {
5021 case CreateSelection:
5022 _pointer_frame_offset = 0;
5025 case SelectionStartTrim:
5027 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
5030 case SelectionEndTrim:
5031 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
5034 case SelectionExtend:
5040 SelectionDrag::motion (GdkEvent* event, bool first_move)
5042 framepos_t start = 0;
5044 framecnt_t length = 0;
5045 framecnt_t distance = 0;
5047 framepos_t const pending_position = adjusted_current_frame (event);
5049 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
5053 switch (_operation) {
5054 case CreateSelection:
5056 framepos_t grab = grab_frame ();
5059 grab = adjusted_current_frame (event, false);
5060 if (grab < pending_position) {
5061 _editor->snap_to (grab, RoundDownMaybe);
5063 _editor->snap_to (grab, RoundUpMaybe);
5067 if (pending_position < grab) {
5068 start = pending_position;
5071 end = pending_position;
5075 /* first drag: Either add to the selection
5076 or create a new selection
5083 /* adding to the selection */
5084 _editor->set_selected_track_as_side_effect (Selection::Add);
5085 _editor->clicked_selection = _editor->selection->add (start, end);
5092 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5093 _editor->set_selected_track_as_side_effect (Selection::Set);
5096 _editor->clicked_selection = _editor->selection->set (start, end);
5100 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5101 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5102 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5104 _editor->selection->add (atest);
5108 /* select all tracks within the rectangle that we've marked out so far */
5109 TrackViewList new_selection;
5110 TrackViewList& all_tracks (_editor->track_views);
5112 ArdourCanvas::Coord const top = grab_y();
5113 ArdourCanvas::Coord const bottom = current_pointer_y();
5115 if (top >= 0 && bottom >= 0) {
5117 //first, find the tracks that are covered in the y range selection
5118 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5119 if ((*i)->covered_by_y_range (top, bottom)) {
5120 new_selection.push_back (*i);
5124 //now find any tracks that are GROUPED with the tracks we selected
5125 TrackViewList grouped_add = new_selection;
5126 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5127 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
5128 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::select.property_id) ) {
5129 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
5130 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
5131 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
5132 grouped_add.push_back (*j);
5137 //now compare our list with the current selection, and add or remove as necessary
5138 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5139 TrackViewList tracks_to_add;
5140 TrackViewList tracks_to_remove;
5141 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
5142 if ( !_editor->selection->tracks.contains ( *i ) )
5143 tracks_to_add.push_back ( *i );
5144 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
5145 if ( !grouped_add.contains ( *i ) )
5146 tracks_to_remove.push_back ( *i );
5147 _editor->selection->add(tracks_to_add);
5148 _editor->selection->remove(tracks_to_remove);
5154 case SelectionStartTrim:
5156 end = _editor->selection->time[_editor->clicked_selection].end;
5158 if (pending_position > end) {
5161 start = pending_position;
5165 case SelectionEndTrim:
5167 start = _editor->selection->time[_editor->clicked_selection].start;
5169 if (pending_position < start) {
5172 end = pending_position;
5179 start = _editor->selection->time[_editor->clicked_selection].start;
5180 end = _editor->selection->time[_editor->clicked_selection].end;
5182 length = end - start;
5183 distance = pending_position - start;
5184 start = pending_position;
5185 _editor->snap_to (start);
5187 end = start + length;
5191 case SelectionExtend:
5196 switch (_operation) {
5198 if (_time_selection_at_start) {
5199 _editor->selection->move_time (distance);
5203 _editor->selection->replace (_editor->clicked_selection, start, end);
5207 if (_operation == SelectionMove) {
5208 show_verbose_cursor_time(start);
5210 show_verbose_cursor_time(pending_position);
5215 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5217 Session* s = _editor->session();
5219 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5220 if (movement_occurred) {
5221 motion (event, false);
5222 /* XXX this is not object-oriented programming at all. ick */
5223 if (_editor->selection->time.consolidate()) {
5224 _editor->selection->TimeChanged ();
5227 /* XXX what if its a music time selection? */
5229 if (s->get_play_range() && s->transport_rolling()) {
5230 s->request_play_range (&_editor->selection->time, true);
5231 } else if (!s->config.get_external_sync()) {
5232 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5233 if (_operation == SelectionEndTrim)
5234 _editor->maybe_locate_with_edit_preroll( _editor->get_selection().time.end_frame());
5236 s->request_locate (_editor->get_selection().time.start());
5240 if (_editor->get_selection().time.length() != 0) {
5241 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame());
5243 s->clear_range_selection ();
5248 /* just a click, no pointer movement.
5251 if (_operation == SelectionExtend) {
5252 if (_time_selection_at_start) {
5253 framepos_t pos = adjusted_current_frame (event, false);
5254 framepos_t start = min (pos, start_at_start);
5255 framepos_t end = max (pos, end_at_start);
5256 _editor->selection->set (start, end);
5259 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5260 if (_editor->clicked_selection) {
5261 _editor->selection->remove (_editor->clicked_selection);
5264 if (!_editor->clicked_selection) {
5265 _editor->selection->clear_time();
5270 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5271 _editor->selection->set (_editor->clicked_axisview);
5274 if (s && s->get_play_range () && s->transport_rolling()) {
5275 s->request_stop (false, false);
5280 _editor->stop_canvas_autoscroll ();
5281 _editor->clicked_selection = 0;
5282 _editor->commit_reversible_selection_op ();
5286 SelectionDrag::aborted (bool)
5291 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5292 : Drag (e, i, false),
5296 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5298 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5299 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5300 physical_screen_height (_editor->current_toplevel()->get_window())));
5301 _drag_rect->hide ();
5303 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5304 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5307 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5309 /* normal canvas items will be cleaned up when their parent group is deleted. But
5310 this item is created as the child of a long-lived parent group, and so we
5311 need to explicitly delete it.
5317 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5319 if (_editor->session() == 0) {
5323 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5325 if (!_editor->temp_location) {
5326 _editor->temp_location = new Location (*_editor->session());
5329 switch (_operation) {
5330 case CreateSkipMarker:
5331 case CreateRangeMarker:
5332 case CreateTransportMarker:
5333 case CreateCDMarker:
5335 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5340 cursor = _editor->cursors()->selector;
5344 Drag::start_grab (event, cursor);
5346 show_verbose_cursor_time (adjusted_current_frame (event));
5350 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5352 framepos_t start = 0;
5354 ArdourCanvas::Rectangle *crect;
5356 switch (_operation) {
5357 case CreateSkipMarker:
5358 crect = _editor->range_bar_drag_rect;
5360 case CreateRangeMarker:
5361 crect = _editor->range_bar_drag_rect;
5363 case CreateTransportMarker:
5364 crect = _editor->transport_bar_drag_rect;
5366 case CreateCDMarker:
5367 crect = _editor->cd_marker_bar_drag_rect;
5370 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5375 framepos_t const pf = adjusted_current_frame (event);
5377 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5378 framepos_t grab = grab_frame ();
5379 _editor->snap_to (grab);
5381 if (pf < grab_frame()) {
5389 /* first drag: Either add to the selection
5390 or create a new selection.
5395 _editor->temp_location->set (start, end);
5399 update_item (_editor->temp_location);
5401 //_drag_rect->raise_to_top();
5407 _editor->temp_location->set (start, end);
5409 double x1 = _editor->sample_to_pixel (start);
5410 double x2 = _editor->sample_to_pixel (end);
5414 update_item (_editor->temp_location);
5417 show_verbose_cursor_time (pf);
5422 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5424 Location * newloc = 0;
5428 if (movement_occurred) {
5429 motion (event, false);
5432 switch (_operation) {
5433 case CreateSkipMarker:
5434 case CreateRangeMarker:
5435 case CreateCDMarker:
5437 XMLNode &before = _editor->session()->locations()->get_state();
5438 if (_operation == CreateSkipMarker) {
5439 _editor->begin_reversible_command (_("new skip marker"));
5440 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5441 flags = Location::IsRangeMarker | Location::IsSkip;
5442 _editor->range_bar_drag_rect->hide();
5443 } else if (_operation == CreateCDMarker) {
5444 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5445 _editor->begin_reversible_command (_("new CD marker"));
5446 flags = Location::IsRangeMarker | Location::IsCDMarker;
5447 _editor->cd_marker_bar_drag_rect->hide();
5449 _editor->begin_reversible_command (_("new skip marker"));
5450 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5451 flags = Location::IsRangeMarker;
5452 _editor->range_bar_drag_rect->hide();
5454 newloc = new Location (
5455 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5458 _editor->session()->locations()->add (newloc, true);
5459 XMLNode &after = _editor->session()->locations()->get_state();
5460 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5461 _editor->commit_reversible_command ();
5465 case CreateTransportMarker:
5466 // popup menu to pick loop or punch
5467 _editor->new_transport_marker_context_menu (&event->button, _item);
5473 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5475 if (_operation == CreateTransportMarker) {
5477 /* didn't drag, so just locate */
5479 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5481 } else if (_operation == CreateCDMarker) {
5483 /* didn't drag, but mark is already created so do
5486 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5491 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5493 if (end == max_framepos) {
5494 end = _editor->session()->current_end_frame ();
5497 if (start == max_framepos) {
5498 start = _editor->session()->current_start_frame ();
5501 switch (_editor->mouse_mode) {
5503 /* find the two markers on either side and then make the selection from it */
5504 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5508 /* find the two markers on either side of the click and make the range out of it */
5509 _editor->selection->set (start, end);
5518 _editor->stop_canvas_autoscroll ();
5522 RangeMarkerBarDrag::aborted (bool movement_occurred)
5524 if (movement_occurred) {
5525 _drag_rect->hide ();
5530 RangeMarkerBarDrag::update_item (Location* location)
5532 double const x1 = _editor->sample_to_pixel (location->start());
5533 double const x2 = _editor->sample_to_pixel (location->end());
5535 _drag_rect->set_x0 (x1);
5536 _drag_rect->set_x1 (x2);
5539 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5541 , _cumulative_dx (0)
5542 , _cumulative_dy (0)
5543 , _was_selected (false)
5545 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5547 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5549 _region = &_primary->region_view ();
5550 _note_height = _region->midi_stream_view()->note_height ();
5554 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5556 Drag::start_grab (event);
5557 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5559 if (!(_was_selected = _primary->selected())) {
5561 /* tertiary-click means extend selection - we'll do that on button release,
5562 so don't add it here, because otherwise we make it hard to figure
5563 out the "extend-to" range.
5566 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5569 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5572 _region->note_selected (_primary, true);
5574 _editor->get_selection().clear_points();
5575 _region->unique_select (_primary);
5581 /** @return Current total drag x change in frames */
5583 NoteDrag::total_dx (const guint state) const
5586 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5588 /* primary note time */
5589 frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
5591 /* new time of the primary note in session frames */
5592 frameoffset_t st = n + dx + snap_delta (state);
5594 framepos_t const rp = _region->region()->position ();
5596 /* prevent the note being dragged earlier than the region's position */
5599 /* possibly snap and return corresponding delta */
5603 if (ArdourKeyboard::indicates_snap (state)) {
5604 if (_editor->snap_mode () != SnapOff) {
5608 if (_editor->snap_mode () == SnapOff) {
5610 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
5611 if (ArdourKeyboard::indicates_snap_delta (state)) {
5619 bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
5620 ret = _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
5622 ret = st - n - snap_delta (state);
5627 /** @return Current total drag y change in note number */
5629 NoteDrag::total_dy () const
5631 MidiStreamView* msv = _region->midi_stream_view ();
5632 double const y = _region->midi_view()->y_position ();
5633 /* new current note */
5634 uint8_t n = msv->y_to_note (current_pointer_y () - y);
5636 n = max (msv->lowest_note(), n);
5637 n = min (msv->highest_note(), n);
5638 /* and work out delta */
5639 return n - msv->y_to_note (grab_y() - y);
5643 NoteDrag::motion (GdkEvent * event, bool)
5645 /* Total change in x and y since the start of the drag */
5646 frameoffset_t const dx = total_dx (event->button.state);
5647 int8_t const dy = total_dy ();
5649 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5650 double const tdx = _editor->sample_to_pixel (dx) - _cumulative_dx;
5651 double const tdy = -dy * _note_height - _cumulative_dy;
5654 _cumulative_dx += tdx;
5655 _cumulative_dy += tdy;
5657 int8_t note_delta = total_dy();
5659 _region->move_selection (tdx, tdy, note_delta);
5661 /* the new note value may be the same as the old one, but we
5662 * don't know what that means because the selection may have
5663 * involved more than one note and we might be doing something
5664 * odd with them. so show the note value anyway, always.
5667 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5669 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
5674 NoteDrag::finished (GdkEvent* ev, bool moved)
5677 /* no motion - select note */
5679 if (_editor->current_mouse_mode() == Editing::MouseContent ||
5680 _editor->current_mouse_mode() == Editing::MouseDraw) {
5682 bool changed = false;
5684 if (_was_selected) {
5685 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5687 _region->note_deselected (_primary);
5690 _editor->get_selection().clear_points();
5691 _region->unique_select (_primary);
5695 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5696 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5698 if (!extend && !add && _region->selection_size() > 1) {
5699 _editor->get_selection().clear_points();
5700 _region->unique_select (_primary);
5702 } else if (extend) {
5703 _region->note_selected (_primary, true, true);
5706 /* it was added during button press */
5713 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5714 _editor->commit_reversible_selection_op();
5718 _region->note_dropped (_primary, total_dx (ev->button.state), total_dy());
5723 NoteDrag::aborted (bool)
5728 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5729 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5730 : Drag (editor, atv->base_item ())
5732 , _y_origin (atv->y_position())
5733 , _nothing_to_drag (false)
5735 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5736 setup (atv->lines ());
5739 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5740 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5741 : Drag (editor, rv->get_canvas_group ())
5743 , _y_origin (rv->get_time_axis_view().y_position())
5744 , _nothing_to_drag (false)
5747 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5749 list<boost::shared_ptr<AutomationLine> > lines;
5751 AudioRegionView* audio_view;
5752 AutomationRegionView* automation_view;
5753 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5754 lines.push_back (audio_view->get_gain_line ());
5755 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5756 lines.push_back (automation_view->line ());
5759 error << _("Automation range drag created for invalid region type") << endmsg;
5765 /** @param lines AutomationLines to drag.
5766 * @param offset Offset from the session start to the points in the AutomationLines.
5769 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5771 /* find the lines that overlap the ranges being dragged */
5772 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5773 while (i != lines.end ()) {
5774 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5777 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5779 /* check this range against all the AudioRanges that we are using */
5780 list<AudioRange>::const_iterator k = _ranges.begin ();
5781 while (k != _ranges.end()) {
5782 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5788 /* add it to our list if it overlaps at all */
5789 if (k != _ranges.end()) {
5794 _lines.push_back (n);
5800 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5804 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5806 return 1.0 - ((global_y - _y_origin) / line->height());
5810 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5812 const double v = list->eval(x);
5813 return _integral ? rint(v) : v;
5817 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5819 Drag::start_grab (event, cursor);
5821 /* Get line states before we start changing things */
5822 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5823 i->state = &i->line->get_state ();
5824 i->original_fraction = y_fraction (i->line, current_pointer_y());
5827 if (_ranges.empty()) {
5829 /* No selected time ranges: drag all points */
5830 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5831 uint32_t const N = i->line->npoints ();
5832 for (uint32_t j = 0; j < N; ++j) {
5833 i->points.push_back (i->line->nth (j));
5839 if (_nothing_to_drag) {
5845 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
5847 if (_nothing_to_drag && !first_move) {
5852 _editor->begin_reversible_command (_("automation range move"));
5854 if (!_ranges.empty()) {
5856 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5858 framecnt_t const half = (i->start + i->end) / 2;
5860 /* find the line that this audio range starts in */
5861 list<Line>::iterator j = _lines.begin();
5862 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5866 if (j != _lines.end()) {
5867 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5869 /* j is the line that this audio range starts in; fade into it;
5870 64 samples length plucked out of thin air.
5873 framepos_t a = i->start + 64;
5878 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5879 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5881 XMLNode &before = the_list->get_state();
5882 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5883 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5885 if (add_p || add_q) {
5886 _editor->session()->add_command (
5887 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
5891 /* same thing for the end */
5894 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5898 if (j != _lines.end()) {
5899 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5901 /* j is the line that this audio range starts in; fade out of it;
5902 64 samples length plucked out of thin air.
5905 framepos_t b = i->end - 64;
5910 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
5911 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
5913 XMLNode &before = the_list->get_state();
5914 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5915 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5917 if (add_p || add_q) {
5918 _editor->session()->add_command (
5919 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
5924 _nothing_to_drag = true;
5926 /* Find all the points that should be dragged and put them in the relevant
5927 points lists in the Line structs.
5930 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5932 uint32_t const N = i->line->npoints ();
5933 for (uint32_t j = 0; j < N; ++j) {
5935 /* here's a control point on this line */
5936 ControlPoint* p = i->line->nth (j);
5937 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
5939 /* see if it's inside a range */
5940 list<AudioRange>::const_iterator k = _ranges.begin ();
5941 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
5945 if (k != _ranges.end()) {
5946 /* dragging this point */
5947 _nothing_to_drag = false;
5948 i->points.push_back (p);
5954 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5955 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
5959 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
5960 float const f = y_fraction (l->line, current_pointer_y());
5961 /* we are ignoring x position for this drag, so we can just pass in anything */
5962 pair<double, float> result;
5964 result = l->line->drag_motion (0, f, true, false, ignored);
5965 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, result.second));
5970 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
5972 if (_nothing_to_drag || !motion_occurred) {
5976 motion (event, false);
5977 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5978 i->line->end_drag (false, 0);
5981 _editor->commit_reversible_command ();
5985 AutomationRangeDrag::aborted (bool)
5987 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5992 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
5994 , initial_time_axis_view (itav)
5996 /* note that time_axis_view may be null if the regionview was created
5997 * as part of a copy operation.
5999 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6000 layer = v->region()->layer ();
6001 initial_y = v->get_canvas_group()->position().y;
6002 initial_playlist = v->region()->playlist ();
6003 initial_position = v->region()->position ();
6004 initial_end = v->region()->position () + v->region()->length ();
6007 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6008 : Drag (e, i->canvas_item ())
6011 , _cumulative_dx (0)
6013 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6014 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
6019 PatchChangeDrag::motion (GdkEvent* ev, bool)
6021 framepos_t f = adjusted_current_frame (ev);
6022 boost::shared_ptr<Region> r = _region_view->region ();
6023 f = max (f, r->position ());
6024 f = min (f, r->last_frame ());
6026 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
6027 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6028 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6029 _cumulative_dx = dxu;
6033 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6035 if (!movement_occurred) {
6036 if (was_double_click()) {
6037 _region_view->edit_patch_change (_patch_change);
6042 boost::shared_ptr<Region> r (_region_view->region ());
6043 framepos_t f = adjusted_current_frame (ev);
6044 f = max (f, r->position ());
6045 f = min (f, r->last_frame ());
6047 _region_view->move_patch_change (
6049 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
6054 PatchChangeDrag::aborted (bool)
6056 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6060 PatchChangeDrag::setup_pointer_frame_offset ()
6062 boost::shared_ptr<Region> region = _region_view->region ();
6063 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
6066 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6067 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6074 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6076 _region_view->update_drag_selection (
6078 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6082 MidiRubberbandSelectDrag::deselect_things ()
6087 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6088 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6091 _vertical_only = true;
6095 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6097 double const y = _region_view->midi_view()->y_position ();
6099 y1 = max (0.0, y1 - y);
6100 y2 = max (0.0, y2 - y);
6102 _region_view->update_vertical_drag_selection (
6105 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6110 MidiVerticalSelectDrag::deselect_things ()
6115 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6116 : RubberbandSelectDrag (e, i)
6122 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
6124 if (drag_in_progress) {
6125 /* We just want to select things at the end of the drag, not during it */
6129 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6131 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6133 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6135 _editor->commit_reversible_selection_op ();
6139 EditorRubberbandSelectDrag::deselect_things ()
6141 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6143 _editor->selection->clear_tracks();
6144 _editor->selection->clear_regions();
6145 _editor->selection->clear_points ();
6146 _editor->selection->clear_lines ();
6147 _editor->selection->clear_midi_notes ();
6149 _editor->commit_reversible_selection_op();
6152 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6157 _note[0] = _note[1] = 0;
6160 NoteCreateDrag::~NoteCreateDrag ()
6166 NoteCreateDrag::grid_frames (framepos_t t) const
6169 Evoral::Beats grid_beats = _editor->get_grid_type_as_beats (success, t);
6171 grid_beats = Evoral::Beats(1);
6174 return _region_view->region_beats_to_region_frames (grid_beats);
6178 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6180 Drag::start_grab (event, cursor);
6182 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6184 framepos_t pf = _drags->current_pointer_frame ();
6185 framecnt_t const g = grid_frames (pf);
6187 /* Hack so that we always snap to the note that we are over, instead of snapping
6188 to the next one if we're more than halfway through the one we're over.
6190 if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
6194 _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
6195 _note[1] = _note[0];
6197 MidiStreamView* sv = _region_view->midi_stream_view ();
6198 double const x = _editor->sample_to_pixel (_note[0]);
6199 double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
6201 _drag_rect->set (ArdourCanvas::Rect (x, y, x, y + floor (_region_view->midi_stream_view()->note_height ())));
6202 _drag_rect->set_outline_all ();
6203 _drag_rect->set_outline_color (0xffffff99);
6204 _drag_rect->set_fill_color (0xffffff66);
6208 NoteCreateDrag::motion (GdkEvent* event, bool)
6210 _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
6211 double const x0 = _editor->sample_to_pixel (_note[0]);
6212 double const x1 = _editor->sample_to_pixel (_note[1]);
6213 _drag_rect->set_x0 (std::min(x0, x1));
6214 _drag_rect->set_x1 (std::max(x0, x1));
6218 NoteCreateDrag::finished (GdkEvent*, bool had_movement)
6220 if (!had_movement) {
6224 framepos_t const start = min (_note[0], _note[1]);
6225 framecnt_t length = (framecnt_t) fabs ((double)(_note[0] - _note[1]));
6227 framecnt_t const g = grid_frames (start);
6228 Evoral::Beats const one_tick = Evoral::Beats::ticks(1);
6230 if (_editor->snap_mode() == SnapNormal && length < g) {
6234 Evoral::Beats length_beats = max (
6235 one_tick, _region_view->region_frames_to_region_beats (length) - one_tick);
6237 _region_view->create_note_at (start, _drag_rect->y0(), length_beats, false);
6241 NoteCreateDrag::y_to_region (double y) const
6244 _region_view->get_canvas_group()->canvas_to_item (x, y);
6249 NoteCreateDrag::aborted (bool)
6254 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6259 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6263 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6265 Drag::start_grab (event, cursor);
6269 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6275 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6278 distance = _drags->current_pointer_x() - grab_x();
6279 len = ar->fade_in()->back()->when;
6281 distance = grab_x() - _drags->current_pointer_x();
6282 len = ar->fade_out()->back()->when;
6285 /* how long should it be ? */
6287 new_length = len + _editor->pixel_to_sample (distance);
6289 /* now check with the region that this is legal */
6291 new_length = ar->verify_xfade_bounds (new_length, start);
6294 arv->reset_fade_in_shape_width (ar, new_length);
6296 arv->reset_fade_out_shape_width (ar, new_length);
6301 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6307 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6310 distance = _drags->current_pointer_x() - grab_x();
6311 len = ar->fade_in()->back()->when;
6313 distance = grab_x() - _drags->current_pointer_x();
6314 len = ar->fade_out()->back()->when;
6317 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6319 _editor->begin_reversible_command ("xfade trim");
6320 ar->playlist()->clear_owned_changes ();
6323 ar->set_fade_in_length (new_length);
6325 ar->set_fade_out_length (new_length);
6328 /* Adjusting the xfade may affect other regions in the playlist, so we need
6329 to get undo Commands from the whole playlist rather than just the
6333 vector<Command*> cmds;
6334 ar->playlist()->rdiff (cmds);
6335 _editor->session()->add_commands (cmds);
6336 _editor->commit_reversible_command ();
6341 CrossfadeEdgeDrag::aborted (bool)
6344 // arv->redraw_start_xfade ();
6346 // arv->redraw_end_xfade ();
6350 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
6351 : Drag (e, item, true)
6352 , line (new EditorCursor (*e))
6354 line->set_position (pos);
6358 RegionCutDrag::~RegionCutDrag ()
6364 RegionCutDrag::motion (GdkEvent*, bool)
6366 framepos_t where = _drags->current_pointer_frame();
6367 _editor->snap_to (where);
6369 line->set_position (where);
6373 RegionCutDrag::finished (GdkEvent*, bool)
6375 _editor->get_track_canvas()->canvas()->re_enter();
6377 framepos_t pos = _drags->current_pointer_frame();
6381 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
6387 _editor->split_regions_at (pos, rs);
6391 RegionCutDrag::aborted (bool)