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/profile.h"
41 #include "ardour/region_factory.h"
42 #include "ardour/session.h"
44 #include "canvas/canvas.h"
45 #include "canvas/scroll_group.h"
50 #include "audio_region_view.h"
51 #include "automation_region_view.h"
52 #include "midi_region_view.h"
53 #include "ardour_ui.h"
54 #include "gui_thread.h"
55 #include "control_point.h"
56 #include "region_gain_line.h"
57 #include "editor_drag.h"
58 #include "audio_time_axis.h"
59 #include "midi_time_axis.h"
60 #include "selection.h"
61 #include "midi_selection.h"
62 #include "automation_time_axis.h"
64 #include "editor_cursors.h"
65 #include "mouse_cursors.h"
66 #include "note_base.h"
67 #include "patch_change.h"
68 #include "ui_config.h"
69 #include "verbose_cursor.h"
72 using namespace ARDOUR;
75 using namespace Gtkmm2ext;
76 using namespace Editing;
77 using namespace ArdourCanvas;
79 using Gtkmm2ext::Keyboard;
81 double ControlPointDrag::_zero_gain_fraction = -1.0;
83 DragManager::DragManager (Editor* e)
86 , _current_pointer_x (0.0)
87 , _current_pointer_y (0.0)
88 , _current_pointer_frame (0)
89 , _old_follow_playhead (false)
93 DragManager::~DragManager ()
98 /** Call abort for each active drag */
100 DragManager::abort ()
104 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
109 if (!_drags.empty ()) {
110 _editor->set_follow_playhead (_old_follow_playhead, false);
114 _editor->abort_reversible_command();
120 DragManager::add (Drag* d)
122 d->set_manager (this);
123 _drags.push_back (d);
127 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
129 d->set_manager (this);
130 _drags.push_back (d);
135 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
137 /* Prevent follow playhead during the drag to be nice to the user */
138 _old_follow_playhead = _editor->follow_playhead ();
139 _editor->set_follow_playhead (false);
141 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
143 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
144 (*i)->start_grab (e, c);
148 /** Call end_grab for each active drag.
149 * @return true if any drag reported movement having occurred.
152 DragManager::end_grab (GdkEvent* e)
157 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
158 bool const t = (*i)->end_grab (e);
169 _editor->set_follow_playhead (_old_follow_playhead, false);
175 DragManager::mark_double_click ()
177 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
178 (*i)->set_double_click (true);
183 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
187 /* calling this implies that we expect the event to have canvas
190 * Can we guarantee that this is true?
193 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
195 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
196 bool const t = (*i)->motion_handler (e, from_autoscroll);
197 /* run all handlers; return true if at least one of them
198 returns true (indicating that the event has been handled).
210 DragManager::have_item (ArdourCanvas::Item* i) const
212 list<Drag*>::const_iterator j = _drags.begin ();
213 while (j != _drags.end() && (*j)->item () != i) {
217 return j != _drags.end ();
220 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
224 , _pointer_frame_offset (0)
225 , _x_constrained (false)
226 , _y_constrained (false)
227 , _was_rolling (false)
228 , _trackview_only (trackview_only)
229 , _move_threshold_passed (false)
230 , _starting_point_passed (false)
231 , _initially_vertical (false)
232 , _was_double_click (false)
235 , _last_pointer_x (0.0)
236 , _last_pointer_y (0.0)
237 , _raw_grab_frame (0)
239 , _last_pointer_frame (0)
241 , _constraint_pressed (false)
247 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
253 _cursor_ctx = CursorContext::create (*_editor, cursor);
255 _cursor_ctx->change (cursor);
262 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
265 /* we set up x/y dragging constraints on first move */
266 _constraint_pressed = ArdourKeyboard::indicates_constraint (event->button.state);
268 _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
270 setup_pointer_frame_offset ();
271 _grab_frame = adjusted_frame (_raw_grab_frame, event);
272 _last_pointer_frame = _grab_frame;
273 _last_pointer_x = _grab_x;
275 if (_trackview_only) {
276 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
279 _last_pointer_y = _grab_y;
283 if (!_editor->cursors()->is_invalid (cursor)) {
284 /* CAIROCANVAS need a variant here that passes *cursor */
285 _cursor_ctx = CursorContext::create (*_editor, cursor);
288 if (_editor->session() && _editor->session()->transport_rolling()) {
291 _was_rolling = false;
294 switch (_editor->snap_type()) {
295 case SnapToRegionStart:
296 case SnapToRegionEnd:
297 case SnapToRegionSync:
298 case SnapToRegionBoundary:
299 _editor->build_region_boundary_cache ();
306 /** Call to end a drag `successfully'. Ungrabs item and calls
307 * subclass' finished() method.
309 * @param event GDK event, or 0.
310 * @return true if some movement occurred, otherwise false.
313 Drag::end_grab (GdkEvent* event)
315 _editor->stop_canvas_autoscroll ();
319 finished (event, _move_threshold_passed);
321 _editor->verbose_cursor()->hide ();
324 return _move_threshold_passed;
328 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
332 if (f > _pointer_frame_offset) {
333 pos = f - _pointer_frame_offset;
337 _editor->snap_to_with_modifier (pos, event);
344 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
346 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
350 Drag::snap_delta (guint state) const
352 if (ArdourKeyboard::indicates_snap_delta (state)) {
360 Drag::current_pointer_x() const
362 return _drags->current_pointer_x ();
366 Drag::current_pointer_y () const
368 if (!_trackview_only) {
369 return _drags->current_pointer_y ();
372 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
376 Drag::setup_snap_delta (framepos_t pos)
378 framepos_t temp = pos;
379 _editor->snap_to (temp, ARDOUR::RoundNearest, false, true);
380 _snap_delta = temp - pos;
384 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
386 /* check to see if we have moved in any way that matters since the last motion event */
387 if (_move_threshold_passed &&
388 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
389 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
393 pair<framecnt_t, int> const threshold = move_threshold ();
395 bool const old_move_threshold_passed = _move_threshold_passed;
397 if (!_move_threshold_passed) {
399 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
400 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
402 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
405 if (active (_editor->mouse_mode) && _move_threshold_passed) {
407 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
409 if (old_move_threshold_passed != _move_threshold_passed) {
413 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
414 _initially_vertical = true;
416 _initially_vertical = false;
418 /** check constraints for this drag.
419 * Note that the current convention is to use "contains" for
420 * key modifiers during motion and "equals" when initiating a drag.
421 * In this case we haven't moved yet, so "equals" applies here.
423 if (Config->get_edit_mode() != Lock) {
424 if (event->motion.state & Gdk::BUTTON2_MASK) {
425 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
426 if (_constraint_pressed) {
427 _x_constrained = false;
428 _y_constrained = true;
430 _x_constrained = true;
431 _y_constrained = false;
433 } else if (_constraint_pressed) {
434 // if dragging normally, the motion is constrained to the first direction of movement.
435 if (_initially_vertical) {
436 _x_constrained = true;
437 _y_constrained = false;
439 _x_constrained = false;
440 _y_constrained = true;
444 if (event->button.state & Gdk::BUTTON2_MASK) {
445 _x_constrained = false;
447 _x_constrained = true;
449 _y_constrained = false;
453 if (!from_autoscroll) {
454 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
457 if (!_editor->autoscroll_active() || from_autoscroll) {
460 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
462 motion (event, first_move && !_starting_point_passed);
464 if (first_move && !_starting_point_passed) {
465 _starting_point_passed = true;
468 _last_pointer_x = _drags->current_pointer_x ();
469 _last_pointer_y = current_pointer_y ();
470 _last_pointer_frame = adjusted_current_frame (event, false);
480 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
488 aborted (_move_threshold_passed);
490 _editor->stop_canvas_autoscroll ();
491 _editor->verbose_cursor()->hide ();
495 Drag::show_verbose_cursor_time (framepos_t frame)
497 _editor->verbose_cursor()->set_time (frame);
498 _editor->verbose_cursor()->show ();
502 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
504 _editor->verbose_cursor()->set_duration (start, end);
505 _editor->verbose_cursor()->show ();
509 Drag::show_verbose_cursor_text (string const & text)
511 _editor->verbose_cursor()->set (text);
512 _editor->verbose_cursor()->show ();
515 boost::shared_ptr<Region>
516 Drag::add_midi_region (MidiTimeAxisView* view, bool commit, const int32_t sub_num)
518 if (_editor->session()) {
519 const TempoMap& map (_editor->session()->tempo_map());
520 framecnt_t pos = grab_frame();
521 /* not that the frame rate used here can be affected by pull up/down which
524 framecnt_t len = map.frame_at_beat (max (0.0, map.beat_at_frame (pos)) + 1.0) - pos;
525 return view->add_region (grab_frame(), len, commit, sub_num);
528 return boost::shared_ptr<Region>();
531 struct PresentationInfoTimeAxisViewSorter {
532 bool operator() (TimeAxisView* a, TimeAxisView* b) {
533 return a->stripable()->presentation_info().order() < b->stripable()->presentation_info().order();
537 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
542 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
544 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
545 as some of the regions we are dragging may be on such tracks.
548 TrackViewList track_views = _editor->track_views;
549 track_views.sort (PresentationInfoTimeAxisViewSorter ());
551 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
552 _time_axis_views.push_back (*i);
554 TimeAxisView::Children children_list = (*i)->get_child_list ();
555 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
556 _time_axis_views.push_back (j->get());
560 /* the list of views can be empty at this point if this is a region list-insert drag
563 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
564 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
567 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
571 RegionDrag::region_going_away (RegionView* v)
573 list<DraggingView>::iterator i = _views.begin ();
574 while (i != _views.end() && i->view != v) {
578 if (i != _views.end()) {
583 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
584 * or -1 if it is not found.
587 RegionDrag::find_time_axis_view (TimeAxisView* t) const
590 int const N = _time_axis_views.size ();
591 while (i < N && _time_axis_views[i] != t) {
595 if (_time_axis_views[i] != t) {
602 /** determines if @pos is the closest frame to an exact musical division when using SnapMagnetic.
603 * (SnapMagnetic may snap to an exact division or no division at all).
604 * this is a hotfix for musical region position/trim. we should really
605 * deliver musical divisors when snapping magnetically to avoid this.
608 RegionDrag::current_music_divisor (framepos_t pos, int32_t button_state)
610 int32_t divisions = _editor->get_grid_music_divisions (button_state);
612 if (_editor->snap_mode() == Editing::SnapMagnetic && !ArdourKeyboard::indicates_snap (button_state)) {
613 TempoMap& tmap (_editor->session()->tempo_map());
614 const framepos_t exact_qn_pos = tmap.frame_at_quarter_note (tmap.exact_qn_at_frame (pos, divisions));
616 if (pos != exact_qn_pos) {
617 /* magnetic has not snapped */
625 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
626 : RegionDrag (e, i, p, v)
628 , _ignore_video_lock (false)
630 , _last_pointer_time_axis_view (0)
631 , _last_pointer_layer (0)
636 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
640 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
642 Drag::start_grab (event, cursor);
643 setup_snap_delta (_last_frame_position);
645 show_verbose_cursor_time (_last_frame_position);
647 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
649 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
650 assert(_last_pointer_time_axis_view >= 0);
651 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
654 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
655 _ignore_video_lock = true;
659 /* cross track dragging seems broken here. disabled for now. */
660 _y_constrained = true;
665 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
667 /* compute the amount of pointer motion in frames, and where
668 the region would be if we moved it by that much.
670 *pending_region_position = adjusted_frame (_drags->current_pointer_frame (), event, false);
672 framepos_t sync_frame;
673 framecnt_t sync_offset;
676 sync_offset = _primary->region()->sync_offset (sync_dir);
678 /* we don't handle a sync point that lies before zero.
680 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
682 framecnt_t const sd = snap_delta (event->button.state);
683 sync_frame = *pending_region_position + (sync_dir * sync_offset) + sd;
685 _editor->snap_to_with_modifier (sync_frame, event);
687 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - sd;
690 *pending_region_position = _last_frame_position;
693 if (*pending_region_position > max_framepos - _primary->region()->length()) {
694 *pending_region_position = _last_frame_position;
699 bool const x_move_allowed = !_x_constrained;
701 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
703 /* x movement since last time (in pixels) */
704 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
706 /* total x movement */
707 framecnt_t total_dx = *pending_region_position;
708 if (regions_came_from_canvas()) {
709 total_dx = total_dx - grab_frame ();
712 /* check that no regions have gone off the start of the session */
713 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
714 if ((i->view->region()->position() + total_dx) < 0) {
716 *pending_region_position = _last_frame_position;
727 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
733 const int tavsize = _time_axis_views.size();
734 const int dt = delta > 0 ? +1 : -1;
736 int target = start + delta - skip;
738 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
739 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
741 while (current >= 0 && current != target) {
743 if (current < 0 && dt < 0) {
746 if (current >= tavsize && dt > 0) {
749 if (current < 0 || current >= tavsize) {
753 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
754 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
758 if (distance_only && current == start + delta) {
766 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
768 if (_y_constrained) {
772 const int tavsize = _time_axis_views.size();
773 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
774 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
775 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
777 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
778 /* already in the drop zone */
779 if (delta_track >= 0) {
780 /* downward motion - OK if others are still not in the dropzone */
789 } else if (n >= tavsize) {
790 /* downward motion into drop zone. That's fine. */
794 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
795 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
796 /* not a track, or the wrong type */
800 double const l = i->layer + delta_layer;
802 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
803 mode to allow the user to place a region below another on layer 0.
805 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
806 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
807 If it has, the layers will be munged later anyway, so it's ok.
813 /* all regions being dragged are ok with this change */
817 struct DraggingViewSorter {
818 bool operator() (const DraggingView& a, const DraggingView& b) {
819 return a.time_axis_view < b.time_axis_view;
824 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
826 double delta_layer = 0;
827 int delta_time_axis_view = 0;
828 int current_pointer_time_axis_view = -1;
830 assert (!_views.empty ());
832 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
834 /* Find the TimeAxisView that the pointer is now over */
835 const double cur_y = current_pointer_y ();
836 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
837 TimeAxisView* tv = r.first;
839 if (!tv && cur_y < 0) {
840 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
844 /* find drop-zone y-position */
845 Coord last_track_bottom_edge;
846 last_track_bottom_edge = 0;
847 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
848 if (!(*t)->hidden()) {
849 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
854 if (tv && tv->view()) {
855 /* the mouse is over a track */
856 double layer = r.second;
858 if (first_move && tv->view()->layer_display() == Stacked) {
859 tv->view()->set_layer_display (Expanded);
862 /* Here's the current pointer position in terms of time axis view and layer */
863 current_pointer_time_axis_view = find_time_axis_view (tv);
864 assert(current_pointer_time_axis_view >= 0);
866 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
868 /* Work out the change in y */
870 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
871 if (!rtv || !rtv->is_track()) {
872 /* ignore non-tracks early on. we can't move any regions on them */
873 } else if (_last_pointer_time_axis_view < 0) {
874 /* Was in the drop-zone, now over a track.
875 * Hence it must be an upward move (from the bottom)
877 * track_index is still -1, so delta must be set to
878 * move up the correct number of tracks from the bottom.
880 * This is necessary because steps may be skipped if
881 * the bottom-most track is not a valid target and/or
882 * if there are hidden tracks at the bottom.
883 * Hence the initial offset (_ddropzone) as well as the
884 * last valid pointer position (_pdropzone) need to be
885 * taken into account.
887 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
889 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
892 /* TODO needs adjustment per DraggingView,
894 * e.g. select one region on the top-layer of a track
895 * and one region which is at the bottom-layer of another track
898 * Indicated drop-zones and layering is wrong.
899 * and may infer additional layers on the target-track
900 * (depending how many layers the original track had).
902 * Or select two regions (different layers) on a same track,
903 * move across a non-layer track.. -> layering info is lost.
904 * on drop either of the regions may be on top.
906 * Proposed solution: screw it :) well,
907 * don't use delta_layer, use an absolute value
908 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
909 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
910 * 3) iterate over all DraggingView, find the one that is over the track with most layers
911 * 4) proportionally scale layer to layers available on target
913 delta_layer = current_pointer_layer - _last_pointer_layer;
916 /* for automation lanes, there is a TimeAxisView but no ->view()
917 * if (!tv) -> dropzone
919 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
920 /* Moving into the drop-zone.. */
921 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
922 /* delta_time_axis_view may not be sufficient to move into the DZ
923 * the mouse may enter it, but it may not be a valid move due to
926 * -> remember the delta needed to move into the dropzone
928 _ddropzone = delta_time_axis_view;
929 /* ..but subtract hidden tracks (or routes) at the bottom.
930 * we silently move mover them
932 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
933 - _time_axis_views.size();
935 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
936 /* move around inside the zone.
937 * This allows to move further down until all regions are in the zone.
939 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
940 assert(ptr_y >= last_track_bottom_edge);
941 assert(_ddropzone > 0);
943 /* calculate mouse position in 'tracks' below last track. */
944 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
945 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
947 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
949 delta_time_axis_view = dzpos - _pdropzone;
950 } else if (dzpos < _pdropzone && _ndropzone > 0) {
951 // move up inside the DZ
952 delta_time_axis_view = dzpos - _pdropzone;
956 /* Work out the change in x */
957 framepos_t pending_region_position;
958 double const x_delta = compute_x_delta (event, &pending_region_position);
959 _last_frame_position = pending_region_position;
961 /* calculate hidden tracks in current y-axis delta */
963 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
964 /* The mouse is more than one track below the dropzone.
965 * distance calculation is not needed (and would not work, either
966 * because the dropzone is "packed").
968 * Except when [partially] moving regions out of dropzone in a large step.
969 * (the mouse may or may not remain in the DZ)
970 * Hidden tracks at the bottom of the TAV need to be skipped.
972 * This also handles the case if the mouse entered the DZ
973 * in a large step (exessive delta), either due to fast-movement,
974 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
976 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
977 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
979 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
980 -_time_axis_views.size() - dt;
983 else if (_last_pointer_time_axis_view < 0) {
984 /* Moving out of the zone. Check for hidden tracks at the bottom. */
985 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
986 -_time_axis_views.size() - delta_time_axis_view;
988 /* calculate hidden tracks that are skipped by the pointer movement */
989 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
990 - _last_pointer_time_axis_view
991 - delta_time_axis_view;
994 /* Verify change in y */
995 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
996 /* this y movement is not allowed, so do no y movement this time */
997 delta_time_axis_view = 0;
1002 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1003 /* haven't reached next snap point, and we're not switching
1004 trackviews nor layers. nothing to do.
1009 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1010 PlaylistDropzoneMap playlist_dropzone_map;
1011 _ndropzone = 0; // number of elements currently in the dropzone
1014 /* sort views by time_axis.
1015 * This retains track order in the dropzone, regardless
1016 * of actual selection order
1018 _views.sort (DraggingViewSorter());
1020 /* count number of distinct tracks of all regions
1021 * being dragged, used for dropzone.
1023 int prev_track = -1;
1024 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1025 if (i->time_axis_view != prev_track) {
1026 prev_track = i->time_axis_view;
1032 _views.back().time_axis_view -
1033 _views.front().time_axis_view;
1035 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1036 - _views.back().time_axis_view;
1038 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1042 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1044 RegionView* rv = i->view;
1049 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1056 /* reparent the regionview into a group above all
1060 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1061 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1062 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1063 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1064 /* move the item so that it continues to appear at the
1065 same location now that its parent has changed.
1067 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1070 /* If we have moved tracks, we'll fudge the layer delta so that the
1071 region gets moved back onto layer 0 on its new track; this avoids
1072 confusion when dragging regions from non-zero layers onto different
1075 double this_delta_layer = delta_layer;
1076 if (delta_time_axis_view != 0) {
1077 this_delta_layer = - i->layer;
1080 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1082 int track_index = i->time_axis_view + this_delta_time_axis_view;
1083 assert(track_index >= 0);
1085 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1086 /* Track is in the Dropzone */
1088 i->time_axis_view = track_index;
1089 assert(i->time_axis_view >= (int) _time_axis_views.size());
1092 double yposition = 0;
1093 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1094 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1097 /* store index of each new playlist as a negative count, starting at -1 */
1099 if (pdz == playlist_dropzone_map.end()) {
1100 /* compute where this new track (which doesn't exist yet) will live
1103 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1105 /* How high is this region view ? */
1107 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1108 ArdourCanvas::Rect bbox;
1111 bbox = obbox.get ();
1114 last_track_bottom_edge += bbox.height();
1116 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1119 yposition = pdz->second;
1122 /* values are zero or negative, hence the use of min() */
1123 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1126 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1128 mrv->apply_note_range (60, 71, true);
1132 /* The TimeAxisView that this region is now over */
1133 TimeAxisView* current_tv = _time_axis_views[track_index];
1135 /* Ensure it is moved from stacked -> expanded if appropriate */
1136 if (current_tv->view()->layer_display() == Stacked) {
1137 current_tv->view()->set_layer_display (Expanded);
1140 /* We're only allowed to go -ve in layer on Expanded views */
1141 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1142 this_delta_layer = - i->layer;
1146 rv->set_height (current_tv->view()->child_height ());
1148 /* Update show/hidden status as the region view may have come from a hidden track,
1149 or have moved to one.
1151 if (current_tv->hidden ()) {
1152 rv->get_canvas_group()->hide ();
1154 rv->get_canvas_group()->show ();
1157 /* Update the DraggingView */
1158 i->time_axis_view = track_index;
1159 i->layer += this_delta_layer;
1162 _editor->mouse_brush_insert_region (rv, pending_region_position);
1166 /* Get the y coordinate of the top of the track that this region is now over */
1167 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1169 /* And adjust for the layer that it should be on */
1170 StreamView* cv = current_tv->view ();
1171 switch (cv->layer_display ()) {
1175 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1178 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1182 /* need to get the parent of the regionview
1183 * canvas group and get its position in
1184 * equivalent coordinate space as the trackview
1185 * we are now dragging over.
1188 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1192 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1194 MidiStreamView* msv;
1195 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1196 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1201 /* Now move the region view */
1202 rv->move (x_delta, y_delta);
1204 } /* foreach region */
1206 _total_x_delta += x_delta;
1208 if (x_delta != 0 && !_brushing) {
1209 show_verbose_cursor_time (_last_frame_position);
1212 /* keep track of pointer movement */
1214 /* the pointer is currently over a time axis view */
1216 if (_last_pointer_time_axis_view < 0) {
1217 /* last motion event was not over a time axis view
1218 * or last y-movement out of the dropzone was not valid
1221 if (delta_time_axis_view < 0) {
1222 /* in the drop zone, moving up */
1224 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1225 * We do not use negative _last_pointer_time_axis_view because
1226 * the dropzone is "packed" (the actual track offset is ignored)
1228 * As opposed to the actual number
1229 * of elements in the dropzone (_ndropzone)
1230 * _pdropzone is not constrained. This is necessary
1231 * to allow moving multiple regions with y-distance
1234 * There can be 0 elements in the dropzone,
1235 * even though the drag-pointer is inside the DZ.
1238 * [ Audio-track, Midi-track, Audio-track, DZ ]
1239 * move regions from both audio tracks at the same time into the
1240 * DZ by grabbing the region in the bottom track.
1242 assert(current_pointer_time_axis_view >= 0);
1243 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1247 /* only move out of the zone if the movement is OK */
1248 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1249 assert(delta_time_axis_view < 0);
1250 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1251 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1252 * the current position can be calculated as follows:
1254 // a well placed oofus attack can still throw this off.
1255 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1256 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1259 /* last motion event was also over a time axis view */
1260 _last_pointer_time_axis_view += delta_time_axis_view;
1261 assert(_last_pointer_time_axis_view >= 0);
1266 /* the pointer is not over a time axis view */
1267 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1268 _pdropzone += delta_time_axis_view - delta_skip;
1269 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1272 _last_pointer_layer += delta_layer;
1276 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1278 if (_copy && first_move) {
1279 if (_x_constrained && !_brushing) {
1280 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1281 } else if (!_brushing) {
1282 _editor->begin_reversible_command (Operations::region_copy);
1283 } else if (_brushing) {
1284 _editor->begin_reversible_command (Operations::drag_region_brush);
1286 /* duplicate the regionview(s) and region(s) */
1288 list<DraggingView> new_regionviews;
1290 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1292 RegionView* rv = i->view;
1293 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1294 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1296 const boost::shared_ptr<const Region> original = rv->region();
1297 boost::shared_ptr<Region> region_copy;
1299 if (rv == _primary) {
1300 region_copy = RegionFactory::create (original, true
1301 , current_music_divisor (original->position(), event->button.state));
1303 region_copy = RegionFactory::create (original, true, 0);
1306 /* need to set this so that the drop zone code can work. This doesn't
1307 actually put the region into the playlist, but just sets a weak pointer
1310 region_copy->set_playlist (original->playlist());
1314 boost::shared_ptr<AudioRegion> audioregion_copy
1315 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1317 nrv = new AudioRegionView (*arv, audioregion_copy);
1319 boost::shared_ptr<MidiRegion> midiregion_copy
1320 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1321 nrv = new MidiRegionView (*mrv, midiregion_copy);
1326 nrv->get_canvas_group()->show ();
1327 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1329 /* swap _primary to the copy */
1331 if (rv == _primary) {
1335 /* ..and deselect the one we copied */
1337 rv->set_selected (false);
1340 if (!new_regionviews.empty()) {
1342 /* reflect the fact that we are dragging the copies */
1344 _views = new_regionviews;
1346 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1349 } else if (!_copy && first_move) {
1350 if (_x_constrained && !_brushing) {
1351 _editor->begin_reversible_command (_("fixed time region drag"));
1352 } else if (!_brushing) {
1353 _editor->begin_reversible_command (Operations::region_drag);
1354 } else if (_brushing) {
1355 _editor->begin_reversible_command (Operations::drag_region_brush);
1358 RegionMotionDrag::motion (event, first_move);
1362 RegionMotionDrag::finished (GdkEvent *, bool)
1364 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1365 if (!(*i)->view()) {
1369 if ((*i)->view()->layer_display() == Expanded) {
1370 (*i)->view()->set_layer_display (Stacked);
1376 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1378 RegionMotionDrag::finished (ev, movement_occurred);
1380 if (!movement_occurred) {
1384 if (was_double_click() && !_views.empty()) {
1385 DraggingView dv = _views.front();
1386 _editor->edit_region (dv.view);
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;
1428 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1430 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1433 TimeAxisView* tav = 0;
1435 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1436 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1437 uint32_t output_chan = region->n_channels();
1438 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1439 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1441 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1442 tav =_editor->axis_view_from_stripable (audio_tracks.front());
1444 ChanCount one_midi_port (DataType::MIDI, 1);
1445 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1446 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1447 Config->get_strict_io () || Profile->get_mixbus (),
1448 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;
1471 int32_t divisor = current_music_divisor (_primary->region()->position() - drag_delta, ev_state);
1472 TempoMap& tmap (_editor->session()->tempo_map());
1473 double qn_delta = _primary->region()->quarter_note() - tmap.exact_qn_at_frame (_primary->region()->position() - drag_delta, divisor);
1476 /* all changes were made during motion event handlers */
1478 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1482 _editor->commit_reversible_command ();
1486 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1487 PlaylistMapping playlist_mapping;
1489 /* insert the regions into their new playlists */
1490 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1492 RouteTimeAxisView* dest_rtv = 0;
1494 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1500 if (changed_position && !_x_constrained) {
1501 where = i->view->region()->position() - drag_delta;
1503 where = i->view->region()->position();
1506 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1507 /* dragged to drop zone */
1509 PlaylistMapping::iterator pm;
1511 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1512 /* first region from this original playlist: create a new track */
1513 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1514 if(!new_time_axis_view) {
1518 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1519 dest_rtv = new_time_axis_view;
1521 /* we already created a new track for regions from this playlist, use it */
1522 dest_rtv = pm->second;
1525 /* destination time axis view is the one we dragged to */
1526 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1529 if (dest_rtv != 0) {
1530 RegionView* new_view;
1531 if (i->view == _primary) {
1532 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1533 modified_playlists, current_music_divisor (where, ev_state));
1535 if (i->view->region()->position_lock_style() == AudioTime) {
1536 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1537 modified_playlists, 0);
1539 where = tmap.frame_at_quarter_note (i->view->region()->quarter_note() - qn_delta);
1540 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1541 modified_playlists, 0);
1545 if (new_view != 0) {
1546 new_views.push_back (new_view);
1550 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1551 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1554 list<DraggingView>::const_iterator next = i;
1560 /* If we've created new regions either by copying or moving
1561 to a new track, we want to replace the old selection with the new ones
1564 if (new_views.size() > 0) {
1565 _editor->selection->set (new_views);
1568 /* write commands for the accumulated diffs for all our modified playlists */
1569 add_stateful_diff_commands_for_playlists (modified_playlists);
1571 _editor->commit_reversible_command ();
1575 RegionMoveDrag::finished_no_copy (
1576 bool const changed_position,
1577 bool const changed_tracks,
1578 framecnt_t const drag_delta,
1579 int32_t const ev_state
1582 RegionSelection new_views;
1583 PlaylistSet modified_playlists;
1584 PlaylistSet frozen_playlists;
1585 set<RouteTimeAxisView*> views_to_update;
1586 RouteTimeAxisView* new_time_axis_view = 0;
1588 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1589 PlaylistMapping playlist_mapping;
1591 int32_t divisor = current_music_divisor (_primary->region()->position() - drag_delta, ev_state);
1592 TempoMap& tmap (_editor->session()->tempo_map());
1593 double qn_delta = _primary->region()->quarter_note() - tmap.exact_qn_at_frame (_primary->region()->position() - drag_delta, divisor);
1595 std::set<boost::shared_ptr<const Region> > uniq;
1596 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1598 RegionView* rv = i->view;
1599 RouteTimeAxisView* dest_rtv = 0;
1601 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1606 if (uniq.find (rv->region()) != uniq.end()) {
1607 /* prevent duplicate moves when selecting regions from shared playlists */
1611 uniq.insert(rv->region());
1613 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1614 /* dragged to drop zone */
1616 PlaylistMapping::iterator pm;
1618 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1619 /* first region from this original playlist: create a new track */
1620 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1621 if(!new_time_axis_view) { // New track creation failed
1625 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1626 dest_rtv = new_time_axis_view;
1628 /* we already created a new track for regions from this playlist, use it */
1629 dest_rtv = pm->second;
1633 /* destination time axis view is the one we dragged to */
1634 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1639 double const dest_layer = i->layer;
1641 views_to_update.insert (dest_rtv);
1645 if (changed_position && !_x_constrained) {
1646 where = rv->region()->position() - drag_delta;
1648 where = rv->region()->position();
1651 if (changed_tracks) {
1653 /* insert into new playlist */
1654 RegionView* new_view;
1655 if (rv == _primary) {
1656 new_view = insert_region_into_playlist (
1657 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1658 modified_playlists, current_music_divisor (where, ev_state)
1661 if (rv->region()->position_lock_style() == AudioTime) {
1663 new_view = insert_region_into_playlist (
1664 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1665 modified_playlists, 0
1668 where = tmap.frame_at_quarter_note (rv->region()->quarter_note() - qn_delta);
1669 new_view = insert_region_into_playlist (
1670 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1671 modified_playlists, 0
1676 if (new_view == 0) {
1681 new_views.push_back (new_view);
1683 /* remove from old playlist */
1685 /* the region that used to be in the old playlist is not
1686 moved to the new one - we use a copy of it. as a result,
1687 any existing editor for the region should no longer be
1690 rv->hide_region_editor();
1693 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1697 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1699 /* this movement may result in a crossfade being modified, or a layering change,
1700 so we need to get undo data from the playlist as well as the region.
1703 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1705 playlist->clear_changes ();
1708 rv->region()->clear_changes ();
1711 motion on the same track. plonk the previously reparented region
1712 back to its original canvas group (its streamview).
1713 No need to do anything for copies as they are fake regions which will be deleted.
1716 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1717 rv->get_canvas_group()->set_y_position (i->initial_y);
1720 /* just change the model */
1721 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1722 playlist->set_layer (rv->region(), dest_layer);
1725 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1727 r = frozen_playlists.insert (playlist);
1730 playlist->freeze ();
1732 if (rv == _primary) {
1733 rv->region()->set_position (where, current_music_divisor (where, ev_state));
1735 if (rv->region()->position_lock_style() == AudioTime) {
1736 rv->region()->set_position (where, 0);
1738 rv->region()->set_position (tmap.frame_at_quarter_note (rv->region()->quarter_note() - qn_delta), 0);
1742 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1745 if (changed_tracks) {
1747 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1748 was selected in all of them, then removing it from a playlist will have removed all
1749 trace of it from _views (i.e. there were N regions selected, we removed 1,
1750 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1751 corresponding regionview, and _views is now empty).
1753 This could have invalidated any and all iterators into _views.
1755 The heuristic we use here is: if the region selection is empty, break out of the loop
1756 here. if the region selection is not empty, then restart the loop because we know that
1757 we must have removed at least the region(view) we've just been working on as well as any
1758 that we processed on previous iterations.
1760 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1761 we can just iterate.
1765 if (_views.empty()) {
1776 /* If we've created new regions either by copying or moving
1777 to a new track, we want to replace the old selection with the new ones
1780 if (new_views.size() > 0) {
1781 _editor->selection->set (new_views);
1784 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1788 /* write commands for the accumulated diffs for all our modified playlists */
1789 add_stateful_diff_commands_for_playlists (modified_playlists);
1790 /* applies to _brushing */
1791 _editor->commit_reversible_command ();
1793 /* We have futzed with the layering of canvas items on our streamviews.
1794 If any region changed layer, this will have resulted in the stream
1795 views being asked to set up their region views, and all will be well.
1796 If not, we might now have badly-ordered region views. Ask the StreamViews
1797 involved to sort themselves out, just in case.
1800 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1801 (*i)->view()->playlist_layered ((*i)->track ());
1805 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1806 * @param region Region to remove.
1807 * @param playlist playlist To remove from.
1808 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1809 * that clear_changes () is only called once per playlist.
1812 RegionMoveDrag::remove_region_from_playlist (
1813 boost::shared_ptr<Region> region,
1814 boost::shared_ptr<Playlist> playlist,
1815 PlaylistSet& modified_playlists
1818 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1821 playlist->clear_changes ();
1824 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1828 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1829 * clearing the playlist's diff history first if necessary.
1830 * @param region Region to insert.
1831 * @param dest_rtv Destination RouteTimeAxisView.
1832 * @param dest_layer Destination layer.
1833 * @param where Destination position.
1834 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1835 * that clear_changes () is only called once per playlist.
1836 * @return New RegionView, or 0 if no insert was performed.
1839 RegionMoveDrag::insert_region_into_playlist (
1840 boost::shared_ptr<Region> region,
1841 RouteTimeAxisView* dest_rtv,
1844 PlaylistSet& modified_playlists,
1845 const int32_t sub_num
1848 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1849 if (!dest_playlist) {
1853 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1854 _new_region_view = 0;
1855 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1857 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1858 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1860 dest_playlist->clear_changes ();
1862 dest_playlist->add_region (region, where, 1.0, false, sub_num);
1864 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1865 dest_playlist->set_layer (region, dest_layer);
1870 assert (_new_region_view);
1872 return _new_region_view;
1876 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1878 _new_region_view = rv;
1882 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1884 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1885 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1887 _editor->session()->add_command (c);
1896 RegionMoveDrag::aborted (bool movement_occurred)
1900 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1901 list<DraggingView>::const_iterator next = i;
1910 RegionMotionDrag::aborted (movement_occurred);
1915 RegionMotionDrag::aborted (bool)
1917 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1919 StreamView* sview = (*i)->view();
1922 if (sview->layer_display() == Expanded) {
1923 sview->set_layer_display (Stacked);
1928 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1929 RegionView* rv = i->view;
1930 TimeAxisView* tv = &(rv->get_time_axis_view ());
1931 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1933 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1934 rv->get_canvas_group()->set_y_position (0);
1936 rv->move (-_total_x_delta, 0);
1937 rv->set_height (rtv->view()->child_height ());
1941 /** @param b true to brush, otherwise false.
1942 * @param c true to make copies of the regions being moved, otherwise false.
1944 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1945 : RegionMotionDrag (e, i, p, v, b)
1947 , _new_region_view (0)
1949 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1952 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1953 if (rtv && rtv->is_track()) {
1954 speed = rtv->track()->speed ();
1957 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1961 RegionMoveDrag::setup_pointer_frame_offset ()
1963 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1966 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1967 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1969 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1971 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1972 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1974 _primary = v->view()->create_region_view (r, false, false);
1976 _primary->get_canvas_group()->show ();
1977 _primary->set_position (pos, 0);
1978 _views.push_back (DraggingView (_primary, this, v));
1980 _last_frame_position = pos;
1982 _item = _primary->get_canvas_group ();
1986 RegionInsertDrag::finished (GdkEvent * event, bool)
1988 int pos = _views.front().time_axis_view;
1989 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1991 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1993 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1994 _primary->get_canvas_group()->set_y_position (0);
1996 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1998 _editor->begin_reversible_command (Operations::insert_region);
1999 playlist->clear_changes ();
2000 playlist->add_region (_primary->region (), _last_frame_position);
2002 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2003 if (Config->get_edit_mode() == Ripple) {
2004 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
2007 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2008 _editor->commit_reversible_command ();
2016 RegionInsertDrag::aborted (bool)
2023 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2024 : RegionMoveDrag (e, i, p, v, false, false)
2026 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2029 struct RegionSelectionByPosition {
2030 bool operator() (RegionView*a, RegionView* b) {
2031 return a->region()->position () < b->region()->position();
2036 RegionSpliceDrag::motion (GdkEvent* event, bool)
2038 /* Which trackview is this ? */
2040 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2041 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2043 /* The region motion is only processed if the pointer is over
2047 if (!tv || !tv->is_track()) {
2048 /* To make sure we hide the verbose canvas cursor when the mouse is
2049 not held over an audio track.
2051 _editor->verbose_cursor()->hide ();
2054 _editor->verbose_cursor()->show ();
2059 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2065 RegionSelection copy;
2066 _editor->selection->regions.by_position(copy);
2068 framepos_t const pf = adjusted_current_frame (event);
2070 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2072 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2078 boost::shared_ptr<Playlist> playlist;
2080 if ((playlist = atv->playlist()) == 0) {
2084 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2089 if (pf < (*i)->region()->last_frame() + 1) {
2093 if (pf > (*i)->region()->first_frame()) {
2099 playlist->shuffle ((*i)->region(), dir);
2104 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2106 RegionMoveDrag::finished (event, movement_occurred);
2110 RegionSpliceDrag::aborted (bool)
2120 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2123 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
2125 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2126 RegionSelection to_ripple;
2127 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2128 if ((*i)->position() >= where) {
2129 to_ripple.push_back (rtv->view()->find_view(*i));
2133 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2134 if (!exclude.contains (*i)) {
2135 // the selection has already been added to _views
2137 if (drag_in_progress) {
2138 // do the same things that RegionMotionDrag::motion does when
2139 // first_move is true, for the region views that we're adding
2140 // to _views this time
2143 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2144 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2145 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2146 rvg->reparent (_editor->_drag_motion_group);
2148 // we only need to move in the y direction
2149 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2154 _views.push_back (DraggingView (*i, this, tav));
2160 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2163 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2164 // we added all the regions after the selection
2166 std::list<DraggingView>::iterator to_erase = i++;
2167 if (!_editor->selection->regions.contains (to_erase->view)) {
2168 // restore the non-selected regions to their original playlist & positions,
2169 // and then ripple them back by the length of the regions that were dragged away
2170 // do the same things as RegionMotionDrag::aborted
2172 RegionView *rv = to_erase->view;
2173 TimeAxisView* tv = &(rv->get_time_axis_view ());
2174 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2177 // plonk them back onto their own track
2178 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2179 rv->get_canvas_group()->set_y_position (0);
2183 // move the underlying region to match the view
2184 rv->region()->set_position (rv->region()->position() + amount);
2186 // restore the view to match the underlying region's original position
2187 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2190 rv->set_height (rtv->view()->child_height ());
2191 _views.erase (to_erase);
2197 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2199 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2201 return allow_moves_across_tracks;
2209 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2210 : RegionMoveDrag (e, i, p, v, false, false)
2212 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2213 // compute length of selection
2214 RegionSelection selected_regions = _editor->selection->regions;
2215 selection_length = selected_regions.end_frame() - selected_regions.start();
2217 // we'll only allow dragging to another track in ripple mode if all the regions
2218 // being dragged start off on the same track
2219 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2222 exclude = new RegionList;
2223 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2224 exclude->push_back((*i)->region());
2227 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2228 RegionSelection copy;
2229 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2231 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2232 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2234 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2235 // find ripple start point on each applicable playlist
2236 RegionView *first_selected_on_this_track = NULL;
2237 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2238 if ((*i)->region()->playlist() == (*pi)) {
2239 // region is on this playlist - it's the first, because they're sorted
2240 first_selected_on_this_track = *i;
2244 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2245 add_all_after_to_views (
2246 &first_selected_on_this_track->get_time_axis_view(),
2247 first_selected_on_this_track->region()->position(),
2248 selected_regions, false);
2251 if (allow_moves_across_tracks) {
2252 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2260 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2262 /* Which trackview is this ? */
2264 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2265 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2267 /* The region motion is only processed if the pointer is over
2271 if (!tv || !tv->is_track()) {
2272 /* To make sure we hide the verbose canvas cursor when the mouse is
2273 not held over an audiotrack.
2275 _editor->verbose_cursor()->hide ();
2279 framepos_t where = adjusted_current_frame (event);
2280 assert (where >= 0);
2282 double delta = compute_x_delta (event, &after);
2284 framecnt_t amount = _editor->pixel_to_sample (delta);
2286 if (allow_moves_across_tracks) {
2287 // all the originally selected regions were on the same track
2289 framecnt_t adjust = 0;
2290 if (prev_tav && tv != prev_tav) {
2291 // dragged onto a different track
2292 // remove the unselected regions from _views, restore them to their original positions
2293 // and add the regions after the drop point on the new playlist to _views instead.
2294 // undo the effect of rippling the previous playlist, and include the effect of removing
2295 // the dragged region(s) from this track
2297 remove_unselected_from_views (prev_amount, false);
2298 // ripple previous playlist according to the regions that have been removed onto the new playlist
2299 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2302 // move just the selected regions
2303 RegionMoveDrag::motion(event, first_move);
2305 // ensure that the ripple operation on the new playlist inserts selection_length time
2306 adjust = selection_length;
2307 // ripple the new current playlist
2308 tv->playlist()->ripple (where, amount+adjust, exclude);
2310 // add regions after point where drag entered this track to subsequent ripples
2311 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2314 // motion on same track
2315 RegionMoveDrag::motion(event, first_move);
2319 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2320 prev_position = where;
2322 // selection encompasses multiple tracks - just drag
2323 // cross-track drags are forbidden
2324 RegionMoveDrag::motion(event, first_move);
2327 if (!_x_constrained) {
2328 prev_amount += amount;
2331 _last_frame_position = after;
2335 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2337 if (!movement_occurred) {
2341 if (was_double_click() && !_views.empty()) {
2342 DraggingView dv = _views.front();
2343 _editor->edit_region (dv.view);
2349 _editor->begin_reversible_command(_("Ripple drag"));
2351 // remove the regions being rippled from the dragging view, updating them to
2352 // their new positions
2353 remove_unselected_from_views (prev_amount, true);
2355 if (allow_moves_across_tracks) {
2357 // if regions were dragged across tracks, we've rippled any later
2358 // regions on the track the regions were dragged off, so we need
2359 // to add the original track to the undo record
2360 orig_tav->playlist()->clear_changes();
2361 vector<Command*> cmds;
2362 orig_tav->playlist()->rdiff (cmds);
2363 _editor->session()->add_commands (cmds);
2365 if (prev_tav && prev_tav != orig_tav) {
2366 prev_tav->playlist()->clear_changes();
2367 vector<Command*> cmds;
2368 prev_tav->playlist()->rdiff (cmds);
2369 _editor->session()->add_commands (cmds);
2372 // selection spanned multiple tracks - all will need adding to undo record
2374 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2375 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2377 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2378 (*pi)->clear_changes();
2379 vector<Command*> cmds;
2380 (*pi)->rdiff (cmds);
2381 _editor->session()->add_commands (cmds);
2385 // other modified playlists are added to undo by RegionMoveDrag::finished()
2386 RegionMoveDrag::finished (event, movement_occurred);
2387 _editor->commit_reversible_command();
2391 RegionRippleDrag::aborted (bool movement_occurred)
2393 RegionMoveDrag::aborted (movement_occurred);
2398 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2400 _view (dynamic_cast<MidiTimeAxisView*> (v))
2402 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2408 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2411 _editor->begin_reversible_command (_("create region"));
2412 _region = add_midi_region (_view, false, _editor->get_grid_music_divisions (event->button.state));
2413 _view->playlist()->freeze ();
2416 framepos_t const f = adjusted_current_frame (event);
2417 if (f < grab_frame()) {
2418 _region->set_initial_position (f);
2421 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2422 so that if this region is duplicated, its duplicate starts on
2423 a snap point rather than 1 frame after a snap point. Otherwise things get
2424 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2425 place snapped notes at the start of the region.
2428 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2429 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2435 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2437 if (!movement_occurred) {
2438 add_midi_region (_view, true, _editor->get_grid_music_divisions (event->button.state));
2440 _view->playlist()->thaw ();
2441 _editor->commit_reversible_command();
2446 RegionCreateDrag::aborted (bool)
2449 _view->playlist()->thaw ();
2455 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2460 , _was_selected (false)
2463 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2467 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2469 Gdk::Cursor* cursor;
2470 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2472 float x_fraction = cnote->mouse_x_fraction ();
2474 if (x_fraction > 0.0 && x_fraction < 0.25) {
2475 cursor = _editor->cursors()->left_side_trim;
2478 cursor = _editor->cursors()->right_side_trim;
2482 Drag::start_grab (event, cursor);
2484 region = &cnote->region_view();
2487 temp = region->snap_to_pixel (cnote->x0 (), true);
2488 _snap_delta = temp - cnote->x0 ();
2492 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2497 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2498 if (ms.size() > 1) {
2499 /* has to be relative, may make no sense otherwise */
2503 if (!(_was_selected = cnote->selected())) {
2505 /* tertiary-click means extend selection - we'll do that on button release,
2506 so don't add it here, because otherwise we make it hard to figure
2507 out the "extend-to" range.
2510 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2513 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2516 region->note_selected (cnote, true);
2518 _editor->get_selection().clear_points();
2519 region->unique_select (cnote);
2526 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2528 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2530 _editor->begin_reversible_command (_("resize notes"));
2532 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2533 MidiRegionSelection::iterator next;
2536 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2538 mrv->begin_resizing (at_front);
2544 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2545 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2547 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2551 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2553 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2554 if (_editor->snap_mode () != SnapOff) {
2558 if (_editor->snap_mode () == SnapOff) {
2560 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2561 if (apply_snap_delta) {
2567 if (apply_snap_delta) {
2571 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2577 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2579 if (!movement_occurred) {
2580 /* no motion - select note */
2581 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2582 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2583 _editor->current_mouse_mode() == Editing::MouseDraw) {
2585 bool changed = false;
2587 if (_was_selected) {
2588 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2590 region->note_deselected (cnote);
2593 _editor->get_selection().clear_points();
2594 region->unique_select (cnote);
2598 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2599 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2601 if (!extend && !add && region->selection_size() > 1) {
2602 _editor->get_selection().clear_points();
2603 region->unique_select (cnote);
2605 } else if (extend) {
2606 region->note_selected (cnote, true, true);
2609 /* it was added during button press */
2615 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2616 _editor->commit_reversible_selection_op();
2623 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2624 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2625 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2627 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2630 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2632 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2633 if (_editor->snap_mode () != SnapOff) {
2637 if (_editor->snap_mode () == SnapOff) {
2639 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2640 if (apply_snap_delta) {
2646 if (apply_snap_delta) {
2650 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2654 _editor->commit_reversible_command ();
2658 NoteResizeDrag::aborted (bool)
2660 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2661 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2662 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2664 mrv->abort_resizing ();
2669 AVDraggingView::AVDraggingView (RegionView* v)
2672 initial_position = v->region()->position ();
2675 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2678 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2681 TrackViewList empty;
2683 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2684 std::list<RegionView*> views = rs.by_layer();
2687 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2688 RegionView* rv = (*i);
2689 if (!rv->region()->video_locked()) {
2692 if (rv->region()->locked()) {
2695 _views.push_back (AVDraggingView (rv));
2700 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2702 Drag::start_grab (event);
2703 if (_editor->session() == 0) {
2707 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2713 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2717 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2718 _max_backwards_drag = (
2719 ARDOUR_UI::instance()->video_timeline->get_duration()
2720 + ARDOUR_UI::instance()->video_timeline->get_offset()
2721 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2724 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2725 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2726 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2729 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2732 Timecode::Time timecode;
2733 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2734 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);
2735 show_verbose_cursor_text (buf);
2739 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2741 if (_editor->session() == 0) {
2744 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2748 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2752 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2753 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2755 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2756 dt = - _max_backwards_drag;
2759 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2760 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2762 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2763 RegionView* rv = i->view;
2764 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2767 rv->region()->clear_changes ();
2768 rv->region()->suspend_property_changes();
2770 rv->region()->set_position(i->initial_position + dt);
2771 rv->region_changed(ARDOUR::Properties::position);
2774 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2775 Timecode::Time timecode;
2776 Timecode::Time timediff;
2778 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2779 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2780 snprintf (buf, sizeof (buf),
2781 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2782 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2783 , _("Video Start:"),
2784 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2786 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2788 show_verbose_cursor_text (buf);
2792 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2794 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2801 if (!movement_occurred || ! _editor->session()) {
2805 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2807 _editor->begin_reversible_command (_("Move Video"));
2809 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2810 ARDOUR_UI::instance()->video_timeline->save_undo();
2811 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2812 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2814 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2815 i->view->drag_end();
2816 i->view->region()->resume_property_changes ();
2818 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2821 _editor->session()->maybe_update_session_range(
2822 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2823 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2827 _editor->commit_reversible_command ();
2831 VideoTimeLineDrag::aborted (bool)
2833 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2836 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2837 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2839 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2840 i->view->region()->resume_property_changes ();
2841 i->view->region()->set_position(i->initial_position);
2845 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2846 : RegionDrag (e, i, p, v)
2847 , _operation (StartTrim)
2848 , _preserve_fade_anchor (preserve_fade_anchor)
2849 , _jump_position_when_done (false)
2851 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2855 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2858 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2859 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2861 if (tv && tv->is_track()) {
2862 speed = tv->track()->speed();
2865 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2866 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2867 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2869 framepos_t const pf = adjusted_current_frame (event);
2870 setup_snap_delta (region_start);
2872 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2873 /* Move the contents of the region around without changing the region bounds */
2874 _operation = ContentsTrim;
2875 Drag::start_grab (event, _editor->cursors()->trimmer);
2877 /* These will get overridden for a point trim.*/
2878 if (pf < (region_start + region_length/2)) {
2879 /* closer to front */
2880 _operation = StartTrim;
2881 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2882 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2884 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2888 _operation = EndTrim;
2889 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2890 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2892 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2896 /* jump trim disabled for now
2897 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2898 _jump_position_when_done = true;
2902 switch (_operation) {
2904 show_verbose_cursor_time (region_start);
2907 show_verbose_cursor_duration (region_start, region_end);
2910 show_verbose_cursor_time (pf);
2914 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2915 i->view->region()->suspend_property_changes ();
2920 TrimDrag::motion (GdkEvent* event, bool first_move)
2922 RegionView* rv = _primary;
2925 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2926 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2927 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2928 frameoffset_t frame_delta = 0;
2930 if (tv && tv->is_track()) {
2931 speed = tv->track()->speed();
2933 framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
2934 framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state);
2940 switch (_operation) {
2942 trim_type = "Region start trim";
2945 trim_type = "Region end trim";
2948 trim_type = "Region content trim";
2955 _editor->begin_reversible_command (trim_type);
2957 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2958 RegionView* rv = i->view;
2959 rv->region()->playlist()->clear_owned_changes ();
2961 if (_operation == StartTrim) {
2962 rv->trim_front_starting ();
2965 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2968 arv->temporarily_hide_envelope ();
2972 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2973 insert_result = _editor->motion_frozen_playlists.insert (pl);
2975 if (insert_result.second) {
2981 bool non_overlap_trim = false;
2983 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
2984 non_overlap_trim = true;
2987 /* contstrain trim to fade length */
2988 if (_preserve_fade_anchor) {
2989 switch (_operation) {
2991 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2992 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2994 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2995 if (ar->locked()) continue;
2996 framecnt_t len = ar->fade_in()->back()->when;
2997 if (len < dt) dt = min(dt, len);
3001 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3002 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3004 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3005 if (ar->locked()) continue;
3006 framecnt_t len = ar->fade_out()->back()->when;
3007 if (len < -dt) dt = max(dt, -len);
3015 int32_t divisions = current_music_divisor (adj_frame, event->button.state);
3017 switch (_operation) {
3019 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3020 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3023 if (changed && _preserve_fade_anchor) {
3024 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3026 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3027 framecnt_t len = ar->fade_in()->back()->when;
3028 framecnt_t diff = ar->first_frame() - i->initial_position;
3029 framepos_t new_length = len - diff;
3030 i->anchored_fade_length = min (ar->length(), new_length);
3031 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3032 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3039 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3040 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, divisions);
3041 if (changed && _preserve_fade_anchor) {
3042 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3044 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3045 framecnt_t len = ar->fade_out()->back()->when;
3046 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
3047 framepos_t new_length = len + diff;
3048 i->anchored_fade_length = min (ar->length(), new_length);
3049 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3050 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3058 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
3060 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3061 i->view->move_contents (frame_delta);
3067 switch (_operation) {
3069 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
3072 show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
3075 // show_verbose_cursor_time (frame_delta);
3081 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3083 if (movement_occurred) {
3084 motion (event, false);
3086 if (_operation == StartTrim) {
3087 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3089 /* This must happen before the region's StatefulDiffCommand is created, as it may
3090 `correct' (ahem) the region's _start from being negative to being zero. It
3091 needs to be zero in the undo record.
3093 i->view->trim_front_ending ();
3095 if (_preserve_fade_anchor) {
3096 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3098 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3099 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3100 ar->set_fade_in_length(i->anchored_fade_length);
3101 ar->set_fade_in_active(true);
3104 if (_jump_position_when_done) {
3105 i->view->region()->set_position (i->initial_position);
3108 } else if (_operation == EndTrim) {
3109 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3110 if (_preserve_fade_anchor) {
3111 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3113 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3114 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3115 ar->set_fade_out_length(i->anchored_fade_length);
3116 ar->set_fade_out_active(true);
3119 if (_jump_position_when_done) {
3120 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3125 if (!_editor->selection->selected (_primary)) {
3126 _primary->thaw_after_trim ();
3129 set<boost::shared_ptr<Playlist> > diffed_playlists;
3131 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3132 i->view->thaw_after_trim ();
3133 i->view->enable_display (true);
3135 /* Trimming one region may affect others on the playlist, so we need
3136 to get undo Commands from the whole playlist rather than just the
3137 region. Use diffed_playlists to make sure we don't diff a given
3138 playlist more than once.
3140 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
3141 if (diffed_playlists.find (p) == diffed_playlists.end()) {
3142 vector<Command*> cmds;
3144 _editor->session()->add_commands (cmds);
3145 diffed_playlists.insert (p);
3150 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3154 _editor->motion_frozen_playlists.clear ();
3155 _editor->commit_reversible_command();
3158 /* no mouse movement */
3159 if (adjusted_current_frame (event) != adjusted_frame (_drags->current_pointer_frame(), event, false)) {
3160 _editor->point_trim (event, adjusted_current_frame (event));
3164 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3165 i->view->region()->resume_property_changes ();
3170 TrimDrag::aborted (bool movement_occurred)
3172 /* Our motion method is changing model state, so use the Undo system
3173 to cancel. Perhaps not ideal, as this will leave an Undo point
3174 behind which may be slightly odd from the user's point of view.
3178 finished (&ev, true);
3180 if (movement_occurred) {
3181 _editor->session()->undo (1);
3184 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3185 i->view->region()->resume_property_changes ();
3190 TrimDrag::setup_pointer_frame_offset ()
3192 list<DraggingView>::iterator i = _views.begin ();
3193 while (i != _views.end() && i->view != _primary) {
3197 if (i == _views.end()) {
3201 switch (_operation) {
3203 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
3206 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
3213 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3216 , _old_snap_type (e->snap_type())
3217 , _old_snap_mode (e->snap_mode())
3220 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3221 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3223 _real_section = &_marker->meter();
3228 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3230 Drag::start_grab (event, cursor);
3231 show_verbose_cursor_time (adjusted_current_frame(event));
3235 MeterMarkerDrag::setup_pointer_frame_offset ()
3237 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
3241 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3244 // create a dummy marker to catch events, then hide it.
3247 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3249 _marker = new MeterMarker (
3251 *_editor->meter_group,
3252 UIConfiguration::instance().color ("meter marker"),
3254 *new MeterSection (_marker->meter())
3257 /* use the new marker for the grab */
3258 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3261 TempoMap& map (_editor->session()->tempo_map());
3262 /* get current state */
3263 before_state = &map.get_state();
3266 _editor->begin_reversible_command (_("move meter mark"));
3268 _editor->begin_reversible_command (_("copy meter mark"));
3270 Timecode::BBT_Time bbt = _real_section->bbt();
3272 /* we can't add a meter where one currently exists */
3273 if (_real_section->frame() < adjusted_current_frame (event, false)) {
3278 const double beat = map.beat_at_bbt (bbt);
3279 const framepos_t frame = map.frame_at_beat (beat);
3280 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3281 , beat, bbt, frame, _real_section->position_lock_style());
3282 if (!_real_section) {
3288 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3289 if (_real_section->position_lock_style() != AudioTime) {
3290 _editor->set_snap_to (SnapToBar);
3291 _editor->set_snap_mode (SnapNormal);
3295 framepos_t pf = adjusted_current_frame (event);
3297 if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3298 /* never snap to music for audio locked */
3299 pf = adjusted_current_frame (event, false);
3302 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3304 /* fake marker meeds to stay under the mouse, unlike the real one. */
3305 _marker->set_position (adjusted_current_frame (event, false));
3307 show_verbose_cursor_time (_real_section->frame());
3311 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3313 if (!movement_occurred) {
3314 if (was_double_click()) {
3315 _editor->edit_meter_marker (*_marker);
3320 /* reinstate old snap setting */
3321 _editor->set_snap_to (_old_snap_type);
3322 _editor->set_snap_mode (_old_snap_mode);
3324 TempoMap& map (_editor->session()->tempo_map());
3326 XMLNode &after = map.get_state();
3327 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3328 _editor->commit_reversible_command ();
3330 // delete the dummy marker we used for visual representation while moving.
3331 // a new visual marker will show up automatically.
3336 MeterMarkerDrag::aborted (bool moved)
3338 _marker->set_position (_marker->meter().frame ());
3340 /* reinstate old snap setting */
3341 _editor->set_snap_to (_old_snap_type);
3342 _editor->set_snap_mode (_old_snap_mode);
3344 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3345 // delete the dummy marker we used for visual representation while moving.
3346 // a new visual marker will show up automatically.
3351 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3357 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3359 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3360 _real_section = &_marker->tempo();
3361 _movable = !_real_section->initial();
3362 _grab_bpm = _real_section->note_types_per_minute();
3367 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3369 Drag::start_grab (event, cursor);
3370 if (!_real_section->active()) {
3371 show_verbose_cursor_text (_("inactive"));
3373 show_verbose_cursor_time (adjusted_current_frame (event));
3378 TempoMarkerDrag::setup_pointer_frame_offset ()
3380 _pointer_frame_offset = raw_grab_frame() - _real_section->frame();
3384 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3386 if (!_real_section->active()) {
3392 // mvc drag - create a dummy marker to catch events, hide it.
3395 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3397 TempoSection section (_marker->tempo());
3399 _marker = new TempoMarker (
3401 *_editor->tempo_group,
3402 UIConfiguration::instance().color ("tempo marker"),
3404 *new TempoSection (_marker->tempo())
3407 /* use the new marker for the grab */
3408 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3411 TempoMap& map (_editor->session()->tempo_map());
3412 /* get current state */
3413 before_state = &map.get_state();
3416 _editor->begin_reversible_command (_("move tempo mark"));
3419 const Tempo tempo (_marker->tempo());
3420 const framepos_t frame = adjusted_current_frame (event) + 1;
3421 const TempoSection::Type type = _real_section->type();
3423 _editor->begin_reversible_command (_("copy tempo mark"));
3425 if (_real_section->position_lock_style() == MusicTime) {
3426 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3427 _real_section = map.add_tempo (tempo, map.exact_qn_at_frame (frame, divisions), 0, type, MusicTime);
3429 _real_section = map.add_tempo (tempo, 0.0, frame, type, AudioTime);
3432 if (!_real_section) {
3440 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3441 /* use vertical movement to alter tempo .. should be log */
3442 double new_bpm = max (1.5, _grab_bpm + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3444 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type()));
3446 show_verbose_cursor_text (strs.str());
3448 } else if (_movable && !_real_section->locked_to_meter()) {
3451 if (_editor->snap_musical()) {
3452 /* we can't snap to a grid that we are about to move.
3453 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3455 pf = adjusted_current_frame (event, false);
3457 pf = adjusted_current_frame (event);
3460 TempoMap& map (_editor->session()->tempo_map());
3462 /* snap to beat is 1, snap to bar is -1 (sorry) */
3463 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3465 map.gui_set_tempo_position (_real_section, pf, sub_num);
3467 show_verbose_cursor_time (_real_section->frame());
3469 _marker->set_position (adjusted_current_frame (event, false));
3473 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3475 if (!_real_section->active()) {
3478 if (!movement_occurred) {
3479 if (was_double_click()) {
3480 _editor->edit_tempo_marker (*_marker);
3485 TempoMap& map (_editor->session()->tempo_map());
3487 XMLNode &after = map.get_state();
3488 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3489 _editor->commit_reversible_command ();
3491 // delete the dummy marker we used for visual representation while moving.
3492 // a new visual marker will show up automatically.
3497 TempoMarkerDrag::aborted (bool moved)
3499 _marker->set_position (_marker->tempo().frame());
3501 TempoMap& map (_editor->session()->tempo_map());
3502 map.set_state (*before_state, Stateful::current_state_version);
3503 // delete the dummy (hidden) marker we used for events while moving.
3508 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3514 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3519 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3521 Drag::start_grab (event, cursor);
3522 TempoMap& map (_editor->session()->tempo_map());
3523 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
3526 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).note_types_per_minute() << "\n";
3527 sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3528 show_verbose_cursor_text (sstr.str());
3529 finished (event, false);
3533 BBTRulerDrag::setup_pointer_frame_offset ()
3535 TempoMap& map (_editor->session()->tempo_map());
3536 const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame()));
3537 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3540 if (divisions > 0) {
3541 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions);
3543 /* while it makes some sense for the user to determine the division to 'grab',
3544 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3545 and the result over steep tempo curves. Use sixteenths.
3547 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4);
3550 _grab_qn = map.quarter_note_at_beat (beat);
3552 _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn);
3557 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3559 TempoMap& map (_editor->session()->tempo_map());
3562 /* get current state */
3563 before_state = &map.get_state();
3564 _editor->begin_reversible_command (_("stretch tempo"));
3569 if (_editor->snap_musical()) {
3570 pf = adjusted_current_frame (event, false);
3572 pf = adjusted_current_frame (event);
3575 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3576 /* adjust previous tempo to match pointer frame */
3577 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.frame_at_quarter_note (_grab_qn), pf);
3580 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute() << "\n";
3581 sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3582 show_verbose_cursor_text (sstr.str());
3586 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3588 if (!movement_occurred) {
3592 TempoMap& map (_editor->session()->tempo_map());
3594 XMLNode &after = map.get_state();
3595 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3596 _editor->commit_reversible_command ();
3600 BBTRulerDrag::aborted (bool moved)
3603 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3608 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3609 : Drag (e, &c.track_canvas_item(), false)
3614 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3617 /** Do all the things we do when dragging the playhead to make it look as though
3618 * we have located, without actually doing the locate (because that would cause
3619 * the diskstream buffers to be refilled, which is too slow).
3622 CursorDrag::fake_locate (framepos_t t)
3624 if (_editor->session () == 0) {
3628 _editor->playhead_cursor->set_position (t);
3630 Session* s = _editor->session ();
3631 if (s->timecode_transmission_suspended ()) {
3632 framepos_t const f = _editor->playhead_cursor->current_frame ();
3633 /* This is asynchronous so it will be sent "now"
3635 s->send_mmc_locate (f);
3636 /* These are synchronous and will be sent during the next
3639 s->queue_full_time_code ();
3640 s->queue_song_position_pointer ();
3643 show_verbose_cursor_time (t);
3644 _editor->UpdateAllTransportClocks (t);
3648 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3650 Drag::start_grab (event, c);
3651 setup_snap_delta (_editor->playhead_cursor->current_frame ());
3653 _grab_zoom = _editor->samples_per_pixel;
3655 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3657 _editor->snap_to_with_modifier (where, event);
3659 _editor->_dragging_playhead = true;
3661 Session* s = _editor->session ();
3663 /* grab the track canvas item as well */
3665 _cursor.track_canvas_item().grab();
3668 if (_was_rolling && _stop) {
3672 if (s->is_auditioning()) {
3673 s->cancel_audition ();
3677 if (AudioEngine::instance()->connected()) {
3679 /* do this only if we're the engine is connected
3680 * because otherwise this request will never be
3681 * serviced and we'll busy wait forever. likewise,
3682 * notice if we are disconnected while waiting for the
3683 * request to be serviced.
3686 s->request_suspend_timecode_transmission ();
3687 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3688 /* twiddle our thumbs */
3693 fake_locate (where - snap_delta (event->button.state));
3697 CursorDrag::motion (GdkEvent* event, bool)
3699 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3700 _editor->snap_to_with_modifier (where, event);
3701 if (where != last_pointer_frame()) {
3702 fake_locate (where - snap_delta (event->button.state));
3707 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3709 _editor->_dragging_playhead = false;
3711 _cursor.track_canvas_item().ungrab();
3713 if (!movement_occurred && _stop) {
3717 motion (event, false);
3719 Session* s = _editor->session ();
3721 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3722 _editor->_pending_locate_request = true;
3723 s->request_resume_timecode_transmission ();
3728 CursorDrag::aborted (bool)
3730 _cursor.track_canvas_item().ungrab();
3732 if (_editor->_dragging_playhead) {
3733 _editor->session()->request_resume_timecode_transmission ();
3734 _editor->_dragging_playhead = false;
3737 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3740 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3741 : RegionDrag (e, i, p, v)
3743 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3747 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3749 Drag::start_grab (event, cursor);
3751 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3752 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3753 setup_snap_delta (r->position ());
3755 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3759 FadeInDrag::setup_pointer_frame_offset ()
3761 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3762 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3763 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3767 FadeInDrag::motion (GdkEvent* event, bool)
3769 framecnt_t fade_length;
3771 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3772 _editor->snap_to_with_modifier (pos, event);
3773 pos -= snap_delta (event->button.state);
3775 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3777 if (pos < (region->position() + 64)) {
3778 fade_length = 64; // this should be a minimum defined somewhere
3779 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3780 fade_length = region->length() - region->fade_out()->back()->when - 1;
3782 fade_length = pos - region->position();
3785 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3787 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3793 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3796 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3800 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3802 if (!movement_occurred) {
3806 framecnt_t fade_length;
3807 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3808 _editor->snap_to_with_modifier (pos, event);
3809 pos -= snap_delta (event->button.state);
3811 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3813 if (pos < (region->position() + 64)) {
3814 fade_length = 64; // this should be a minimum defined somewhere
3815 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3816 fade_length = region->length() - region->fade_out()->back()->when - 1;
3818 fade_length = pos - region->position();
3821 bool in_command = false;
3823 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3825 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3831 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3832 XMLNode &before = alist->get_state();
3834 tmp->audio_region()->set_fade_in_length (fade_length);
3835 tmp->audio_region()->set_fade_in_active (true);
3838 _editor->begin_reversible_command (_("change fade in length"));
3841 XMLNode &after = alist->get_state();
3842 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3846 _editor->commit_reversible_command ();
3851 FadeInDrag::aborted (bool)
3853 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3854 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3860 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3864 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3865 : RegionDrag (e, i, p, v)
3867 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3871 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3873 Drag::start_grab (event, cursor);
3875 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3876 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3877 setup_snap_delta (r->last_frame ());
3879 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3883 FadeOutDrag::setup_pointer_frame_offset ()
3885 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3886 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3887 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3891 FadeOutDrag::motion (GdkEvent* event, bool)
3893 framecnt_t fade_length;
3895 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3896 _editor->snap_to_with_modifier (pos, event);
3897 pos -= snap_delta (event->button.state);
3899 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3901 if (pos > (region->last_frame() - 64)) {
3902 fade_length = 64; // this should really be a minimum fade defined somewhere
3903 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3904 fade_length = region->length() - region->fade_in()->back()->when - 1;
3906 fade_length = region->last_frame() - pos;
3909 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3911 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3917 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3920 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3924 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3926 if (!movement_occurred) {
3930 framecnt_t fade_length;
3932 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3933 _editor->snap_to_with_modifier (pos, event);
3934 pos -= snap_delta (event->button.state);
3936 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3938 if (pos > (region->last_frame() - 64)) {
3939 fade_length = 64; // this should really be a minimum fade defined somewhere
3940 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3941 fade_length = region->length() - region->fade_in()->back()->when - 1;
3943 fade_length = region->last_frame() - pos;
3946 bool in_command = false;
3948 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3950 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3956 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3957 XMLNode &before = alist->get_state();
3959 tmp->audio_region()->set_fade_out_length (fade_length);
3960 tmp->audio_region()->set_fade_out_active (true);
3963 _editor->begin_reversible_command (_("change fade out length"));
3966 XMLNode &after = alist->get_state();
3967 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3971 _editor->commit_reversible_command ();
3976 FadeOutDrag::aborted (bool)
3978 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3979 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3985 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3989 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3991 , _selection_changed (false)
3993 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3994 Gtk::Window* toplevel = _editor->current_toplevel();
3995 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
3999 _points.push_back (ArdourCanvas::Duple (0, 0));
4001 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4004 MarkerDrag::~MarkerDrag ()
4006 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4011 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4013 location = new Location (*l);
4014 markers.push_back (m);
4019 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4021 Drag::start_grab (event, cursor);
4025 Location *location = _editor->find_location_from_marker (_marker, is_start);
4026 _editor->_dragging_edit_point = true;
4028 update_item (location);
4030 // _drag_line->show();
4031 // _line->raise_to_top();
4034 show_verbose_cursor_time (location->start());
4036 show_verbose_cursor_time (location->end());
4038 setup_snap_delta (is_start ? location->start() : location->end());
4040 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4043 case Selection::Toggle:
4044 /* we toggle on the button release */
4046 case Selection::Set:
4047 if (!_editor->selection->selected (_marker)) {
4048 _editor->selection->set (_marker);
4049 _selection_changed = true;
4052 case Selection::Extend:
4054 Locations::LocationList ll;
4055 list<ArdourMarker*> to_add;
4057 _editor->selection->markers.range (s, e);
4058 s = min (_marker->position(), s);
4059 e = max (_marker->position(), e);
4062 if (e < max_framepos) {
4065 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4066 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4067 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4070 to_add.push_back (lm->start);
4073 to_add.push_back (lm->end);
4077 if (!to_add.empty()) {
4078 _editor->selection->add (to_add);
4079 _selection_changed = true;
4083 case Selection::Add:
4084 _editor->selection->add (_marker);
4085 _selection_changed = true;
4090 /* Set up copies for us to manipulate during the drag
4093 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4095 Location* l = _editor->find_location_from_marker (*i, is_start);
4102 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4104 /* range: check that the other end of the range isn't
4107 CopiedLocationInfo::iterator x;
4108 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4109 if (*(*x).location == *l) {
4113 if (x == _copied_locations.end()) {
4114 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4116 (*x).markers.push_back (*i);
4117 (*x).move_both = true;
4125 MarkerDrag::setup_pointer_frame_offset ()
4128 Location *location = _editor->find_location_from_marker (_marker, is_start);
4129 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
4133 MarkerDrag::motion (GdkEvent* event, bool)
4135 framecnt_t f_delta = 0;
4137 bool move_both = false;
4138 Location *real_location;
4139 Location *copy_location = 0;
4140 framecnt_t const sd = snap_delta (event->button.state);
4142 framecnt_t const newframe = adjusted_frame (_drags->current_pointer_frame () + sd, event, true) - sd;
4143 framepos_t next = newframe;
4145 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4149 CopiedLocationInfo::iterator x;
4151 /* find the marker we're dragging, and compute the delta */
4153 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4155 copy_location = (*x).location;
4157 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4159 /* this marker is represented by this
4160 * CopiedLocationMarkerInfo
4163 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4168 if (real_location->is_mark()) {
4169 f_delta = newframe - copy_location->start();
4173 switch (_marker->type()) {
4174 case ArdourMarker::SessionStart:
4175 case ArdourMarker::RangeStart:
4176 case ArdourMarker::LoopStart:
4177 case ArdourMarker::PunchIn:
4178 f_delta = newframe - copy_location->start();
4181 case ArdourMarker::SessionEnd:
4182 case ArdourMarker::RangeEnd:
4183 case ArdourMarker::LoopEnd:
4184 case ArdourMarker::PunchOut:
4185 f_delta = newframe - copy_location->end();
4188 /* what kind of marker is this ? */
4197 if (x == _copied_locations.end()) {
4198 /* hmm, impossible - we didn't find the dragged marker */
4202 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4204 /* now move them all */
4206 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4208 copy_location = x->location;
4210 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4214 if (real_location->locked()) {
4218 if (copy_location->is_mark()) {
4221 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4225 framepos_t new_start = copy_location->start() + f_delta;
4226 framepos_t new_end = copy_location->end() + f_delta;
4228 if (is_start) { // start-of-range marker
4230 if (move_both || (*x).move_both) {
4231 copy_location->set_start (new_start, false, true, divisions);
4232 copy_location->set_end (new_end, false, true, divisions);
4233 } else if (new_start < copy_location->end()) {
4234 copy_location->set_start (new_start, false, true, divisions);
4235 } else if (newframe > 0) {
4236 //_editor->snap_to (next, RoundUpAlways, true);
4237 copy_location->set_end (next, false, true, divisions);
4238 copy_location->set_start (newframe, false, true, divisions);
4241 } else { // end marker
4243 if (move_both || (*x).move_both) {
4244 copy_location->set_end (new_end, divisions);
4245 copy_location->set_start (new_start, false, true, divisions);
4246 } else if (new_end > copy_location->start()) {
4247 copy_location->set_end (new_end, false, true, divisions);
4248 } else if (newframe > 0) {
4249 //_editor->snap_to (next, RoundDownAlways, true);
4250 copy_location->set_start (next, false, true, divisions);
4251 copy_location->set_end (newframe, false, true, divisions);
4256 update_item (copy_location);
4258 /* now lookup the actual GUI items used to display this
4259 * location and move them to wherever the copy of the location
4260 * is now. This means that the logic in ARDOUR::Location is
4261 * still enforced, even though we are not (yet) modifying
4262 * the real Location itself.
4265 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4268 lm->set_position (copy_location->start(), copy_location->end());
4273 assert (!_copied_locations.empty());
4275 show_verbose_cursor_time (newframe);
4279 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4281 if (!movement_occurred) {
4283 if (was_double_click()) {
4284 _editor->rename_marker (_marker);
4288 /* just a click, do nothing but finish
4289 off the selection process
4292 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4294 case Selection::Set:
4295 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4296 _editor->selection->set (_marker);
4297 _selection_changed = true;
4301 case Selection::Toggle:
4302 /* we toggle on the button release, click only */
4303 _editor->selection->toggle (_marker);
4304 _selection_changed = true;
4308 case Selection::Extend:
4309 case Selection::Add:
4313 if (_selection_changed) {
4314 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4315 _editor->commit_reversible_selection_op();
4321 _editor->_dragging_edit_point = false;
4323 XMLNode &before = _editor->session()->locations()->get_state();
4324 bool in_command = false;
4326 MarkerSelection::iterator i;
4327 CopiedLocationInfo::iterator x;
4328 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4331 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4332 x != _copied_locations.end() && i != _editor->selection->markers.end();
4335 Location * location = _editor->find_location_from_marker (*i, is_start);
4339 if (location->locked()) {
4343 _editor->begin_reversible_command ( _("move marker") );
4346 if (location->is_mark()) {
4347 location->set_start (((*x).location)->start(), false, true, divisions);
4349 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4352 if (location->is_session_range()) {
4353 _editor->session()->set_end_is_free (false);
4359 XMLNode &after = _editor->session()->locations()->get_state();
4360 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4361 _editor->commit_reversible_command ();
4366 MarkerDrag::aborted (bool movement_occurred)
4368 if (!movement_occurred) {
4372 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4374 /* move all markers to their original location */
4377 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4380 Location * location = _editor->find_location_from_marker (*m, is_start);
4383 (*m)->set_position (is_start ? location->start() : location->end());
4390 MarkerDrag::update_item (Location*)
4395 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4397 , _fixed_grab_x (0.0)
4398 , _fixed_grab_y (0.0)
4399 , _cumulative_x_drag (0.0)
4400 , _cumulative_y_drag (0.0)
4404 if (_zero_gain_fraction < 0.0) {
4405 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4408 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4410 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4416 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4418 Drag::start_grab (event, _editor->cursors()->fader);
4420 // start the grab at the center of the control point so
4421 // the point doesn't 'jump' to the mouse after the first drag
4422 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4423 _fixed_grab_y = _point->get_y();
4425 framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
4426 setup_snap_delta (pos);
4428 float const fraction = 1 - (_point->get_y() / _point->line().height());
4429 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4431 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4433 if (!_point->can_slide ()) {
4434 _x_constrained = true;
4439 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4441 double dx = _drags->current_pointer_x() - last_pointer_x();
4442 double dy = current_pointer_y() - last_pointer_y();
4443 bool need_snap = true;
4445 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4451 /* coordinate in pixels relative to the start of the region (for region-based automation)
4452 or track (for track-based automation) */
4453 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4454 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4456 // calculate zero crossing point. back off by .01 to stay on the
4457 // positive side of zero
4458 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4460 if (_x_constrained) {
4463 if (_y_constrained) {
4467 _cumulative_x_drag = cx - _fixed_grab_x;
4468 _cumulative_y_drag = cy - _fixed_grab_y;
4472 cy = min ((double) _point->line().height(), cy);
4474 // make sure we hit zero when passing through
4475 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4479 framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state);
4480 if (!_x_constrained && need_snap) {
4481 _editor->snap_to_with_modifier (cx_frames, event);
4484 cx_frames -= snap_delta (event->button.state);
4485 cx_frames = min (cx_frames, _point->line().maximum_time() + _point->line().offset());
4487 float const fraction = 1.0 - (cy / _point->line().height());
4490 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4491 _editor->begin_reversible_command (_("automation event move"));
4492 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4494 pair<double, float> result;
4495 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4497 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (result.second));
4501 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4503 if (!movement_occurred) {
4506 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4507 _editor->reset_point_selection ();
4511 _point->line().end_drag (_pushing, _final_index);
4512 _editor->commit_reversible_command ();
4517 ControlPointDrag::aborted (bool)
4519 _point->line().reset ();
4523 ControlPointDrag::active (Editing::MouseMode m)
4525 if (m == Editing::MouseDraw) {
4526 /* always active in mouse draw */
4530 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4531 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4534 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4537 , _fixed_grab_x (0.0)
4538 , _fixed_grab_y (0.0)
4539 , _cumulative_y_drag (0)
4543 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4547 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4549 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4552 _item = &_line->grab_item ();
4554 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4555 origin, and ditto for y.
4558 double mx = event->button.x;
4559 double my = event->button.y;
4561 _line->grab_item().canvas_to_item (mx, my);
4563 framecnt_t const frame_within_region = (framecnt_t) floor (mx * _editor->samples_per_pixel);
4565 if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
4566 /* no adjacent points */
4570 Drag::start_grab (event, _editor->cursors()->fader);
4572 /* store grab start in item frame */
4573 double const bx = _line->nth (_before)->get_x();
4574 double const ax = _line->nth (_after)->get_x();
4575 double const click_ratio = (ax - mx) / (ax - bx);
4577 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4582 double fraction = 1.0 - (cy / _line->height());
4584 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4588 LineDrag::motion (GdkEvent* event, bool first_move)
4590 double dy = current_pointer_y() - last_pointer_y();
4592 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4596 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4598 _cumulative_y_drag = cy - _fixed_grab_y;
4601 cy = min ((double) _line->height(), cy);
4603 double const fraction = 1.0 - (cy / _line->height());
4607 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4609 _editor->begin_reversible_command (_("automation range move"));
4610 _line->start_drag_line (_before, _after, initial_fraction);
4613 /* we are ignoring x position for this drag, so we can just pass in anything */
4614 pair<double, float> result;
4616 result = _line->drag_motion (0, fraction, true, false, ignored);
4617 show_verbose_cursor_text (_line->get_verbose_cursor_string (result.second));
4621 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4623 if (movement_occurred) {
4624 motion (event, false);
4625 _line->end_drag (false, 0);
4626 _editor->commit_reversible_command ();
4628 /* add a new control point on the line */
4630 AutomationTimeAxisView* atv;
4632 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4633 framepos_t where = grab_frame ();
4636 double cy = _fixed_grab_y;
4638 _line->grab_item().item_to_canvas (cx, cy);
4640 atv->add_automation_event (event, where, cy, false);
4641 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4642 AudioRegionView* arv;
4644 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4645 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4652 LineDrag::aborted (bool)
4657 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4661 _region_view_grab_x (0.0),
4662 _cumulative_x_drag (0),
4666 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4670 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4672 Drag::start_grab (event);
4674 _line = reinterpret_cast<Line*> (_item);
4677 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4679 double cx = event->button.x;
4680 double cy = event->button.y;
4682 _item->parent()->canvas_to_item (cx, cy);
4684 /* store grab start in parent frame */
4685 _region_view_grab_x = cx;
4687 _before = *(float*) _item->get_data ("position");
4689 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4691 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4695 FeatureLineDrag::motion (GdkEvent*, bool)
4697 double dx = _drags->current_pointer_x() - last_pointer_x();
4699 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4701 _cumulative_x_drag += dx;
4703 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4712 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4714 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4716 float *pos = new float;
4719 _line->set_data ("position", pos);
4725 FeatureLineDrag::finished (GdkEvent*, bool)
4727 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4728 _arv->update_transient(_before, _before);
4732 FeatureLineDrag::aborted (bool)
4737 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4739 , _vertical_only (false)
4741 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4745 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4747 Drag::start_grab (event);
4748 show_verbose_cursor_time (adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
4752 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4759 framepos_t const pf = adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
4761 framepos_t grab = grab_frame ();
4762 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4763 _editor->snap_to_with_modifier (grab, event);
4765 grab = raw_grab_frame ();
4768 /* base start and end on initial click position */
4778 if (current_pointer_y() < grab_y()) {
4779 y1 = current_pointer_y();
4782 y2 = current_pointer_y();
4786 if (start != end || y1 != y2) {
4788 double x1 = _editor->sample_to_pixel (start);
4789 double x2 = _editor->sample_to_pixel (end);
4790 const double min_dimension = 2.0;
4792 if (_vertical_only) {
4793 /* fixed 10 pixel width */
4797 x2 = min (x1 - min_dimension, x2);
4799 x2 = max (x1 + min_dimension, x2);
4804 y2 = min (y1 - min_dimension, y2);
4806 y2 = max (y1 + min_dimension, y2);
4809 /* translate rect into item space and set */
4811 ArdourCanvas::Rect r (x1, y1, x2, y2);
4813 /* this drag is a _trackview_only == true drag, so the y1 and
4814 * y2 (computed using current_pointer_y() and grab_y()) will be
4815 * relative to the top of the trackview group). The
4816 * rubberband rect has the same parent/scroll offset as the
4817 * the trackview group, so we can use the "r" rect directly
4818 * to set the shape of the rubberband.
4821 _editor->rubberband_rect->set (r);
4822 _editor->rubberband_rect->show();
4823 _editor->rubberband_rect->raise_to_top();
4825 show_verbose_cursor_time (pf);
4827 do_select_things (event, true);
4832 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4836 framepos_t grab = grab_frame ();
4837 framepos_t lpf = last_pointer_frame ();
4839 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4840 grab = raw_grab_frame ();
4841 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4855 if (current_pointer_y() < grab_y()) {
4856 y1 = current_pointer_y();
4859 y2 = current_pointer_y();
4863 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4867 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4869 if (movement_occurred) {
4871 motion (event, false);
4872 do_select_things (event, false);
4878 bool do_deselect = true;
4879 MidiTimeAxisView* mtv;
4881 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4883 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4884 /* nothing selected */
4885 add_midi_region (mtv, true, _editor->get_grid_music_divisions(event->button.state));
4886 do_deselect = false;
4890 /* do not deselect if Primary or Tertiary (toggle-select or
4891 * extend-select are pressed.
4894 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4895 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4902 _editor->rubberband_rect->hide();
4906 RubberbandSelectDrag::aborted (bool)
4908 _editor->rubberband_rect->hide ();
4911 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4912 : RegionDrag (e, i, p, v)
4914 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4918 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4920 Drag::start_grab (event, cursor);
4922 _editor->get_selection().add (_primary);
4924 framepos_t where = _primary->region()->position();
4925 setup_snap_delta (where);
4927 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4931 TimeFXDrag::motion (GdkEvent* event, bool)
4933 RegionView* rv = _primary;
4934 StreamView* cv = rv->get_time_axis_view().view ();
4936 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4937 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4938 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4939 framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
4940 _editor->snap_to_with_modifier (pf, event);
4941 pf -= snap_delta (event->button.state);
4943 if (pf > rv->region()->position()) {
4944 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4947 show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4951 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
4953 /* this may have been a single click, no drag. We still want the dialog
4954 to show up in that case, so that the user can manually edit the
4955 parameters for the timestretch.
4958 float fraction = 1.0;
4960 if (movement_occurred) {
4962 motion (event, false);
4964 _primary->get_time_axis_view().hide_timestretch ();
4966 framepos_t adjusted_frame_pos = adjusted_current_frame (event);
4968 if (adjusted_frame_pos < _primary->region()->position()) {
4969 /* backwards drag of the left edge - not usable */
4973 framecnt_t newlen = adjusted_frame_pos - _primary->region()->position();
4975 fraction = (double) newlen / (double) _primary->region()->length();
4977 #ifndef USE_RUBBERBAND
4978 // Soundtouch uses fraction / 100 instead of normal (/ 1)
4979 if (_primary->region()->data_type() == DataType::AUDIO) {
4980 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4985 if (!_editor->get_selection().regions.empty()) {
4986 /* primary will already be included in the selection, and edit
4987 group shared editing will propagate selection across
4988 equivalent regions, so just use the current region
4992 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
4993 error << _("An error occurred while executing time stretch operation") << endmsg;
4999 TimeFXDrag::aborted (bool)
5001 _primary->get_time_axis_view().hide_timestretch ();
5004 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5007 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5011 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5013 Drag::start_grab (event);
5017 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5019 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
5023 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5025 if (movement_occurred && _editor->session()) {
5026 /* make sure we stop */
5027 _editor->session()->request_transport_speed (0.0);
5032 ScrubDrag::aborted (bool)
5037 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5041 , _track_selection_at_start (e)
5042 , _time_selection_at_start (!_editor->get_selection().time.empty())
5044 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5046 if (_time_selection_at_start) {
5047 start_at_start = _editor->get_selection().time.start();
5048 end_at_start = _editor->get_selection().time.end_frame();
5053 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5055 if (_editor->session() == 0) {
5059 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5061 switch (_operation) {
5062 case CreateSelection:
5063 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5068 cursor = _editor->cursors()->selector;
5069 Drag::start_grab (event, cursor);
5072 case SelectionStartTrim:
5073 if (_editor->clicked_axisview) {
5074 _editor->clicked_axisview->order_selection_trims (_item, true);
5076 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5079 case SelectionEndTrim:
5080 if (_editor->clicked_axisview) {
5081 _editor->clicked_axisview->order_selection_trims (_item, false);
5083 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5087 Drag::start_grab (event, cursor);
5090 case SelectionExtend:
5091 Drag::start_grab (event, cursor);
5095 if (_operation == SelectionMove) {
5096 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5098 show_verbose_cursor_time (adjusted_current_frame (event));
5103 SelectionDrag::setup_pointer_frame_offset ()
5105 switch (_operation) {
5106 case CreateSelection:
5107 _pointer_frame_offset = 0;
5110 case SelectionStartTrim:
5112 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
5115 case SelectionEndTrim:
5116 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
5119 case SelectionExtend:
5125 SelectionDrag::motion (GdkEvent* event, bool first_move)
5127 framepos_t start = 0;
5129 framecnt_t length = 0;
5130 framecnt_t distance = 0;
5132 framepos_t const pending_position = adjusted_current_frame (event);
5134 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
5139 _track_selection_at_start = _editor->selection->tracks;
5142 switch (_operation) {
5143 case CreateSelection:
5145 framepos_t grab = grab_frame ();
5148 grab = adjusted_current_frame (event, false);
5149 if (grab < pending_position) {
5150 _editor->snap_to (grab, RoundDownMaybe);
5152 _editor->snap_to (grab, RoundUpMaybe);
5156 if (pending_position < grab) {
5157 start = pending_position;
5160 end = pending_position;
5164 /* first drag: Either add to the selection
5165 or create a new selection
5172 /* adding to the selection */
5173 _editor->set_selected_track_as_side_effect (Selection::Add);
5174 _editor->clicked_selection = _editor->selection->add (start, end);
5181 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5182 _editor->set_selected_track_as_side_effect (Selection::Set);
5185 _editor->clicked_selection = _editor->selection->set (start, end);
5189 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5190 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5191 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5193 _editor->selection->add (atest);
5197 /* select all tracks within the rectangle that we've marked out so far */
5198 TrackViewList new_selection;
5199 TrackViewList& all_tracks (_editor->track_views);
5201 ArdourCanvas::Coord const top = grab_y();
5202 ArdourCanvas::Coord const bottom = current_pointer_y();
5204 if (top >= 0 && bottom >= 0) {
5206 //first, find the tracks that are covered in the y range selection
5207 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5208 if ((*i)->covered_by_y_range (top, bottom)) {
5209 new_selection.push_back (*i);
5213 //now compare our list with the current selection, and add as necessary
5214 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5215 TrackViewList tracks_to_add;
5216 TrackViewList tracks_to_remove;
5219 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5220 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5221 tracks_to_remove.push_back (*i);
5226 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5227 if (!_editor->selection->tracks.contains (*i)) {
5228 tracks_to_add.push_back (*i);
5232 _editor->selection->add (tracks_to_add);
5234 if (!tracks_to_remove.empty()) {
5235 _editor->selection->remove (tracks_to_remove);
5241 case SelectionStartTrim:
5243 end = _editor->selection->time[_editor->clicked_selection].end;
5245 if (pending_position > end) {
5248 start = pending_position;
5252 case SelectionEndTrim:
5254 start = _editor->selection->time[_editor->clicked_selection].start;
5256 if (pending_position < start) {
5259 end = pending_position;
5266 start = _editor->selection->time[_editor->clicked_selection].start;
5267 end = _editor->selection->time[_editor->clicked_selection].end;
5269 length = end - start;
5270 distance = pending_position - start;
5271 start = pending_position;
5272 _editor->snap_to (start);
5274 end = start + length;
5278 case SelectionExtend:
5283 switch (_operation) {
5285 if (_time_selection_at_start) {
5286 _editor->selection->move_time (distance);
5290 _editor->selection->replace (_editor->clicked_selection, start, end);
5294 if (_operation == SelectionMove) {
5295 show_verbose_cursor_time(start);
5297 show_verbose_cursor_time(pending_position);
5302 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5304 Session* s = _editor->session();
5306 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5307 if (movement_occurred) {
5308 motion (event, false);
5309 /* XXX this is not object-oriented programming at all. ick */
5310 if (_editor->selection->time.consolidate()) {
5311 _editor->selection->TimeChanged ();
5314 /* XXX what if its a music time selection? */
5316 if (s->get_play_range() && s->transport_rolling()) {
5317 s->request_play_range (&_editor->selection->time, true);
5318 } else if (!s->config.get_external_sync()) {
5319 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5320 s->request_locate (_editor->get_selection().time.start());
5324 if (_editor->get_selection().time.length() != 0) {
5325 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame());
5327 s->clear_range_selection ();
5332 /* just a click, no pointer movement.
5335 if (was_double_click()) {
5336 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5337 _editor->temporal_zoom_selection (Both);
5342 if (_operation == SelectionExtend) {
5343 if (_time_selection_at_start) {
5344 framepos_t pos = adjusted_current_frame (event, false);
5345 framepos_t start = min (pos, start_at_start);
5346 framepos_t end = max (pos, end_at_start);
5347 _editor->selection->set (start, end);
5350 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5351 if (_editor->clicked_selection) {
5352 _editor->selection->remove (_editor->clicked_selection);
5355 if (!_editor->clicked_selection) {
5356 _editor->selection->clear_time();
5361 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5362 _editor->selection->set (_editor->clicked_axisview);
5365 if (s && s->get_play_range () && s->transport_rolling()) {
5366 s->request_stop (false, false);
5371 _editor->stop_canvas_autoscroll ();
5372 _editor->clicked_selection = 0;
5373 _editor->commit_reversible_selection_op ();
5377 SelectionDrag::aborted (bool)
5382 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5383 : Drag (e, i, false),
5387 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5389 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5390 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5391 physical_screen_height (_editor->current_toplevel()->get_window())));
5392 _drag_rect->hide ();
5394 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5395 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5398 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5400 /* normal canvas items will be cleaned up when their parent group is deleted. But
5401 this item is created as the child of a long-lived parent group, and so we
5402 need to explicitly delete it.
5408 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5410 if (_editor->session() == 0) {
5414 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5416 if (!_editor->temp_location) {
5417 _editor->temp_location = new Location (*_editor->session());
5420 switch (_operation) {
5421 case CreateSkipMarker:
5422 case CreateRangeMarker:
5423 case CreateTransportMarker:
5424 case CreateCDMarker:
5426 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5431 cursor = _editor->cursors()->selector;
5435 Drag::start_grab (event, cursor);
5437 show_verbose_cursor_time (adjusted_current_frame (event));
5441 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5443 framepos_t start = 0;
5445 ArdourCanvas::Rectangle *crect;
5447 switch (_operation) {
5448 case CreateSkipMarker:
5449 crect = _editor->range_bar_drag_rect;
5451 case CreateRangeMarker:
5452 crect = _editor->range_bar_drag_rect;
5454 case CreateTransportMarker:
5455 crect = _editor->transport_bar_drag_rect;
5457 case CreateCDMarker:
5458 crect = _editor->cd_marker_bar_drag_rect;
5461 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5466 framepos_t const pf = adjusted_current_frame (event);
5468 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5469 framepos_t grab = grab_frame ();
5470 _editor->snap_to (grab);
5472 if (pf < grab_frame()) {
5480 /* first drag: Either add to the selection
5481 or create a new selection.
5486 _editor->temp_location->set (start, end);
5490 update_item (_editor->temp_location);
5492 //_drag_rect->raise_to_top();
5498 _editor->temp_location->set (start, end);
5500 double x1 = _editor->sample_to_pixel (start);
5501 double x2 = _editor->sample_to_pixel (end);
5505 update_item (_editor->temp_location);
5508 show_verbose_cursor_time (pf);
5513 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5515 Location * newloc = 0;
5519 if (movement_occurred) {
5520 motion (event, false);
5523 switch (_operation) {
5524 case CreateSkipMarker:
5525 case CreateRangeMarker:
5526 case CreateCDMarker:
5528 XMLNode &before = _editor->session()->locations()->get_state();
5529 if (_operation == CreateSkipMarker) {
5530 _editor->begin_reversible_command (_("new skip marker"));
5531 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5532 flags = Location::IsRangeMarker | Location::IsSkip;
5533 _editor->range_bar_drag_rect->hide();
5534 } else if (_operation == CreateCDMarker) {
5535 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5536 _editor->begin_reversible_command (_("new CD marker"));
5537 flags = Location::IsRangeMarker | Location::IsCDMarker;
5538 _editor->cd_marker_bar_drag_rect->hide();
5540 _editor->begin_reversible_command (_("new skip marker"));
5541 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5542 flags = Location::IsRangeMarker;
5543 _editor->range_bar_drag_rect->hide();
5545 newloc = new Location (
5546 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5547 , _editor->get_grid_music_divisions (event->button.state));
5549 _editor->session()->locations()->add (newloc, true);
5550 XMLNode &after = _editor->session()->locations()->get_state();
5551 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5552 _editor->commit_reversible_command ();
5556 case CreateTransportMarker:
5557 // popup menu to pick loop or punch
5558 _editor->new_transport_marker_context_menu (&event->button, _item);
5564 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5566 if (_operation == CreateTransportMarker) {
5568 /* didn't drag, so just locate */
5570 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5572 } else if (_operation == CreateCDMarker) {
5574 /* didn't drag, but mark is already created so do
5577 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5582 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5584 if (end == max_framepos) {
5585 end = _editor->session()->current_end_frame ();
5588 if (start == max_framepos) {
5589 start = _editor->session()->current_start_frame ();
5592 switch (_editor->mouse_mode) {
5594 /* find the two markers on either side and then make the selection from it */
5595 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5599 /* find the two markers on either side of the click and make the range out of it */
5600 _editor->selection->set (start, end);
5609 _editor->stop_canvas_autoscroll ();
5613 RangeMarkerBarDrag::aborted (bool movement_occurred)
5615 if (movement_occurred) {
5616 _drag_rect->hide ();
5621 RangeMarkerBarDrag::update_item (Location* location)
5623 double const x1 = _editor->sample_to_pixel (location->start());
5624 double const x2 = _editor->sample_to_pixel (location->end());
5626 _drag_rect->set_x0 (x1);
5627 _drag_rect->set_x1 (x2);
5630 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5632 , _cumulative_dx (0)
5633 , _cumulative_dy (0)
5634 , _was_selected (false)
5637 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5639 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5641 _region = &_primary->region_view ();
5642 _note_height = _region->midi_stream_view()->note_height ();
5646 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5648 Drag::start_grab (event);
5650 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5656 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5658 if (!(_was_selected = _primary->selected())) {
5660 /* tertiary-click means extend selection - we'll do that on button release,
5661 so don't add it here, because otherwise we make it hard to figure
5662 out the "extend-to" range.
5665 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5668 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5671 _region->note_selected (_primary, true);
5673 _editor->get_selection().clear_points();
5674 _region->unique_select (_primary);
5680 /** @return Current total drag x change in frames */
5682 NoteDrag::total_dx (const guint state) const
5684 if (_x_constrained) {
5687 TempoMap& map (_editor->session()->tempo_map());
5690 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5692 /* primary note time */
5693 double const quarter_note_start = _region->region()->quarter_note() - _region->midi_region()->start_beats();
5694 frameoffset_t const n = map.frame_at_quarter_note (quarter_note_start + _primary->note()->time().to_double());
5696 /* new time of the primary note in session frames */
5697 frameoffset_t st = n + dx + snap_delta (state);
5699 framepos_t const rp = _region->region()->position ();
5701 /* prevent the note being dragged earlier than the region's position */
5704 /* possibly snap and return corresponding delta */
5708 if (ArdourKeyboard::indicates_snap (state)) {
5709 if (_editor->snap_mode () != SnapOff) {
5713 if (_editor->snap_mode () == SnapOff) {
5715 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
5716 if (ArdourKeyboard::indicates_snap_delta (state)) {
5724 bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
5725 ret = _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
5727 ret = st - n - snap_delta (state);
5732 /** @return Current total drag y change in note number */
5734 NoteDrag::total_dy () const
5736 if (_y_constrained) {
5740 double const y = _region->midi_view()->y_position ();
5741 /* new current note */
5742 uint8_t n = _region->y_to_note (current_pointer_y () - y);
5744 MidiStreamView* msv = _region->midi_stream_view ();
5745 n = max (msv->lowest_note(), n);
5746 n = min (msv->highest_note(), n);
5747 /* and work out delta */
5748 return n - _region->y_to_note (grab_y() - y);
5752 NoteDrag::motion (GdkEvent * event, bool first_move)
5754 if (_copy && first_move) {
5755 /* make copies of all the selected notes */
5756 _primary = _region->copy_selection ();
5759 /* Total change in x and y since the start of the drag */
5760 frameoffset_t const dx = total_dx (event->button.state);
5761 int8_t const dy = total_dy ();
5763 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5764 double const tdx = _x_constrained ? 0 : _editor->sample_to_pixel (dx) - _cumulative_dx;
5765 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
5768 _cumulative_dx += tdx;
5769 _cumulative_dy += tdy;
5771 int8_t note_delta = total_dy();
5775 _region->move_copies (tdx, tdy, note_delta);
5777 _region->move_selection (tdx, tdy, note_delta);
5780 /* the new note value may be the same as the old one, but we
5781 * don't know what that means because the selection may have
5782 * involved more than one note and we might be doing something
5783 * odd with them. so show the note value anyway, always.
5786 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5788 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
5794 NoteDrag::finished (GdkEvent* ev, bool moved)
5797 /* no motion - select note */
5799 if (_editor->current_mouse_mode() == Editing::MouseContent ||
5800 _editor->current_mouse_mode() == Editing::MouseDraw) {
5802 bool changed = false;
5804 if (_was_selected) {
5805 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5807 _region->note_deselected (_primary);
5810 _editor->get_selection().clear_points();
5811 _region->unique_select (_primary);
5815 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5816 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5818 if (!extend && !add && _region->selection_size() > 1) {
5819 _editor->get_selection().clear_points();
5820 _region->unique_select (_primary);
5822 } else if (extend) {
5823 _region->note_selected (_primary, true, true);
5826 /* it was added during button press */
5833 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5834 _editor->commit_reversible_selection_op();
5838 _region->note_dropped (_primary, total_dx (ev->button.state), total_dy(), _copy);
5843 NoteDrag::aborted (bool)
5848 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5849 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5850 : Drag (editor, atv->base_item ())
5852 , _y_origin (atv->y_position())
5853 , _nothing_to_drag (false)
5855 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5856 setup (atv->lines ());
5859 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5860 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5861 : Drag (editor, rv->get_canvas_group ())
5863 , _y_origin (rv->get_time_axis_view().y_position())
5864 , _nothing_to_drag (false)
5867 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5869 list<boost::shared_ptr<AutomationLine> > lines;
5871 AudioRegionView* audio_view;
5872 AutomationRegionView* automation_view;
5873 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5874 lines.push_back (audio_view->get_gain_line ());
5875 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5876 lines.push_back (automation_view->line ());
5879 error << _("Automation range drag created for invalid region type") << endmsg;
5885 /** @param lines AutomationLines to drag.
5886 * @param offset Offset from the session start to the points in the AutomationLines.
5889 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5891 /* find the lines that overlap the ranges being dragged */
5892 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5893 while (i != lines.end ()) {
5894 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5897 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5899 /* check this range against all the AudioRanges that we are using */
5900 list<AudioRange>::const_iterator k = _ranges.begin ();
5901 while (k != _ranges.end()) {
5902 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5908 /* add it to our list if it overlaps at all */
5909 if (k != _ranges.end()) {
5914 _lines.push_back (n);
5920 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5924 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5926 return 1.0 - ((global_y - _y_origin) / line->height());
5930 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5932 const double v = list->eval(x);
5933 return _integral ? rint(v) : v;
5937 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5939 Drag::start_grab (event, cursor);
5941 /* Get line states before we start changing things */
5942 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5943 i->state = &i->line->get_state ();
5944 i->original_fraction = y_fraction (i->line, current_pointer_y());
5947 if (_ranges.empty()) {
5949 /* No selected time ranges: drag all points */
5950 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5951 uint32_t const N = i->line->npoints ();
5952 for (uint32_t j = 0; j < N; ++j) {
5953 i->points.push_back (i->line->nth (j));
5959 if (_nothing_to_drag) {
5965 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
5967 if (_nothing_to_drag && !first_move) {
5972 _editor->begin_reversible_command (_("automation range move"));
5974 if (!_ranges.empty()) {
5976 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5978 framecnt_t const half = (i->start + i->end) / 2;
5980 /* find the line that this audio range starts in */
5981 list<Line>::iterator j = _lines.begin();
5982 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5986 if (j != _lines.end()) {
5987 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5989 /* j is the line that this audio range starts in; fade into it;
5990 64 samples length plucked out of thin air.
5993 framepos_t a = i->start + 64;
5998 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5999 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
6001 XMLNode &before = the_list->get_state();
6002 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6003 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6005 if (add_p || add_q) {
6006 _editor->session()->add_command (
6007 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6011 /* same thing for the end */
6014 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
6018 if (j != _lines.end()) {
6019 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6021 /* j is the line that this audio range starts in; fade out of it;
6022 64 samples length plucked out of thin air.
6025 framepos_t b = i->end - 64;
6030 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6031 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6033 XMLNode &before = the_list->get_state();
6034 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6035 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6037 if (add_p || add_q) {
6038 _editor->session()->add_command (
6039 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6044 _nothing_to_drag = true;
6046 /* Find all the points that should be dragged and put them in the relevant
6047 points lists in the Line structs.
6050 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6052 uint32_t const N = i->line->npoints ();
6053 for (uint32_t j = 0; j < N; ++j) {
6055 /* here's a control point on this line */
6056 ControlPoint* p = i->line->nth (j);
6057 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6059 /* see if it's inside a range */
6060 list<AudioRange>::const_iterator k = _ranges.begin ();
6061 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6065 if (k != _ranges.end()) {
6066 /* dragging this point */
6067 _nothing_to_drag = false;
6068 i->points.push_back (p);
6074 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6075 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6079 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6080 float const f = y_fraction (l->line, current_pointer_y());
6081 /* we are ignoring x position for this drag, so we can just pass in anything */
6082 pair<double, float> result;
6084 result = l->line->drag_motion (0, f, true, false, ignored);
6085 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, result.second));
6090 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6092 if (_nothing_to_drag || !motion_occurred) {
6096 motion (event, false);
6097 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6098 i->line->end_drag (false, 0);
6101 _editor->commit_reversible_command ();
6105 AutomationRangeDrag::aborted (bool)
6107 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6112 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6114 , initial_time_axis_view (itav)
6116 /* note that time_axis_view may be null if the regionview was created
6117 * as part of a copy operation.
6119 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6120 layer = v->region()->layer ();
6121 initial_y = v->get_canvas_group()->position().y;
6122 initial_playlist = v->region()->playlist ();
6123 initial_position = v->region()->position ();
6124 initial_end = v->region()->position () + v->region()->length ();
6127 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6128 : Drag (e, i->canvas_item ())
6131 , _cumulative_dx (0)
6133 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6134 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
6139 PatchChangeDrag::motion (GdkEvent* ev, bool)
6141 framepos_t f = adjusted_current_frame (ev);
6142 boost::shared_ptr<Region> r = _region_view->region ();
6143 f = max (f, r->position ());
6144 f = min (f, r->last_frame ());
6146 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
6147 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6148 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6149 _cumulative_dx = dxu;
6153 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6155 if (!movement_occurred) {
6156 if (was_double_click()) {
6157 _region_view->edit_patch_change (_patch_change);
6162 boost::shared_ptr<Region> r (_region_view->region ());
6163 framepos_t f = adjusted_current_frame (ev);
6164 f = max (f, r->position ());
6165 f = min (f, r->last_frame ());
6167 _region_view->move_patch_change (
6169 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
6174 PatchChangeDrag::aborted (bool)
6176 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6180 PatchChangeDrag::setup_pointer_frame_offset ()
6182 boost::shared_ptr<Region> region = _region_view->region ();
6183 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
6186 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6187 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6194 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6196 _region_view->update_drag_selection (
6198 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6202 MidiRubberbandSelectDrag::deselect_things ()
6207 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6208 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6211 _vertical_only = true;
6215 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6217 double const y = _region_view->midi_view()->y_position ();
6219 y1 = max (0.0, y1 - y);
6220 y2 = max (0.0, y2 - y);
6222 _region_view->update_vertical_drag_selection (
6225 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6230 MidiVerticalSelectDrag::deselect_things ()
6235 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6236 : RubberbandSelectDrag (e, i)
6242 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
6244 if (drag_in_progress) {
6245 /* We just want to select things at the end of the drag, not during it */
6249 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6251 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6253 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6255 _editor->commit_reversible_selection_op ();
6259 EditorRubberbandSelectDrag::deselect_things ()
6261 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6263 _editor->selection->clear_tracks();
6264 _editor->selection->clear_regions();
6265 _editor->selection->clear_points ();
6266 _editor->selection->clear_lines ();
6267 _editor->selection->clear_midi_notes ();
6269 _editor->commit_reversible_selection_op();
6272 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6277 _note[0] = _note[1] = 0;
6280 NoteCreateDrag::~NoteCreateDrag ()
6286 NoteCreateDrag::grid_frames (framepos_t t) const
6289 const Evoral::Beats grid_beats = _region_view->get_grid_beats (t);
6290 const Evoral::Beats t_beats = _region_view->region_frames_to_region_beats (t);
6292 return _region_view->region_beats_to_region_frames (t_beats + grid_beats)
6293 - _region_view->region_beats_to_region_frames (t_beats);
6297 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6299 Drag::start_grab (event, cursor);
6301 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6302 TempoMap& map (_editor->session()->tempo_map());
6304 const framepos_t pf = _drags->current_pointer_frame ();
6305 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6307 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6309 double eqaf = map.exact_qn_at_frame (pf, divisions);
6311 if (divisions != 0) {
6313 const double qaf = map.quarter_note_at_frame (pf);
6315 /* Hack so that we always snap to the note that we are over, instead of snapping
6316 to the next one if we're more than halfway through the one we're over.
6319 const double rem = eqaf - qaf;
6321 eqaf -= grid_beats.to_double();
6325 _note[0] = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6326 /* minimum initial length is grid beats */
6327 _note[1] = map.frame_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6329 double const x0 = _editor->sample_to_pixel (_note[0]);
6330 double const x1 = _editor->sample_to_pixel (_note[1]);
6331 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6333 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6334 _drag_rect->set_outline_all ();
6335 _drag_rect->set_outline_color (0xffffff99);
6336 _drag_rect->set_fill_color (0xffffff66);
6340 NoteCreateDrag::motion (GdkEvent* event, bool)
6342 TempoMap& map (_editor->session()->tempo_map());
6343 const framepos_t pf = _drags->current_pointer_frame ();
6344 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6345 double eqaf = map.exact_qn_at_frame (pf, divisions);
6347 if (divisions != 0) {
6349 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6351 const double qaf = map.quarter_note_at_frame (pf);
6352 /* Hack so that we always snap to the note that we are over, instead of snapping
6353 to the next one if we're more than halfway through the one we're over.
6356 const double rem = eqaf - qaf;
6358 eqaf -= grid_beats.to_double();
6361 eqaf += grid_beats.to_double();
6363 _note[1] = max ((framepos_t)0, map.frame_at_quarter_note (eqaf) - _region_view->region()->position ());
6365 double const x0 = _editor->sample_to_pixel (_note[0]);
6366 double const x1 = _editor->sample_to_pixel (_note[1]);
6367 _drag_rect->set_x0 (std::min(x0, x1));
6368 _drag_rect->set_x1 (std::max(x0, x1));
6372 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6374 /* we create a note even if there was no movement */
6375 framepos_t const start = min (_note[0], _note[1]);
6376 framepos_t const start_sess_rel = start + _region_view->region()->position();
6377 framecnt_t length = max (_editor->pixel_to_sample (1.0), (framecnt_t) fabs ((double)(_note[0] - _note[1])));
6378 framecnt_t const g = grid_frames (start);
6380 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6384 TempoMap& map (_editor->session()->tempo_map());
6385 const double qn_length = map.quarter_notes_between_frames (start_sess_rel, start_sess_rel + length);
6386 Evoral::Beats qn_length_beats = max (Evoral::Beats::ticks(1), Evoral::Beats (qn_length));
6388 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6392 NoteCreateDrag::y_to_region (double y) const
6395 _region_view->get_canvas_group()->canvas_to_item (x, y);
6400 NoteCreateDrag::aborted (bool)
6405 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6413 HitCreateDrag::~HitCreateDrag ()
6418 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6420 Drag::start_grab (event, cursor);
6422 TempoMap& map (_editor->session()->tempo_map());
6424 const framepos_t pf = _drags->current_pointer_frame ();
6425 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6427 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6429 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6431 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6435 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6436 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6438 Evoral::Beats length = _region_view->get_grid_beats (pf);
6440 _region_view->create_note_at (start, y, length, event->button.state, false);
6447 HitCreateDrag::motion (GdkEvent* event, bool)
6449 TempoMap& map (_editor->session()->tempo_map());
6451 const framepos_t pf = _drags->current_pointer_frame ();
6452 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6454 if (divisions == 0) {
6458 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6459 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position ();
6460 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6462 if (_last_pos == start && y == _last_y) {
6466 Evoral::Beats length = _region_view->get_grid_beats (pf);
6468 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6469 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6473 _region_view->create_note_at (start, y, length, event->button.state, false);
6480 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6486 HitCreateDrag::y_to_region (double y) const
6489 _region_view->get_canvas_group()->canvas_to_item (x, y);
6494 HitCreateDrag::aborted (bool)
6499 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6504 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6508 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6510 Drag::start_grab (event, cursor);
6514 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6520 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6523 distance = _drags->current_pointer_x() - grab_x();
6524 len = ar->fade_in()->back()->when;
6526 distance = grab_x() - _drags->current_pointer_x();
6527 len = ar->fade_out()->back()->when;
6530 /* how long should it be ? */
6532 new_length = len + _editor->pixel_to_sample (distance);
6534 /* now check with the region that this is legal */
6536 new_length = ar->verify_xfade_bounds (new_length, start);
6539 arv->reset_fade_in_shape_width (ar, new_length);
6541 arv->reset_fade_out_shape_width (ar, new_length);
6546 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6552 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6555 distance = _drags->current_pointer_x() - grab_x();
6556 len = ar->fade_in()->back()->when;
6558 distance = grab_x() - _drags->current_pointer_x();
6559 len = ar->fade_out()->back()->when;
6562 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6564 _editor->begin_reversible_command ("xfade trim");
6565 ar->playlist()->clear_owned_changes ();
6568 ar->set_fade_in_length (new_length);
6570 ar->set_fade_out_length (new_length);
6573 /* Adjusting the xfade may affect other regions in the playlist, so we need
6574 to get undo Commands from the whole playlist rather than just the
6578 vector<Command*> cmds;
6579 ar->playlist()->rdiff (cmds);
6580 _editor->session()->add_commands (cmds);
6581 _editor->commit_reversible_command ();
6586 CrossfadeEdgeDrag::aborted (bool)
6589 // arv->redraw_start_xfade ();
6591 // arv->redraw_end_xfade ();
6595 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
6596 : Drag (e, item, true)
6597 , line (new EditorCursor (*e))
6599 line->set_position (pos);
6601 line->track_canvas_item().reparent (_editor->_drag_motion_group);
6604 RegionCutDrag::~RegionCutDrag ()
6610 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6612 Drag::start_grab (event, c);
6613 motion (event, false);
6617 RegionCutDrag::motion (GdkEvent* event, bool)
6619 framepos_t pos = _drags->current_pointer_frame();
6620 _editor->snap_to_with_modifier (pos, event);
6622 line->set_position (pos);
6626 RegionCutDrag::finished (GdkEvent* event, bool)
6628 _editor->get_track_canvas()->canvas()->re_enter();
6630 framepos_t pos = _drags->current_pointer_frame();
6631 _editor->snap_to_with_modifier (pos, event);
6635 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
6641 _editor->split_regions_at (pos, rs, _editor->get_grid_music_divisions (event->button.state),
6646 RegionCutDrag::aborted (bool)
6650 RulerZoomDrag::RulerZoomDrag (Editor* e, ArdourCanvas::Item* item)
6651 : Drag (e, item, true)
6656 RulerZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6658 Drag::start_grab (event, c);
6660 framepos_t where = _editor->canvas_event_sample(event);
6662 _editor->_dragging_playhead = true;
6664 _editor->playhead_cursor->set_position (where);
6668 RulerZoomDrag::motion (GdkEvent* event, bool)
6670 framepos_t where = _editor->canvas_event_sample(event);
6672 _editor->playhead_cursor->set_position (where);
6674 const double movement_limit = 20.0;
6675 const double scale = 1.08;
6676 const double y_delta = last_pointer_y() - current_pointer_y();
6678 if (y_delta > 0 && y_delta < movement_limit) {
6679 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
6680 } else if (y_delta < 0 && y_delta > -movement_limit) {
6681 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
6686 RulerZoomDrag::finished (GdkEvent*, bool)
6688 _editor->_dragging_playhead = false;
6690 Session* s = _editor->session ();
6692 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
6693 _editor->_pending_locate_request = true;
6699 RulerZoomDrag::aborted (bool)