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)
240 , _constraint_pressed (false)
246 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
252 _cursor_ctx = CursorContext::create (*_editor, cursor);
254 _cursor_ctx->change (cursor);
261 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
264 /* we set up x/y dragging constraints on first move */
265 _constraint_pressed = ArdourKeyboard::indicates_constraint (event->button.state);
267 if (_constraint_pressed) {
268 /* if constraint was indicated at the beginning of the drag, constrain x.
269 if the user presses these modifiers after this point in time (first move),
270 the drag will be constrained to the first direction of motion.
272 _x_constrained = true;
273 _y_constrained = false;
276 _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
278 setup_pointer_frame_offset ();
279 _grab_frame = adjusted_frame (_raw_grab_frame, event);
280 _last_pointer_frame = _grab_frame;
281 _last_pointer_x = _grab_x;
283 if (_trackview_only) {
284 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
287 _last_pointer_y = _grab_y;
291 if (!_editor->cursors()->is_invalid (cursor)) {
292 /* CAIROCANVAS need a variant here that passes *cursor */
293 _cursor_ctx = CursorContext::create (*_editor, cursor);
296 if (_editor->session() && _editor->session()->transport_rolling()) {
299 _was_rolling = false;
302 switch (_editor->snap_type()) {
303 case SnapToRegionStart:
304 case SnapToRegionEnd:
305 case SnapToRegionSync:
306 case SnapToRegionBoundary:
307 _editor->build_region_boundary_cache ();
314 /** Call to end a drag `successfully'. Ungrabs item and calls
315 * subclass' finished() method.
317 * @param event GDK event, or 0.
318 * @return true if some movement occurred, otherwise false.
321 Drag::end_grab (GdkEvent* event)
323 _editor->stop_canvas_autoscroll ();
327 finished (event, _move_threshold_passed);
329 _editor->verbose_cursor()->hide ();
332 return _move_threshold_passed;
336 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
340 if (f > _pointer_frame_offset) {
341 pos = f - _pointer_frame_offset;
345 _editor->snap_to_with_modifier (pos, event);
352 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
354 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
358 Drag::snap_delta (guint state) const
360 if (ArdourKeyboard::indicates_snap_delta (state)) {
368 Drag::current_pointer_x() const
370 return _drags->current_pointer_x ();
374 Drag::current_pointer_y () const
376 if (!_trackview_only) {
377 return _drags->current_pointer_y ();
380 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
384 Drag::setup_snap_delta (framepos_t pos)
386 framepos_t temp = pos;
387 _editor->snap_to (temp, ARDOUR::RoundNearest, false, true);
388 _snap_delta = temp - pos;
392 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
394 /* check to see if we have moved in any way that matters since the last motion event */
395 if (_move_threshold_passed &&
396 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
397 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
401 pair<framecnt_t, int> const threshold = move_threshold ();
403 bool const old_move_threshold_passed = _move_threshold_passed;
405 if (!_move_threshold_passed) {
407 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
408 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
410 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
413 if (active (_editor->mouse_mode) && _move_threshold_passed) {
415 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
417 if (old_move_threshold_passed != _move_threshold_passed) {
421 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
422 _initially_vertical = true;
424 _initially_vertical = false;
426 /** check constraints for this drag.
427 * Note that the current convention is to use "contains" for
428 * key modifiers during motion and "equals" when initiating a drag.
429 * In this case we haven't moved yet, so "equals" applies here.
431 if (Config->get_edit_mode() != Lock) {
432 if (event->motion.state & Gdk::BUTTON2_MASK) {
433 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
434 if (_constraint_pressed) {
435 _x_constrained = false;
436 _y_constrained = true;
438 _x_constrained = true;
439 _y_constrained = false;
441 } else if (!_constraint_pressed && ArdourKeyboard::indicates_constraint (event->button.state)) {
442 // if dragging normally, the motion is constrained to the first direction of movement.
443 if (_initially_vertical) {
444 _x_constrained = true;
445 _y_constrained = false;
447 _x_constrained = false;
448 _y_constrained = true;
452 if (event->button.state & Gdk::BUTTON2_MASK) {
453 _x_constrained = false;
455 _x_constrained = true;
457 _y_constrained = false;
461 if (!from_autoscroll) {
462 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
465 if (!_editor->autoscroll_active() || from_autoscroll) {
468 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
470 motion (event, first_move && !_starting_point_passed);
472 if (first_move && !_starting_point_passed) {
473 _starting_point_passed = true;
476 _last_pointer_x = _drags->current_pointer_x ();
477 _last_pointer_y = current_pointer_y ();
478 _last_pointer_frame = adjusted_current_frame (event, false);
488 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
496 aborted (_move_threshold_passed);
498 _editor->stop_canvas_autoscroll ();
499 _editor->verbose_cursor()->hide ();
503 Drag::show_verbose_cursor_time (framepos_t frame)
505 _editor->verbose_cursor()->set_time (frame);
506 _editor->verbose_cursor()->show ();
510 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
512 _editor->verbose_cursor()->set_duration (start, end);
513 _editor->verbose_cursor()->show ();
517 Drag::show_verbose_cursor_text (string const & text)
519 _editor->verbose_cursor()->set (text);
520 _editor->verbose_cursor()->show ();
523 boost::shared_ptr<Region>
524 Drag::add_midi_region (MidiTimeAxisView* view, bool commit, const int32_t sub_num)
526 if (_editor->session()) {
527 const TempoMap& map (_editor->session()->tempo_map());
528 framecnt_t pos = grab_frame();
529 /* not that the frame rate used here can be affected by pull up/down which
532 framecnt_t len = map.frame_at_beat (max (0.0, map.beat_at_frame (pos)) + 1.0) - pos;
533 return view->add_region (grab_frame(), len, commit, sub_num);
536 return boost::shared_ptr<Region>();
539 struct PresentationInfoTimeAxisViewSorter {
540 bool operator() (TimeAxisView* a, TimeAxisView* b) {
541 return a->stripable()->presentation_info().order() < b->stripable()->presentation_info().order();
545 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
550 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
552 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
553 as some of the regions we are dragging may be on such tracks.
556 TrackViewList track_views = _editor->track_views;
557 track_views.sort (PresentationInfoTimeAxisViewSorter ());
559 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
560 _time_axis_views.push_back (*i);
562 TimeAxisView::Children children_list = (*i)->get_child_list ();
563 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
564 _time_axis_views.push_back (j->get());
568 /* the list of views can be empty at this point if this is a region list-insert drag
571 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
572 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
575 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
579 RegionDrag::region_going_away (RegionView* v)
581 list<DraggingView>::iterator i = _views.begin ();
582 while (i != _views.end() && i->view != v) {
586 if (i != _views.end()) {
591 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
592 * or -1 if it is not found.
595 RegionDrag::find_time_axis_view (TimeAxisView* t) const
598 int const N = _time_axis_views.size ();
599 while (i < N && _time_axis_views[i] != t) {
603 if (_time_axis_views[i] != t) {
610 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
611 : RegionDrag (e, i, p, v)
613 , _ignore_video_lock (false)
615 , _last_pointer_time_axis_view (0)
616 , _last_pointer_layer (0)
621 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
625 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
627 Drag::start_grab (event, cursor);
628 setup_snap_delta (_last_frame_position);
630 show_verbose_cursor_time (_last_frame_position);
632 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
634 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
635 assert(_last_pointer_time_axis_view >= 0);
636 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
639 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
640 _ignore_video_lock = true;
644 /* cross track dragging seems broken here. disabled for now. */
645 _y_constrained = true;
650 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
652 /* compute the amount of pointer motion in frames, and where
653 the region would be if we moved it by that much.
655 *pending_region_position = adjusted_frame (_drags->current_pointer_frame (), event, false);
657 framepos_t sync_frame;
658 framecnt_t sync_offset;
661 sync_offset = _primary->region()->sync_offset (sync_dir);
663 /* we don't handle a sync point that lies before zero.
665 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
667 framecnt_t const sd = snap_delta (event->button.state);
668 sync_frame = *pending_region_position + (sync_dir * sync_offset) + sd;
670 _editor->snap_to_with_modifier (sync_frame, event);
672 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - sd;
675 *pending_region_position = _last_frame_position;
678 if (*pending_region_position > max_framepos - _primary->region()->length()) {
679 *pending_region_position = _last_frame_position;
684 bool const x_move_allowed = !_x_constrained;
686 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
688 /* x movement since last time (in pixels) */
689 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
691 /* total x movement */
692 framecnt_t total_dx = *pending_region_position;
693 if (regions_came_from_canvas()) {
694 total_dx = total_dx - grab_frame ();
697 /* check that no regions have gone off the start of the session */
698 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
699 if ((i->view->region()->position() + total_dx) < 0) {
701 *pending_region_position = _last_frame_position;
712 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
718 const int tavsize = _time_axis_views.size();
719 const int dt = delta > 0 ? +1 : -1;
721 int target = start + delta - skip;
723 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
724 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
726 while (current >= 0 && current != target) {
728 if (current < 0 && dt < 0) {
731 if (current >= tavsize && dt > 0) {
734 if (current < 0 || current >= tavsize) {
738 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
739 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
743 if (distance_only && current == start + delta) {
751 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
753 if (_y_constrained) {
757 const int tavsize = _time_axis_views.size();
758 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
759 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
760 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
762 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
763 /* already in the drop zone */
764 if (delta_track >= 0) {
765 /* downward motion - OK if others are still not in the dropzone */
774 } else if (n >= tavsize) {
775 /* downward motion into drop zone. That's fine. */
779 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
780 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
781 /* not a track, or the wrong type */
785 double const l = i->layer + delta_layer;
787 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
788 mode to allow the user to place a region below another on layer 0.
790 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
791 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
792 If it has, the layers will be munged later anyway, so it's ok.
798 /* all regions being dragged are ok with this change */
802 struct DraggingViewSorter {
803 bool operator() (const DraggingView& a, const DraggingView& b) {
804 return a.time_axis_view < b.time_axis_view;
809 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
811 double delta_layer = 0;
812 int delta_time_axis_view = 0;
813 int current_pointer_time_axis_view = -1;
815 assert (!_views.empty ());
817 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
819 /* Find the TimeAxisView that the pointer is now over */
820 const double cur_y = current_pointer_y ();
821 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
822 TimeAxisView* tv = r.first;
824 if (!tv && cur_y < 0) {
825 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
829 /* find drop-zone y-position */
830 Coord last_track_bottom_edge;
831 last_track_bottom_edge = 0;
832 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
833 if (!(*t)->hidden()) {
834 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
839 if (tv && tv->view()) {
840 /* the mouse is over a track */
841 double layer = r.second;
843 if (first_move && tv->view()->layer_display() == Stacked) {
844 tv->view()->set_layer_display (Expanded);
847 /* Here's the current pointer position in terms of time axis view and layer */
848 current_pointer_time_axis_view = find_time_axis_view (tv);
849 assert(current_pointer_time_axis_view >= 0);
851 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
853 /* Work out the change in y */
855 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
856 if (!rtv || !rtv->is_track()) {
857 /* ignore non-tracks early on. we can't move any regions on them */
858 } else if (_last_pointer_time_axis_view < 0) {
859 /* Was in the drop-zone, now over a track.
860 * Hence it must be an upward move (from the bottom)
862 * track_index is still -1, so delta must be set to
863 * move up the correct number of tracks from the bottom.
865 * This is necessary because steps may be skipped if
866 * the bottom-most track is not a valid target and/or
867 * if there are hidden tracks at the bottom.
868 * Hence the initial offset (_ddropzone) as well as the
869 * last valid pointer position (_pdropzone) need to be
870 * taken into account.
872 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
874 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
877 /* TODO needs adjustment per DraggingView,
879 * e.g. select one region on the top-layer of a track
880 * and one region which is at the bottom-layer of another track
883 * Indicated drop-zones and layering is wrong.
884 * and may infer additional layers on the target-track
885 * (depending how many layers the original track had).
887 * Or select two regions (different layers) on a same track,
888 * move across a non-layer track.. -> layering info is lost.
889 * on drop either of the regions may be on top.
891 * Proposed solution: screw it :) well,
892 * don't use delta_layer, use an absolute value
893 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
894 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
895 * 3) iterate over all DraggingView, find the one that is over the track with most layers
896 * 4) proportionally scale layer to layers available on target
898 delta_layer = current_pointer_layer - _last_pointer_layer;
901 /* for automation lanes, there is a TimeAxisView but no ->view()
902 * if (!tv) -> dropzone
904 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
905 /* Moving into the drop-zone.. */
906 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
907 /* delta_time_axis_view may not be sufficient to move into the DZ
908 * the mouse may enter it, but it may not be a valid move due to
911 * -> remember the delta needed to move into the dropzone
913 _ddropzone = delta_time_axis_view;
914 /* ..but subtract hidden tracks (or routes) at the bottom.
915 * we silently move mover them
917 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
918 - _time_axis_views.size();
920 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
921 /* move around inside the zone.
922 * This allows to move further down until all regions are in the zone.
924 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
925 assert(ptr_y >= last_track_bottom_edge);
926 assert(_ddropzone > 0);
928 /* calculate mouse position in 'tracks' below last track. */
929 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
930 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
932 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
934 delta_time_axis_view = dzpos - _pdropzone;
935 } else if (dzpos < _pdropzone && _ndropzone > 0) {
936 // move up inside the DZ
937 delta_time_axis_view = dzpos - _pdropzone;
941 /* Work out the change in x */
942 framepos_t pending_region_position;
943 double const x_delta = compute_x_delta (event, &pending_region_position);
944 _last_frame_position = pending_region_position;
946 /* calculate hidden tracks in current y-axis delta */
948 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
949 /* The mouse is more than one track below the dropzone.
950 * distance calculation is not needed (and would not work, either
951 * because the dropzone is "packed").
953 * Except when [partially] moving regions out of dropzone in a large step.
954 * (the mouse may or may not remain in the DZ)
955 * Hidden tracks at the bottom of the TAV need to be skipped.
957 * This also handles the case if the mouse entered the DZ
958 * in a large step (exessive delta), either due to fast-movement,
959 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
961 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
962 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
964 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
965 -_time_axis_views.size() - dt;
968 else if (_last_pointer_time_axis_view < 0) {
969 /* Moving out of the zone. Check for hidden tracks at the bottom. */
970 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
971 -_time_axis_views.size() - delta_time_axis_view;
973 /* calculate hidden tracks that are skipped by the pointer movement */
974 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
975 - _last_pointer_time_axis_view
976 - delta_time_axis_view;
979 /* Verify change in y */
980 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
981 /* this y movement is not allowed, so do no y movement this time */
982 delta_time_axis_view = 0;
987 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
988 /* haven't reached next snap point, and we're not switching
989 trackviews nor layers. nothing to do.
994 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
995 PlaylistDropzoneMap playlist_dropzone_map;
996 _ndropzone = 0; // number of elements currently in the dropzone
999 /* sort views by time_axis.
1000 * This retains track order in the dropzone, regardless
1001 * of actual selection order
1003 _views.sort (DraggingViewSorter());
1005 /* count number of distinct tracks of all regions
1006 * being dragged, used for dropzone.
1008 int prev_track = -1;
1009 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1010 if (i->time_axis_view != prev_track) {
1011 prev_track = i->time_axis_view;
1017 _views.back().time_axis_view -
1018 _views.front().time_axis_view;
1020 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1021 - _views.back().time_axis_view;
1023 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1027 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1029 RegionView* rv = i->view;
1034 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1041 /* reparent the regionview into a group above all
1045 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1046 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1047 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1048 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1049 /* move the item so that it continues to appear at the
1050 same location now that its parent has changed.
1052 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1055 /* If we have moved tracks, we'll fudge the layer delta so that the
1056 region gets moved back onto layer 0 on its new track; this avoids
1057 confusion when dragging regions from non-zero layers onto different
1060 double this_delta_layer = delta_layer;
1061 if (delta_time_axis_view != 0) {
1062 this_delta_layer = - i->layer;
1065 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1067 int track_index = i->time_axis_view + this_delta_time_axis_view;
1068 assert(track_index >= 0);
1070 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1071 /* Track is in the Dropzone */
1073 i->time_axis_view = track_index;
1074 assert(i->time_axis_view >= (int) _time_axis_views.size());
1077 double yposition = 0;
1078 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1079 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1082 /* store index of each new playlist as a negative count, starting at -1 */
1084 if (pdz == playlist_dropzone_map.end()) {
1085 /* compute where this new track (which doesn't exist yet) will live
1088 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1090 /* How high is this region view ? */
1092 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1093 ArdourCanvas::Rect bbox;
1096 bbox = obbox.get ();
1099 last_track_bottom_edge += bbox.height();
1101 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1104 yposition = pdz->second;
1107 /* values are zero or negative, hence the use of min() */
1108 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1113 /* The TimeAxisView that this region is now over */
1114 TimeAxisView* current_tv = _time_axis_views[track_index];
1116 /* Ensure it is moved from stacked -> expanded if appropriate */
1117 if (current_tv->view()->layer_display() == Stacked) {
1118 current_tv->view()->set_layer_display (Expanded);
1121 /* We're only allowed to go -ve in layer on Expanded views */
1122 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1123 this_delta_layer = - i->layer;
1127 rv->set_height (current_tv->view()->child_height ());
1129 /* Update show/hidden status as the region view may have come from a hidden track,
1130 or have moved to one.
1132 if (current_tv->hidden ()) {
1133 rv->get_canvas_group()->hide ();
1135 rv->get_canvas_group()->show ();
1138 /* Update the DraggingView */
1139 i->time_axis_view = track_index;
1140 i->layer += this_delta_layer;
1143 _editor->mouse_brush_insert_region (rv, pending_region_position);
1147 /* Get the y coordinate of the top of the track that this region is now over */
1148 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1150 /* And adjust for the layer that it should be on */
1151 StreamView* cv = current_tv->view ();
1152 switch (cv->layer_display ()) {
1156 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1159 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1163 /* need to get the parent of the regionview
1164 * canvas group and get its position in
1165 * equivalent coordinate space as the trackview
1166 * we are now dragging over.
1169 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1174 /* Now move the region view */
1175 rv->move (x_delta, y_delta);
1177 } /* foreach region */
1179 _total_x_delta += x_delta;
1181 if (x_delta != 0 && !_brushing) {
1182 show_verbose_cursor_time (_last_frame_position);
1185 /* keep track of pointer movement */
1187 /* the pointer is currently over a time axis view */
1189 if (_last_pointer_time_axis_view < 0) {
1190 /* last motion event was not over a time axis view
1191 * or last y-movement out of the dropzone was not valid
1194 if (delta_time_axis_view < 0) {
1195 /* in the drop zone, moving up */
1197 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1198 * We do not use negative _last_pointer_time_axis_view because
1199 * the dropzone is "packed" (the actual track offset is ignored)
1201 * As opposed to the actual number
1202 * of elements in the dropzone (_ndropzone)
1203 * _pdropzone is not constrained. This is necessary
1204 * to allow moving multiple regions with y-distance
1207 * There can be 0 elements in the dropzone,
1208 * even though the drag-pointer is inside the DZ.
1211 * [ Audio-track, Midi-track, Audio-track, DZ ]
1212 * move regions from both audio tracks at the same time into the
1213 * DZ by grabbing the region in the bottom track.
1215 assert(current_pointer_time_axis_view >= 0);
1216 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1220 /* only move out of the zone if the movement is OK */
1221 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1222 assert(delta_time_axis_view < 0);
1223 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1224 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1225 * the current position can be calculated as follows:
1227 // a well placed oofus attack can still throw this off.
1228 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1229 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1232 /* last motion event was also over a time axis view */
1233 _last_pointer_time_axis_view += delta_time_axis_view;
1234 assert(_last_pointer_time_axis_view >= 0);
1239 /* the pointer is not over a time axis view */
1240 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1241 _pdropzone += delta_time_axis_view - delta_skip;
1242 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1245 _last_pointer_layer += delta_layer;
1249 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1251 if (_copy && first_move) {
1252 if (_x_constrained && !_brushing) {
1253 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1254 } else if (!_brushing) {
1255 _editor->begin_reversible_command (Operations::region_copy);
1256 } else if (_brushing) {
1257 _editor->begin_reversible_command (Operations::drag_region_brush);
1259 /* duplicate the regionview(s) and region(s) */
1261 list<DraggingView> new_regionviews;
1263 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1265 RegionView* rv = i->view;
1266 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1267 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1269 const boost::shared_ptr<const Region> original = rv->region();
1270 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true
1271 , _editor->get_grid_music_divisions (event->button.state));
1272 /* need to set this so that the drop zone code can work. This doesn't
1273 actually put the region into the playlist, but just sets a weak pointer
1276 region_copy->set_playlist (original->playlist());
1280 boost::shared_ptr<AudioRegion> audioregion_copy
1281 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1283 nrv = new AudioRegionView (*arv, audioregion_copy);
1285 boost::shared_ptr<MidiRegion> midiregion_copy
1286 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1287 nrv = new MidiRegionView (*mrv, midiregion_copy);
1292 nrv->get_canvas_group()->show ();
1293 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1295 /* swap _primary to the copy */
1297 if (rv == _primary) {
1301 /* ..and deselect the one we copied */
1303 rv->set_selected (false);
1306 if (!new_regionviews.empty()) {
1308 /* reflect the fact that we are dragging the copies */
1310 _views = new_regionviews;
1312 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1315 } else if (!_copy && first_move) {
1316 if (_x_constrained && !_brushing) {
1317 _editor->begin_reversible_command (_("fixed time region drag"));
1318 } else if (!_brushing) {
1319 _editor->begin_reversible_command (Operations::region_drag);
1320 } else if (_brushing) {
1321 _editor->begin_reversible_command (Operations::drag_region_brush);
1324 RegionMotionDrag::motion (event, first_move);
1328 RegionMotionDrag::finished (GdkEvent *, bool)
1330 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1331 if (!(*i)->view()) {
1335 if ((*i)->view()->layer_display() == Expanded) {
1336 (*i)->view()->set_layer_display (Stacked);
1342 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1344 RegionMotionDrag::finished (ev, movement_occurred);
1346 if (!movement_occurred) {
1350 if (was_double_click() && !_views.empty()) {
1351 DraggingView dv = _views.front();
1352 dv.view->show_region_editor ();
1359 assert (!_views.empty ());
1361 /* We might have hidden region views so that they weren't visible during the drag
1362 (when they have been reparented). Now everything can be shown again, as region
1363 views are back in their track parent groups.
1365 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1366 i->view->get_canvas_group()->show ();
1369 bool const changed_position = (_last_frame_position != _primary->region()->position());
1370 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1371 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1393 _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
1397 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1399 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1404 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1405 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1406 uint32_t output_chan = region->n_channels();
1407 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1408 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1410 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1411 TimeAxisView* tav =_editor->axis_view_from_stripable (audio_tracks.front());
1413 tav->set_height (original->current_height());
1415 return dynamic_cast<RouteTimeAxisView*>(tav);
1417 ChanCount one_midi_port (DataType::MIDI, 1);
1418 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1419 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port, boost::shared_ptr<ARDOUR::PluginInfo>(),
1420 (ARDOUR::Plugin::PresetRecord*) 0,
1421 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1422 TimeAxisView* tav = _editor->axis_view_from_stripable (midi_tracks.front());
1424 tav->set_height (original->current_height());
1426 return dynamic_cast<RouteTimeAxisView*> (tav);
1429 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1435 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta, int32_t const ev_state)
1437 RegionSelection new_views;
1438 PlaylistSet modified_playlists;
1439 RouteTimeAxisView* new_time_axis_view = 0;
1442 /* all changes were made during motion event handlers */
1444 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1448 _editor->commit_reversible_command ();
1452 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1453 PlaylistMapping playlist_mapping;
1455 /* insert the regions into their new playlists */
1456 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1458 RouteTimeAxisView* dest_rtv = 0;
1460 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1466 if (changed_position && !_x_constrained) {
1467 where = i->view->region()->position() - drag_delta;
1469 where = i->view->region()->position();
1472 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1473 /* dragged to drop zone */
1475 PlaylistMapping::iterator pm;
1477 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1478 /* first region from this original playlist: create a new track */
1479 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1480 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1481 dest_rtv = new_time_axis_view;
1483 /* we already created a new track for regions from this playlist, use it */
1484 dest_rtv = pm->second;
1487 /* destination time axis view is the one we dragged to */
1488 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1491 if (dest_rtv != 0) {
1492 RegionView* new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1493 modified_playlists, _editor->get_grid_music_divisions (ev_state));
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,
1529 int32_t const ev_state
1532 RegionSelection new_views;
1533 PlaylistSet modified_playlists;
1534 PlaylistSet frozen_playlists;
1535 set<RouteTimeAxisView*> views_to_update;
1536 RouteTimeAxisView* new_time_axis_view = 0;
1538 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1539 PlaylistMapping playlist_mapping;
1541 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1543 RegionView* rv = i->view;
1544 RouteTimeAxisView* dest_rtv = 0;
1546 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1551 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1552 /* dragged to drop zone */
1554 PlaylistMapping::iterator pm;
1556 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1557 /* first region from this original playlist: create a new track */
1558 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1559 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1560 dest_rtv = new_time_axis_view;
1562 /* we already created a new track for regions from this playlist, use it */
1563 dest_rtv = pm->second;
1567 /* destination time axis view is the one we dragged to */
1568 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1573 double const dest_layer = i->layer;
1575 views_to_update.insert (dest_rtv);
1579 if (changed_position && !_x_constrained) {
1580 where = rv->region()->position() - drag_delta;
1582 where = rv->region()->position();
1585 if (changed_tracks) {
1587 /* insert into new playlist */
1589 RegionView* new_view = insert_region_into_playlist (
1590 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1591 modified_playlists, _editor->get_grid_music_divisions (ev_state)
1594 if (new_view == 0) {
1599 new_views.push_back (new_view);
1601 /* remove from old playlist */
1603 /* the region that used to be in the old playlist is not
1604 moved to the new one - we use a copy of it. as a result,
1605 any existing editor for the region should no longer be
1608 rv->hide_region_editor();
1611 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1615 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1617 /* this movement may result in a crossfade being modified, or a layering change,
1618 so we need to get undo data from the playlist as well as the region.
1621 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1623 playlist->clear_changes ();
1626 rv->region()->clear_changes ();
1629 motion on the same track. plonk the previously reparented region
1630 back to its original canvas group (its streamview).
1631 No need to do anything for copies as they are fake regions which will be deleted.
1634 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1635 rv->get_canvas_group()->set_y_position (i->initial_y);
1638 /* just change the model */
1639 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1640 playlist->set_layer (rv->region(), dest_layer);
1643 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1645 r = frozen_playlists.insert (playlist);
1648 playlist->freeze ();
1651 rv->region()->set_position (where, _editor->get_grid_music_divisions (ev_state));
1652 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1655 if (changed_tracks) {
1657 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1658 was selected in all of them, then removing it from a playlist will have removed all
1659 trace of it from _views (i.e. there were N regions selected, we removed 1,
1660 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1661 corresponding regionview, and _views is now empty).
1663 This could have invalidated any and all iterators into _views.
1665 The heuristic we use here is: if the region selection is empty, break out of the loop
1666 here. if the region selection is not empty, then restart the loop because we know that
1667 we must have removed at least the region(view) we've just been working on as well as any
1668 that we processed on previous iterations.
1670 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1671 we can just iterate.
1675 if (_views.empty()) {
1686 /* If we've created new regions either by copying or moving
1687 to a new track, we want to replace the old selection with the new ones
1690 if (new_views.size() > 0) {
1691 _editor->selection->set (new_views);
1694 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1698 /* write commands for the accumulated diffs for all our modified playlists */
1699 add_stateful_diff_commands_for_playlists (modified_playlists);
1700 /* applies to _brushing */
1701 _editor->commit_reversible_command ();
1703 /* We have futzed with the layering of canvas items on our streamviews.
1704 If any region changed layer, this will have resulted in the stream
1705 views being asked to set up their region views, and all will be well.
1706 If not, we might now have badly-ordered region views. Ask the StreamViews
1707 involved to sort themselves out, just in case.
1710 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1711 (*i)->view()->playlist_layered ((*i)->track ());
1715 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1716 * @param region Region to remove.
1717 * @param playlist playlist To remove from.
1718 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1719 * that clear_changes () is only called once per playlist.
1722 RegionMoveDrag::remove_region_from_playlist (
1723 boost::shared_ptr<Region> region,
1724 boost::shared_ptr<Playlist> playlist,
1725 PlaylistSet& modified_playlists
1728 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1731 playlist->clear_changes ();
1734 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1738 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1739 * clearing the playlist's diff history first if necessary.
1740 * @param region Region to insert.
1741 * @param dest_rtv Destination RouteTimeAxisView.
1742 * @param dest_layer Destination layer.
1743 * @param where Destination position.
1744 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1745 * that clear_changes () is only called once per playlist.
1746 * @return New RegionView, or 0 if no insert was performed.
1749 RegionMoveDrag::insert_region_into_playlist (
1750 boost::shared_ptr<Region> region,
1751 RouteTimeAxisView* dest_rtv,
1754 PlaylistSet& modified_playlists,
1755 const int32_t sub_num
1758 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1759 if (!dest_playlist) {
1763 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1764 _new_region_view = 0;
1765 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1767 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1768 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1770 dest_playlist->clear_changes ();
1772 dest_playlist->add_region (region, where, 1.0, false, sub_num);
1774 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1775 dest_playlist->set_layer (region, dest_layer);
1780 assert (_new_region_view);
1782 return _new_region_view;
1786 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1788 _new_region_view = rv;
1792 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1794 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1795 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1797 _editor->session()->add_command (c);
1806 RegionMoveDrag::aborted (bool movement_occurred)
1810 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1811 list<DraggingView>::const_iterator next = i;
1820 RegionMotionDrag::aborted (movement_occurred);
1825 RegionMotionDrag::aborted (bool)
1827 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1829 StreamView* sview = (*i)->view();
1832 if (sview->layer_display() == Expanded) {
1833 sview->set_layer_display (Stacked);
1838 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1839 RegionView* rv = i->view;
1840 TimeAxisView* tv = &(rv->get_time_axis_view ());
1841 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1843 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1844 rv->get_canvas_group()->set_y_position (0);
1846 rv->move (-_total_x_delta, 0);
1847 rv->set_height (rtv->view()->child_height ());
1851 /** @param b true to brush, otherwise false.
1852 * @param c true to make copies of the regions being moved, otherwise false.
1854 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1855 : RegionMotionDrag (e, i, p, v, b)
1857 , _new_region_view (0)
1859 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1862 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1863 if (rtv && rtv->is_track()) {
1864 speed = rtv->track()->speed ();
1867 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1871 RegionMoveDrag::setup_pointer_frame_offset ()
1873 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1876 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1877 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1879 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1881 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1882 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1884 _primary = v->view()->create_region_view (r, false, false);
1886 _primary->get_canvas_group()->show ();
1887 _primary->set_position (pos, 0);
1888 _views.push_back (DraggingView (_primary, this, v));
1890 _last_frame_position = pos;
1892 _item = _primary->get_canvas_group ();
1896 RegionInsertDrag::finished (GdkEvent * event, bool)
1898 int pos = _views.front().time_axis_view;
1899 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1901 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1903 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1904 _primary->get_canvas_group()->set_y_position (0);
1906 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1908 _editor->begin_reversible_command (Operations::insert_region);
1909 playlist->clear_changes ();
1910 playlist->add_region (_primary->region (), _last_frame_position);
1912 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
1913 if (Config->get_edit_mode() == Ripple) {
1914 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
1917 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1918 _editor->commit_reversible_command ();
1926 RegionInsertDrag::aborted (bool)
1933 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1934 : RegionMoveDrag (e, i, p, v, false, false)
1936 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1939 struct RegionSelectionByPosition {
1940 bool operator() (RegionView*a, RegionView* b) {
1941 return a->region()->position () < b->region()->position();
1946 RegionSpliceDrag::motion (GdkEvent* event, bool)
1948 /* Which trackview is this ? */
1950 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
1951 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1953 /* The region motion is only processed if the pointer is over
1957 if (!tv || !tv->is_track()) {
1958 /* To make sure we hide the verbose canvas cursor when the mouse is
1959 not held over an audio track.
1961 _editor->verbose_cursor()->hide ();
1964 _editor->verbose_cursor()->show ();
1969 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1975 RegionSelection copy;
1976 _editor->selection->regions.by_position(copy);
1978 framepos_t const pf = adjusted_current_frame (event);
1980 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1982 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1988 boost::shared_ptr<Playlist> playlist;
1990 if ((playlist = atv->playlist()) == 0) {
1994 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1999 if (pf < (*i)->region()->last_frame() + 1) {
2003 if (pf > (*i)->region()->first_frame()) {
2009 playlist->shuffle ((*i)->region(), dir);
2014 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2016 RegionMoveDrag::finished (event, movement_occurred);
2020 RegionSpliceDrag::aborted (bool)
2030 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2033 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
2035 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2036 RegionSelection to_ripple;
2037 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2038 if ((*i)->position() >= where) {
2039 to_ripple.push_back (rtv->view()->find_view(*i));
2043 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2044 if (!exclude.contains (*i)) {
2045 // the selection has already been added to _views
2047 if (drag_in_progress) {
2048 // do the same things that RegionMotionDrag::motion does when
2049 // first_move is true, for the region views that we're adding
2050 // to _views this time
2053 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2054 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2055 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2056 rvg->reparent (_editor->_drag_motion_group);
2058 // we only need to move in the y direction
2059 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2064 _views.push_back (DraggingView (*i, this, tav));
2070 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2073 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2074 // we added all the regions after the selection
2076 std::list<DraggingView>::iterator to_erase = i++;
2077 if (!_editor->selection->regions.contains (to_erase->view)) {
2078 // restore the non-selected regions to their original playlist & positions,
2079 // and then ripple them back by the length of the regions that were dragged away
2080 // do the same things as RegionMotionDrag::aborted
2082 RegionView *rv = to_erase->view;
2083 TimeAxisView* tv = &(rv->get_time_axis_view ());
2084 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2087 // plonk them back onto their own track
2088 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2089 rv->get_canvas_group()->set_y_position (0);
2093 // move the underlying region to match the view
2094 rv->region()->set_position (rv->region()->position() + amount);
2096 // restore the view to match the underlying region's original position
2097 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2100 rv->set_height (rtv->view()->child_height ());
2101 _views.erase (to_erase);
2107 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2109 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2111 return allow_moves_across_tracks;
2119 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2120 : RegionMoveDrag (e, i, p, v, false, false)
2122 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2123 // compute length of selection
2124 RegionSelection selected_regions = _editor->selection->regions;
2125 selection_length = selected_regions.end_frame() - selected_regions.start();
2127 // we'll only allow dragging to another track in ripple mode if all the regions
2128 // being dragged start off on the same track
2129 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2132 exclude = new RegionList;
2133 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2134 exclude->push_back((*i)->region());
2137 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2138 RegionSelection copy;
2139 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2141 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2142 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2144 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2145 // find ripple start point on each applicable playlist
2146 RegionView *first_selected_on_this_track = NULL;
2147 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2148 if ((*i)->region()->playlist() == (*pi)) {
2149 // region is on this playlist - it's the first, because they're sorted
2150 first_selected_on_this_track = *i;
2154 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2155 add_all_after_to_views (
2156 &first_selected_on_this_track->get_time_axis_view(),
2157 first_selected_on_this_track->region()->position(),
2158 selected_regions, false);
2161 if (allow_moves_across_tracks) {
2162 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2170 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2172 /* Which trackview is this ? */
2174 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2175 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2177 /* The region motion is only processed if the pointer is over
2181 if (!tv || !tv->is_track()) {
2182 /* To make sure we hide the verbose canvas cursor when the mouse is
2183 not held over an audiotrack.
2185 _editor->verbose_cursor()->hide ();
2189 framepos_t where = adjusted_current_frame (event);
2190 assert (where >= 0);
2192 double delta = compute_x_delta (event, &after);
2194 framecnt_t amount = _editor->pixel_to_sample (delta);
2196 if (allow_moves_across_tracks) {
2197 // all the originally selected regions were on the same track
2199 framecnt_t adjust = 0;
2200 if (prev_tav && tv != prev_tav) {
2201 // dragged onto a different track
2202 // remove the unselected regions from _views, restore them to their original positions
2203 // and add the regions after the drop point on the new playlist to _views instead.
2204 // undo the effect of rippling the previous playlist, and include the effect of removing
2205 // the dragged region(s) from this track
2207 remove_unselected_from_views (prev_amount, false);
2208 // ripple previous playlist according to the regions that have been removed onto the new playlist
2209 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2212 // move just the selected regions
2213 RegionMoveDrag::motion(event, first_move);
2215 // ensure that the ripple operation on the new playlist inserts selection_length time
2216 adjust = selection_length;
2217 // ripple the new current playlist
2218 tv->playlist()->ripple (where, amount+adjust, exclude);
2220 // add regions after point where drag entered this track to subsequent ripples
2221 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2224 // motion on same track
2225 RegionMoveDrag::motion(event, first_move);
2229 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2230 prev_position = where;
2232 // selection encompasses multiple tracks - just drag
2233 // cross-track drags are forbidden
2234 RegionMoveDrag::motion(event, first_move);
2237 if (!_x_constrained) {
2238 prev_amount += amount;
2241 _last_frame_position = after;
2245 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2247 if (!movement_occurred) {
2251 if (was_double_click() && !_views.empty()) {
2252 DraggingView dv = _views.front();
2253 dv.view->show_region_editor ();
2260 _editor->begin_reversible_command(_("Ripple drag"));
2262 // remove the regions being rippled from the dragging view, updating them to
2263 // their new positions
2264 remove_unselected_from_views (prev_amount, true);
2266 if (allow_moves_across_tracks) {
2268 // if regions were dragged across tracks, we've rippled any later
2269 // regions on the track the regions were dragged off, so we need
2270 // to add the original track to the undo record
2271 orig_tav->playlist()->clear_changes();
2272 vector<Command*> cmds;
2273 orig_tav->playlist()->rdiff (cmds);
2274 _editor->session()->add_commands (cmds);
2276 if (prev_tav && prev_tav != orig_tav) {
2277 prev_tav->playlist()->clear_changes();
2278 vector<Command*> cmds;
2279 prev_tav->playlist()->rdiff (cmds);
2280 _editor->session()->add_commands (cmds);
2283 // selection spanned multiple tracks - all will need adding to undo record
2285 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2286 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2288 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2289 (*pi)->clear_changes();
2290 vector<Command*> cmds;
2291 (*pi)->rdiff (cmds);
2292 _editor->session()->add_commands (cmds);
2296 // other modified playlists are added to undo by RegionMoveDrag::finished()
2297 RegionMoveDrag::finished (event, movement_occurred);
2298 _editor->commit_reversible_command();
2302 RegionRippleDrag::aborted (bool movement_occurred)
2304 RegionMoveDrag::aborted (movement_occurred);
2309 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2311 _view (dynamic_cast<MidiTimeAxisView*> (v))
2313 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2319 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2322 _editor->begin_reversible_command (_("create region"));
2323 _region = add_midi_region (_view, false, _editor->get_grid_music_divisions (event->button.state));
2324 _view->playlist()->freeze ();
2327 framepos_t const f = adjusted_current_frame (event);
2328 if (f < grab_frame()) {
2329 _region->set_initial_position (f);
2332 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2333 so that if this region is duplicated, its duplicate starts on
2334 a snap point rather than 1 frame after a snap point. Otherwise things get
2335 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2336 place snapped notes at the start of the region.
2339 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2340 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2346 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2348 if (!movement_occurred) {
2349 add_midi_region (_view, true, _editor->get_grid_music_divisions (event->button.state));
2351 _view->playlist()->thaw ();
2352 _editor->commit_reversible_command();
2357 RegionCreateDrag::aborted (bool)
2360 _view->playlist()->thaw ();
2366 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2371 , _was_selected (false)
2374 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2378 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2380 Gdk::Cursor* cursor;
2381 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2383 float x_fraction = cnote->mouse_x_fraction ();
2385 if (x_fraction > 0.0 && x_fraction < 0.25) {
2386 cursor = _editor->cursors()->left_side_trim;
2389 cursor = _editor->cursors()->right_side_trim;
2393 Drag::start_grab (event, cursor);
2395 region = &cnote->region_view();
2398 temp = region->snap_to_pixel (cnote->x0 (), true);
2399 _snap_delta = temp - cnote->x0 ();
2403 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2408 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2409 if (ms.size() > 1) {
2410 /* has to be relative, may make no sense otherwise */
2414 if (!(_was_selected = cnote->selected())) {
2416 /* tertiary-click means extend selection - we'll do that on button release,
2417 so don't add it here, because otherwise we make it hard to figure
2418 out the "extend-to" range.
2421 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2424 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2427 region->note_selected (cnote, true);
2429 _editor->get_selection().clear_points();
2430 region->unique_select (cnote);
2437 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2439 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2441 _editor->begin_reversible_command (_("resize notes"));
2443 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2444 MidiRegionSelection::iterator next;
2447 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2449 mrv->begin_resizing (at_front);
2455 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2456 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2458 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2462 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2464 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2465 if (_editor->snap_mode () != SnapOff) {
2469 if (_editor->snap_mode () == SnapOff) {
2471 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2472 if (apply_snap_delta) {
2478 if (apply_snap_delta) {
2482 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2488 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2490 if (!movement_occurred) {
2491 /* no motion - select note */
2492 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2493 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2494 _editor->current_mouse_mode() == Editing::MouseDraw) {
2496 bool changed = false;
2498 if (_was_selected) {
2499 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2501 region->note_deselected (cnote);
2504 _editor->get_selection().clear_points();
2505 region->unique_select (cnote);
2509 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2510 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2512 if (!extend && !add && region->selection_size() > 1) {
2513 _editor->get_selection().clear_points();
2514 region->unique_select (cnote);
2516 } else if (extend) {
2517 region->note_selected (cnote, true, true);
2520 /* it was added during button press */
2526 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2527 _editor->commit_reversible_selection_op();
2534 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2535 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2536 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2538 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2541 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2543 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2544 if (_editor->snap_mode () != SnapOff) {
2548 if (_editor->snap_mode () == SnapOff) {
2550 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2551 if (apply_snap_delta) {
2557 if (apply_snap_delta) {
2561 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2565 _editor->commit_reversible_command ();
2569 NoteResizeDrag::aborted (bool)
2571 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2572 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2573 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2575 mrv->abort_resizing ();
2580 AVDraggingView::AVDraggingView (RegionView* v)
2583 initial_position = v->region()->position ();
2586 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2589 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2592 TrackViewList empty;
2594 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2595 std::list<RegionView*> views = rs.by_layer();
2598 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2599 RegionView* rv = (*i);
2600 if (!rv->region()->video_locked()) {
2603 if (rv->region()->locked()) {
2606 _views.push_back (AVDraggingView (rv));
2611 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2613 Drag::start_grab (event);
2614 if (_editor->session() == 0) {
2618 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2624 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2628 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2629 _max_backwards_drag = (
2630 ARDOUR_UI::instance()->video_timeline->get_duration()
2631 + ARDOUR_UI::instance()->video_timeline->get_offset()
2632 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2635 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2636 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2637 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2640 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2643 Timecode::Time timecode;
2644 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2645 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);
2646 show_verbose_cursor_text (buf);
2650 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2652 if (_editor->session() == 0) {
2655 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2659 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2663 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2664 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2666 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2667 dt = - _max_backwards_drag;
2670 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2671 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2673 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2674 RegionView* rv = i->view;
2675 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2678 rv->region()->clear_changes ();
2679 rv->region()->suspend_property_changes();
2681 rv->region()->set_position(i->initial_position + dt);
2682 rv->region_changed(ARDOUR::Properties::position);
2685 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2686 Timecode::Time timecode;
2687 Timecode::Time timediff;
2689 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2690 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2691 snprintf (buf, sizeof (buf),
2692 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2693 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2694 , _("Video Start:"),
2695 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2697 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2699 show_verbose_cursor_text (buf);
2703 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2705 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2712 if (!movement_occurred || ! _editor->session()) {
2716 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2718 _editor->begin_reversible_command (_("Move Video"));
2720 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2721 ARDOUR_UI::instance()->video_timeline->save_undo();
2722 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2723 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2725 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2726 i->view->drag_end();
2727 i->view->region()->resume_property_changes ();
2729 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2732 _editor->session()->maybe_update_session_range(
2733 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2734 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2738 _editor->commit_reversible_command ();
2742 VideoTimeLineDrag::aborted (bool)
2744 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2747 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2748 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2750 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2751 i->view->region()->resume_property_changes ();
2752 i->view->region()->set_position(i->initial_position);
2756 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2757 : RegionDrag (e, i, p, v)
2758 , _operation (StartTrim)
2759 , _preserve_fade_anchor (preserve_fade_anchor)
2760 , _jump_position_when_done (false)
2762 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2766 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2769 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2770 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2772 if (tv && tv->is_track()) {
2773 speed = tv->track()->speed();
2776 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2777 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2778 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2780 framepos_t const pf = adjusted_current_frame (event);
2781 setup_snap_delta (region_start);
2783 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2784 /* Move the contents of the region around without changing the region bounds */
2785 _operation = ContentsTrim;
2786 Drag::start_grab (event, _editor->cursors()->trimmer);
2788 /* These will get overridden for a point trim.*/
2789 if (pf < (region_start + region_length/2)) {
2790 /* closer to front */
2791 _operation = StartTrim;
2792 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2793 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2795 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2799 _operation = EndTrim;
2800 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2801 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2803 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2807 /* jump trim disabled for now
2808 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2809 _jump_position_when_done = true;
2813 switch (_operation) {
2815 show_verbose_cursor_time (region_start);
2818 show_verbose_cursor_duration (region_start, region_end);
2821 show_verbose_cursor_time (pf);
2825 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2826 i->view->region()->suspend_property_changes ();
2831 TrimDrag::motion (GdkEvent* event, bool first_move)
2833 RegionView* rv = _primary;
2836 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2837 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2838 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2839 frameoffset_t frame_delta = 0;
2841 if (tv && tv->is_track()) {
2842 speed = tv->track()->speed();
2844 framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
2845 framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state);
2851 switch (_operation) {
2853 trim_type = "Region start trim";
2856 trim_type = "Region end trim";
2859 trim_type = "Region content trim";
2866 _editor->begin_reversible_command (trim_type);
2868 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2869 RegionView* rv = i->view;
2870 rv->region()->playlist()->clear_owned_changes ();
2872 if (_operation == StartTrim) {
2873 rv->trim_front_starting ();
2876 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2879 arv->temporarily_hide_envelope ();
2883 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2884 insert_result = _editor->motion_frozen_playlists.insert (pl);
2886 if (insert_result.second) {
2892 bool non_overlap_trim = false;
2894 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
2895 non_overlap_trim = true;
2898 /* contstrain trim to fade length */
2899 if (_preserve_fade_anchor) {
2900 switch (_operation) {
2902 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2903 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2905 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2906 if (ar->locked()) continue;
2907 framecnt_t len = ar->fade_in()->back()->when;
2908 if (len < dt) dt = min(dt, len);
2912 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2913 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2915 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2916 if (ar->locked()) continue;
2917 framecnt_t len = ar->fade_out()->back()->when;
2918 if (len < -dt) dt = max(dt, -len);
2927 switch (_operation) {
2929 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2930 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
2931 , _editor->get_grid_music_divisions (event->button.state));
2933 if (changed && _preserve_fade_anchor) {
2934 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2936 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2937 framecnt_t len = ar->fade_in()->back()->when;
2938 framecnt_t diff = ar->first_frame() - i->initial_position;
2939 framepos_t new_length = len - diff;
2940 i->anchored_fade_length = min (ar->length(), new_length);
2941 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
2942 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
2949 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2950 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, _editor->get_grid_music_divisions (event->button.state));
2951 if (changed && _preserve_fade_anchor) {
2952 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2954 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2955 framecnt_t len = ar->fade_out()->back()->when;
2956 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
2957 framepos_t new_length = len + diff;
2958 i->anchored_fade_length = min (ar->length(), new_length);
2959 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
2960 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
2968 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
2970 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2971 i->view->move_contents (frame_delta);
2977 switch (_operation) {
2979 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
2982 show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
2985 // show_verbose_cursor_time (frame_delta);
2991 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
2993 if (movement_occurred) {
2994 motion (event, false);
2996 if (_operation == StartTrim) {
2997 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2999 /* This must happen before the region's StatefulDiffCommand is created, as it may
3000 `correct' (ahem) the region's _start from being negative to being zero. It
3001 needs to be zero in the undo record.
3003 i->view->trim_front_ending ();
3005 if (_preserve_fade_anchor) {
3006 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3008 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3009 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3010 ar->set_fade_in_length(i->anchored_fade_length);
3011 ar->set_fade_in_active(true);
3014 if (_jump_position_when_done) {
3015 i->view->region()->set_position (i->initial_position);
3018 } else if (_operation == EndTrim) {
3019 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3020 if (_preserve_fade_anchor) {
3021 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3023 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3024 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3025 ar->set_fade_out_length(i->anchored_fade_length);
3026 ar->set_fade_out_active(true);
3029 if (_jump_position_when_done) {
3030 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3035 if (!_views.empty()) {
3036 if (_operation == StartTrim) {
3037 _editor->maybe_locate_with_edit_preroll(
3038 _views.begin()->view->region()->position());
3040 if (_operation == EndTrim) {
3041 _editor->maybe_locate_with_edit_preroll(
3042 _views.begin()->view->region()->position() +
3043 _views.begin()->view->region()->length());
3047 if (!_editor->selection->selected (_primary)) {
3048 _primary->thaw_after_trim ();
3051 set<boost::shared_ptr<Playlist> > diffed_playlists;
3053 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3054 i->view->thaw_after_trim ();
3055 i->view->enable_display (true);
3057 /* Trimming one region may affect others on the playlist, so we need
3058 to get undo Commands from the whole playlist rather than just the
3059 region. Use diffed_playlists to make sure we don't diff a given
3060 playlist more than once.
3062 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
3063 if (diffed_playlists.find (p) == diffed_playlists.end()) {
3064 vector<Command*> cmds;
3066 _editor->session()->add_commands (cmds);
3067 diffed_playlists.insert (p);
3072 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3076 _editor->motion_frozen_playlists.clear ();
3077 _editor->commit_reversible_command();
3080 /* no mouse movement */
3081 if (adjusted_current_frame (event) != adjusted_frame (_drags->current_pointer_frame(), event, false)) {
3082 _editor->point_trim (event, adjusted_current_frame (event));
3086 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3087 i->view->region()->resume_property_changes ();
3092 TrimDrag::aborted (bool movement_occurred)
3094 /* Our motion method is changing model state, so use the Undo system
3095 to cancel. Perhaps not ideal, as this will leave an Undo point
3096 behind which may be slightly odd from the user's point of view.
3101 if (movement_occurred) {
3105 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3106 i->view->region()->resume_property_changes ();
3111 TrimDrag::setup_pointer_frame_offset ()
3113 list<DraggingView>::iterator i = _views.begin ();
3114 while (i != _views.end() && i->view != _primary) {
3118 if (i == _views.end()) {
3122 switch (_operation) {
3124 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
3127 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
3134 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3137 , _old_snap_type (e->snap_type())
3138 , _old_snap_mode (e->snap_mode())
3141 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3142 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3144 _real_section = &_marker->meter();
3149 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3151 Drag::start_grab (event, cursor);
3152 show_verbose_cursor_time (adjusted_current_frame(event));
3156 MeterMarkerDrag::setup_pointer_frame_offset ()
3158 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
3162 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3165 // create a dummy marker to catch events, then hide it.
3168 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3170 _marker = new MeterMarker (
3172 *_editor->meter_group,
3173 UIConfiguration::instance().color ("meter marker"),
3175 *new MeterSection (_marker->meter())
3178 /* use the new marker for the grab */
3179 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3182 TempoMap& map (_editor->session()->tempo_map());
3183 /* get current state */
3184 before_state = &map.get_state();
3187 _editor->begin_reversible_command (_("move meter mark"));
3189 _editor->begin_reversible_command (_("copy meter mark"));
3191 Timecode::BBT_Time bbt = _real_section->bbt();
3193 /* we can't add a meter where one currently exists */
3194 if (_real_section->frame() < adjusted_current_frame (event, false)) {
3199 const double beat = map.beat_at_bbt (bbt);
3200 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3201 , beat, bbt, map.frame_at_bbt (bbt), _real_section->position_lock_style());
3202 if (!_real_section) {
3208 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3209 if (_real_section->position_lock_style() != AudioTime) {
3210 _editor->set_snap_to (SnapToBar);
3211 _editor->set_snap_mode (SnapNormal);
3215 framepos_t pf = adjusted_current_frame (event);
3217 if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3218 /* never snap to music for audio locked */
3219 pf = adjusted_current_frame (event, false);
3222 _editor->session()->tempo_map().gui_move_meter (_real_section, pf);
3224 /* fake marker meeds to stay under the mouse, unlike the real one. */
3225 _marker->set_position (adjusted_current_frame (event, false));
3227 show_verbose_cursor_time (_real_section->frame());
3231 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3233 if (!movement_occurred) {
3234 if (was_double_click()) {
3235 _editor->edit_meter_marker (*_marker);
3240 /* reinstate old snap setting */
3241 _editor->set_snap_to (_old_snap_type);
3242 _editor->set_snap_mode (_old_snap_mode);
3244 TempoMap& map (_editor->session()->tempo_map());
3246 XMLNode &after = map.get_state();
3247 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3248 _editor->commit_reversible_command ();
3250 // delete the dummy marker we used for visual representation while moving.
3251 // a new visual marker will show up automatically.
3256 MeterMarkerDrag::aborted (bool moved)
3258 _marker->set_position (_marker->meter().frame ());
3260 /* reinstate old snap setting */
3261 _editor->set_snap_to (_old_snap_type);
3262 _editor->set_snap_mode (_old_snap_mode);
3264 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3265 // delete the dummy marker we used for visual representation while moving.
3266 // a new visual marker will show up automatically.
3271 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3276 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3278 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3279 _real_section = &_marker->tempo();
3280 _movable = _real_section->movable();
3285 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3287 Drag::start_grab (event, cursor);
3288 if (!_real_section->active()) {
3289 show_verbose_cursor_text (_("inactive"));
3291 show_verbose_cursor_time (adjusted_current_frame (event));
3296 TempoMarkerDrag::setup_pointer_frame_offset ()
3298 _pointer_frame_offset = raw_grab_frame() - _real_section->frame();
3302 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3304 if (!_real_section->active()) {
3310 // mvc drag - create a dummy marker to catch events, hide it.
3313 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
3315 TempoSection section (_marker->tempo());
3317 _marker = new TempoMarker (
3319 *_editor->tempo_group,
3320 UIConfiguration::instance().color ("tempo marker"),
3322 *new TempoSection (_marker->tempo())
3325 /* use the new marker for the grab */
3326 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3329 TempoMap& map (_editor->session()->tempo_map());
3330 /* get current state */
3331 before_state = &map.get_state();
3334 _editor->begin_reversible_command (_("move tempo mark"));
3337 const Tempo tempo (_marker->tempo());
3338 const framepos_t frame = adjusted_current_frame (event) + 1;
3339 const TempoSection::Type type = _real_section->type();
3341 _editor->begin_reversible_command (_("copy tempo mark"));
3343 if (_real_section->position_lock_style() == MusicTime) {
3344 _real_section = map.add_tempo (tempo, map.pulse_at_frame (frame), 0, type, MusicTime);
3346 _real_section = map.add_tempo (tempo, 0.0, frame, type, AudioTime);
3349 if (!_real_section) {
3357 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3358 /* use vertical movement to alter tempo .. should be log */
3359 double new_bpm = _real_section->beats_per_minute() + ((last_pointer_y() - current_pointer_y()) / 5.0);
3362 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type()));
3364 show_verbose_cursor_text (strs.str());
3366 } else if (_movable && !_real_section->locked_to_meter()) {
3367 const framepos_t pf = adjusted_current_frame (event);
3368 TempoMap& map (_editor->session()->tempo_map());
3370 /* snap to beat is 1, snap to bar is -1 (sorry) */
3371 int sub_num = _editor->get_grid_music_divisions (event->button.state);
3373 map.gui_move_tempo (_real_section, pf, sub_num);
3375 show_verbose_cursor_time (_real_section->frame());
3377 _marker->set_position (adjusted_current_frame (event, false));
3381 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3383 if (!_real_section->active()) {
3386 if (!movement_occurred) {
3387 if (was_double_click()) {
3388 _editor->edit_tempo_marker (*_marker);
3393 TempoMap& map (_editor->session()->tempo_map());
3395 XMLNode &after = map.get_state();
3396 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3397 _editor->commit_reversible_command ();
3399 // delete the dummy marker we used for visual representation while moving.
3400 // a new visual marker will show up automatically.
3405 TempoMarkerDrag::aborted (bool moved)
3407 _marker->set_position (_marker->tempo().frame());
3409 TempoMap& map (_editor->session()->tempo_map());
3410 map.set_state (*before_state, Stateful::current_state_version);
3411 // delete the dummy (hidden) marker we used for events while moving.
3416 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3422 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3427 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3429 Drag::start_grab (event, cursor);
3430 TempoMap& map (_editor->session()->tempo_map());
3431 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
3434 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).beats_per_minute() << "\n";
3435 sstr << "<" << fixed << setprecision(3) << _tempo->beats_per_minute();
3436 show_verbose_cursor_text (sstr.str());
3437 finished (event, false);
3441 BBTRulerDrag::setup_pointer_frame_offset ()
3443 TempoMap& map (_editor->session()->tempo_map());
3444 const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame()));
3445 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3448 if (divisions > 0) {
3449 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions);
3451 /* while it makes some sense for the user to determine the division to 'grab',
3452 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3453 and the result over steep tempo curves. Use sixteenths.
3455 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4);
3458 _pulse = map.pulse_at_beat (beat);
3460 _pointer_frame_offset = raw_grab_frame() - map.frame_at_pulse (_pulse);
3465 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3467 TempoMap& map (_editor->session()->tempo_map());
3470 /* get current state */
3471 before_state = &map.get_state();
3472 _editor->begin_reversible_command (_("dilate tempo"));
3477 if (_editor->snap_musical()) {
3478 pf = adjusted_current_frame (event, false);
3480 pf = adjusted_current_frame (event);
3483 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3484 /* adjust previous tempo to match pointer frame */
3485 _editor->session()->tempo_map().gui_dilate_tempo (_tempo, map.frame_at_pulse (_pulse), pf, _pulse);
3488 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).beats_per_minute() << "\n";
3489 sstr << "<" << fixed << setprecision(3) << _tempo->beats_per_minute();
3490 show_verbose_cursor_text (sstr.str());
3494 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3496 if (!movement_occurred) {
3500 TempoMap& map (_editor->session()->tempo_map());
3502 XMLNode &after = map.get_state();
3503 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3504 _editor->commit_reversible_command ();
3508 BBTRulerDrag::aborted (bool moved)
3511 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3516 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3517 : Drag (e, &c.track_canvas_item(), false)
3522 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3525 /** Do all the things we do when dragging the playhead to make it look as though
3526 * we have located, without actually doing the locate (because that would cause
3527 * the diskstream buffers to be refilled, which is too slow).
3530 CursorDrag::fake_locate (framepos_t t)
3532 if (_editor->session () == 0) {
3536 _editor->playhead_cursor->set_position (t);
3538 Session* s = _editor->session ();
3539 if (s->timecode_transmission_suspended ()) {
3540 framepos_t const f = _editor->playhead_cursor->current_frame ();
3541 /* This is asynchronous so it will be sent "now"
3543 s->send_mmc_locate (f);
3544 /* These are synchronous and will be sent during the next
3547 s->queue_full_time_code ();
3548 s->queue_song_position_pointer ();
3551 show_verbose_cursor_time (t);
3552 _editor->UpdateAllTransportClocks (t);
3556 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3558 Drag::start_grab (event, c);
3559 setup_snap_delta (_editor->playhead_cursor->current_frame ());
3561 _grab_zoom = _editor->samples_per_pixel;
3563 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3565 _editor->snap_to_with_modifier (where, event);
3567 _editor->_dragging_playhead = true;
3569 Session* s = _editor->session ();
3571 /* grab the track canvas item as well */
3573 _cursor.track_canvas_item().grab();
3576 if (_was_rolling && _stop) {
3580 if (s->is_auditioning()) {
3581 s->cancel_audition ();
3585 if (AudioEngine::instance()->connected()) {
3587 /* do this only if we're the engine is connected
3588 * because otherwise this request will never be
3589 * serviced and we'll busy wait forever. likewise,
3590 * notice if we are disconnected while waiting for the
3591 * request to be serviced.
3594 s->request_suspend_timecode_transmission ();
3595 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3596 /* twiddle our thumbs */
3601 fake_locate (where - snap_delta (event->button.state));
3605 CursorDrag::motion (GdkEvent* event, bool)
3607 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3608 _editor->snap_to_with_modifier (where, event);
3609 if (where != last_pointer_frame()) {
3610 fake_locate (where - snap_delta (event->button.state));
3615 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3617 _editor->_dragging_playhead = false;
3619 _cursor.track_canvas_item().ungrab();
3621 if (!movement_occurred && _stop) {
3625 motion (event, false);
3627 Session* s = _editor->session ();
3629 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3630 _editor->_pending_locate_request = true;
3631 s->request_resume_timecode_transmission ();
3636 CursorDrag::aborted (bool)
3638 _cursor.track_canvas_item().ungrab();
3640 if (_editor->_dragging_playhead) {
3641 _editor->session()->request_resume_timecode_transmission ();
3642 _editor->_dragging_playhead = false;
3645 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3648 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3649 : RegionDrag (e, i, p, v)
3651 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3655 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3657 Drag::start_grab (event, cursor);
3659 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3660 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3661 setup_snap_delta (r->position ());
3663 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3667 FadeInDrag::setup_pointer_frame_offset ()
3669 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3670 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3671 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3675 FadeInDrag::motion (GdkEvent* event, bool)
3677 framecnt_t fade_length;
3679 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3680 _editor->snap_to_with_modifier (pos, event);
3681 pos -= snap_delta (event->button.state);
3683 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3685 if (pos < (region->position() + 64)) {
3686 fade_length = 64; // this should be a minimum defined somewhere
3687 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3688 fade_length = region->length() - region->fade_out()->back()->when - 1;
3690 fade_length = pos - region->position();
3693 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3695 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3701 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3704 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3708 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3710 if (!movement_occurred) {
3714 framecnt_t fade_length;
3715 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3716 _editor->snap_to_with_modifier (pos, event);
3717 pos -= snap_delta (event->button.state);
3719 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3721 if (pos < (region->position() + 64)) {
3722 fade_length = 64; // this should be a minimum defined somewhere
3723 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3724 fade_length = region->length() - region->fade_out()->back()->when - 1;
3726 fade_length = pos - region->position();
3729 bool in_command = false;
3731 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3733 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3739 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3740 XMLNode &before = alist->get_state();
3742 tmp->audio_region()->set_fade_in_length (fade_length);
3743 tmp->audio_region()->set_fade_in_active (true);
3746 _editor->begin_reversible_command (_("change fade in length"));
3749 XMLNode &after = alist->get_state();
3750 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3754 _editor->commit_reversible_command ();
3759 FadeInDrag::aborted (bool)
3761 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3762 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3768 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3772 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3773 : RegionDrag (e, i, p, v)
3775 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3779 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3781 Drag::start_grab (event, cursor);
3783 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3784 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3785 setup_snap_delta (r->last_frame ());
3787 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3791 FadeOutDrag::setup_pointer_frame_offset ()
3793 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3794 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3795 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3799 FadeOutDrag::motion (GdkEvent* event, bool)
3801 framecnt_t fade_length;
3803 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3804 _editor->snap_to_with_modifier (pos, event);
3805 pos -= snap_delta (event->button.state);
3807 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3809 if (pos > (region->last_frame() - 64)) {
3810 fade_length = 64; // this should really be a minimum fade defined somewhere
3811 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3812 fade_length = region->length() - region->fade_in()->back()->when - 1;
3814 fade_length = region->last_frame() - pos;
3817 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3819 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3825 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3828 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3832 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3834 if (!movement_occurred) {
3838 framecnt_t fade_length;
3840 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3841 _editor->snap_to_with_modifier (pos, event);
3842 pos -= snap_delta (event->button.state);
3844 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3846 if (pos > (region->last_frame() - 64)) {
3847 fade_length = 64; // this should really be a minimum fade defined somewhere
3848 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3849 fade_length = region->length() - region->fade_in()->back()->when - 1;
3851 fade_length = region->last_frame() - pos;
3854 bool in_command = false;
3856 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3858 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3864 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3865 XMLNode &before = alist->get_state();
3867 tmp->audio_region()->set_fade_out_length (fade_length);
3868 tmp->audio_region()->set_fade_out_active (true);
3871 _editor->begin_reversible_command (_("change fade out length"));
3874 XMLNode &after = alist->get_state();
3875 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3879 _editor->commit_reversible_command ();
3884 FadeOutDrag::aborted (bool)
3886 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3887 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3893 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3897 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3899 , _selection_changed (false)
3901 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3902 Gtk::Window* toplevel = _editor->current_toplevel();
3903 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
3907 _points.push_back (ArdourCanvas::Duple (0, 0));
3909 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
3912 MarkerDrag::~MarkerDrag ()
3914 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
3919 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
3921 location = new Location (*l);
3922 markers.push_back (m);
3927 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3929 Drag::start_grab (event, cursor);
3933 Location *location = _editor->find_location_from_marker (_marker, is_start);
3934 _editor->_dragging_edit_point = true;
3936 update_item (location);
3938 // _drag_line->show();
3939 // _line->raise_to_top();
3942 show_verbose_cursor_time (location->start());
3944 show_verbose_cursor_time (location->end());
3946 setup_snap_delta (is_start ? location->start() : location->end());
3948 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3951 case Selection::Toggle:
3952 /* we toggle on the button release */
3954 case Selection::Set:
3955 if (!_editor->selection->selected (_marker)) {
3956 _editor->selection->set (_marker);
3957 _selection_changed = true;
3960 case Selection::Extend:
3962 Locations::LocationList ll;
3963 list<ArdourMarker*> to_add;
3965 _editor->selection->markers.range (s, e);
3966 s = min (_marker->position(), s);
3967 e = max (_marker->position(), e);
3970 if (e < max_framepos) {
3973 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
3974 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
3975 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
3978 to_add.push_back (lm->start);
3981 to_add.push_back (lm->end);
3985 if (!to_add.empty()) {
3986 _editor->selection->add (to_add);
3987 _selection_changed = true;
3991 case Selection::Add:
3992 _editor->selection->add (_marker);
3993 _selection_changed = true;
3998 /* Set up copies for us to manipulate during the drag
4001 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4003 Location* l = _editor->find_location_from_marker (*i, is_start);
4010 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4012 /* range: check that the other end of the range isn't
4015 CopiedLocationInfo::iterator x;
4016 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4017 if (*(*x).location == *l) {
4021 if (x == _copied_locations.end()) {
4022 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4024 (*x).markers.push_back (*i);
4025 (*x).move_both = true;
4033 MarkerDrag::setup_pointer_frame_offset ()
4036 Location *location = _editor->find_location_from_marker (_marker, is_start);
4037 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
4041 MarkerDrag::motion (GdkEvent* event, bool)
4043 framecnt_t f_delta = 0;
4045 bool move_both = false;
4046 Location *real_location;
4047 Location *copy_location = 0;
4048 framecnt_t const sd = snap_delta (event->button.state);
4050 framecnt_t const newframe = adjusted_frame (_drags->current_pointer_frame () + sd, event, true) - sd;
4051 framepos_t next = newframe;
4053 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4057 CopiedLocationInfo::iterator x;
4059 /* find the marker we're dragging, and compute the delta */
4061 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4063 copy_location = (*x).location;
4065 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4067 /* this marker is represented by this
4068 * CopiedLocationMarkerInfo
4071 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4076 if (real_location->is_mark()) {
4077 f_delta = newframe - copy_location->start();
4081 switch (_marker->type()) {
4082 case ArdourMarker::SessionStart:
4083 case ArdourMarker::RangeStart:
4084 case ArdourMarker::LoopStart:
4085 case ArdourMarker::PunchIn:
4086 f_delta = newframe - copy_location->start();
4089 case ArdourMarker::SessionEnd:
4090 case ArdourMarker::RangeEnd:
4091 case ArdourMarker::LoopEnd:
4092 case ArdourMarker::PunchOut:
4093 f_delta = newframe - copy_location->end();
4096 /* what kind of marker is this ? */
4105 if (x == _copied_locations.end()) {
4106 /* hmm, impossible - we didn't find the dragged marker */
4110 /* now move them all */
4112 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4114 copy_location = x->location;
4116 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4120 if (real_location->locked()) {
4124 if (copy_location->is_mark()) {
4128 copy_location->set_start (copy_location->start() + f_delta);
4132 framepos_t new_start = copy_location->start() + f_delta;
4133 framepos_t new_end = copy_location->end() + f_delta;
4135 if (is_start) { // start-of-range marker
4137 if (move_both || (*x).move_both) {
4138 copy_location->set_start (new_start);
4139 copy_location->set_end (new_end);
4140 } else if (new_start < copy_location->end()) {
4141 copy_location->set_start (new_start);
4142 } else if (newframe > 0) {
4143 //_editor->snap_to (next, RoundUpAlways, true);
4144 copy_location->set_end (next);
4145 copy_location->set_start (newframe);
4148 } else { // end marker
4150 if (move_both || (*x).move_both) {
4151 copy_location->set_end (new_end);
4152 copy_location->set_start (new_start);
4153 } else if (new_end > copy_location->start()) {
4154 copy_location->set_end (new_end);
4155 } else if (newframe > 0) {
4156 //_editor->snap_to (next, RoundDownAlways, true);
4157 copy_location->set_start (next);
4158 copy_location->set_end (newframe);
4163 update_item (copy_location);
4165 /* now lookup the actual GUI items used to display this
4166 * location and move them to wherever the copy of the location
4167 * is now. This means that the logic in ARDOUR::Location is
4168 * still enforced, even though we are not (yet) modifying
4169 * the real Location itself.
4172 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4175 lm->set_position (copy_location->start(), copy_location->end());
4180 assert (!_copied_locations.empty());
4182 show_verbose_cursor_time (newframe);
4186 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4188 if (!movement_occurred) {
4190 if (was_double_click()) {
4191 _editor->rename_marker (_marker);
4195 /* just a click, do nothing but finish
4196 off the selection process
4199 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4201 case Selection::Set:
4202 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4203 _editor->selection->set (_marker);
4204 _selection_changed = true;
4208 case Selection::Toggle:
4209 /* we toggle on the button release, click only */
4210 _editor->selection->toggle (_marker);
4211 _selection_changed = true;
4215 case Selection::Extend:
4216 case Selection::Add:
4220 if (_selection_changed) {
4221 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4222 _editor->commit_reversible_selection_op();
4228 _editor->_dragging_edit_point = false;
4230 XMLNode &before = _editor->session()->locations()->get_state();
4231 bool in_command = false;
4233 MarkerSelection::iterator i;
4234 CopiedLocationInfo::iterator x;
4237 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4238 x != _copied_locations.end() && i != _editor->selection->markers.end();
4241 Location * location = _editor->find_location_from_marker (*i, is_start);
4245 if (location->locked()) {
4249 _editor->begin_reversible_command ( _("move marker") );
4252 if (location->is_mark()) {
4253 location->set_start (((*x).location)->start());
4255 location->set (((*x).location)->start(), ((*x).location)->end());
4258 if (location->is_session_range()) {
4259 _editor->session()->set_end_is_free (false);
4265 XMLNode &after = _editor->session()->locations()->get_state();
4266 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4267 _editor->commit_reversible_command ();
4272 MarkerDrag::aborted (bool movement_occurred)
4274 if (!movement_occurred) {
4278 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4280 /* move all markers to their original location */
4283 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4286 Location * location = _editor->find_location_from_marker (*m, is_start);
4289 (*m)->set_position (is_start ? location->start() : location->end());
4296 MarkerDrag::update_item (Location*)
4301 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4303 , _fixed_grab_x (0.0)
4304 , _fixed_grab_y (0.0)
4305 , _cumulative_x_drag (0.0)
4306 , _cumulative_y_drag (0.0)
4310 if (_zero_gain_fraction < 0.0) {
4311 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4314 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4316 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4322 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4324 Drag::start_grab (event, _editor->cursors()->fader);
4326 // start the grab at the center of the control point so
4327 // the point doesn't 'jump' to the mouse after the first drag
4328 _fixed_grab_x = _point->get_x();
4329 _fixed_grab_y = _point->get_y();
4331 framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
4332 setup_snap_delta (pos);
4334 float const fraction = 1 - (_point->get_y() / _point->line().height());
4335 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4337 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4339 if (!_point->can_slide ()) {
4340 _x_constrained = true;
4345 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4347 double dx = _drags->current_pointer_x() - last_pointer_x();
4348 double dy = current_pointer_y() - last_pointer_y();
4349 bool need_snap = true;
4351 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4357 /* coordinate in pixels relative to the start of the region (for region-based automation)
4358 or track (for track-based automation) */
4359 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4360 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4362 // calculate zero crossing point. back off by .01 to stay on the
4363 // positive side of zero
4364 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4366 if (_x_constrained) {
4369 if (_y_constrained) {
4373 _cumulative_x_drag = cx - _fixed_grab_x;
4374 _cumulative_y_drag = cy - _fixed_grab_y;
4378 cy = min ((double) _point->line().height(), cy);
4380 // make sure we hit zero when passing through
4381 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4385 framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state);
4387 if (!_x_constrained && need_snap) {
4388 _editor->snap_to_with_modifier (cx_frames, event);
4391 cx_frames -= snap_delta (event->button.state);
4392 cx_frames = min (cx_frames, _point->line().maximum_time());
4394 float const fraction = 1.0 - (cy / _point->line().height());
4397 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4398 _editor->begin_reversible_command (_("automation event move"));
4399 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4401 pair<double, float> result;
4402 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4404 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (result.second));
4408 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4410 if (!movement_occurred) {
4413 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4414 _editor->reset_point_selection ();
4418 _point->line().end_drag (_pushing, _final_index);
4419 _editor->commit_reversible_command ();
4424 ControlPointDrag::aborted (bool)
4426 _point->line().reset ();
4430 ControlPointDrag::active (Editing::MouseMode m)
4432 if (m == Editing::MouseDraw) {
4433 /* always active in mouse draw */
4437 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4438 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4441 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4444 , _fixed_grab_x (0.0)
4445 , _fixed_grab_y (0.0)
4446 , _cumulative_y_drag (0)
4450 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4454 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4456 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4459 _item = &_line->grab_item ();
4461 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4462 origin, and ditto for y.
4465 double mx = event->button.x;
4466 double my = event->button.y;
4468 _line->grab_item().canvas_to_item (mx, my);
4470 framecnt_t const frame_within_region = (framecnt_t) floor (mx * _editor->samples_per_pixel);
4472 if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
4473 /* no adjacent points */
4477 Drag::start_grab (event, _editor->cursors()->fader);
4479 /* store grab start in item frame */
4480 double const bx = _line->nth (_before)->get_x();
4481 double const ax = _line->nth (_after)->get_x();
4482 double const click_ratio = (ax - mx) / (ax - bx);
4484 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4489 double fraction = 1.0 - (cy / _line->height());
4491 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4495 LineDrag::motion (GdkEvent* event, bool first_move)
4497 double dy = current_pointer_y() - last_pointer_y();
4499 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4503 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4505 _cumulative_y_drag = cy - _fixed_grab_y;
4508 cy = min ((double) _line->height(), cy);
4510 double const fraction = 1.0 - (cy / _line->height());
4514 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4516 _editor->begin_reversible_command (_("automation range move"));
4517 _line->start_drag_line (_before, _after, initial_fraction);
4520 /* we are ignoring x position for this drag, so we can just pass in anything */
4521 pair<double, float> result;
4523 result = _line->drag_motion (0, fraction, true, false, ignored);
4524 show_verbose_cursor_text (_line->get_verbose_cursor_string (result.second));
4528 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4530 if (movement_occurred) {
4531 motion (event, false);
4532 _line->end_drag (false, 0);
4533 _editor->commit_reversible_command ();
4535 /* add a new control point on the line */
4537 AutomationTimeAxisView* atv;
4539 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4540 framepos_t where = grab_frame ();
4543 double cy = _fixed_grab_y;
4545 _line->grab_item().item_to_canvas (cx, cy);
4547 atv->add_automation_event (event, where, cy, false);
4548 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4549 AudioRegionView* arv;
4551 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4552 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4559 LineDrag::aborted (bool)
4564 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4568 _region_view_grab_x (0.0),
4569 _cumulative_x_drag (0),
4573 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4577 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4579 Drag::start_grab (event);
4581 _line = reinterpret_cast<Line*> (_item);
4584 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4586 double cx = event->button.x;
4587 double cy = event->button.y;
4589 _item->parent()->canvas_to_item (cx, cy);
4591 /* store grab start in parent frame */
4592 _region_view_grab_x = cx;
4594 _before = *(float*) _item->get_data ("position");
4596 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4598 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4602 FeatureLineDrag::motion (GdkEvent*, bool)
4604 double dx = _drags->current_pointer_x() - last_pointer_x();
4606 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4608 _cumulative_x_drag += dx;
4610 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4619 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4621 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4623 float *pos = new float;
4626 _line->set_data ("position", pos);
4632 FeatureLineDrag::finished (GdkEvent*, bool)
4634 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4635 _arv->update_transient(_before, _before);
4639 FeatureLineDrag::aborted (bool)
4644 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4646 , _vertical_only (false)
4648 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4652 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4654 Drag::start_grab (event);
4655 show_verbose_cursor_time (adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
4659 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4666 framepos_t const pf = adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
4668 framepos_t grab = grab_frame ();
4669 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4670 _editor->snap_to_with_modifier (grab, event);
4672 grab = raw_grab_frame ();
4675 /* base start and end on initial click position */
4685 if (current_pointer_y() < grab_y()) {
4686 y1 = current_pointer_y();
4689 y2 = current_pointer_y();
4693 if (start != end || y1 != y2) {
4695 double x1 = _editor->sample_to_pixel (start);
4696 double x2 = _editor->sample_to_pixel (end);
4697 const double min_dimension = 2.0;
4699 if (_vertical_only) {
4700 /* fixed 10 pixel width */
4704 x2 = min (x1 - min_dimension, x2);
4706 x2 = max (x1 + min_dimension, x2);
4711 y2 = min (y1 - min_dimension, y2);
4713 y2 = max (y1 + min_dimension, y2);
4716 /* translate rect into item space and set */
4718 ArdourCanvas::Rect r (x1, y1, x2, y2);
4720 /* this drag is a _trackview_only == true drag, so the y1 and
4721 * y2 (computed using current_pointer_y() and grab_y()) will be
4722 * relative to the top of the trackview group). The
4723 * rubberband rect has the same parent/scroll offset as the
4724 * the trackview group, so we can use the "r" rect directly
4725 * to set the shape of the rubberband.
4728 _editor->rubberband_rect->set (r);
4729 _editor->rubberband_rect->show();
4730 _editor->rubberband_rect->raise_to_top();
4732 show_verbose_cursor_time (pf);
4734 do_select_things (event, true);
4739 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4743 framepos_t grab = grab_frame ();
4744 framepos_t lpf = last_pointer_frame ();
4746 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4747 grab = raw_grab_frame ();
4748 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4762 if (current_pointer_y() < grab_y()) {
4763 y1 = current_pointer_y();
4766 y2 = current_pointer_y();
4770 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4774 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4776 if (movement_occurred) {
4778 motion (event, false);
4779 do_select_things (event, false);
4785 bool do_deselect = true;
4786 MidiTimeAxisView* mtv;
4788 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4790 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4791 /* nothing selected */
4792 add_midi_region (mtv, true, _editor->get_grid_music_divisions(event->button.state));
4793 do_deselect = false;
4797 /* do not deselect if Primary or Tertiary (toggle-select or
4798 * extend-select are pressed.
4801 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4802 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4809 _editor->rubberband_rect->hide();
4813 RubberbandSelectDrag::aborted (bool)
4815 _editor->rubberband_rect->hide ();
4818 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4819 : RegionDrag (e, i, p, v)
4821 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4825 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4827 Drag::start_grab (event, cursor);
4829 _editor->get_selection().add (_primary);
4831 framepos_t where = _primary->region()->position();
4832 setup_snap_delta (where);
4834 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4838 TimeFXDrag::motion (GdkEvent* event, bool)
4840 RegionView* rv = _primary;
4841 StreamView* cv = rv->get_time_axis_view().view ();
4843 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4844 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4845 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4846 framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
4847 _editor->snap_to_with_modifier (pf, event);
4848 pf -= snap_delta (event->button.state);
4850 if (pf > rv->region()->position()) {
4851 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4854 show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4858 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
4860 /* this may have been a single click, no drag. We still want the dialog
4861 to show up in that case, so that the user can manually edit the
4862 parameters for the timestretch.
4865 float fraction = 1.0;
4867 if (movement_occurred) {
4869 motion (event, false);
4871 _primary->get_time_axis_view().hide_timestretch ();
4873 framepos_t adjusted_frame_pos = adjusted_current_frame (event);
4875 if (adjusted_frame_pos < _primary->region()->position()) {
4876 /* backwards drag of the left edge - not usable */
4880 framecnt_t newlen = adjusted_frame_pos - _primary->region()->position();
4882 fraction = (double) newlen / (double) _primary->region()->length();
4884 #ifndef USE_RUBBERBAND
4885 // Soundtouch uses fraction / 100 instead of normal (/ 1)
4886 if (_primary->region()->data_type() == DataType::AUDIO) {
4887 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4892 if (!_editor->get_selection().regions.empty()) {
4893 /* primary will already be included in the selection, and edit
4894 group shared editing will propagate selection across
4895 equivalent regions, so just use the current region
4899 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
4900 error << _("An error occurred while executing time stretch operation") << endmsg;
4906 TimeFXDrag::aborted (bool)
4908 _primary->get_time_axis_view().hide_timestretch ();
4911 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
4914 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
4918 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4920 Drag::start_grab (event);
4924 ScrubDrag::motion (GdkEvent* /*event*/, bool)
4926 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
4930 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4932 if (movement_occurred && _editor->session()) {
4933 /* make sure we stop */
4934 _editor->session()->request_transport_speed (0.0);
4939 ScrubDrag::aborted (bool)
4944 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4948 , _time_selection_at_start (!_editor->get_selection().time.empty())
4950 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
4952 if (_time_selection_at_start) {
4953 start_at_start = _editor->get_selection().time.start();
4954 end_at_start = _editor->get_selection().time.end_frame();
4959 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
4961 if (_editor->session() == 0) {
4965 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4967 switch (_operation) {
4968 case CreateSelection:
4969 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4974 cursor = _editor->cursors()->selector;
4975 Drag::start_grab (event, cursor);
4978 case SelectionStartTrim:
4979 if (_editor->clicked_axisview) {
4980 _editor->clicked_axisview->order_selection_trims (_item, true);
4982 Drag::start_grab (event, _editor->cursors()->left_side_trim);
4985 case SelectionEndTrim:
4986 if (_editor->clicked_axisview) {
4987 _editor->clicked_axisview->order_selection_trims (_item, false);
4989 Drag::start_grab (event, _editor->cursors()->right_side_trim);
4993 Drag::start_grab (event, cursor);
4996 case SelectionExtend:
4997 Drag::start_grab (event, cursor);
5001 if (_operation == SelectionMove) {
5002 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5004 show_verbose_cursor_time (adjusted_current_frame (event));
5009 SelectionDrag::setup_pointer_frame_offset ()
5011 switch (_operation) {
5012 case CreateSelection:
5013 _pointer_frame_offset = 0;
5016 case SelectionStartTrim:
5018 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
5021 case SelectionEndTrim:
5022 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
5025 case SelectionExtend:
5031 SelectionDrag::motion (GdkEvent* event, bool first_move)
5033 framepos_t start = 0;
5035 framecnt_t length = 0;
5036 framecnt_t distance = 0;
5038 framepos_t const pending_position = adjusted_current_frame (event);
5040 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
5044 switch (_operation) {
5045 case CreateSelection:
5047 framepos_t grab = grab_frame ();
5050 grab = adjusted_current_frame (event, false);
5051 if (grab < pending_position) {
5052 _editor->snap_to (grab, RoundDownMaybe);
5054 _editor->snap_to (grab, RoundUpMaybe);
5058 if (pending_position < grab) {
5059 start = pending_position;
5062 end = pending_position;
5066 /* first drag: Either add to the selection
5067 or create a new selection
5074 /* adding to the selection */
5075 _editor->set_selected_track_as_side_effect (Selection::Add);
5076 _editor->clicked_selection = _editor->selection->add (start, end);
5083 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5084 _editor->set_selected_track_as_side_effect (Selection::Set);
5087 _editor->clicked_selection = _editor->selection->set (start, end);
5091 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5092 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5093 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5095 _editor->selection->add (atest);
5099 /* select all tracks within the rectangle that we've marked out so far */
5100 TrackViewList new_selection;
5101 TrackViewList& all_tracks (_editor->track_views);
5103 ArdourCanvas::Coord const top = grab_y();
5104 ArdourCanvas::Coord const bottom = current_pointer_y();
5106 if (top >= 0 && bottom >= 0) {
5108 //first, find the tracks that are covered in the y range selection
5109 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5110 if ((*i)->covered_by_y_range (top, bottom)) {
5111 new_selection.push_back (*i);
5115 //now find any tracks that are GROUPED with the tracks we selected
5116 TrackViewList grouped_add = new_selection;
5117 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5118 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
5119 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::group_select.property_id) ) {
5120 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
5121 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
5122 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
5123 grouped_add.push_back (*j);
5128 //now compare our list with the current selection, and add or remove as necessary
5129 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5130 TrackViewList tracks_to_add;
5131 TrackViewList tracks_to_remove;
5132 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
5133 if ( !_editor->selection->tracks.contains ( *i ) )
5134 tracks_to_add.push_back ( *i );
5135 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
5136 if ( !grouped_add.contains ( *i ) )
5137 tracks_to_remove.push_back ( *i );
5138 _editor->selection->add(tracks_to_add);
5139 _editor->selection->remove(tracks_to_remove);
5145 case SelectionStartTrim:
5147 end = _editor->selection->time[_editor->clicked_selection].end;
5149 if (pending_position > end) {
5152 start = pending_position;
5156 case SelectionEndTrim:
5158 start = _editor->selection->time[_editor->clicked_selection].start;
5160 if (pending_position < start) {
5163 end = pending_position;
5170 start = _editor->selection->time[_editor->clicked_selection].start;
5171 end = _editor->selection->time[_editor->clicked_selection].end;
5173 length = end - start;
5174 distance = pending_position - start;
5175 start = pending_position;
5176 _editor->snap_to (start);
5178 end = start + length;
5182 case SelectionExtend:
5187 switch (_operation) {
5189 if (_time_selection_at_start) {
5190 _editor->selection->move_time (distance);
5194 _editor->selection->replace (_editor->clicked_selection, start, end);
5198 if (_operation == SelectionMove) {
5199 show_verbose_cursor_time(start);
5201 show_verbose_cursor_time(pending_position);
5206 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5208 Session* s = _editor->session();
5210 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5211 if (movement_occurred) {
5212 motion (event, false);
5213 /* XXX this is not object-oriented programming at all. ick */
5214 if (_editor->selection->time.consolidate()) {
5215 _editor->selection->TimeChanged ();
5218 /* XXX what if its a music time selection? */
5220 if (s->get_play_range() && s->transport_rolling()) {
5221 s->request_play_range (&_editor->selection->time, true);
5222 } else if (!s->config.get_external_sync()) {
5223 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5224 if (_operation == SelectionEndTrim)
5225 _editor->maybe_locate_with_edit_preroll( _editor->get_selection().time.end_frame());
5227 s->request_locate (_editor->get_selection().time.start());
5231 if (_editor->get_selection().time.length() != 0) {
5232 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame());
5234 s->clear_range_selection ();
5239 /* just a click, no pointer movement.
5242 if (_operation == SelectionExtend) {
5243 if (_time_selection_at_start) {
5244 framepos_t pos = adjusted_current_frame (event, false);
5245 framepos_t start = min (pos, start_at_start);
5246 framepos_t end = max (pos, end_at_start);
5247 _editor->selection->set (start, end);
5250 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5251 if (_editor->clicked_selection) {
5252 _editor->selection->remove (_editor->clicked_selection);
5255 if (!_editor->clicked_selection) {
5256 _editor->selection->clear_time();
5261 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5262 _editor->selection->set (_editor->clicked_axisview);
5265 if (s && s->get_play_range () && s->transport_rolling()) {
5266 s->request_stop (false, false);
5271 _editor->stop_canvas_autoscroll ();
5272 _editor->clicked_selection = 0;
5273 _editor->commit_reversible_selection_op ();
5277 SelectionDrag::aborted (bool)
5282 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5283 : Drag (e, i, false),
5287 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5289 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5290 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5291 physical_screen_height (_editor->current_toplevel()->get_window())));
5292 _drag_rect->hide ();
5294 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5295 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5298 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5300 /* normal canvas items will be cleaned up when their parent group is deleted. But
5301 this item is created as the child of a long-lived parent group, and so we
5302 need to explicitly delete it.
5308 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5310 if (_editor->session() == 0) {
5314 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5316 if (!_editor->temp_location) {
5317 _editor->temp_location = new Location (*_editor->session());
5320 switch (_operation) {
5321 case CreateSkipMarker:
5322 case CreateRangeMarker:
5323 case CreateTransportMarker:
5324 case CreateCDMarker:
5326 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5331 cursor = _editor->cursors()->selector;
5335 Drag::start_grab (event, cursor);
5337 show_verbose_cursor_time (adjusted_current_frame (event));
5341 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5343 framepos_t start = 0;
5345 ArdourCanvas::Rectangle *crect;
5347 switch (_operation) {
5348 case CreateSkipMarker:
5349 crect = _editor->range_bar_drag_rect;
5351 case CreateRangeMarker:
5352 crect = _editor->range_bar_drag_rect;
5354 case CreateTransportMarker:
5355 crect = _editor->transport_bar_drag_rect;
5357 case CreateCDMarker:
5358 crect = _editor->cd_marker_bar_drag_rect;
5361 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5366 framepos_t const pf = adjusted_current_frame (event);
5368 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5369 framepos_t grab = grab_frame ();
5370 _editor->snap_to (grab);
5372 if (pf < grab_frame()) {
5380 /* first drag: Either add to the selection
5381 or create a new selection.
5386 _editor->temp_location->set (start, end);
5390 update_item (_editor->temp_location);
5392 //_drag_rect->raise_to_top();
5398 _editor->temp_location->set (start, end);
5400 double x1 = _editor->sample_to_pixel (start);
5401 double x2 = _editor->sample_to_pixel (end);
5405 update_item (_editor->temp_location);
5408 show_verbose_cursor_time (pf);
5413 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5415 Location * newloc = 0;
5419 if (movement_occurred) {
5420 motion (event, false);
5423 switch (_operation) {
5424 case CreateSkipMarker:
5425 case CreateRangeMarker:
5426 case CreateCDMarker:
5428 XMLNode &before = _editor->session()->locations()->get_state();
5429 if (_operation == CreateSkipMarker) {
5430 _editor->begin_reversible_command (_("new skip marker"));
5431 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5432 flags = Location::IsRangeMarker | Location::IsSkip;
5433 _editor->range_bar_drag_rect->hide();
5434 } else if (_operation == CreateCDMarker) {
5435 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5436 _editor->begin_reversible_command (_("new CD marker"));
5437 flags = Location::IsRangeMarker | Location::IsCDMarker;
5438 _editor->cd_marker_bar_drag_rect->hide();
5440 _editor->begin_reversible_command (_("new skip marker"));
5441 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5442 flags = Location::IsRangeMarker;
5443 _editor->range_bar_drag_rect->hide();
5445 newloc = new Location (
5446 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5449 _editor->session()->locations()->add (newloc, true);
5450 XMLNode &after = _editor->session()->locations()->get_state();
5451 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5452 _editor->commit_reversible_command ();
5456 case CreateTransportMarker:
5457 // popup menu to pick loop or punch
5458 _editor->new_transport_marker_context_menu (&event->button, _item);
5464 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5466 if (_operation == CreateTransportMarker) {
5468 /* didn't drag, so just locate */
5470 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5472 } else if (_operation == CreateCDMarker) {
5474 /* didn't drag, but mark is already created so do
5477 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5482 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5484 if (end == max_framepos) {
5485 end = _editor->session()->current_end_frame ();
5488 if (start == max_framepos) {
5489 start = _editor->session()->current_start_frame ();
5492 switch (_editor->mouse_mode) {
5494 /* find the two markers on either side and then make the selection from it */
5495 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5499 /* find the two markers on either side of the click and make the range out of it */
5500 _editor->selection->set (start, end);
5509 _editor->stop_canvas_autoscroll ();
5513 RangeMarkerBarDrag::aborted (bool movement_occurred)
5515 if (movement_occurred) {
5516 _drag_rect->hide ();
5521 RangeMarkerBarDrag::update_item (Location* location)
5523 double const x1 = _editor->sample_to_pixel (location->start());
5524 double const x2 = _editor->sample_to_pixel (location->end());
5526 _drag_rect->set_x0 (x1);
5527 _drag_rect->set_x1 (x2);
5530 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5532 , _cumulative_dx (0)
5533 , _cumulative_dy (0)
5534 , _was_selected (false)
5536 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5538 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5540 _region = &_primary->region_view ();
5541 _note_height = _region->midi_stream_view()->note_height ();
5545 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5547 Drag::start_grab (event);
5548 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5550 if (!(_was_selected = _primary->selected())) {
5552 /* tertiary-click means extend selection - we'll do that on button release,
5553 so don't add it here, because otherwise we make it hard to figure
5554 out the "extend-to" range.
5557 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5560 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5563 _region->note_selected (_primary, true);
5565 _editor->get_selection().clear_points();
5566 _region->unique_select (_primary);
5572 /** @return Current total drag x change in frames */
5574 NoteDrag::total_dx (const guint state) const
5576 if (_x_constrained) {
5581 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5583 /* primary note time */
5584 frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
5586 /* new time of the primary note in session frames */
5587 frameoffset_t st = n + dx + snap_delta (state);
5589 framepos_t const rp = _region->region()->position ();
5591 /* prevent the note being dragged earlier than the region's position */
5594 /* possibly snap and return corresponding delta */
5598 if (ArdourKeyboard::indicates_snap (state)) {
5599 if (_editor->snap_mode () != SnapOff) {
5603 if (_editor->snap_mode () == SnapOff) {
5605 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
5606 if (ArdourKeyboard::indicates_snap_delta (state)) {
5614 bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
5615 ret = _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
5617 ret = st - n - snap_delta (state);
5622 /** @return Current total drag y change in note number */
5624 NoteDrag::total_dy () const
5626 if (_y_constrained) {
5630 MidiStreamView* msv = _region->midi_stream_view ();
5631 double const y = _region->midi_view()->y_position ();
5632 /* new current note */
5633 uint8_t n = msv->y_to_note (current_pointer_y () - y);
5635 n = max (msv->lowest_note(), n);
5636 n = min (msv->highest_note(), n);
5637 /* and work out delta */
5638 return n - msv->y_to_note (grab_y() - y);
5642 NoteDrag::motion (GdkEvent * event, bool)
5644 /* Total change in x and y since the start of the drag */
5645 frameoffset_t const dx = total_dx (event->button.state);
5646 int8_t const dy = total_dy ();
5648 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5649 double const tdx = _x_constrained ? 0 : _editor->sample_to_pixel (dx) - _cumulative_dx;
5650 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
5653 _cumulative_dx += tdx;
5654 _cumulative_dy += tdy;
5656 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);
5675 NoteDrag::finished (GdkEvent* ev, bool moved)
5678 /* no motion - select note */
5680 if (_editor->current_mouse_mode() == Editing::MouseContent ||
5681 _editor->current_mouse_mode() == Editing::MouseDraw) {
5683 bool changed = false;
5685 if (_was_selected) {
5686 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5688 _region->note_deselected (_primary);
5691 _editor->get_selection().clear_points();
5692 _region->unique_select (_primary);
5696 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5697 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5699 if (!extend && !add && _region->selection_size() > 1) {
5700 _editor->get_selection().clear_points();
5701 _region->unique_select (_primary);
5703 } else if (extend) {
5704 _region->note_selected (_primary, true, true);
5707 /* it was added during button press */
5714 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5715 _editor->commit_reversible_selection_op();
5719 _region->note_dropped (_primary, total_dx (ev->button.state), total_dy());
5724 NoteDrag::aborted (bool)
5729 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5730 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5731 : Drag (editor, atv->base_item ())
5733 , _y_origin (atv->y_position())
5734 , _nothing_to_drag (false)
5736 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5737 setup (atv->lines ());
5740 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5741 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5742 : Drag (editor, rv->get_canvas_group ())
5744 , _y_origin (rv->get_time_axis_view().y_position())
5745 , _nothing_to_drag (false)
5748 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5750 list<boost::shared_ptr<AutomationLine> > lines;
5752 AudioRegionView* audio_view;
5753 AutomationRegionView* automation_view;
5754 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5755 lines.push_back (audio_view->get_gain_line ());
5756 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5757 lines.push_back (automation_view->line ());
5760 error << _("Automation range drag created for invalid region type") << endmsg;
5766 /** @param lines AutomationLines to drag.
5767 * @param offset Offset from the session start to the points in the AutomationLines.
5770 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5772 /* find the lines that overlap the ranges being dragged */
5773 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5774 while (i != lines.end ()) {
5775 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5778 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5780 /* check this range against all the AudioRanges that we are using */
5781 list<AudioRange>::const_iterator k = _ranges.begin ();
5782 while (k != _ranges.end()) {
5783 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5789 /* add it to our list if it overlaps at all */
5790 if (k != _ranges.end()) {
5795 _lines.push_back (n);
5801 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5805 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5807 return 1.0 - ((global_y - _y_origin) / line->height());
5811 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5813 const double v = list->eval(x);
5814 return _integral ? rint(v) : v;
5818 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5820 Drag::start_grab (event, cursor);
5822 /* Get line states before we start changing things */
5823 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5824 i->state = &i->line->get_state ();
5825 i->original_fraction = y_fraction (i->line, current_pointer_y());
5828 if (_ranges.empty()) {
5830 /* No selected time ranges: drag all points */
5831 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5832 uint32_t const N = i->line->npoints ();
5833 for (uint32_t j = 0; j < N; ++j) {
5834 i->points.push_back (i->line->nth (j));
5840 if (_nothing_to_drag) {
5846 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
5848 if (_nothing_to_drag && !first_move) {
5853 _editor->begin_reversible_command (_("automation range move"));
5855 if (!_ranges.empty()) {
5857 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5859 framecnt_t const half = (i->start + i->end) / 2;
5861 /* find the line that this audio range starts in */
5862 list<Line>::iterator j = _lines.begin();
5863 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5867 if (j != _lines.end()) {
5868 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5870 /* j is the line that this audio range starts in; fade into it;
5871 64 samples length plucked out of thin air.
5874 framepos_t a = i->start + 64;
5879 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5880 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5882 XMLNode &before = the_list->get_state();
5883 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5884 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5886 if (add_p || add_q) {
5887 _editor->session()->add_command (
5888 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
5892 /* same thing for the end */
5895 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5899 if (j != _lines.end()) {
5900 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5902 /* j is the line that this audio range starts in; fade out of it;
5903 64 samples length plucked out of thin air.
5906 framepos_t b = i->end - 64;
5911 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
5912 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
5914 XMLNode &before = the_list->get_state();
5915 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5916 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5918 if (add_p || add_q) {
5919 _editor->session()->add_command (
5920 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
5925 _nothing_to_drag = true;
5927 /* Find all the points that should be dragged and put them in the relevant
5928 points lists in the Line structs.
5931 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5933 uint32_t const N = i->line->npoints ();
5934 for (uint32_t j = 0; j < N; ++j) {
5936 /* here's a control point on this line */
5937 ControlPoint* p = i->line->nth (j);
5938 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
5940 /* see if it's inside a range */
5941 list<AudioRange>::const_iterator k = _ranges.begin ();
5942 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
5946 if (k != _ranges.end()) {
5947 /* dragging this point */
5948 _nothing_to_drag = false;
5949 i->points.push_back (p);
5955 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5956 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
5960 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
5961 float const f = y_fraction (l->line, current_pointer_y());
5962 /* we are ignoring x position for this drag, so we can just pass in anything */
5963 pair<double, float> result;
5965 result = l->line->drag_motion (0, f, true, false, ignored);
5966 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, result.second));
5971 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
5973 if (_nothing_to_drag || !motion_occurred) {
5977 motion (event, false);
5978 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5979 i->line->end_drag (false, 0);
5982 _editor->commit_reversible_command ();
5986 AutomationRangeDrag::aborted (bool)
5988 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5993 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
5995 , initial_time_axis_view (itav)
5997 /* note that time_axis_view may be null if the regionview was created
5998 * as part of a copy operation.
6000 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6001 layer = v->region()->layer ();
6002 initial_y = v->get_canvas_group()->position().y;
6003 initial_playlist = v->region()->playlist ();
6004 initial_position = v->region()->position ();
6005 initial_end = v->region()->position () + v->region()->length ();
6008 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6009 : Drag (e, i->canvas_item ())
6012 , _cumulative_dx (0)
6014 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6015 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
6020 PatchChangeDrag::motion (GdkEvent* ev, bool)
6022 framepos_t f = adjusted_current_frame (ev);
6023 boost::shared_ptr<Region> r = _region_view->region ();
6024 f = max (f, r->position ());
6025 f = min (f, r->last_frame ());
6027 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
6028 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6029 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6030 _cumulative_dx = dxu;
6034 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6036 if (!movement_occurred) {
6037 if (was_double_click()) {
6038 _region_view->edit_patch_change (_patch_change);
6043 boost::shared_ptr<Region> r (_region_view->region ());
6044 framepos_t f = adjusted_current_frame (ev);
6045 f = max (f, r->position ());
6046 f = min (f, r->last_frame ());
6048 _region_view->move_patch_change (
6050 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
6055 PatchChangeDrag::aborted (bool)
6057 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6061 PatchChangeDrag::setup_pointer_frame_offset ()
6063 boost::shared_ptr<Region> region = _region_view->region ();
6064 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
6067 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6068 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6075 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6077 _region_view->update_drag_selection (
6079 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6083 MidiRubberbandSelectDrag::deselect_things ()
6088 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6089 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6092 _vertical_only = true;
6096 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6098 double const y = _region_view->midi_view()->y_position ();
6100 y1 = max (0.0, y1 - y);
6101 y2 = max (0.0, y2 - y);
6103 _region_view->update_vertical_drag_selection (
6106 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6111 MidiVerticalSelectDrag::deselect_things ()
6116 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6117 : RubberbandSelectDrag (e, i)
6123 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
6125 if (drag_in_progress) {
6126 /* We just want to select things at the end of the drag, not during it */
6130 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6132 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6134 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6136 _editor->commit_reversible_selection_op ();
6140 EditorRubberbandSelectDrag::deselect_things ()
6142 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6144 _editor->selection->clear_tracks();
6145 _editor->selection->clear_regions();
6146 _editor->selection->clear_points ();
6147 _editor->selection->clear_lines ();
6148 _editor->selection->clear_midi_notes ();
6150 _editor->commit_reversible_selection_op();
6153 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6158 _note[0] = _note[1] = 0;
6161 NoteCreateDrag::~NoteCreateDrag ()
6167 NoteCreateDrag::grid_frames (framepos_t t) const
6170 Evoral::Beats grid_beats = _editor->get_grid_type_as_beats (success, t);
6172 grid_beats = Evoral::Beats(1);
6175 return _region_view->region_beats_to_region_frames (grid_beats);
6179 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6181 Drag::start_grab (event, cursor);
6183 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6185 framepos_t pf = _drags->current_pointer_frame ();
6186 framecnt_t const g = grid_frames (pf);
6188 /* Hack so that we always snap to the note that we are over, instead of snapping
6189 to the next one if we're more than halfway through the one we're over.
6191 if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
6195 _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
6196 _note[1] = _note[0];
6198 MidiStreamView* sv = _region_view->midi_stream_view ();
6199 double const x = _editor->sample_to_pixel (_note[0]);
6200 double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
6202 _drag_rect->set (ArdourCanvas::Rect (x, y, x, y + floor (_region_view->midi_stream_view()->note_height ())));
6203 _drag_rect->set_outline_all ();
6204 _drag_rect->set_outline_color (0xffffff99);
6205 _drag_rect->set_fill_color (0xffffff66);
6209 NoteCreateDrag::motion (GdkEvent* event, bool)
6211 _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
6212 double const x0 = _editor->sample_to_pixel (_note[0]);
6213 double const x1 = _editor->sample_to_pixel (_note[1]);
6214 _drag_rect->set_x0 (std::min(x0, x1));
6215 _drag_rect->set_x1 (std::max(x0, x1));
6219 NoteCreateDrag::finished (GdkEvent*, bool had_movement)
6221 if (!had_movement) {
6225 framepos_t const start = min (_note[0], _note[1]);
6226 framecnt_t length = (framecnt_t) fabs ((double)(_note[0] - _note[1]));
6228 framecnt_t const g = grid_frames (start);
6229 Evoral::Beats const one_tick = Evoral::Beats::ticks(1);
6231 if (_editor->snap_mode() == SnapNormal && length < g) {
6235 Evoral::Beats length_beats = max (
6236 one_tick, _region_view->region_frames_to_region_beats (length) - one_tick);
6238 _region_view->create_note_at (start, _drag_rect->y0(), length_beats, false);
6242 NoteCreateDrag::y_to_region (double y) const
6245 _region_view->get_canvas_group()->canvas_to_item (x, y);
6250 NoteCreateDrag::aborted (bool)
6255 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6260 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6264 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6266 Drag::start_grab (event, cursor);
6270 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6276 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6279 distance = _drags->current_pointer_x() - grab_x();
6280 len = ar->fade_in()->back()->when;
6282 distance = grab_x() - _drags->current_pointer_x();
6283 len = ar->fade_out()->back()->when;
6286 /* how long should it be ? */
6288 new_length = len + _editor->pixel_to_sample (distance);
6290 /* now check with the region that this is legal */
6292 new_length = ar->verify_xfade_bounds (new_length, start);
6295 arv->reset_fade_in_shape_width (ar, new_length);
6297 arv->reset_fade_out_shape_width (ar, new_length);
6302 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6308 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6311 distance = _drags->current_pointer_x() - grab_x();
6312 len = ar->fade_in()->back()->when;
6314 distance = grab_x() - _drags->current_pointer_x();
6315 len = ar->fade_out()->back()->when;
6318 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6320 _editor->begin_reversible_command ("xfade trim");
6321 ar->playlist()->clear_owned_changes ();
6324 ar->set_fade_in_length (new_length);
6326 ar->set_fade_out_length (new_length);
6329 /* Adjusting the xfade may affect other regions in the playlist, so we need
6330 to get undo Commands from the whole playlist rather than just the
6334 vector<Command*> cmds;
6335 ar->playlist()->rdiff (cmds);
6336 _editor->session()->add_commands (cmds);
6337 _editor->commit_reversible_command ();
6342 CrossfadeEdgeDrag::aborted (bool)
6345 // arv->redraw_start_xfade ();
6347 // arv->redraw_end_xfade ();
6351 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
6352 : Drag (e, item, true)
6353 , line (new EditorCursor (*e))
6355 line->set_position (pos);
6359 RegionCutDrag::~RegionCutDrag ()
6365 RegionCutDrag::motion (GdkEvent*, bool)
6367 framepos_t where = _drags->current_pointer_frame();
6368 _editor->snap_to (where);
6370 line->set_position (where);
6374 RegionCutDrag::finished (GdkEvent* event, bool)
6376 _editor->get_track_canvas()->canvas()->re_enter();
6378 framepos_t pos = _drags->current_pointer_frame();
6382 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
6388 _editor->split_regions_at (pos, rs, _editor->get_grid_music_divisions (event->button.state));
6392 RegionCutDrag::aborted (bool)