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 _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
269 setup_pointer_frame_offset ();
270 _grab_frame = adjusted_frame (_raw_grab_frame, event);
271 _last_pointer_frame = _grab_frame;
272 _last_pointer_x = _grab_x;
274 if (_trackview_only) {
275 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
278 _last_pointer_y = _grab_y;
282 if (!_editor->cursors()->is_invalid (cursor)) {
283 /* CAIROCANVAS need a variant here that passes *cursor */
284 _cursor_ctx = CursorContext::create (*_editor, cursor);
287 if (_editor->session() && _editor->session()->transport_rolling()) {
290 _was_rolling = false;
293 switch (_editor->snap_type()) {
294 case SnapToRegionStart:
295 case SnapToRegionEnd:
296 case SnapToRegionSync:
297 case SnapToRegionBoundary:
298 _editor->build_region_boundary_cache ();
305 /** Call to end a drag `successfully'. Ungrabs item and calls
306 * subclass' finished() method.
308 * @param event GDK event, or 0.
309 * @return true if some movement occurred, otherwise false.
312 Drag::end_grab (GdkEvent* event)
314 _editor->stop_canvas_autoscroll ();
318 finished (event, _move_threshold_passed);
320 _editor->verbose_cursor()->hide ();
323 return _move_threshold_passed;
327 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
331 if (f > _pointer_frame_offset) {
332 pos = f - _pointer_frame_offset;
336 _editor->snap_to_with_modifier (pos, event);
343 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
345 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
349 Drag::snap_delta (guint state) const
351 if (ArdourKeyboard::indicates_snap_delta (state)) {
359 Drag::current_pointer_x() const
361 return _drags->current_pointer_x ();
365 Drag::current_pointer_y () const
367 if (!_trackview_only) {
368 return _drags->current_pointer_y ();
371 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
375 Drag::setup_snap_delta (framepos_t pos)
377 framepos_t temp = pos;
378 _editor->snap_to (temp, ARDOUR::RoundNearest, false, true);
379 _snap_delta = temp - pos;
383 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
385 /* check to see if we have moved in any way that matters since the last motion event */
386 if (_move_threshold_passed &&
387 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
388 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
392 pair<framecnt_t, int> const threshold = move_threshold ();
394 bool const old_move_threshold_passed = _move_threshold_passed;
396 if (!_move_threshold_passed) {
398 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
399 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
401 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
404 if (active (_editor->mouse_mode) && _move_threshold_passed) {
406 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
408 if (old_move_threshold_passed != _move_threshold_passed) {
412 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
413 _initially_vertical = true;
415 _initially_vertical = false;
417 /** check constraints for this drag.
418 * Note that the current convention is to use "contains" for
419 * key modifiers during motion and "equals" when initiating a drag.
420 * In this case we haven't moved yet, so "equals" applies here.
422 if (Config->get_edit_mode() != Lock) {
423 if (event->motion.state & Gdk::BUTTON2_MASK) {
424 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
425 if (_constraint_pressed) {
426 _x_constrained = false;
427 _y_constrained = true;
429 _x_constrained = true;
430 _y_constrained = false;
432 } else if (_constraint_pressed) {
433 // if dragging normally, the motion is constrained to the first direction of movement.
434 if (_initially_vertical) {
435 _x_constrained = true;
436 _y_constrained = false;
438 _x_constrained = false;
439 _y_constrained = true;
443 if (event->button.state & Gdk::BUTTON2_MASK) {
444 _x_constrained = false;
446 _x_constrained = true;
448 _y_constrained = false;
452 if (!from_autoscroll) {
453 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
456 if (!_editor->autoscroll_active() || from_autoscroll) {
459 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
461 motion (event, first_move && !_starting_point_passed);
463 if (first_move && !_starting_point_passed) {
464 _starting_point_passed = true;
467 _last_pointer_x = _drags->current_pointer_x ();
468 _last_pointer_y = current_pointer_y ();
469 _last_pointer_frame = adjusted_current_frame (event, false);
479 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
487 aborted (_move_threshold_passed);
489 _editor->stop_canvas_autoscroll ();
490 _editor->verbose_cursor()->hide ();
494 Drag::show_verbose_cursor_time (framepos_t frame)
496 _editor->verbose_cursor()->set_time (frame);
497 _editor->verbose_cursor()->show ();
501 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
503 _editor->verbose_cursor()->set_duration (start, end);
504 _editor->verbose_cursor()->show ();
508 Drag::show_verbose_cursor_text (string const & text)
510 _editor->verbose_cursor()->set (text);
511 _editor->verbose_cursor()->show ();
514 boost::shared_ptr<Region>
515 Drag::add_midi_region (MidiTimeAxisView* view, bool commit, const int32_t sub_num)
517 if (_editor->session()) {
518 const TempoMap& map (_editor->session()->tempo_map());
519 framecnt_t pos = grab_frame();
520 /* not that the frame rate used here can be affected by pull up/down which
523 framecnt_t len = map.frame_at_beat (max (0.0, map.beat_at_frame (pos)) + 1.0) - pos;
524 return view->add_region (grab_frame(), len, commit, sub_num);
527 return boost::shared_ptr<Region>();
530 struct PresentationInfoTimeAxisViewSorter {
531 bool operator() (TimeAxisView* a, TimeAxisView* b) {
532 return a->stripable()->presentation_info().order() < b->stripable()->presentation_info().order();
536 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
541 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
543 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
544 as some of the regions we are dragging may be on such tracks.
547 TrackViewList track_views = _editor->track_views;
548 track_views.sort (PresentationInfoTimeAxisViewSorter ());
550 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
551 _time_axis_views.push_back (*i);
553 TimeAxisView::Children children_list = (*i)->get_child_list ();
554 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
555 _time_axis_views.push_back (j->get());
559 /* the list of views can be empty at this point if this is a region list-insert drag
562 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
563 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
566 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
570 RegionDrag::region_going_away (RegionView* v)
572 list<DraggingView>::iterator i = _views.begin ();
573 while (i != _views.end() && i->view != v) {
577 if (i != _views.end()) {
582 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
583 * or -1 if it is not found.
586 RegionDrag::find_time_axis_view (TimeAxisView* t) const
589 int const N = _time_axis_views.size ();
590 while (i < N && _time_axis_views[i] != t) {
594 if (_time_axis_views[i] != t) {
601 /** determines if @pos is the closest frame to an exact musical division when using SnapMagnetic.
602 * (SnapMagnetic may snap to an exact division or no division at all).
603 * this is a hotfix for musical region position/trim. we should really
604 * deliver musical divisors when snapping magnetically to avoid this.
607 RegionDrag::current_music_divisor (framepos_t pos, int32_t button_state)
609 int32_t divisions = _editor->get_grid_music_divisions (button_state);
611 if (_editor->snap_mode() == Editing::SnapMagnetic && !ArdourKeyboard::indicates_snap (button_state)) {
612 TempoMap& tmap (_editor->session()->tempo_map());
613 const framepos_t exact_qn_pos = tmap.frame_at_quarter_note (tmap.exact_qn_at_frame (pos, divisions));
615 if (pos != exact_qn_pos) {
616 /* magnetic has not snapped */
624 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
625 : RegionDrag (e, i, p, v)
627 , _ignore_video_lock (false)
629 , _last_pointer_time_axis_view (0)
630 , _last_pointer_layer (0)
635 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
639 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
641 Drag::start_grab (event, cursor);
642 setup_snap_delta (_last_frame_position);
644 show_verbose_cursor_time (_last_frame_position);
646 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
648 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
649 assert(_last_pointer_time_axis_view >= 0);
650 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
653 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
654 _ignore_video_lock = true;
658 /* cross track dragging seems broken here. disabled for now. */
659 _y_constrained = true;
664 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
666 /* compute the amount of pointer motion in frames, and where
667 the region would be if we moved it by that much.
669 *pending_region_position = adjusted_frame (_drags->current_pointer_frame (), event, false);
671 framepos_t sync_frame;
672 framecnt_t sync_offset;
675 sync_offset = _primary->region()->sync_offset (sync_dir);
677 /* we don't handle a sync point that lies before zero.
679 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
681 framecnt_t const sd = snap_delta (event->button.state);
682 sync_frame = *pending_region_position + (sync_dir * sync_offset) + sd;
684 _editor->snap_to_with_modifier (sync_frame, event);
686 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - sd;
689 *pending_region_position = _last_frame_position;
692 if (*pending_region_position > max_framepos - _primary->region()->length()) {
693 *pending_region_position = _last_frame_position;
698 bool const x_move_allowed = !_x_constrained;
700 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
702 /* x movement since last time (in pixels) */
703 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
705 /* total x movement */
706 framecnt_t total_dx = *pending_region_position;
707 if (regions_came_from_canvas()) {
708 total_dx = total_dx - grab_frame ();
711 /* check that no regions have gone off the start of the session */
712 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
713 if ((i->view->region()->position() + total_dx) < 0) {
715 *pending_region_position = _last_frame_position;
726 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
732 const int tavsize = _time_axis_views.size();
733 const int dt = delta > 0 ? +1 : -1;
735 int target = start + delta - skip;
737 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
738 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
740 while (current >= 0 && current != target) {
742 if (current < 0 && dt < 0) {
745 if (current >= tavsize && dt > 0) {
748 if (current < 0 || current >= tavsize) {
752 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
753 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
757 if (distance_only && current == start + delta) {
765 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
767 if (_y_constrained) {
771 const int tavsize = _time_axis_views.size();
772 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
773 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
774 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
776 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
777 /* already in the drop zone */
778 if (delta_track >= 0) {
779 /* downward motion - OK if others are still not in the dropzone */
788 } else if (n >= tavsize) {
789 /* downward motion into drop zone. That's fine. */
793 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
794 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
795 /* not a track, or the wrong type */
799 double const l = i->layer + delta_layer;
801 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
802 mode to allow the user to place a region below another on layer 0.
804 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
805 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
806 If it has, the layers will be munged later anyway, so it's ok.
812 /* all regions being dragged are ok with this change */
816 struct DraggingViewSorter {
817 bool operator() (const DraggingView& a, const DraggingView& b) {
818 return a.time_axis_view < b.time_axis_view;
823 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
825 double delta_layer = 0;
826 int delta_time_axis_view = 0;
827 int current_pointer_time_axis_view = -1;
829 assert (!_views.empty ());
831 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
833 /* Find the TimeAxisView that the pointer is now over */
834 const double cur_y = current_pointer_y ();
835 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
836 TimeAxisView* tv = r.first;
838 if (!tv && cur_y < 0) {
839 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
843 /* find drop-zone y-position */
844 Coord last_track_bottom_edge;
845 last_track_bottom_edge = 0;
846 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
847 if (!(*t)->hidden()) {
848 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
853 if (tv && tv->view()) {
854 /* the mouse is over a track */
855 double layer = r.second;
857 if (first_move && tv->view()->layer_display() == Stacked) {
858 tv->view()->set_layer_display (Expanded);
861 /* Here's the current pointer position in terms of time axis view and layer */
862 current_pointer_time_axis_view = find_time_axis_view (tv);
863 assert(current_pointer_time_axis_view >= 0);
865 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
867 /* Work out the change in y */
869 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
870 if (!rtv || !rtv->is_track()) {
871 /* ignore non-tracks early on. we can't move any regions on them */
872 } else if (_last_pointer_time_axis_view < 0) {
873 /* Was in the drop-zone, now over a track.
874 * Hence it must be an upward move (from the bottom)
876 * track_index is still -1, so delta must be set to
877 * move up the correct number of tracks from the bottom.
879 * This is necessary because steps may be skipped if
880 * the bottom-most track is not a valid target and/or
881 * if there are hidden tracks at the bottom.
882 * Hence the initial offset (_ddropzone) as well as the
883 * last valid pointer position (_pdropzone) need to be
884 * taken into account.
886 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
888 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
891 /* TODO needs adjustment per DraggingView,
893 * e.g. select one region on the top-layer of a track
894 * and one region which is at the bottom-layer of another track
897 * Indicated drop-zones and layering is wrong.
898 * and may infer additional layers on the target-track
899 * (depending how many layers the original track had).
901 * Or select two regions (different layers) on a same track,
902 * move across a non-layer track.. -> layering info is lost.
903 * on drop either of the regions may be on top.
905 * Proposed solution: screw it :) well,
906 * don't use delta_layer, use an absolute value
907 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
908 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
909 * 3) iterate over all DraggingView, find the one that is over the track with most layers
910 * 4) proportionally scale layer to layers available on target
912 delta_layer = current_pointer_layer - _last_pointer_layer;
915 /* for automation lanes, there is a TimeAxisView but no ->view()
916 * if (!tv) -> dropzone
918 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
919 /* Moving into the drop-zone.. */
920 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
921 /* delta_time_axis_view may not be sufficient to move into the DZ
922 * the mouse may enter it, but it may not be a valid move due to
925 * -> remember the delta needed to move into the dropzone
927 _ddropzone = delta_time_axis_view;
928 /* ..but subtract hidden tracks (or routes) at the bottom.
929 * we silently move mover them
931 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
932 - _time_axis_views.size();
934 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
935 /* move around inside the zone.
936 * This allows to move further down until all regions are in the zone.
938 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
939 assert(ptr_y >= last_track_bottom_edge);
940 assert(_ddropzone > 0);
942 /* calculate mouse position in 'tracks' below last track. */
943 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
944 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
946 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
948 delta_time_axis_view = dzpos - _pdropzone;
949 } else if (dzpos < _pdropzone && _ndropzone > 0) {
950 // move up inside the DZ
951 delta_time_axis_view = dzpos - _pdropzone;
955 /* Work out the change in x */
956 framepos_t pending_region_position;
957 double const x_delta = compute_x_delta (event, &pending_region_position);
958 _last_frame_position = pending_region_position;
960 /* calculate hidden tracks in current y-axis delta */
962 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
963 /* The mouse is more than one track below the dropzone.
964 * distance calculation is not needed (and would not work, either
965 * because the dropzone is "packed").
967 * Except when [partially] moving regions out of dropzone in a large step.
968 * (the mouse may or may not remain in the DZ)
969 * Hidden tracks at the bottom of the TAV need to be skipped.
971 * This also handles the case if the mouse entered the DZ
972 * in a large step (exessive delta), either due to fast-movement,
973 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
975 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
976 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
978 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
979 -_time_axis_views.size() - dt;
982 else if (_last_pointer_time_axis_view < 0) {
983 /* Moving out of the zone. Check for hidden tracks at the bottom. */
984 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
985 -_time_axis_views.size() - delta_time_axis_view;
987 /* calculate hidden tracks that are skipped by the pointer movement */
988 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
989 - _last_pointer_time_axis_view
990 - delta_time_axis_view;
993 /* Verify change in y */
994 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
995 /* this y movement is not allowed, so do no y movement this time */
996 delta_time_axis_view = 0;
1001 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1002 /* haven't reached next snap point, and we're not switching
1003 trackviews nor layers. nothing to do.
1008 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1009 PlaylistDropzoneMap playlist_dropzone_map;
1010 _ndropzone = 0; // number of elements currently in the dropzone
1013 /* sort views by time_axis.
1014 * This retains track order in the dropzone, regardless
1015 * of actual selection order
1017 _views.sort (DraggingViewSorter());
1019 /* count number of distinct tracks of all regions
1020 * being dragged, used for dropzone.
1022 int prev_track = -1;
1023 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1024 if (i->time_axis_view != prev_track) {
1025 prev_track = i->time_axis_view;
1031 _views.back().time_axis_view -
1032 _views.front().time_axis_view;
1034 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1035 - _views.back().time_axis_view;
1037 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1041 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1043 RegionView* rv = i->view;
1048 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1055 /* reparent the regionview into a group above all
1059 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1060 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1061 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1062 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1063 /* move the item so that it continues to appear at the
1064 same location now that its parent has changed.
1066 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1069 /* If we have moved tracks, we'll fudge the layer delta so that the
1070 region gets moved back onto layer 0 on its new track; this avoids
1071 confusion when dragging regions from non-zero layers onto different
1074 double this_delta_layer = delta_layer;
1075 if (delta_time_axis_view != 0) {
1076 this_delta_layer = - i->layer;
1079 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1081 int track_index = i->time_axis_view + this_delta_time_axis_view;
1082 assert(track_index >= 0);
1084 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1085 /* Track is in the Dropzone */
1087 i->time_axis_view = track_index;
1088 assert(i->time_axis_view >= (int) _time_axis_views.size());
1091 double yposition = 0;
1092 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1093 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1096 /* store index of each new playlist as a negative count, starting at -1 */
1098 if (pdz == playlist_dropzone_map.end()) {
1099 /* compute where this new track (which doesn't exist yet) will live
1102 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1104 /* How high is this region view ? */
1106 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1107 ArdourCanvas::Rect bbox;
1110 bbox = obbox.get ();
1113 last_track_bottom_edge += bbox.height();
1115 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1118 yposition = pdz->second;
1121 /* values are zero or negative, hence the use of min() */
1122 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1125 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1127 mrv->apply_note_range (60, 71, true);
1131 /* The TimeAxisView that this region is now over */
1132 TimeAxisView* current_tv = _time_axis_views[track_index];
1134 /* Ensure it is moved from stacked -> expanded if appropriate */
1135 if (current_tv->view()->layer_display() == Stacked) {
1136 current_tv->view()->set_layer_display (Expanded);
1139 /* We're only allowed to go -ve in layer on Expanded views */
1140 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1141 this_delta_layer = - i->layer;
1145 rv->set_height (current_tv->view()->child_height ());
1147 /* Update show/hidden status as the region view may have come from a hidden track,
1148 or have moved to one.
1150 if (current_tv->hidden ()) {
1151 rv->get_canvas_group()->hide ();
1153 rv->get_canvas_group()->show ();
1156 /* Update the DraggingView */
1157 i->time_axis_view = track_index;
1158 i->layer += this_delta_layer;
1161 _editor->mouse_brush_insert_region (rv, pending_region_position);
1165 /* Get the y coordinate of the top of the track that this region is now over */
1166 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1168 /* And adjust for the layer that it should be on */
1169 StreamView* cv = current_tv->view ();
1170 switch (cv->layer_display ()) {
1174 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1177 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1181 /* need to get the parent of the regionview
1182 * canvas group and get its position in
1183 * equivalent coordinate space as the trackview
1184 * we are now dragging over.
1187 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1191 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1193 MidiStreamView* msv;
1194 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1195 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1200 /* Now move the region view */
1201 rv->move (x_delta, y_delta);
1203 } /* foreach region */
1205 _total_x_delta += x_delta;
1207 if (x_delta != 0 && !_brushing) {
1208 show_verbose_cursor_time (_last_frame_position);
1211 /* keep track of pointer movement */
1213 /* the pointer is currently over a time axis view */
1215 if (_last_pointer_time_axis_view < 0) {
1216 /* last motion event was not over a time axis view
1217 * or last y-movement out of the dropzone was not valid
1220 if (delta_time_axis_view < 0) {
1221 /* in the drop zone, moving up */
1223 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1224 * We do not use negative _last_pointer_time_axis_view because
1225 * the dropzone is "packed" (the actual track offset is ignored)
1227 * As opposed to the actual number
1228 * of elements in the dropzone (_ndropzone)
1229 * _pdropzone is not constrained. This is necessary
1230 * to allow moving multiple regions with y-distance
1233 * There can be 0 elements in the dropzone,
1234 * even though the drag-pointer is inside the DZ.
1237 * [ Audio-track, Midi-track, Audio-track, DZ ]
1238 * move regions from both audio tracks at the same time into the
1239 * DZ by grabbing the region in the bottom track.
1241 assert(current_pointer_time_axis_view >= 0);
1242 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1246 /* only move out of the zone if the movement is OK */
1247 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1248 assert(delta_time_axis_view < 0);
1249 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1250 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1251 * the current position can be calculated as follows:
1253 // a well placed oofus attack can still throw this off.
1254 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1255 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1258 /* last motion event was also over a time axis view */
1259 _last_pointer_time_axis_view += delta_time_axis_view;
1260 assert(_last_pointer_time_axis_view >= 0);
1265 /* the pointer is not over a time axis view */
1266 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1267 _pdropzone += delta_time_axis_view - delta_skip;
1268 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1271 _last_pointer_layer += delta_layer;
1275 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1277 if (_copy && first_move) {
1278 if (_x_constrained && !_brushing) {
1279 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1280 } else if (!_brushing) {
1281 _editor->begin_reversible_command (Operations::region_copy);
1282 } else if (_brushing) {
1283 _editor->begin_reversible_command (Operations::drag_region_brush);
1285 /* duplicate the regionview(s) and region(s) */
1287 list<DraggingView> new_regionviews;
1289 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1291 RegionView* rv = i->view;
1292 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1293 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1295 const boost::shared_ptr<const Region> original = rv->region();
1296 boost::shared_ptr<Region> region_copy;
1298 if (rv == _primary) {
1299 region_copy = RegionFactory::create (original, true
1300 , current_music_divisor (original->position(), event->button.state));
1302 region_copy = RegionFactory::create (original, true, 0);
1305 /* need to set this so that the drop zone code can work. This doesn't
1306 actually put the region into the playlist, but just sets a weak pointer
1309 region_copy->set_playlist (original->playlist());
1313 boost::shared_ptr<AudioRegion> audioregion_copy
1314 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1316 nrv = new AudioRegionView (*arv, audioregion_copy);
1318 boost::shared_ptr<MidiRegion> midiregion_copy
1319 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1320 nrv = new MidiRegionView (*mrv, midiregion_copy);
1325 nrv->get_canvas_group()->show ();
1326 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1328 /* swap _primary to the copy */
1330 if (rv == _primary) {
1334 /* ..and deselect the one we copied */
1336 rv->set_selected (false);
1339 if (!new_regionviews.empty()) {
1341 /* reflect the fact that we are dragging the copies */
1343 _views = new_regionviews;
1345 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1348 } else if (!_copy && first_move) {
1349 if (_x_constrained && !_brushing) {
1350 _editor->begin_reversible_command (_("fixed time region drag"));
1351 } else if (!_brushing) {
1352 _editor->begin_reversible_command (Operations::region_drag);
1353 } else if (_brushing) {
1354 _editor->begin_reversible_command (Operations::drag_region_brush);
1357 RegionMotionDrag::motion (event, first_move);
1361 RegionMotionDrag::finished (GdkEvent *, bool)
1363 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1364 if (!(*i)->view()) {
1368 if ((*i)->view()->layer_display() == Expanded) {
1369 (*i)->view()->set_layer_display (Stacked);
1375 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1377 RegionMotionDrag::finished (ev, movement_occurred);
1379 if (!movement_occurred) {
1383 if (was_double_click() && !_views.empty()) {
1384 DraggingView dv = _views.front();
1385 dv.view->show_region_editor ();
1392 assert (!_views.empty ());
1394 /* We might have hidden region views so that they weren't visible during the drag
1395 (when they have been reparented). Now everything can be shown again, as region
1396 views are back in their track parent groups.
1398 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1399 i->view->get_canvas_group()->show ();
1402 bool const changed_position = (_last_frame_position != _primary->region()->position());
1403 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1404 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1426 _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
1430 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1432 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1435 TimeAxisView* tav = 0;
1437 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1438 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1439 uint32_t output_chan = region->n_channels();
1440 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1441 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1443 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1444 tav =_editor->axis_view_from_stripable (audio_tracks.front());
1446 ChanCount one_midi_port (DataType::MIDI, 1);
1447 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1448 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port, boost::shared_ptr<ARDOUR::PluginInfo>(),
1449 (ARDOUR::Plugin::PresetRecord*) 0,
1450 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1451 tav = _editor->axis_view_from_stripable (midi_tracks.front());
1455 tav->set_height (original->current_height());
1458 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1461 return dynamic_cast<RouteTimeAxisView*> (tav);
1465 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta, int32_t const ev_state)
1467 RegionSelection new_views;
1468 PlaylistSet modified_playlists;
1469 RouteTimeAxisView* new_time_axis_view = 0;
1472 /* all changes were made during motion event handlers */
1474 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1478 _editor->commit_reversible_command ();
1482 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1483 PlaylistMapping playlist_mapping;
1485 /* insert the regions into their new playlists */
1486 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1488 RouteTimeAxisView* dest_rtv = 0;
1490 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1496 if (changed_position && !_x_constrained) {
1497 where = i->view->region()->position() - drag_delta;
1499 where = i->view->region()->position();
1502 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1503 /* dragged to drop zone */
1505 PlaylistMapping::iterator pm;
1507 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1508 /* first region from this original playlist: create a new track */
1509 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1510 if(!new_time_axis_view) {
1514 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1515 dest_rtv = new_time_axis_view;
1517 /* we already created a new track for regions from this playlist, use it */
1518 dest_rtv = pm->second;
1521 /* destination time axis view is the one we dragged to */
1522 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1525 if (dest_rtv != 0) {
1526 RegionView* new_view;
1527 if (i->view == _primary) {
1528 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1529 modified_playlists, current_music_divisor (where, ev_state));
1531 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1532 modified_playlists, 0);
1535 if (new_view != 0) {
1536 new_views.push_back (new_view);
1540 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1541 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1544 list<DraggingView>::const_iterator next = i;
1550 /* If we've created new regions either by copying or moving
1551 to a new track, we want to replace the old selection with the new ones
1554 if (new_views.size() > 0) {
1555 _editor->selection->set (new_views);
1558 /* write commands for the accumulated diffs for all our modified playlists */
1559 add_stateful_diff_commands_for_playlists (modified_playlists);
1561 _editor->commit_reversible_command ();
1565 RegionMoveDrag::finished_no_copy (
1566 bool const changed_position,
1567 bool const changed_tracks,
1568 framecnt_t const drag_delta,
1569 int32_t const ev_state
1572 RegionSelection new_views;
1573 PlaylistSet modified_playlists;
1574 PlaylistSet frozen_playlists;
1575 set<RouteTimeAxisView*> views_to_update;
1576 RouteTimeAxisView* new_time_axis_view = 0;
1578 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1579 PlaylistMapping playlist_mapping;
1581 std::set<boost::shared_ptr<const Region> > uniq;
1582 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1584 RegionView* rv = i->view;
1585 RouteTimeAxisView* dest_rtv = 0;
1587 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1592 if (uniq.find (rv->region()) != uniq.end()) {
1593 /* prevent duplicate moves when selecting regions from shared playlists */
1597 uniq.insert(rv->region());
1599 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1600 /* dragged to drop zone */
1602 PlaylistMapping::iterator pm;
1604 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1605 /* first region from this original playlist: create a new track */
1606 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1607 if(!new_time_axis_view) { // New track creation failed
1611 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1612 dest_rtv = new_time_axis_view;
1614 /* we already created a new track for regions from this playlist, use it */
1615 dest_rtv = pm->second;
1619 /* destination time axis view is the one we dragged to */
1620 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1625 double const dest_layer = i->layer;
1627 views_to_update.insert (dest_rtv);
1631 if (changed_position && !_x_constrained) {
1632 where = rv->region()->position() - drag_delta;
1634 where = rv->region()->position();
1637 if (changed_tracks) {
1639 /* insert into new playlist */
1640 RegionView* new_view;
1641 if (rv == _primary) {
1642 new_view = insert_region_into_playlist (
1643 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1644 modified_playlists, current_music_divisor (where, ev_state)
1647 new_view = insert_region_into_playlist (
1648 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1649 modified_playlists, 0
1653 if (new_view == 0) {
1658 new_views.push_back (new_view);
1660 /* remove from old playlist */
1662 /* the region that used to be in the old playlist is not
1663 moved to the new one - we use a copy of it. as a result,
1664 any existing editor for the region should no longer be
1667 rv->hide_region_editor();
1670 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1674 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1676 /* this movement may result in a crossfade being modified, or a layering change,
1677 so we need to get undo data from the playlist as well as the region.
1680 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1682 playlist->clear_changes ();
1685 rv->region()->clear_changes ();
1688 motion on the same track. plonk the previously reparented region
1689 back to its original canvas group (its streamview).
1690 No need to do anything for copies as they are fake regions which will be deleted.
1693 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1694 rv->get_canvas_group()->set_y_position (i->initial_y);
1697 /* just change the model */
1698 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1699 playlist->set_layer (rv->region(), dest_layer);
1702 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1704 r = frozen_playlists.insert (playlist);
1707 playlist->freeze ();
1709 if (rv == _primary) {
1710 rv->region()->set_position (where, current_music_divisor (where, ev_state));
1712 rv->region()->set_position (where, 0);
1714 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1717 if (changed_tracks) {
1719 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1720 was selected in all of them, then removing it from a playlist will have removed all
1721 trace of it from _views (i.e. there were N regions selected, we removed 1,
1722 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1723 corresponding regionview, and _views is now empty).
1725 This could have invalidated any and all iterators into _views.
1727 The heuristic we use here is: if the region selection is empty, break out of the loop
1728 here. if the region selection is not empty, then restart the loop because we know that
1729 we must have removed at least the region(view) we've just been working on as well as any
1730 that we processed on previous iterations.
1732 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1733 we can just iterate.
1737 if (_views.empty()) {
1748 /* If we've created new regions either by copying or moving
1749 to a new track, we want to replace the old selection with the new ones
1752 if (new_views.size() > 0) {
1753 _editor->selection->set (new_views);
1756 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1760 /* write commands for the accumulated diffs for all our modified playlists */
1761 add_stateful_diff_commands_for_playlists (modified_playlists);
1762 /* applies to _brushing */
1763 _editor->commit_reversible_command ();
1765 /* We have futzed with the layering of canvas items on our streamviews.
1766 If any region changed layer, this will have resulted in the stream
1767 views being asked to set up their region views, and all will be well.
1768 If not, we might now have badly-ordered region views. Ask the StreamViews
1769 involved to sort themselves out, just in case.
1772 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1773 (*i)->view()->playlist_layered ((*i)->track ());
1777 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1778 * @param region Region to remove.
1779 * @param playlist playlist To remove from.
1780 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1781 * that clear_changes () is only called once per playlist.
1784 RegionMoveDrag::remove_region_from_playlist (
1785 boost::shared_ptr<Region> region,
1786 boost::shared_ptr<Playlist> playlist,
1787 PlaylistSet& modified_playlists
1790 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1793 playlist->clear_changes ();
1796 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1800 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1801 * clearing the playlist's diff history first if necessary.
1802 * @param region Region to insert.
1803 * @param dest_rtv Destination RouteTimeAxisView.
1804 * @param dest_layer Destination layer.
1805 * @param where Destination position.
1806 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1807 * that clear_changes () is only called once per playlist.
1808 * @return New RegionView, or 0 if no insert was performed.
1811 RegionMoveDrag::insert_region_into_playlist (
1812 boost::shared_ptr<Region> region,
1813 RouteTimeAxisView* dest_rtv,
1816 PlaylistSet& modified_playlists,
1817 const int32_t sub_num
1820 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1821 if (!dest_playlist) {
1825 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1826 _new_region_view = 0;
1827 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1829 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1830 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1832 dest_playlist->clear_changes ();
1834 dest_playlist->add_region (region, where, 1.0, false, sub_num);
1836 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1837 dest_playlist->set_layer (region, dest_layer);
1842 assert (_new_region_view);
1844 return _new_region_view;
1848 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1850 _new_region_view = rv;
1854 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1856 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1857 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1859 _editor->session()->add_command (c);
1868 RegionMoveDrag::aborted (bool movement_occurred)
1872 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1873 list<DraggingView>::const_iterator next = i;
1882 RegionMotionDrag::aborted (movement_occurred);
1887 RegionMotionDrag::aborted (bool)
1889 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1891 StreamView* sview = (*i)->view();
1894 if (sview->layer_display() == Expanded) {
1895 sview->set_layer_display (Stacked);
1900 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1901 RegionView* rv = i->view;
1902 TimeAxisView* tv = &(rv->get_time_axis_view ());
1903 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1905 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1906 rv->get_canvas_group()->set_y_position (0);
1908 rv->move (-_total_x_delta, 0);
1909 rv->set_height (rtv->view()->child_height ());
1913 /** @param b true to brush, otherwise false.
1914 * @param c true to make copies of the regions being moved, otherwise false.
1916 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1917 : RegionMotionDrag (e, i, p, v, b)
1919 , _new_region_view (0)
1921 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1924 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1925 if (rtv && rtv->is_track()) {
1926 speed = rtv->track()->speed ();
1929 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1933 RegionMoveDrag::setup_pointer_frame_offset ()
1935 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1938 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1939 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1941 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1943 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1944 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1946 _primary = v->view()->create_region_view (r, false, false);
1948 _primary->get_canvas_group()->show ();
1949 _primary->set_position (pos, 0);
1950 _views.push_back (DraggingView (_primary, this, v));
1952 _last_frame_position = pos;
1954 _item = _primary->get_canvas_group ();
1958 RegionInsertDrag::finished (GdkEvent * event, bool)
1960 int pos = _views.front().time_axis_view;
1961 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1963 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1965 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1966 _primary->get_canvas_group()->set_y_position (0);
1968 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1970 _editor->begin_reversible_command (Operations::insert_region);
1971 playlist->clear_changes ();
1972 playlist->add_region (_primary->region (), _last_frame_position);
1974 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
1975 if (Config->get_edit_mode() == Ripple) {
1976 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
1979 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1980 _editor->commit_reversible_command ();
1988 RegionInsertDrag::aborted (bool)
1995 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1996 : RegionMoveDrag (e, i, p, v, false, false)
1998 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2001 struct RegionSelectionByPosition {
2002 bool operator() (RegionView*a, RegionView* b) {
2003 return a->region()->position () < b->region()->position();
2008 RegionSpliceDrag::motion (GdkEvent* event, bool)
2010 /* Which trackview is this ? */
2012 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2013 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2015 /* The region motion is only processed if the pointer is over
2019 if (!tv || !tv->is_track()) {
2020 /* To make sure we hide the verbose canvas cursor when the mouse is
2021 not held over an audio track.
2023 _editor->verbose_cursor()->hide ();
2026 _editor->verbose_cursor()->show ();
2031 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2037 RegionSelection copy;
2038 _editor->selection->regions.by_position(copy);
2040 framepos_t const pf = adjusted_current_frame (event);
2042 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2044 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2050 boost::shared_ptr<Playlist> playlist;
2052 if ((playlist = atv->playlist()) == 0) {
2056 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2061 if (pf < (*i)->region()->last_frame() + 1) {
2065 if (pf > (*i)->region()->first_frame()) {
2071 playlist->shuffle ((*i)->region(), dir);
2076 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2078 RegionMoveDrag::finished (event, movement_occurred);
2082 RegionSpliceDrag::aborted (bool)
2092 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2095 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
2097 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2098 RegionSelection to_ripple;
2099 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2100 if ((*i)->position() >= where) {
2101 to_ripple.push_back (rtv->view()->find_view(*i));
2105 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2106 if (!exclude.contains (*i)) {
2107 // the selection has already been added to _views
2109 if (drag_in_progress) {
2110 // do the same things that RegionMotionDrag::motion does when
2111 // first_move is true, for the region views that we're adding
2112 // to _views this time
2115 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2116 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2117 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2118 rvg->reparent (_editor->_drag_motion_group);
2120 // we only need to move in the y direction
2121 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2126 _views.push_back (DraggingView (*i, this, tav));
2132 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2135 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2136 // we added all the regions after the selection
2138 std::list<DraggingView>::iterator to_erase = i++;
2139 if (!_editor->selection->regions.contains (to_erase->view)) {
2140 // restore the non-selected regions to their original playlist & positions,
2141 // and then ripple them back by the length of the regions that were dragged away
2142 // do the same things as RegionMotionDrag::aborted
2144 RegionView *rv = to_erase->view;
2145 TimeAxisView* tv = &(rv->get_time_axis_view ());
2146 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2149 // plonk them back onto their own track
2150 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2151 rv->get_canvas_group()->set_y_position (0);
2155 // move the underlying region to match the view
2156 rv->region()->set_position (rv->region()->position() + amount);
2158 // restore the view to match the underlying region's original position
2159 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2162 rv->set_height (rtv->view()->child_height ());
2163 _views.erase (to_erase);
2169 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2171 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2173 return allow_moves_across_tracks;
2181 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2182 : RegionMoveDrag (e, i, p, v, false, false)
2184 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2185 // compute length of selection
2186 RegionSelection selected_regions = _editor->selection->regions;
2187 selection_length = selected_regions.end_frame() - selected_regions.start();
2189 // we'll only allow dragging to another track in ripple mode if all the regions
2190 // being dragged start off on the same track
2191 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2194 exclude = new RegionList;
2195 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2196 exclude->push_back((*i)->region());
2199 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2200 RegionSelection copy;
2201 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2203 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2204 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2206 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2207 // find ripple start point on each applicable playlist
2208 RegionView *first_selected_on_this_track = NULL;
2209 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2210 if ((*i)->region()->playlist() == (*pi)) {
2211 // region is on this playlist - it's the first, because they're sorted
2212 first_selected_on_this_track = *i;
2216 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2217 add_all_after_to_views (
2218 &first_selected_on_this_track->get_time_axis_view(),
2219 first_selected_on_this_track->region()->position(),
2220 selected_regions, false);
2223 if (allow_moves_across_tracks) {
2224 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2232 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2234 /* Which trackview is this ? */
2236 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2237 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2239 /* The region motion is only processed if the pointer is over
2243 if (!tv || !tv->is_track()) {
2244 /* To make sure we hide the verbose canvas cursor when the mouse is
2245 not held over an audiotrack.
2247 _editor->verbose_cursor()->hide ();
2251 framepos_t where = adjusted_current_frame (event);
2252 assert (where >= 0);
2254 double delta = compute_x_delta (event, &after);
2256 framecnt_t amount = _editor->pixel_to_sample (delta);
2258 if (allow_moves_across_tracks) {
2259 // all the originally selected regions were on the same track
2261 framecnt_t adjust = 0;
2262 if (prev_tav && tv != prev_tav) {
2263 // dragged onto a different track
2264 // remove the unselected regions from _views, restore them to their original positions
2265 // and add the regions after the drop point on the new playlist to _views instead.
2266 // undo the effect of rippling the previous playlist, and include the effect of removing
2267 // the dragged region(s) from this track
2269 remove_unselected_from_views (prev_amount, false);
2270 // ripple previous playlist according to the regions that have been removed onto the new playlist
2271 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2274 // move just the selected regions
2275 RegionMoveDrag::motion(event, first_move);
2277 // ensure that the ripple operation on the new playlist inserts selection_length time
2278 adjust = selection_length;
2279 // ripple the new current playlist
2280 tv->playlist()->ripple (where, amount+adjust, exclude);
2282 // add regions after point where drag entered this track to subsequent ripples
2283 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2286 // motion on same track
2287 RegionMoveDrag::motion(event, first_move);
2291 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2292 prev_position = where;
2294 // selection encompasses multiple tracks - just drag
2295 // cross-track drags are forbidden
2296 RegionMoveDrag::motion(event, first_move);
2299 if (!_x_constrained) {
2300 prev_amount += amount;
2303 _last_frame_position = after;
2307 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2309 if (!movement_occurred) {
2313 if (was_double_click() && !_views.empty()) {
2314 DraggingView dv = _views.front();
2315 dv.view->show_region_editor ();
2322 _editor->begin_reversible_command(_("Ripple drag"));
2324 // remove the regions being rippled from the dragging view, updating them to
2325 // their new positions
2326 remove_unselected_from_views (prev_amount, true);
2328 if (allow_moves_across_tracks) {
2330 // if regions were dragged across tracks, we've rippled any later
2331 // regions on the track the regions were dragged off, so we need
2332 // to add the original track to the undo record
2333 orig_tav->playlist()->clear_changes();
2334 vector<Command*> cmds;
2335 orig_tav->playlist()->rdiff (cmds);
2336 _editor->session()->add_commands (cmds);
2338 if (prev_tav && prev_tav != orig_tav) {
2339 prev_tav->playlist()->clear_changes();
2340 vector<Command*> cmds;
2341 prev_tav->playlist()->rdiff (cmds);
2342 _editor->session()->add_commands (cmds);
2345 // selection spanned multiple tracks - all will need adding to undo record
2347 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2348 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2350 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2351 (*pi)->clear_changes();
2352 vector<Command*> cmds;
2353 (*pi)->rdiff (cmds);
2354 _editor->session()->add_commands (cmds);
2358 // other modified playlists are added to undo by RegionMoveDrag::finished()
2359 RegionMoveDrag::finished (event, movement_occurred);
2360 _editor->commit_reversible_command();
2364 RegionRippleDrag::aborted (bool movement_occurred)
2366 RegionMoveDrag::aborted (movement_occurred);
2371 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2373 _view (dynamic_cast<MidiTimeAxisView*> (v))
2375 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2381 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2384 _editor->begin_reversible_command (_("create region"));
2385 _region = add_midi_region (_view, false, _editor->get_grid_music_divisions (event->button.state));
2386 _view->playlist()->freeze ();
2389 framepos_t const f = adjusted_current_frame (event);
2390 if (f < grab_frame()) {
2391 _region->set_initial_position (f);
2394 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2395 so that if this region is duplicated, its duplicate starts on
2396 a snap point rather than 1 frame after a snap point. Otherwise things get
2397 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2398 place snapped notes at the start of the region.
2401 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2402 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2408 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2410 if (!movement_occurred) {
2411 add_midi_region (_view, true, _editor->get_grid_music_divisions (event->button.state));
2413 _view->playlist()->thaw ();
2414 _editor->commit_reversible_command();
2419 RegionCreateDrag::aborted (bool)
2422 _view->playlist()->thaw ();
2428 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2433 , _was_selected (false)
2436 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2440 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2442 Gdk::Cursor* cursor;
2443 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2445 float x_fraction = cnote->mouse_x_fraction ();
2447 if (x_fraction > 0.0 && x_fraction < 0.25) {
2448 cursor = _editor->cursors()->left_side_trim;
2451 cursor = _editor->cursors()->right_side_trim;
2455 Drag::start_grab (event, cursor);
2457 region = &cnote->region_view();
2460 temp = region->snap_to_pixel (cnote->x0 (), true);
2461 _snap_delta = temp - cnote->x0 ();
2465 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2470 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2471 if (ms.size() > 1) {
2472 /* has to be relative, may make no sense otherwise */
2476 if (!(_was_selected = cnote->selected())) {
2478 /* tertiary-click means extend selection - we'll do that on button release,
2479 so don't add it here, because otherwise we make it hard to figure
2480 out the "extend-to" range.
2483 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2486 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2489 region->note_selected (cnote, true);
2491 _editor->get_selection().clear_points();
2492 region->unique_select (cnote);
2499 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2501 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2503 _editor->begin_reversible_command (_("resize notes"));
2505 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2506 MidiRegionSelection::iterator next;
2509 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2511 mrv->begin_resizing (at_front);
2517 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2518 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2520 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2524 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2526 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2527 if (_editor->snap_mode () != SnapOff) {
2531 if (_editor->snap_mode () == SnapOff) {
2533 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2534 if (apply_snap_delta) {
2540 if (apply_snap_delta) {
2544 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2550 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2552 if (!movement_occurred) {
2553 /* no motion - select note */
2554 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2555 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2556 _editor->current_mouse_mode() == Editing::MouseDraw) {
2558 bool changed = false;
2560 if (_was_selected) {
2561 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2563 region->note_deselected (cnote);
2566 _editor->get_selection().clear_points();
2567 region->unique_select (cnote);
2571 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2572 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2574 if (!extend && !add && region->selection_size() > 1) {
2575 _editor->get_selection().clear_points();
2576 region->unique_select (cnote);
2578 } else if (extend) {
2579 region->note_selected (cnote, true, true);
2582 /* it was added during button press */
2588 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2589 _editor->commit_reversible_selection_op();
2596 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2597 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2598 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2600 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2603 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2605 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2606 if (_editor->snap_mode () != SnapOff) {
2610 if (_editor->snap_mode () == SnapOff) {
2612 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2613 if (apply_snap_delta) {
2619 if (apply_snap_delta) {
2623 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2627 _editor->commit_reversible_command ();
2631 NoteResizeDrag::aborted (bool)
2633 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2634 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2635 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2637 mrv->abort_resizing ();
2642 AVDraggingView::AVDraggingView (RegionView* v)
2645 initial_position = v->region()->position ();
2648 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2651 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2654 TrackViewList empty;
2656 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2657 std::list<RegionView*> views = rs.by_layer();
2660 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2661 RegionView* rv = (*i);
2662 if (!rv->region()->video_locked()) {
2665 if (rv->region()->locked()) {
2668 _views.push_back (AVDraggingView (rv));
2673 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2675 Drag::start_grab (event);
2676 if (_editor->session() == 0) {
2680 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2686 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2690 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2691 _max_backwards_drag = (
2692 ARDOUR_UI::instance()->video_timeline->get_duration()
2693 + ARDOUR_UI::instance()->video_timeline->get_offset()
2694 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2697 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2698 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2699 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2702 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2705 Timecode::Time timecode;
2706 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2707 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);
2708 show_verbose_cursor_text (buf);
2712 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2714 if (_editor->session() == 0) {
2717 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2721 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2725 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2726 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2728 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2729 dt = - _max_backwards_drag;
2732 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2733 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2735 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2736 RegionView* rv = i->view;
2737 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2740 rv->region()->clear_changes ();
2741 rv->region()->suspend_property_changes();
2743 rv->region()->set_position(i->initial_position + dt);
2744 rv->region_changed(ARDOUR::Properties::position);
2747 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2748 Timecode::Time timecode;
2749 Timecode::Time timediff;
2751 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2752 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2753 snprintf (buf, sizeof (buf),
2754 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2755 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2756 , _("Video Start:"),
2757 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2759 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2761 show_verbose_cursor_text (buf);
2765 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2767 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2774 if (!movement_occurred || ! _editor->session()) {
2778 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2780 _editor->begin_reversible_command (_("Move Video"));
2782 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2783 ARDOUR_UI::instance()->video_timeline->save_undo();
2784 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2785 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2787 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2788 i->view->drag_end();
2789 i->view->region()->resume_property_changes ();
2791 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2794 _editor->session()->maybe_update_session_range(
2795 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2796 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2800 _editor->commit_reversible_command ();
2804 VideoTimeLineDrag::aborted (bool)
2806 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2809 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2810 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2812 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2813 i->view->region()->resume_property_changes ();
2814 i->view->region()->set_position(i->initial_position);
2818 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2819 : RegionDrag (e, i, p, v)
2820 , _operation (StartTrim)
2821 , _preserve_fade_anchor (preserve_fade_anchor)
2822 , _jump_position_when_done (false)
2824 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2828 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2831 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2832 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2834 if (tv && tv->is_track()) {
2835 speed = tv->track()->speed();
2838 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2839 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2840 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2842 framepos_t const pf = adjusted_current_frame (event);
2843 setup_snap_delta (region_start);
2845 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2846 /* Move the contents of the region around without changing the region bounds */
2847 _operation = ContentsTrim;
2848 Drag::start_grab (event, _editor->cursors()->trimmer);
2850 /* These will get overridden for a point trim.*/
2851 if (pf < (region_start + region_length/2)) {
2852 /* closer to front */
2853 _operation = StartTrim;
2854 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2855 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2857 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2861 _operation = EndTrim;
2862 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2863 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2865 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2869 /* jump trim disabled for now
2870 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2871 _jump_position_when_done = true;
2875 switch (_operation) {
2877 show_verbose_cursor_time (region_start);
2880 show_verbose_cursor_duration (region_start, region_end);
2883 show_verbose_cursor_time (pf);
2887 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2888 i->view->region()->suspend_property_changes ();
2893 TrimDrag::motion (GdkEvent* event, bool first_move)
2895 RegionView* rv = _primary;
2898 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2899 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2900 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2901 frameoffset_t frame_delta = 0;
2903 if (tv && tv->is_track()) {
2904 speed = tv->track()->speed();
2906 framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
2907 framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state);
2913 switch (_operation) {
2915 trim_type = "Region start trim";
2918 trim_type = "Region end trim";
2921 trim_type = "Region content trim";
2928 _editor->begin_reversible_command (trim_type);
2930 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2931 RegionView* rv = i->view;
2932 rv->region()->playlist()->clear_owned_changes ();
2934 if (_operation == StartTrim) {
2935 rv->trim_front_starting ();
2938 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2941 arv->temporarily_hide_envelope ();
2945 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2946 insert_result = _editor->motion_frozen_playlists.insert (pl);
2948 if (insert_result.second) {
2954 bool non_overlap_trim = false;
2956 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
2957 non_overlap_trim = true;
2960 /* contstrain trim to fade length */
2961 if (_preserve_fade_anchor) {
2962 switch (_operation) {
2964 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2965 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2967 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2968 if (ar->locked()) continue;
2969 framecnt_t len = ar->fade_in()->back()->when;
2970 if (len < dt) dt = min(dt, len);
2974 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2975 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2977 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2978 if (ar->locked()) continue;
2979 framecnt_t len = ar->fade_out()->back()->when;
2980 if (len < -dt) dt = max(dt, -len);
2988 int32_t divisions = current_music_divisor (adj_frame, event->button.state);
2990 switch (_operation) {
2992 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2993 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
2996 if (changed && _preserve_fade_anchor) {
2997 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2999 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3000 framecnt_t len = ar->fade_in()->back()->when;
3001 framecnt_t diff = ar->first_frame() - i->initial_position;
3002 framepos_t new_length = len - diff;
3003 i->anchored_fade_length = min (ar->length(), new_length);
3004 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3005 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3012 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3013 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, divisions);
3014 if (changed && _preserve_fade_anchor) {
3015 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3017 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3018 framecnt_t len = ar->fade_out()->back()->when;
3019 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
3020 framepos_t new_length = len + diff;
3021 i->anchored_fade_length = min (ar->length(), new_length);
3022 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3023 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3031 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
3033 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3034 i->view->move_contents (frame_delta);
3040 switch (_operation) {
3042 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
3045 show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
3048 // show_verbose_cursor_time (frame_delta);
3054 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3056 if (movement_occurred) {
3057 motion (event, false);
3059 if (_operation == StartTrim) {
3060 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3062 /* This must happen before the region's StatefulDiffCommand is created, as it may
3063 `correct' (ahem) the region's _start from being negative to being zero. It
3064 needs to be zero in the undo record.
3066 i->view->trim_front_ending ();
3068 if (_preserve_fade_anchor) {
3069 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3071 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3072 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3073 ar->set_fade_in_length(i->anchored_fade_length);
3074 ar->set_fade_in_active(true);
3077 if (_jump_position_when_done) {
3078 i->view->region()->set_position (i->initial_position);
3081 } else if (_operation == EndTrim) {
3082 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3083 if (_preserve_fade_anchor) {
3084 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3086 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3087 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3088 ar->set_fade_out_length(i->anchored_fade_length);
3089 ar->set_fade_out_active(true);
3092 if (_jump_position_when_done) {
3093 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3098 if (!_views.empty()) {
3099 if (_operation == StartTrim) {
3100 _editor->maybe_locate_with_edit_preroll(
3101 _views.begin()->view->region()->position());
3103 if (_operation == EndTrim) {
3104 _editor->maybe_locate_with_edit_preroll(
3105 _views.begin()->view->region()->position() +
3106 _views.begin()->view->region()->length());
3110 if (!_editor->selection->selected (_primary)) {
3111 _primary->thaw_after_trim ();
3114 set<boost::shared_ptr<Playlist> > diffed_playlists;
3116 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3117 i->view->thaw_after_trim ();
3118 i->view->enable_display (true);
3120 /* Trimming one region may affect others on the playlist, so we need
3121 to get undo Commands from the whole playlist rather than just the
3122 region. Use diffed_playlists to make sure we don't diff a given
3123 playlist more than once.
3125 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
3126 if (diffed_playlists.find (p) == diffed_playlists.end()) {
3127 vector<Command*> cmds;
3129 _editor->session()->add_commands (cmds);
3130 diffed_playlists.insert (p);
3135 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3139 _editor->motion_frozen_playlists.clear ();
3140 _editor->commit_reversible_command();
3143 /* no mouse movement */
3144 if (adjusted_current_frame (event) != adjusted_frame (_drags->current_pointer_frame(), event, false)) {
3145 _editor->point_trim (event, adjusted_current_frame (event));
3149 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3150 i->view->region()->resume_property_changes ();
3155 TrimDrag::aborted (bool movement_occurred)
3157 /* Our motion method is changing model state, so use the Undo system
3158 to cancel. Perhaps not ideal, as this will leave an Undo point
3159 behind which may be slightly odd from the user's point of view.
3163 finished (&ev, true);
3165 if (movement_occurred) {
3166 _editor->session()->undo (1);
3169 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3170 i->view->region()->resume_property_changes ();
3175 TrimDrag::setup_pointer_frame_offset ()
3177 list<DraggingView>::iterator i = _views.begin ();
3178 while (i != _views.end() && i->view != _primary) {
3182 if (i == _views.end()) {
3186 switch (_operation) {
3188 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
3191 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
3198 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3201 , _old_snap_type (e->snap_type())
3202 , _old_snap_mode (e->snap_mode())
3205 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3206 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3208 _real_section = &_marker->meter();
3213 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3215 Drag::start_grab (event, cursor);
3216 show_verbose_cursor_time (adjusted_current_frame(event));
3220 MeterMarkerDrag::setup_pointer_frame_offset ()
3222 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
3226 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3229 // create a dummy marker to catch events, then hide it.
3232 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3234 _marker = new MeterMarker (
3236 *_editor->meter_group,
3237 UIConfiguration::instance().color ("meter marker"),
3239 *new MeterSection (_marker->meter())
3242 /* use the new marker for the grab */
3243 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3246 TempoMap& map (_editor->session()->tempo_map());
3247 /* get current state */
3248 before_state = &map.get_state();
3251 _editor->begin_reversible_command (_("move meter mark"));
3253 _editor->begin_reversible_command (_("copy meter mark"));
3255 Timecode::BBT_Time bbt = _real_section->bbt();
3257 /* we can't add a meter where one currently exists */
3258 if (_real_section->frame() < adjusted_current_frame (event, false)) {
3263 const double beat = map.beat_at_bbt (bbt);
3264 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3265 , beat, bbt, _real_section->position_lock_style());
3266 if (!_real_section) {
3272 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3273 if (_real_section->position_lock_style() != AudioTime) {
3274 _editor->set_snap_to (SnapToBar);
3275 _editor->set_snap_mode (SnapNormal);
3279 framepos_t pf = adjusted_current_frame (event);
3281 if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3282 /* never snap to music for audio locked */
3283 pf = adjusted_current_frame (event, false);
3286 _editor->session()->tempo_map().gui_move_meter (_real_section, pf);
3288 /* fake marker meeds to stay under the mouse, unlike the real one. */
3289 _marker->set_position (adjusted_current_frame (event, false));
3291 show_verbose_cursor_time (_real_section->frame());
3295 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3297 if (!movement_occurred) {
3298 if (was_double_click()) {
3299 _editor->edit_meter_marker (*_marker);
3304 /* reinstate old snap setting */
3305 _editor->set_snap_to (_old_snap_type);
3306 _editor->set_snap_mode (_old_snap_mode);
3308 TempoMap& map (_editor->session()->tempo_map());
3310 XMLNode &after = map.get_state();
3311 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3312 _editor->commit_reversible_command ();
3314 // delete the dummy marker we used for visual representation while moving.
3315 // a new visual marker will show up automatically.
3320 MeterMarkerDrag::aborted (bool moved)
3322 _marker->set_position (_marker->meter().frame ());
3324 /* reinstate old snap setting */
3325 _editor->set_snap_to (_old_snap_type);
3326 _editor->set_snap_mode (_old_snap_mode);
3328 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3329 // delete the dummy marker we used for visual representation while moving.
3330 // a new visual marker will show up automatically.
3335 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3341 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3343 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3344 _real_section = &_marker->tempo();
3345 _movable = !_real_section->initial();
3346 _grab_bpm = _real_section->note_types_per_minute();
3351 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3353 Drag::start_grab (event, cursor);
3354 if (!_real_section->active()) {
3355 show_verbose_cursor_text (_("inactive"));
3357 show_verbose_cursor_time (adjusted_current_frame (event));
3362 TempoMarkerDrag::setup_pointer_frame_offset ()
3364 _pointer_frame_offset = raw_grab_frame() - _real_section->frame();
3368 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3370 if (!_real_section->active()) {
3376 // mvc drag - create a dummy marker to catch events, hide it.
3379 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3381 TempoSection section (_marker->tempo());
3383 _marker = new TempoMarker (
3385 *_editor->tempo_group,
3386 UIConfiguration::instance().color ("tempo marker"),
3388 *new TempoSection (_marker->tempo())
3391 /* use the new marker for the grab */
3392 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3395 TempoMap& map (_editor->session()->tempo_map());
3396 /* get current state */
3397 before_state = &map.get_state();
3400 _editor->begin_reversible_command (_("move tempo mark"));
3403 const Tempo tempo (_marker->tempo());
3404 const framepos_t frame = adjusted_current_frame (event) + 1;
3405 const TempoSection::Type type = _real_section->type();
3407 _editor->begin_reversible_command (_("copy tempo mark"));
3409 if (_real_section->position_lock_style() == MusicTime) {
3410 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3411 _real_section = map.add_tempo (tempo, map.exact_qn_at_frame (frame, divisions), 0, type, MusicTime);
3413 _real_section = map.add_tempo (tempo, 0.0, frame, type, AudioTime);
3416 if (!_real_section) {
3424 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3425 /* use vertical movement to alter tempo .. should be log */
3426 double new_bpm = max (1.5, _grab_bpm + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3428 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type()));
3430 show_verbose_cursor_text (strs.str());
3432 } else if (_movable && !_real_section->locked_to_meter()) {
3435 if (_editor->snap_musical()) {
3436 /* we can't snap to a grid that we are about to move.
3437 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3439 pf = adjusted_current_frame (event, false);
3441 pf = adjusted_current_frame (event);
3444 TempoMap& map (_editor->session()->tempo_map());
3446 /* snap to beat is 1, snap to bar is -1 (sorry) */
3447 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3449 map.gui_move_tempo (_real_section, pf, sub_num);
3451 show_verbose_cursor_time (_real_section->frame());
3453 _marker->set_position (adjusted_current_frame (event, false));
3457 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3459 if (!_real_section->active()) {
3462 if (!movement_occurred) {
3463 if (was_double_click()) {
3464 _editor->edit_tempo_marker (*_marker);
3469 TempoMap& map (_editor->session()->tempo_map());
3471 XMLNode &after = map.get_state();
3472 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3473 _editor->commit_reversible_command ();
3475 // delete the dummy marker we used for visual representation while moving.
3476 // a new visual marker will show up automatically.
3481 TempoMarkerDrag::aborted (bool moved)
3483 _marker->set_position (_marker->tempo().frame());
3485 TempoMap& map (_editor->session()->tempo_map());
3486 map.set_state (*before_state, Stateful::current_state_version);
3487 // delete the dummy (hidden) marker we used for events while moving.
3492 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3498 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3503 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3505 Drag::start_grab (event, cursor);
3506 TempoMap& map (_editor->session()->tempo_map());
3507 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
3510 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).note_types_per_minute() << "\n";
3511 sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3512 show_verbose_cursor_text (sstr.str());
3513 finished (event, false);
3517 BBTRulerDrag::setup_pointer_frame_offset ()
3519 TempoMap& map (_editor->session()->tempo_map());
3520 const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame()));
3521 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3524 if (divisions > 0) {
3525 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions);
3527 /* while it makes some sense for the user to determine the division to 'grab',
3528 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3529 and the result over steep tempo curves. Use sixteenths.
3531 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4);
3534 _grab_qn = map.quarter_note_at_beat (beat);
3536 _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn);
3541 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3543 TempoMap& map (_editor->session()->tempo_map());
3546 /* get current state */
3547 before_state = &map.get_state();
3548 _editor->begin_reversible_command (_("dilate tempo"));
3553 if (_editor->snap_musical()) {
3554 pf = adjusted_current_frame (event, false);
3556 pf = adjusted_current_frame (event);
3559 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3560 /* adjust previous tempo to match pointer frame */
3561 _editor->session()->tempo_map().gui_dilate_tempo (_tempo, map.frame_at_quarter_note (_grab_qn), pf);
3564 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute() << "\n";
3565 sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3566 show_verbose_cursor_text (sstr.str());
3570 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3572 if (!movement_occurred) {
3576 TempoMap& map (_editor->session()->tempo_map());
3578 XMLNode &after = map.get_state();
3579 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3580 _editor->commit_reversible_command ();
3584 BBTRulerDrag::aborted (bool moved)
3587 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3592 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3593 : Drag (e, &c.track_canvas_item(), false)
3598 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3601 /** Do all the things we do when dragging the playhead to make it look as though
3602 * we have located, without actually doing the locate (because that would cause
3603 * the diskstream buffers to be refilled, which is too slow).
3606 CursorDrag::fake_locate (framepos_t t)
3608 if (_editor->session () == 0) {
3612 _editor->playhead_cursor->set_position (t);
3614 Session* s = _editor->session ();
3615 if (s->timecode_transmission_suspended ()) {
3616 framepos_t const f = _editor->playhead_cursor->current_frame ();
3617 /* This is asynchronous so it will be sent "now"
3619 s->send_mmc_locate (f);
3620 /* These are synchronous and will be sent during the next
3623 s->queue_full_time_code ();
3624 s->queue_song_position_pointer ();
3627 show_verbose_cursor_time (t);
3628 _editor->UpdateAllTransportClocks (t);
3632 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3634 Drag::start_grab (event, c);
3635 setup_snap_delta (_editor->playhead_cursor->current_frame ());
3637 _grab_zoom = _editor->samples_per_pixel;
3639 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3641 _editor->snap_to_with_modifier (where, event);
3643 _editor->_dragging_playhead = true;
3645 Session* s = _editor->session ();
3647 /* grab the track canvas item as well */
3649 _cursor.track_canvas_item().grab();
3652 if (_was_rolling && _stop) {
3656 if (s->is_auditioning()) {
3657 s->cancel_audition ();
3661 if (AudioEngine::instance()->connected()) {
3663 /* do this only if we're the engine is connected
3664 * because otherwise this request will never be
3665 * serviced and we'll busy wait forever. likewise,
3666 * notice if we are disconnected while waiting for the
3667 * request to be serviced.
3670 s->request_suspend_timecode_transmission ();
3671 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3672 /* twiddle our thumbs */
3677 fake_locate (where - snap_delta (event->button.state));
3681 CursorDrag::motion (GdkEvent* event, bool)
3683 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3684 _editor->snap_to_with_modifier (where, event);
3685 if (where != last_pointer_frame()) {
3686 fake_locate (where - snap_delta (event->button.state));
3691 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3693 _editor->_dragging_playhead = false;
3695 _cursor.track_canvas_item().ungrab();
3697 if (!movement_occurred && _stop) {
3701 motion (event, false);
3703 Session* s = _editor->session ();
3705 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3706 _editor->_pending_locate_request = true;
3707 s->request_resume_timecode_transmission ();
3712 CursorDrag::aborted (bool)
3714 _cursor.track_canvas_item().ungrab();
3716 if (_editor->_dragging_playhead) {
3717 _editor->session()->request_resume_timecode_transmission ();
3718 _editor->_dragging_playhead = false;
3721 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3724 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3725 : RegionDrag (e, i, p, v)
3727 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3731 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3733 Drag::start_grab (event, cursor);
3735 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3736 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3737 setup_snap_delta (r->position ());
3739 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3743 FadeInDrag::setup_pointer_frame_offset ()
3745 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3746 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3747 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3751 FadeInDrag::motion (GdkEvent* event, bool)
3753 framecnt_t fade_length;
3755 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3756 _editor->snap_to_with_modifier (pos, event);
3757 pos -= snap_delta (event->button.state);
3759 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3761 if (pos < (region->position() + 64)) {
3762 fade_length = 64; // this should be a minimum defined somewhere
3763 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3764 fade_length = region->length() - region->fade_out()->back()->when - 1;
3766 fade_length = pos - region->position();
3769 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3771 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3777 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3780 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3784 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3786 if (!movement_occurred) {
3790 framecnt_t fade_length;
3791 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3792 _editor->snap_to_with_modifier (pos, event);
3793 pos -= snap_delta (event->button.state);
3795 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3797 if (pos < (region->position() + 64)) {
3798 fade_length = 64; // this should be a minimum defined somewhere
3799 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3800 fade_length = region->length() - region->fade_out()->back()->when - 1;
3802 fade_length = pos - region->position();
3805 bool in_command = false;
3807 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3809 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3815 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3816 XMLNode &before = alist->get_state();
3818 tmp->audio_region()->set_fade_in_length (fade_length);
3819 tmp->audio_region()->set_fade_in_active (true);
3822 _editor->begin_reversible_command (_("change fade in length"));
3825 XMLNode &after = alist->get_state();
3826 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3830 _editor->commit_reversible_command ();
3835 FadeInDrag::aborted (bool)
3837 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3838 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3844 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3848 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3849 : RegionDrag (e, i, p, v)
3851 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3855 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3857 Drag::start_grab (event, cursor);
3859 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3860 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3861 setup_snap_delta (r->last_frame ());
3863 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3867 FadeOutDrag::setup_pointer_frame_offset ()
3869 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3870 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3871 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3875 FadeOutDrag::motion (GdkEvent* event, bool)
3877 framecnt_t fade_length;
3879 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3880 _editor->snap_to_with_modifier (pos, event);
3881 pos -= snap_delta (event->button.state);
3883 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3885 if (pos > (region->last_frame() - 64)) {
3886 fade_length = 64; // this should really be a minimum fade defined somewhere
3887 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3888 fade_length = region->length() - region->fade_in()->back()->when - 1;
3890 fade_length = region->last_frame() - pos;
3893 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3895 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3901 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3904 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3908 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3910 if (!movement_occurred) {
3914 framecnt_t fade_length;
3916 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3917 _editor->snap_to_with_modifier (pos, event);
3918 pos -= snap_delta (event->button.state);
3920 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3922 if (pos > (region->last_frame() - 64)) {
3923 fade_length = 64; // this should really be a minimum fade defined somewhere
3924 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3925 fade_length = region->length() - region->fade_in()->back()->when - 1;
3927 fade_length = region->last_frame() - pos;
3930 bool in_command = false;
3932 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3934 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3940 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3941 XMLNode &before = alist->get_state();
3943 tmp->audio_region()->set_fade_out_length (fade_length);
3944 tmp->audio_region()->set_fade_out_active (true);
3947 _editor->begin_reversible_command (_("change fade out length"));
3950 XMLNode &after = alist->get_state();
3951 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3955 _editor->commit_reversible_command ();
3960 FadeOutDrag::aborted (bool)
3962 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3963 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3969 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3973 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3975 , _selection_changed (false)
3977 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3978 Gtk::Window* toplevel = _editor->current_toplevel();
3979 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
3983 _points.push_back (ArdourCanvas::Duple (0, 0));
3985 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
3988 MarkerDrag::~MarkerDrag ()
3990 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
3995 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
3997 location = new Location (*l);
3998 markers.push_back (m);
4003 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4005 Drag::start_grab (event, cursor);
4009 Location *location = _editor->find_location_from_marker (_marker, is_start);
4010 _editor->_dragging_edit_point = true;
4012 update_item (location);
4014 // _drag_line->show();
4015 // _line->raise_to_top();
4018 show_verbose_cursor_time (location->start());
4020 show_verbose_cursor_time (location->end());
4022 setup_snap_delta (is_start ? location->start() : location->end());
4024 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4027 case Selection::Toggle:
4028 /* we toggle on the button release */
4030 case Selection::Set:
4031 if (!_editor->selection->selected (_marker)) {
4032 _editor->selection->set (_marker);
4033 _selection_changed = true;
4036 case Selection::Extend:
4038 Locations::LocationList ll;
4039 list<ArdourMarker*> to_add;
4041 _editor->selection->markers.range (s, e);
4042 s = min (_marker->position(), s);
4043 e = max (_marker->position(), e);
4046 if (e < max_framepos) {
4049 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4050 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4051 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4054 to_add.push_back (lm->start);
4057 to_add.push_back (lm->end);
4061 if (!to_add.empty()) {
4062 _editor->selection->add (to_add);
4063 _selection_changed = true;
4067 case Selection::Add:
4068 _editor->selection->add (_marker);
4069 _selection_changed = true;
4074 /* Set up copies for us to manipulate during the drag
4077 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4079 Location* l = _editor->find_location_from_marker (*i, is_start);
4086 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4088 /* range: check that the other end of the range isn't
4091 CopiedLocationInfo::iterator x;
4092 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4093 if (*(*x).location == *l) {
4097 if (x == _copied_locations.end()) {
4098 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4100 (*x).markers.push_back (*i);
4101 (*x).move_both = true;
4109 MarkerDrag::setup_pointer_frame_offset ()
4112 Location *location = _editor->find_location_from_marker (_marker, is_start);
4113 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
4117 MarkerDrag::motion (GdkEvent* event, bool)
4119 framecnt_t f_delta = 0;
4121 bool move_both = false;
4122 Location *real_location;
4123 Location *copy_location = 0;
4124 framecnt_t const sd = snap_delta (event->button.state);
4126 framecnt_t const newframe = adjusted_frame (_drags->current_pointer_frame () + sd, event, true) - sd;
4127 framepos_t next = newframe;
4129 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4133 CopiedLocationInfo::iterator x;
4135 /* find the marker we're dragging, and compute the delta */
4137 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4139 copy_location = (*x).location;
4141 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4143 /* this marker is represented by this
4144 * CopiedLocationMarkerInfo
4147 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4152 if (real_location->is_mark()) {
4153 f_delta = newframe - copy_location->start();
4157 switch (_marker->type()) {
4158 case ArdourMarker::SessionStart:
4159 case ArdourMarker::RangeStart:
4160 case ArdourMarker::LoopStart:
4161 case ArdourMarker::PunchIn:
4162 f_delta = newframe - copy_location->start();
4165 case ArdourMarker::SessionEnd:
4166 case ArdourMarker::RangeEnd:
4167 case ArdourMarker::LoopEnd:
4168 case ArdourMarker::PunchOut:
4169 f_delta = newframe - copy_location->end();
4172 /* what kind of marker is this ? */
4181 if (x == _copied_locations.end()) {
4182 /* hmm, impossible - we didn't find the dragged marker */
4186 /* now move them all */
4188 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4190 copy_location = x->location;
4192 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4196 if (real_location->locked()) {
4200 if (copy_location->is_mark()) {
4204 copy_location->set_start (copy_location->start() + f_delta);
4208 framepos_t new_start = copy_location->start() + f_delta;
4209 framepos_t new_end = copy_location->end() + f_delta;
4211 if (is_start) { // start-of-range marker
4213 if (move_both || (*x).move_both) {
4214 copy_location->set_start (new_start);
4215 copy_location->set_end (new_end);
4216 } else if (new_start < copy_location->end()) {
4217 copy_location->set_start (new_start);
4218 } else if (newframe > 0) {
4219 //_editor->snap_to (next, RoundUpAlways, true);
4220 copy_location->set_end (next);
4221 copy_location->set_start (newframe);
4224 } else { // end marker
4226 if (move_both || (*x).move_both) {
4227 copy_location->set_end (new_end);
4228 copy_location->set_start (new_start);
4229 } else if (new_end > copy_location->start()) {
4230 copy_location->set_end (new_end);
4231 } else if (newframe > 0) {
4232 //_editor->snap_to (next, RoundDownAlways, true);
4233 copy_location->set_start (next);
4234 copy_location->set_end (newframe);
4239 update_item (copy_location);
4241 /* now lookup the actual GUI items used to display this
4242 * location and move them to wherever the copy of the location
4243 * is now. This means that the logic in ARDOUR::Location is
4244 * still enforced, even though we are not (yet) modifying
4245 * the real Location itself.
4248 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4251 lm->set_position (copy_location->start(), copy_location->end());
4256 assert (!_copied_locations.empty());
4258 show_verbose_cursor_time (newframe);
4262 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4264 if (!movement_occurred) {
4266 if (was_double_click()) {
4267 _editor->rename_marker (_marker);
4271 /* just a click, do nothing but finish
4272 off the selection process
4275 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4277 case Selection::Set:
4278 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4279 _editor->selection->set (_marker);
4280 _selection_changed = true;
4284 case Selection::Toggle:
4285 /* we toggle on the button release, click only */
4286 _editor->selection->toggle (_marker);
4287 _selection_changed = true;
4291 case Selection::Extend:
4292 case Selection::Add:
4296 if (_selection_changed) {
4297 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4298 _editor->commit_reversible_selection_op();
4304 _editor->_dragging_edit_point = false;
4306 XMLNode &before = _editor->session()->locations()->get_state();
4307 bool in_command = false;
4309 MarkerSelection::iterator i;
4310 CopiedLocationInfo::iterator x;
4313 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4314 x != _copied_locations.end() && i != _editor->selection->markers.end();
4317 Location * location = _editor->find_location_from_marker (*i, is_start);
4321 if (location->locked()) {
4325 _editor->begin_reversible_command ( _("move marker") );
4328 if (location->is_mark()) {
4329 location->set_start (((*x).location)->start());
4331 location->set (((*x).location)->start(), ((*x).location)->end());
4334 if (location->is_session_range()) {
4335 _editor->session()->set_end_is_free (false);
4341 XMLNode &after = _editor->session()->locations()->get_state();
4342 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4343 _editor->commit_reversible_command ();
4348 MarkerDrag::aborted (bool movement_occurred)
4350 if (!movement_occurred) {
4354 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4356 /* move all markers to their original location */
4359 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4362 Location * location = _editor->find_location_from_marker (*m, is_start);
4365 (*m)->set_position (is_start ? location->start() : location->end());
4372 MarkerDrag::update_item (Location*)
4377 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4379 , _fixed_grab_x (0.0)
4380 , _fixed_grab_y (0.0)
4381 , _cumulative_x_drag (0.0)
4382 , _cumulative_y_drag (0.0)
4386 if (_zero_gain_fraction < 0.0) {
4387 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4390 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4392 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4398 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4400 Drag::start_grab (event, _editor->cursors()->fader);
4402 // start the grab at the center of the control point so
4403 // the point doesn't 'jump' to the mouse after the first drag
4404 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4405 _fixed_grab_y = _point->get_y();
4407 framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
4408 setup_snap_delta (pos);
4410 float const fraction = 1 - (_point->get_y() / _point->line().height());
4411 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4413 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4415 if (!_point->can_slide ()) {
4416 _x_constrained = true;
4421 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4423 double dx = _drags->current_pointer_x() - last_pointer_x();
4424 double dy = current_pointer_y() - last_pointer_y();
4425 bool need_snap = true;
4427 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4433 /* coordinate in pixels relative to the start of the region (for region-based automation)
4434 or track (for track-based automation) */
4435 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4436 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4438 // calculate zero crossing point. back off by .01 to stay on the
4439 // positive side of zero
4440 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4442 if (_x_constrained) {
4445 if (_y_constrained) {
4449 _cumulative_x_drag = cx - _fixed_grab_x;
4450 _cumulative_y_drag = cy - _fixed_grab_y;
4454 cy = min ((double) _point->line().height(), cy);
4456 // make sure we hit zero when passing through
4457 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4461 framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state);
4462 if (!_x_constrained && need_snap) {
4463 _editor->snap_to_with_modifier (cx_frames, event);
4466 cx_frames -= snap_delta (event->button.state);
4467 cx_frames = min (cx_frames, _point->line().maximum_time() + _point->line().offset());
4469 float const fraction = 1.0 - (cy / _point->line().height());
4472 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4473 _editor->begin_reversible_command (_("automation event move"));
4474 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4476 pair<double, float> result;
4477 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4479 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (result.second));
4483 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4485 if (!movement_occurred) {
4488 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4489 _editor->reset_point_selection ();
4493 _point->line().end_drag (_pushing, _final_index);
4494 _editor->commit_reversible_command ();
4499 ControlPointDrag::aborted (bool)
4501 _point->line().reset ();
4505 ControlPointDrag::active (Editing::MouseMode m)
4507 if (m == Editing::MouseDraw) {
4508 /* always active in mouse draw */
4512 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4513 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4516 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4519 , _fixed_grab_x (0.0)
4520 , _fixed_grab_y (0.0)
4521 , _cumulative_y_drag (0)
4525 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4529 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4531 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4534 _item = &_line->grab_item ();
4536 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4537 origin, and ditto for y.
4540 double mx = event->button.x;
4541 double my = event->button.y;
4543 _line->grab_item().canvas_to_item (mx, my);
4545 framecnt_t const frame_within_region = (framecnt_t) floor (mx * _editor->samples_per_pixel);
4547 if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
4548 /* no adjacent points */
4552 Drag::start_grab (event, _editor->cursors()->fader);
4554 /* store grab start in item frame */
4555 double const bx = _line->nth (_before)->get_x();
4556 double const ax = _line->nth (_after)->get_x();
4557 double const click_ratio = (ax - mx) / (ax - bx);
4559 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4564 double fraction = 1.0 - (cy / _line->height());
4566 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4570 LineDrag::motion (GdkEvent* event, bool first_move)
4572 double dy = current_pointer_y() - last_pointer_y();
4574 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4578 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4580 _cumulative_y_drag = cy - _fixed_grab_y;
4583 cy = min ((double) _line->height(), cy);
4585 double const fraction = 1.0 - (cy / _line->height());
4589 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4591 _editor->begin_reversible_command (_("automation range move"));
4592 _line->start_drag_line (_before, _after, initial_fraction);
4595 /* we are ignoring x position for this drag, so we can just pass in anything */
4596 pair<double, float> result;
4598 result = _line->drag_motion (0, fraction, true, false, ignored);
4599 show_verbose_cursor_text (_line->get_verbose_cursor_string (result.second));
4603 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4605 if (movement_occurred) {
4606 motion (event, false);
4607 _line->end_drag (false, 0);
4608 _editor->commit_reversible_command ();
4610 /* add a new control point on the line */
4612 AutomationTimeAxisView* atv;
4614 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4615 framepos_t where = grab_frame ();
4618 double cy = _fixed_grab_y;
4620 _line->grab_item().item_to_canvas (cx, cy);
4622 atv->add_automation_event (event, where, cy, false);
4623 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4624 AudioRegionView* arv;
4626 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4627 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4634 LineDrag::aborted (bool)
4639 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4643 _region_view_grab_x (0.0),
4644 _cumulative_x_drag (0),
4648 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4652 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4654 Drag::start_grab (event);
4656 _line = reinterpret_cast<Line*> (_item);
4659 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4661 double cx = event->button.x;
4662 double cy = event->button.y;
4664 _item->parent()->canvas_to_item (cx, cy);
4666 /* store grab start in parent frame */
4667 _region_view_grab_x = cx;
4669 _before = *(float*) _item->get_data ("position");
4671 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4673 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4677 FeatureLineDrag::motion (GdkEvent*, bool)
4679 double dx = _drags->current_pointer_x() - last_pointer_x();
4681 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4683 _cumulative_x_drag += dx;
4685 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4694 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4696 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4698 float *pos = new float;
4701 _line->set_data ("position", pos);
4707 FeatureLineDrag::finished (GdkEvent*, bool)
4709 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4710 _arv->update_transient(_before, _before);
4714 FeatureLineDrag::aborted (bool)
4719 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4721 , _vertical_only (false)
4723 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4727 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4729 Drag::start_grab (event);
4730 show_verbose_cursor_time (adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
4734 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4741 framepos_t const pf = adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
4743 framepos_t grab = grab_frame ();
4744 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4745 _editor->snap_to_with_modifier (grab, event);
4747 grab = raw_grab_frame ();
4750 /* base start and end on initial click position */
4760 if (current_pointer_y() < grab_y()) {
4761 y1 = current_pointer_y();
4764 y2 = current_pointer_y();
4768 if (start != end || y1 != y2) {
4770 double x1 = _editor->sample_to_pixel (start);
4771 double x2 = _editor->sample_to_pixel (end);
4772 const double min_dimension = 2.0;
4774 if (_vertical_only) {
4775 /* fixed 10 pixel width */
4779 x2 = min (x1 - min_dimension, x2);
4781 x2 = max (x1 + min_dimension, x2);
4786 y2 = min (y1 - min_dimension, y2);
4788 y2 = max (y1 + min_dimension, y2);
4791 /* translate rect into item space and set */
4793 ArdourCanvas::Rect r (x1, y1, x2, y2);
4795 /* this drag is a _trackview_only == true drag, so the y1 and
4796 * y2 (computed using current_pointer_y() and grab_y()) will be
4797 * relative to the top of the trackview group). The
4798 * rubberband rect has the same parent/scroll offset as the
4799 * the trackview group, so we can use the "r" rect directly
4800 * to set the shape of the rubberband.
4803 _editor->rubberband_rect->set (r);
4804 _editor->rubberband_rect->show();
4805 _editor->rubberband_rect->raise_to_top();
4807 show_verbose_cursor_time (pf);
4809 do_select_things (event, true);
4814 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4818 framepos_t grab = grab_frame ();
4819 framepos_t lpf = last_pointer_frame ();
4821 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4822 grab = raw_grab_frame ();
4823 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4837 if (current_pointer_y() < grab_y()) {
4838 y1 = current_pointer_y();
4841 y2 = current_pointer_y();
4845 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4849 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4851 if (movement_occurred) {
4853 motion (event, false);
4854 do_select_things (event, false);
4860 bool do_deselect = true;
4861 MidiTimeAxisView* mtv;
4863 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4865 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4866 /* nothing selected */
4867 add_midi_region (mtv, true, _editor->get_grid_music_divisions(event->button.state));
4868 do_deselect = false;
4872 /* do not deselect if Primary or Tertiary (toggle-select or
4873 * extend-select are pressed.
4876 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4877 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4884 _editor->rubberband_rect->hide();
4888 RubberbandSelectDrag::aborted (bool)
4890 _editor->rubberband_rect->hide ();
4893 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4894 : RegionDrag (e, i, p, v)
4896 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4900 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4902 Drag::start_grab (event, cursor);
4904 _editor->get_selection().add (_primary);
4906 framepos_t where = _primary->region()->position();
4907 setup_snap_delta (where);
4909 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4913 TimeFXDrag::motion (GdkEvent* event, bool)
4915 RegionView* rv = _primary;
4916 StreamView* cv = rv->get_time_axis_view().view ();
4918 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4919 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4920 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4921 framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
4922 _editor->snap_to_with_modifier (pf, event);
4923 pf -= snap_delta (event->button.state);
4925 if (pf > rv->region()->position()) {
4926 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4929 show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4933 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
4935 /* this may have been a single click, no drag. We still want the dialog
4936 to show up in that case, so that the user can manually edit the
4937 parameters for the timestretch.
4940 float fraction = 1.0;
4942 if (movement_occurred) {
4944 motion (event, false);
4946 _primary->get_time_axis_view().hide_timestretch ();
4948 framepos_t adjusted_frame_pos = adjusted_current_frame (event);
4950 if (adjusted_frame_pos < _primary->region()->position()) {
4951 /* backwards drag of the left edge - not usable */
4955 framecnt_t newlen = adjusted_frame_pos - _primary->region()->position();
4957 fraction = (double) newlen / (double) _primary->region()->length();
4959 #ifndef USE_RUBBERBAND
4960 // Soundtouch uses fraction / 100 instead of normal (/ 1)
4961 if (_primary->region()->data_type() == DataType::AUDIO) {
4962 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4967 if (!_editor->get_selection().regions.empty()) {
4968 /* primary will already be included in the selection, and edit
4969 group shared editing will propagate selection across
4970 equivalent regions, so just use the current region
4974 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
4975 error << _("An error occurred while executing time stretch operation") << endmsg;
4981 TimeFXDrag::aborted (bool)
4983 _primary->get_time_axis_view().hide_timestretch ();
4986 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
4989 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
4993 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4995 Drag::start_grab (event);
4999 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5001 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
5005 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5007 if (movement_occurred && _editor->session()) {
5008 /* make sure we stop */
5009 _editor->session()->request_transport_speed (0.0);
5014 ScrubDrag::aborted (bool)
5019 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5023 , _time_selection_at_start (!_editor->get_selection().time.empty())
5025 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5027 if (_time_selection_at_start) {
5028 start_at_start = _editor->get_selection().time.start();
5029 end_at_start = _editor->get_selection().time.end_frame();
5034 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5036 if (_editor->session() == 0) {
5040 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5042 switch (_operation) {
5043 case CreateSelection:
5044 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5049 cursor = _editor->cursors()->selector;
5050 Drag::start_grab (event, cursor);
5053 case SelectionStartTrim:
5054 if (_editor->clicked_axisview) {
5055 _editor->clicked_axisview->order_selection_trims (_item, true);
5057 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5060 case SelectionEndTrim:
5061 if (_editor->clicked_axisview) {
5062 _editor->clicked_axisview->order_selection_trims (_item, false);
5064 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5068 Drag::start_grab (event, cursor);
5071 case SelectionExtend:
5072 Drag::start_grab (event, cursor);
5076 if (_operation == SelectionMove) {
5077 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5079 show_verbose_cursor_time (adjusted_current_frame (event));
5084 SelectionDrag::setup_pointer_frame_offset ()
5086 switch (_operation) {
5087 case CreateSelection:
5088 _pointer_frame_offset = 0;
5091 case SelectionStartTrim:
5093 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
5096 case SelectionEndTrim:
5097 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
5100 case SelectionExtend:
5106 SelectionDrag::motion (GdkEvent* event, bool first_move)
5108 framepos_t start = 0;
5110 framecnt_t length = 0;
5111 framecnt_t distance = 0;
5113 framepos_t const pending_position = adjusted_current_frame (event);
5115 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
5119 switch (_operation) {
5120 case CreateSelection:
5122 framepos_t grab = grab_frame ();
5125 grab = adjusted_current_frame (event, false);
5126 if (grab < pending_position) {
5127 _editor->snap_to (grab, RoundDownMaybe);
5129 _editor->snap_to (grab, RoundUpMaybe);
5133 if (pending_position < grab) {
5134 start = pending_position;
5137 end = pending_position;
5141 /* first drag: Either add to the selection
5142 or create a new selection
5149 /* adding to the selection */
5150 _editor->set_selected_track_as_side_effect (Selection::Add);
5151 _editor->clicked_selection = _editor->selection->add (start, end);
5158 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5159 _editor->set_selected_track_as_side_effect (Selection::Set);
5162 _editor->clicked_selection = _editor->selection->set (start, end);
5166 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5167 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5168 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5170 _editor->selection->add (atest);
5174 /* select all tracks within the rectangle that we've marked out so far */
5175 TrackViewList new_selection;
5176 TrackViewList& all_tracks (_editor->track_views);
5178 ArdourCanvas::Coord const top = grab_y();
5179 ArdourCanvas::Coord const bottom = current_pointer_y();
5181 if (top >= 0 && bottom >= 0) {
5183 //first, find the tracks that are covered in the y range selection
5184 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5185 if ((*i)->covered_by_y_range (top, bottom)) {
5186 new_selection.push_back (*i);
5190 //now find any tracks that are GROUPED with the tracks we selected
5191 TrackViewList grouped_add = new_selection;
5192 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5193 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
5194 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::group_select.property_id) ) {
5195 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
5196 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
5197 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
5198 grouped_add.push_back (*j);
5203 //now compare our list with the current selection, and add or remove as necessary
5204 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5205 TrackViewList tracks_to_add;
5206 TrackViewList tracks_to_remove;
5207 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
5208 if ( !_editor->selection->tracks.contains ( *i ) )
5209 tracks_to_add.push_back ( *i );
5210 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
5211 if ( !grouped_add.contains ( *i ) )
5212 tracks_to_remove.push_back ( *i );
5213 _editor->selection->add(tracks_to_add);
5214 _editor->selection->remove(tracks_to_remove);
5220 case SelectionStartTrim:
5222 end = _editor->selection->time[_editor->clicked_selection].end;
5224 if (pending_position > end) {
5227 start = pending_position;
5231 case SelectionEndTrim:
5233 start = _editor->selection->time[_editor->clicked_selection].start;
5235 if (pending_position < start) {
5238 end = pending_position;
5245 start = _editor->selection->time[_editor->clicked_selection].start;
5246 end = _editor->selection->time[_editor->clicked_selection].end;
5248 length = end - start;
5249 distance = pending_position - start;
5250 start = pending_position;
5251 _editor->snap_to (start);
5253 end = start + length;
5257 case SelectionExtend:
5262 switch (_operation) {
5264 if (_time_selection_at_start) {
5265 _editor->selection->move_time (distance);
5269 _editor->selection->replace (_editor->clicked_selection, start, end);
5273 if (_operation == SelectionMove) {
5274 show_verbose_cursor_time(start);
5276 show_verbose_cursor_time(pending_position);
5281 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5283 Session* s = _editor->session();
5285 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5286 if (movement_occurred) {
5287 motion (event, false);
5288 /* XXX this is not object-oriented programming at all. ick */
5289 if (_editor->selection->time.consolidate()) {
5290 _editor->selection->TimeChanged ();
5293 /* XXX what if its a music time selection? */
5295 if (s->get_play_range() && s->transport_rolling()) {
5296 s->request_play_range (&_editor->selection->time, true);
5297 } else if (!s->config.get_external_sync()) {
5298 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5299 if (_operation == SelectionEndTrim)
5300 _editor->maybe_locate_with_edit_preroll( _editor->get_selection().time.end_frame());
5302 s->request_locate (_editor->get_selection().time.start());
5306 if (_editor->get_selection().time.length() != 0) {
5307 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame());
5309 s->clear_range_selection ();
5314 /* just a click, no pointer movement.
5317 if (_operation == SelectionExtend) {
5318 if (_time_selection_at_start) {
5319 framepos_t pos = adjusted_current_frame (event, false);
5320 framepos_t start = min (pos, start_at_start);
5321 framepos_t end = max (pos, end_at_start);
5322 _editor->selection->set (start, end);
5325 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5326 if (_editor->clicked_selection) {
5327 _editor->selection->remove (_editor->clicked_selection);
5330 if (!_editor->clicked_selection) {
5331 _editor->selection->clear_time();
5336 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5337 _editor->selection->set (_editor->clicked_axisview);
5340 if (s && s->get_play_range () && s->transport_rolling()) {
5341 s->request_stop (false, false);
5346 _editor->stop_canvas_autoscroll ();
5347 _editor->clicked_selection = 0;
5348 _editor->commit_reversible_selection_op ();
5352 SelectionDrag::aborted (bool)
5357 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5358 : Drag (e, i, false),
5362 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5364 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5365 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5366 physical_screen_height (_editor->current_toplevel()->get_window())));
5367 _drag_rect->hide ();
5369 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5370 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5373 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5375 /* normal canvas items will be cleaned up when their parent group is deleted. But
5376 this item is created as the child of a long-lived parent group, and so we
5377 need to explicitly delete it.
5383 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5385 if (_editor->session() == 0) {
5389 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5391 if (!_editor->temp_location) {
5392 _editor->temp_location = new Location (*_editor->session());
5395 switch (_operation) {
5396 case CreateSkipMarker:
5397 case CreateRangeMarker:
5398 case CreateTransportMarker:
5399 case CreateCDMarker:
5401 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5406 cursor = _editor->cursors()->selector;
5410 Drag::start_grab (event, cursor);
5412 show_verbose_cursor_time (adjusted_current_frame (event));
5416 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5418 framepos_t start = 0;
5420 ArdourCanvas::Rectangle *crect;
5422 switch (_operation) {
5423 case CreateSkipMarker:
5424 crect = _editor->range_bar_drag_rect;
5426 case CreateRangeMarker:
5427 crect = _editor->range_bar_drag_rect;
5429 case CreateTransportMarker:
5430 crect = _editor->transport_bar_drag_rect;
5432 case CreateCDMarker:
5433 crect = _editor->cd_marker_bar_drag_rect;
5436 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5441 framepos_t const pf = adjusted_current_frame (event);
5443 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5444 framepos_t grab = grab_frame ();
5445 _editor->snap_to (grab);
5447 if (pf < grab_frame()) {
5455 /* first drag: Either add to the selection
5456 or create a new selection.
5461 _editor->temp_location->set (start, end);
5465 update_item (_editor->temp_location);
5467 //_drag_rect->raise_to_top();
5473 _editor->temp_location->set (start, end);
5475 double x1 = _editor->sample_to_pixel (start);
5476 double x2 = _editor->sample_to_pixel (end);
5480 update_item (_editor->temp_location);
5483 show_verbose_cursor_time (pf);
5488 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5490 Location * newloc = 0;
5494 if (movement_occurred) {
5495 motion (event, false);
5498 switch (_operation) {
5499 case CreateSkipMarker:
5500 case CreateRangeMarker:
5501 case CreateCDMarker:
5503 XMLNode &before = _editor->session()->locations()->get_state();
5504 if (_operation == CreateSkipMarker) {
5505 _editor->begin_reversible_command (_("new skip marker"));
5506 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5507 flags = Location::IsRangeMarker | Location::IsSkip;
5508 _editor->range_bar_drag_rect->hide();
5509 } else if (_operation == CreateCDMarker) {
5510 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5511 _editor->begin_reversible_command (_("new CD marker"));
5512 flags = Location::IsRangeMarker | Location::IsCDMarker;
5513 _editor->cd_marker_bar_drag_rect->hide();
5515 _editor->begin_reversible_command (_("new skip marker"));
5516 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5517 flags = Location::IsRangeMarker;
5518 _editor->range_bar_drag_rect->hide();
5520 newloc = new Location (
5521 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5524 _editor->session()->locations()->add (newloc, true);
5525 XMLNode &after = _editor->session()->locations()->get_state();
5526 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5527 _editor->commit_reversible_command ();
5531 case CreateTransportMarker:
5532 // popup menu to pick loop or punch
5533 _editor->new_transport_marker_context_menu (&event->button, _item);
5539 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5541 if (_operation == CreateTransportMarker) {
5543 /* didn't drag, so just locate */
5545 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5547 } else if (_operation == CreateCDMarker) {
5549 /* didn't drag, but mark is already created so do
5552 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5557 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5559 if (end == max_framepos) {
5560 end = _editor->session()->current_end_frame ();
5563 if (start == max_framepos) {
5564 start = _editor->session()->current_start_frame ();
5567 switch (_editor->mouse_mode) {
5569 /* find the two markers on either side and then make the selection from it */
5570 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5574 /* find the two markers on either side of the click and make the range out of it */
5575 _editor->selection->set (start, end);
5584 _editor->stop_canvas_autoscroll ();
5588 RangeMarkerBarDrag::aborted (bool movement_occurred)
5590 if (movement_occurred) {
5591 _drag_rect->hide ();
5596 RangeMarkerBarDrag::update_item (Location* location)
5598 double const x1 = _editor->sample_to_pixel (location->start());
5599 double const x2 = _editor->sample_to_pixel (location->end());
5601 _drag_rect->set_x0 (x1);
5602 _drag_rect->set_x1 (x2);
5605 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5607 , _cumulative_dx (0)
5608 , _cumulative_dy (0)
5609 , _was_selected (false)
5611 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5613 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5615 _region = &_primary->region_view ();
5616 _note_height = _region->midi_stream_view()->note_height ();
5620 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5622 Drag::start_grab (event);
5623 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5625 if (!(_was_selected = _primary->selected())) {
5627 /* tertiary-click means extend selection - we'll do that on button release,
5628 so don't add it here, because otherwise we make it hard to figure
5629 out the "extend-to" range.
5632 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5635 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5638 _region->note_selected (_primary, true);
5640 _editor->get_selection().clear_points();
5641 _region->unique_select (_primary);
5647 /** @return Current total drag x change in frames */
5649 NoteDrag::total_dx (const guint state) const
5651 if (_x_constrained) {
5654 TempoMap& map (_editor->session()->tempo_map());
5657 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5659 /* primary note time */
5660 double const quarter_note_start = _region->region()->quarter_note() - _region->midi_region()->start_beats();
5661 frameoffset_t const n = map.frame_at_quarter_note (quarter_note_start + _primary->note()->time().to_double());
5663 /* new time of the primary note in session frames */
5664 frameoffset_t st = n + dx + snap_delta (state);
5666 framepos_t const rp = _region->region()->position ();
5668 /* prevent the note being dragged earlier than the region's position */
5671 /* possibly snap and return corresponding delta */
5675 if (ArdourKeyboard::indicates_snap (state)) {
5676 if (_editor->snap_mode () != SnapOff) {
5680 if (_editor->snap_mode () == SnapOff) {
5682 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
5683 if (ArdourKeyboard::indicates_snap_delta (state)) {
5691 bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
5692 ret = _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
5694 ret = st - n - snap_delta (state);
5699 /** @return Current total drag y change in note number */
5701 NoteDrag::total_dy () const
5703 if (_y_constrained) {
5707 double const y = _region->midi_view()->y_position ();
5708 /* new current note */
5709 uint8_t n = _region->y_to_note (current_pointer_y () - y);
5711 MidiStreamView* msv = _region->midi_stream_view ();
5712 n = max (msv->lowest_note(), n);
5713 n = min (msv->highest_note(), n);
5714 /* and work out delta */
5715 return n - _region->y_to_note (grab_y() - y);
5719 NoteDrag::motion (GdkEvent * event, bool)
5721 /* Total change in x and y since the start of the drag */
5722 frameoffset_t const dx = total_dx (event->button.state);
5723 int8_t const dy = total_dy ();
5725 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5726 double const tdx = _x_constrained ? 0 : _editor->sample_to_pixel (dx) - _cumulative_dx;
5727 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
5730 _cumulative_dx += tdx;
5731 _cumulative_dy += tdy;
5733 int8_t note_delta = total_dy();
5736 _region->move_selection (tdx, tdy, note_delta);
5738 /* the new note value may be the same as the old one, but we
5739 * don't know what that means because the selection may have
5740 * involved more than one note and we might be doing something
5741 * odd with them. so show the note value anyway, always.
5744 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5746 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
5752 NoteDrag::finished (GdkEvent* ev, bool moved)
5755 /* no motion - select note */
5757 if (_editor->current_mouse_mode() == Editing::MouseContent ||
5758 _editor->current_mouse_mode() == Editing::MouseDraw) {
5760 bool changed = false;
5762 if (_was_selected) {
5763 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5765 _region->note_deselected (_primary);
5768 _editor->get_selection().clear_points();
5769 _region->unique_select (_primary);
5773 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5774 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5776 if (!extend && !add && _region->selection_size() > 1) {
5777 _editor->get_selection().clear_points();
5778 _region->unique_select (_primary);
5780 } else if (extend) {
5781 _region->note_selected (_primary, true, true);
5784 /* it was added during button press */
5791 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5792 _editor->commit_reversible_selection_op();
5796 _region->note_dropped (_primary, total_dx (ev->button.state), total_dy());
5801 NoteDrag::aborted (bool)
5806 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5807 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5808 : Drag (editor, atv->base_item ())
5810 , _y_origin (atv->y_position())
5811 , _nothing_to_drag (false)
5813 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5814 setup (atv->lines ());
5817 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5818 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5819 : Drag (editor, rv->get_canvas_group ())
5821 , _y_origin (rv->get_time_axis_view().y_position())
5822 , _nothing_to_drag (false)
5825 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5827 list<boost::shared_ptr<AutomationLine> > lines;
5829 AudioRegionView* audio_view;
5830 AutomationRegionView* automation_view;
5831 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5832 lines.push_back (audio_view->get_gain_line ());
5833 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5834 lines.push_back (automation_view->line ());
5837 error << _("Automation range drag created for invalid region type") << endmsg;
5843 /** @param lines AutomationLines to drag.
5844 * @param offset Offset from the session start to the points in the AutomationLines.
5847 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5849 /* find the lines that overlap the ranges being dragged */
5850 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5851 while (i != lines.end ()) {
5852 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5855 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5857 /* check this range against all the AudioRanges that we are using */
5858 list<AudioRange>::const_iterator k = _ranges.begin ();
5859 while (k != _ranges.end()) {
5860 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5866 /* add it to our list if it overlaps at all */
5867 if (k != _ranges.end()) {
5872 _lines.push_back (n);
5878 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5882 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5884 return 1.0 - ((global_y - _y_origin) / line->height());
5888 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5890 const double v = list->eval(x);
5891 return _integral ? rint(v) : v;
5895 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5897 Drag::start_grab (event, cursor);
5899 /* Get line states before we start changing things */
5900 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5901 i->state = &i->line->get_state ();
5902 i->original_fraction = y_fraction (i->line, current_pointer_y());
5905 if (_ranges.empty()) {
5907 /* No selected time ranges: drag all points */
5908 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5909 uint32_t const N = i->line->npoints ();
5910 for (uint32_t j = 0; j < N; ++j) {
5911 i->points.push_back (i->line->nth (j));
5917 if (_nothing_to_drag) {
5923 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
5925 if (_nothing_to_drag && !first_move) {
5930 _editor->begin_reversible_command (_("automation range move"));
5932 if (!_ranges.empty()) {
5934 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5936 framecnt_t const half = (i->start + i->end) / 2;
5938 /* find the line that this audio range starts in */
5939 list<Line>::iterator j = _lines.begin();
5940 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5944 if (j != _lines.end()) {
5945 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5947 /* j is the line that this audio range starts in; fade into it;
5948 64 samples length plucked out of thin air.
5951 framepos_t a = i->start + 64;
5956 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5957 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5959 XMLNode &before = the_list->get_state();
5960 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5961 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5963 if (add_p || add_q) {
5964 _editor->session()->add_command (
5965 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
5969 /* same thing for the end */
5972 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5976 if (j != _lines.end()) {
5977 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5979 /* j is the line that this audio range starts in; fade out of it;
5980 64 samples length plucked out of thin air.
5983 framepos_t b = i->end - 64;
5988 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
5989 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
5991 XMLNode &before = the_list->get_state();
5992 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5993 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5995 if (add_p || add_q) {
5996 _editor->session()->add_command (
5997 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6002 _nothing_to_drag = true;
6004 /* Find all the points that should be dragged and put them in the relevant
6005 points lists in the Line structs.
6008 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6010 uint32_t const N = i->line->npoints ();
6011 for (uint32_t j = 0; j < N; ++j) {
6013 /* here's a control point on this line */
6014 ControlPoint* p = i->line->nth (j);
6015 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6017 /* see if it's inside a range */
6018 list<AudioRange>::const_iterator k = _ranges.begin ();
6019 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6023 if (k != _ranges.end()) {
6024 /* dragging this point */
6025 _nothing_to_drag = false;
6026 i->points.push_back (p);
6032 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6033 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6037 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6038 float const f = y_fraction (l->line, current_pointer_y());
6039 /* we are ignoring x position for this drag, so we can just pass in anything */
6040 pair<double, float> result;
6042 result = l->line->drag_motion (0, f, true, false, ignored);
6043 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, result.second));
6048 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6050 if (_nothing_to_drag || !motion_occurred) {
6054 motion (event, false);
6055 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6056 i->line->end_drag (false, 0);
6059 _editor->commit_reversible_command ();
6063 AutomationRangeDrag::aborted (bool)
6065 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6070 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6072 , initial_time_axis_view (itav)
6074 /* note that time_axis_view may be null if the regionview was created
6075 * as part of a copy operation.
6077 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6078 layer = v->region()->layer ();
6079 initial_y = v->get_canvas_group()->position().y;
6080 initial_playlist = v->region()->playlist ();
6081 initial_position = v->region()->position ();
6082 initial_end = v->region()->position () + v->region()->length ();
6085 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6086 : Drag (e, i->canvas_item ())
6089 , _cumulative_dx (0)
6091 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6092 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
6097 PatchChangeDrag::motion (GdkEvent* ev, bool)
6099 framepos_t f = adjusted_current_frame (ev);
6100 boost::shared_ptr<Region> r = _region_view->region ();
6101 f = max (f, r->position ());
6102 f = min (f, r->last_frame ());
6104 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
6105 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6106 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6107 _cumulative_dx = dxu;
6111 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6113 if (!movement_occurred) {
6114 if (was_double_click()) {
6115 _region_view->edit_patch_change (_patch_change);
6120 boost::shared_ptr<Region> r (_region_view->region ());
6121 framepos_t f = adjusted_current_frame (ev);
6122 f = max (f, r->position ());
6123 f = min (f, r->last_frame ());
6125 _region_view->move_patch_change (
6127 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
6132 PatchChangeDrag::aborted (bool)
6134 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6138 PatchChangeDrag::setup_pointer_frame_offset ()
6140 boost::shared_ptr<Region> region = _region_view->region ();
6141 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
6144 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6145 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6152 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6154 _region_view->update_drag_selection (
6156 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6160 MidiRubberbandSelectDrag::deselect_things ()
6165 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6166 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6169 _vertical_only = true;
6173 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6175 double const y = _region_view->midi_view()->y_position ();
6177 y1 = max (0.0, y1 - y);
6178 y2 = max (0.0, y2 - y);
6180 _region_view->update_vertical_drag_selection (
6183 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6188 MidiVerticalSelectDrag::deselect_things ()
6193 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6194 : RubberbandSelectDrag (e, i)
6200 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
6202 if (drag_in_progress) {
6203 /* We just want to select things at the end of the drag, not during it */
6207 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6209 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6211 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6213 _editor->commit_reversible_selection_op ();
6217 EditorRubberbandSelectDrag::deselect_things ()
6219 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6221 _editor->selection->clear_tracks();
6222 _editor->selection->clear_regions();
6223 _editor->selection->clear_points ();
6224 _editor->selection->clear_lines ();
6225 _editor->selection->clear_midi_notes ();
6227 _editor->commit_reversible_selection_op();
6230 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6235 _note[0] = _note[1] = 0;
6238 NoteCreateDrag::~NoteCreateDrag ()
6244 NoteCreateDrag::grid_frames (framepos_t t) const
6247 const Evoral::Beats grid_beats = _region_view->get_grid_beats (t);
6248 const Evoral::Beats t_beats = _region_view->region_frames_to_region_beats (t);
6250 return _region_view->region_beats_to_region_frames (t_beats + grid_beats)
6251 - _region_view->region_beats_to_region_frames (t_beats);
6255 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6257 Drag::start_grab (event, cursor);
6259 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6260 TempoMap& map (_editor->session()->tempo_map());
6262 const framepos_t pf = _drags->current_pointer_frame ();
6263 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6265 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6267 double eqaf = map.exact_qn_at_frame (pf, divisions);
6269 if (divisions != 0) {
6271 const double qaf = map.quarter_note_at_frame (pf);
6273 /* Hack so that we always snap to the note that we are over, instead of snapping
6274 to the next one if we're more than halfway through the one we're over.
6277 const double rem = eqaf - qaf;
6279 eqaf -= grid_beats.to_double();
6283 _note[0] = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6284 /* minimum initial length is grid beats */
6285 _note[1] = map.frame_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6287 double const x0 = _editor->sample_to_pixel (_note[0]);
6288 double const x1 = _editor->sample_to_pixel (_note[1]);
6289 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6291 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6292 _drag_rect->set_outline_all ();
6293 _drag_rect->set_outline_color (0xffffff99);
6294 _drag_rect->set_fill_color (0xffffff66);
6298 NoteCreateDrag::motion (GdkEvent* event, bool)
6300 TempoMap& map (_editor->session()->tempo_map());
6301 const framepos_t pf = _drags->current_pointer_frame ();
6302 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6303 double eqaf = map.exact_qn_at_frame (pf, divisions);
6305 if (divisions != 0) {
6307 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6309 const double qaf = map.quarter_note_at_frame (pf);
6310 /* Hack so that we always snap to the note that we are over, instead of snapping
6311 to the next one if we're more than halfway through the one we're over.
6314 const double rem = eqaf - qaf;
6316 eqaf -= grid_beats.to_double();
6319 eqaf += grid_beats.to_double();
6321 _note[1] = max ((framepos_t)0, map.frame_at_quarter_note (eqaf) - _region_view->region()->position ());
6323 double const x0 = _editor->sample_to_pixel (_note[0]);
6324 double const x1 = _editor->sample_to_pixel (_note[1]);
6325 _drag_rect->set_x0 (std::min(x0, x1));
6326 _drag_rect->set_x1 (std::max(x0, x1));
6330 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6332 /* we create a note even if there was no movement */
6333 framepos_t const start = min (_note[0], _note[1]);
6334 framepos_t const start_sess_rel = start + _region_view->region()->position();
6335 framecnt_t length = max (_editor->pixel_to_sample (1.0), (framecnt_t) fabs ((double)(_note[0] - _note[1])));
6336 framecnt_t const g = grid_frames (start);
6338 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6342 TempoMap& map (_editor->session()->tempo_map());
6343 const double qn_length = map.quarter_notes_between_frames (start_sess_rel, start_sess_rel + length);
6344 Evoral::Beats qn_length_beats = max (Evoral::Beats::ticks(1), Evoral::Beats (qn_length));
6346 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6350 NoteCreateDrag::y_to_region (double y) const
6353 _region_view->get_canvas_group()->canvas_to_item (x, y);
6358 NoteCreateDrag::aborted (bool)
6363 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6371 HitCreateDrag::~HitCreateDrag ()
6376 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6378 Drag::start_grab (event, cursor);
6380 TempoMap& map (_editor->session()->tempo_map());
6382 const framepos_t pf = _drags->current_pointer_frame ();
6383 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6385 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6387 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6389 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6393 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6394 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6396 Evoral::Beats length = _region_view->get_grid_beats (pf);
6398 _region_view->create_note_at (start, y, length, event->button.state, false);
6405 HitCreateDrag::motion (GdkEvent* event, bool)
6407 TempoMap& map (_editor->session()->tempo_map());
6409 const framepos_t pf = _drags->current_pointer_frame ();
6410 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6412 if (divisions == 0) {
6416 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6417 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position ();
6418 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6420 if (_last_pos == start && y == _last_y) {
6424 Evoral::Beats length = _region_view->get_grid_beats (pf);
6426 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6427 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6431 _region_view->create_note_at (start, y, length, event->button.state, false);
6438 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6444 HitCreateDrag::y_to_region (double y) const
6447 _region_view->get_canvas_group()->canvas_to_item (x, y);
6452 HitCreateDrag::aborted (bool)
6457 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6462 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6466 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6468 Drag::start_grab (event, cursor);
6472 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6478 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6481 distance = _drags->current_pointer_x() - grab_x();
6482 len = ar->fade_in()->back()->when;
6484 distance = grab_x() - _drags->current_pointer_x();
6485 len = ar->fade_out()->back()->when;
6488 /* how long should it be ? */
6490 new_length = len + _editor->pixel_to_sample (distance);
6492 /* now check with the region that this is legal */
6494 new_length = ar->verify_xfade_bounds (new_length, start);
6497 arv->reset_fade_in_shape_width (ar, new_length);
6499 arv->reset_fade_out_shape_width (ar, new_length);
6504 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6510 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6513 distance = _drags->current_pointer_x() - grab_x();
6514 len = ar->fade_in()->back()->when;
6516 distance = grab_x() - _drags->current_pointer_x();
6517 len = ar->fade_out()->back()->when;
6520 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6522 _editor->begin_reversible_command ("xfade trim");
6523 ar->playlist()->clear_owned_changes ();
6526 ar->set_fade_in_length (new_length);
6528 ar->set_fade_out_length (new_length);
6531 /* Adjusting the xfade may affect other regions in the playlist, so we need
6532 to get undo Commands from the whole playlist rather than just the
6536 vector<Command*> cmds;
6537 ar->playlist()->rdiff (cmds);
6538 _editor->session()->add_commands (cmds);
6539 _editor->commit_reversible_command ();
6544 CrossfadeEdgeDrag::aborted (bool)
6547 // arv->redraw_start_xfade ();
6549 // arv->redraw_end_xfade ();
6553 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
6554 : Drag (e, item, true)
6555 , line (new EditorCursor (*e))
6557 line->set_position (pos);
6559 line->track_canvas_item().reparent (_editor->_drag_motion_group);
6562 RegionCutDrag::~RegionCutDrag ()
6568 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6570 Drag::start_grab (event, c);
6571 motion (event, false);
6575 RegionCutDrag::motion (GdkEvent* event, bool)
6577 framepos_t pos = _drags->current_pointer_frame();
6578 _editor->snap_to_with_modifier (pos, event);
6580 line->set_position (pos);
6584 RegionCutDrag::finished (GdkEvent* event, bool)
6586 _editor->get_track_canvas()->canvas()->re_enter();
6588 framepos_t pos = _drags->current_pointer_frame();
6589 _editor->snap_to_with_modifier (pos, event);
6593 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
6599 _editor->split_regions_at (pos, rs, _editor->get_grid_music_divisions (event->button.state),
6604 RegionCutDrag::aborted (bool)
6608 RulerZoomDrag::RulerZoomDrag (Editor* e, ArdourCanvas::Item* item)
6609 : Drag (e, item, true)
6614 RulerZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6616 Drag::start_grab (event, c);
6618 framepos_t where = _editor->canvas_event_sample(event);
6620 _editor->_dragging_playhead = true;
6622 _editor->playhead_cursor->set_position (where);
6626 RulerZoomDrag::motion (GdkEvent* event, bool)
6628 framepos_t where = _editor->canvas_event_sample(event);
6630 _editor->playhead_cursor->set_position (where);
6632 const double movement_limit = 20.0;
6633 const double scale = 1.08;
6634 const double y_delta = last_pointer_y() - current_pointer_y();
6636 if (y_delta > 0 && y_delta < movement_limit) {
6637 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
6638 } else if (y_delta < 0 && y_delta > -movement_limit) {
6639 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
6644 RulerZoomDrag::finished (GdkEvent*, bool)
6646 _editor->_dragging_playhead = false;
6648 Session* s = _editor->session ();
6650 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
6651 _editor->_pending_locate_request = true;
6657 RulerZoomDrag::aborted (bool)