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).frame;
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
330 MusicFrame pos (0, 0);
332 if (f > _pointer_frame_offset) {
333 pos.frame = 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).frame;
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 MusicFrame snap (pos, 0);
379 _editor->snap_to (snap, ARDOUR::RoundNearest, false, true);
380 _snap_delta = snap.frame - 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)
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);
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 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
603 : RegionDrag (e, i, p, v)
605 , _ignore_video_lock (false)
606 , _last_position (0, 0)
608 , _last_pointer_time_axis_view (0)
609 , _last_pointer_layer (0)
614 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
618 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
620 Drag::start_grab (event, cursor);
621 setup_snap_delta (_last_position.frame);
623 show_verbose_cursor_time (_last_position.frame);
625 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
627 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
628 assert(_last_pointer_time_axis_view >= 0);
629 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
632 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
633 _ignore_video_lock = true;
637 /* cross track dragging seems broken here. disabled for now. */
638 _y_constrained = true;
643 RegionMotionDrag::compute_x_delta (GdkEvent const * event, MusicFrame* pending_region_position)
645 /* compute the amount of pointer motion in frames, and where
646 the region would be if we moved it by that much.
648 *pending_region_position = adjusted_frame (_drags->current_pointer_frame (), event, false);
650 framecnt_t sync_offset;
653 sync_offset = _primary->region()->sync_offset (sync_dir);
655 /* we don't handle a sync point that lies before zero.
657 if (sync_dir >= 0 || (sync_dir < 0 && pending_region_position->frame >= sync_offset)) {
659 framecnt_t const sd = snap_delta (event->button.state);
660 MusicFrame sync_snap (pending_region_position->frame + (sync_dir * sync_offset) + sd, 0);
661 _editor->snap_to_with_modifier (sync_snap, event);
662 if (sync_offset == 0 && sd == 0) {
663 *pending_region_position = sync_snap;
665 pending_region_position->set (_primary->region()->adjust_to_sync (sync_snap.frame) - sd, 0);
668 *pending_region_position = _last_position;
671 if (pending_region_position->frame > max_framepos - _primary->region()->length()) {
672 *pending_region_position = _last_position;
677 bool const x_move_allowed = !_x_constrained;
679 if ((pending_region_position->frame != _last_position.frame) && x_move_allowed) {
681 /* x movement since last time (in pixels) */
682 dx = _editor->sample_to_pixel_unrounded (pending_region_position->frame - _last_position.frame);
684 /* total x movement */
685 framecnt_t total_dx = _editor->pixel_to_sample (_total_x_delta + dx);
687 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
688 frameoffset_t const off = i->view->region()->position() + total_dx;
690 dx = dx - _editor->sample_to_pixel_unrounded (off);
691 *pending_region_position = MusicFrame (pending_region_position->frame - off, 0);
701 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
707 const int tavsize = _time_axis_views.size();
708 const int dt = delta > 0 ? +1 : -1;
710 int target = start + delta - skip;
712 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
713 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
715 while (current >= 0 && current != target) {
717 if (current < 0 && dt < 0) {
720 if (current >= tavsize && dt > 0) {
723 if (current < 0 || current >= tavsize) {
727 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
728 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
732 if (distance_only && current == start + delta) {
740 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
742 if (_y_constrained) {
746 const int tavsize = _time_axis_views.size();
747 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
748 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
749 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
751 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
752 /* already in the drop zone */
753 if (delta_track >= 0) {
754 /* downward motion - OK if others are still not in the dropzone */
763 } else if (n >= tavsize) {
764 /* downward motion into drop zone. That's fine. */
768 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
769 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
770 /* not a track, or the wrong type */
774 double const l = i->layer + delta_layer;
776 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
777 mode to allow the user to place a region below another on layer 0.
779 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
780 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
781 If it has, the layers will be munged later anyway, so it's ok.
787 /* all regions being dragged are ok with this change */
791 struct DraggingViewSorter {
792 bool operator() (const DraggingView& a, const DraggingView& b) {
793 return a.time_axis_view < b.time_axis_view;
798 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
800 double delta_layer = 0;
801 int delta_time_axis_view = 0;
802 int current_pointer_time_axis_view = -1;
804 assert (!_views.empty ());
806 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
808 /* Find the TimeAxisView that the pointer is now over */
809 const double cur_y = current_pointer_y ();
810 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
811 TimeAxisView* tv = r.first;
813 if (!tv && cur_y < 0) {
814 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
818 /* find drop-zone y-position */
819 Coord last_track_bottom_edge;
820 last_track_bottom_edge = 0;
821 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
822 if (!(*t)->hidden()) {
823 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
828 if (tv && tv->view()) {
829 /* the mouse is over a track */
830 double layer = r.second;
832 if (first_move && tv->view()->layer_display() == Stacked) {
833 tv->view()->set_layer_display (Expanded);
836 /* Here's the current pointer position in terms of time axis view and layer */
837 current_pointer_time_axis_view = find_time_axis_view (tv);
838 assert(current_pointer_time_axis_view >= 0);
840 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
842 /* Work out the change in y */
844 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
845 if (!rtv || !rtv->is_track()) {
846 /* ignore non-tracks early on. we can't move any regions on them */
847 } else if (_last_pointer_time_axis_view < 0) {
848 /* Was in the drop-zone, now over a track.
849 * Hence it must be an upward move (from the bottom)
851 * track_index is still -1, so delta must be set to
852 * move up the correct number of tracks from the bottom.
854 * This is necessary because steps may be skipped if
855 * the bottom-most track is not a valid target and/or
856 * if there are hidden tracks at the bottom.
857 * Hence the initial offset (_ddropzone) as well as the
858 * last valid pointer position (_pdropzone) need to be
859 * taken into account.
861 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
863 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
866 /* TODO needs adjustment per DraggingView,
868 * e.g. select one region on the top-layer of a track
869 * and one region which is at the bottom-layer of another track
872 * Indicated drop-zones and layering is wrong.
873 * and may infer additional layers on the target-track
874 * (depending how many layers the original track had).
876 * Or select two regions (different layers) on a same track,
877 * move across a non-layer track.. -> layering info is lost.
878 * on drop either of the regions may be on top.
880 * Proposed solution: screw it :) well,
881 * don't use delta_layer, use an absolute value
882 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
883 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
884 * 3) iterate over all DraggingView, find the one that is over the track with most layers
885 * 4) proportionally scale layer to layers available on target
887 delta_layer = current_pointer_layer - _last_pointer_layer;
890 /* for automation lanes, there is a TimeAxisView but no ->view()
891 * if (!tv) -> dropzone
893 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
894 /* Moving into the drop-zone.. */
895 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
896 /* delta_time_axis_view may not be sufficient to move into the DZ
897 * the mouse may enter it, but it may not be a valid move due to
900 * -> remember the delta needed to move into the dropzone
902 _ddropzone = delta_time_axis_view;
903 /* ..but subtract hidden tracks (or routes) at the bottom.
904 * we silently move mover them
906 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
907 - _time_axis_views.size();
909 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
910 /* move around inside the zone.
911 * This allows to move further down until all regions are in the zone.
913 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
914 assert(ptr_y >= last_track_bottom_edge);
915 assert(_ddropzone > 0);
917 /* calculate mouse position in 'tracks' below last track. */
918 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
919 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
921 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
923 delta_time_axis_view = dzpos - _pdropzone;
924 } else if (dzpos < _pdropzone && _ndropzone > 0) {
925 // move up inside the DZ
926 delta_time_axis_view = dzpos - _pdropzone;
930 /* Work out the change in x */
931 TempoMap& tmap = _editor->session()->tempo_map();
932 MusicFrame pending_region_position (0, 0);
933 double const x_delta = compute_x_delta (event, &pending_region_position);
935 double const last_pos_qn = tmap.exact_qn_at_frame (_last_position.frame, _last_position.division);
936 double const qn_delta = tmap.exact_qn_at_frame (pending_region_position.frame, pending_region_position.division) - last_pos_qn;
938 _last_position = pending_region_position;
940 /* calculate hidden tracks in current y-axis delta */
942 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
943 /* The mouse is more than one track below the dropzone.
944 * distance calculation is not needed (and would not work, either
945 * because the dropzone is "packed").
947 * Except when [partially] moving regions out of dropzone in a large step.
948 * (the mouse may or may not remain in the DZ)
949 * Hidden tracks at the bottom of the TAV need to be skipped.
951 * This also handles the case if the mouse entered the DZ
952 * in a large step (exessive delta), either due to fast-movement,
953 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
955 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
956 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
958 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
959 -_time_axis_views.size() - dt;
962 else if (_last_pointer_time_axis_view < 0) {
963 /* Moving out of the zone. Check for hidden tracks at the bottom. */
964 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
965 -_time_axis_views.size() - delta_time_axis_view;
967 /* calculate hidden tracks that are skipped by the pointer movement */
968 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
969 - _last_pointer_time_axis_view
970 - delta_time_axis_view;
973 /* Verify change in y */
974 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
975 /* this y movement is not allowed, so do no y movement this time */
976 delta_time_axis_view = 0;
981 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
982 /* haven't reached next snap point, and we're not switching
983 trackviews nor layers. nothing to do.
988 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
989 PlaylistDropzoneMap playlist_dropzone_map;
990 _ndropzone = 0; // number of elements currently in the dropzone
993 /* sort views by time_axis.
994 * This retains track order in the dropzone, regardless
995 * of actual selection order
997 _views.sort (DraggingViewSorter());
999 /* count number of distinct tracks of all regions
1000 * being dragged, used for dropzone.
1002 int prev_track = -1;
1003 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1004 if (i->time_axis_view != prev_track) {
1005 prev_track = i->time_axis_view;
1011 _views.back().time_axis_view -
1012 _views.front().time_axis_view;
1014 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1015 - _views.back().time_axis_view;
1017 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1021 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1023 RegionView* rv = i->view;
1028 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1035 /* reparent the regionview into a group above all
1039 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1040 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1041 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1042 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1043 /* move the item so that it continues to appear at the
1044 same location now that its parent has changed.
1046 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1049 /* If we have moved tracks, we'll fudge the layer delta so that the
1050 region gets moved back onto layer 0 on its new track; this avoids
1051 confusion when dragging regions from non-zero layers onto different
1054 double this_delta_layer = delta_layer;
1055 if (delta_time_axis_view != 0) {
1056 this_delta_layer = - i->layer;
1059 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1061 int track_index = i->time_axis_view + this_delta_time_axis_view;
1062 assert(track_index >= 0);
1064 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1065 /* Track is in the Dropzone */
1067 i->time_axis_view = track_index;
1068 assert(i->time_axis_view >= (int) _time_axis_views.size());
1071 double yposition = 0;
1072 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1073 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1076 /* store index of each new playlist as a negative count, starting at -1 */
1078 if (pdz == playlist_dropzone_map.end()) {
1079 /* compute where this new track (which doesn't exist yet) will live
1082 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1084 /* How high is this region view ? */
1086 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1087 ArdourCanvas::Rect bbox;
1090 bbox = obbox.get ();
1093 last_track_bottom_edge += bbox.height();
1095 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1098 yposition = pdz->second;
1101 /* values are zero or negative, hence the use of min() */
1102 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1105 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1107 mrv->apply_note_range (60, 71, true);
1111 /* The TimeAxisView that this region is now over */
1112 TimeAxisView* current_tv = _time_axis_views[track_index];
1114 /* Ensure it is moved from stacked -> expanded if appropriate */
1115 if (current_tv->view()->layer_display() == Stacked) {
1116 current_tv->view()->set_layer_display (Expanded);
1119 /* We're only allowed to go -ve in layer on Expanded views */
1120 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1121 this_delta_layer = - i->layer;
1125 rv->set_height (current_tv->view()->child_height ());
1127 /* Update show/hidden status as the region view may have come from a hidden track,
1128 or have moved to one.
1130 if (current_tv->hidden ()) {
1131 rv->get_canvas_group()->hide ();
1133 rv->get_canvas_group()->show ();
1136 /* Update the DraggingView */
1137 i->time_axis_view = track_index;
1138 i->layer += this_delta_layer;
1141 _editor->mouse_brush_insert_region (rv, pending_region_position.frame);
1145 /* Get the y coordinate of the top of the track that this region is now over */
1146 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1148 /* And adjust for the layer that it should be on */
1149 StreamView* cv = current_tv->view ();
1150 switch (cv->layer_display ()) {
1154 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1157 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1161 /* need to get the parent of the regionview
1162 * canvas group and get its position in
1163 * equivalent coordinate space as the trackview
1164 * we are now dragging over.
1167 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1171 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1173 MidiStreamView* msv;
1174 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1175 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1180 /* Now move the region view */
1181 if (rv->region()->position_lock_style() == MusicTime) {
1182 double const last_qn = tmap.quarter_note_at_frame (rv->get_position());
1183 framepos_t const x_pos_music = tmap.frame_at_quarter_note (last_qn + qn_delta);
1185 rv->set_position (x_pos_music, 0);
1187 rv->move (x_delta, y_delta);
1190 } /* foreach region */
1192 _total_x_delta += x_delta;
1194 if (x_delta != 0 && !_brushing) {
1195 show_verbose_cursor_time (_last_position.frame);
1198 /* keep track of pointer movement */
1200 /* the pointer is currently over a time axis view */
1202 if (_last_pointer_time_axis_view < 0) {
1203 /* last motion event was not over a time axis view
1204 * or last y-movement out of the dropzone was not valid
1207 if (delta_time_axis_view < 0) {
1208 /* in the drop zone, moving up */
1210 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1211 * We do not use negative _last_pointer_time_axis_view because
1212 * the dropzone is "packed" (the actual track offset is ignored)
1214 * As opposed to the actual number
1215 * of elements in the dropzone (_ndropzone)
1216 * _pdropzone is not constrained. This is necessary
1217 * to allow moving multiple regions with y-distance
1220 * There can be 0 elements in the dropzone,
1221 * even though the drag-pointer is inside the DZ.
1224 * [ Audio-track, Midi-track, Audio-track, DZ ]
1225 * move regions from both audio tracks at the same time into the
1226 * DZ by grabbing the region in the bottom track.
1228 assert(current_pointer_time_axis_view >= 0);
1229 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1233 /* only move out of the zone if the movement is OK */
1234 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1235 assert(delta_time_axis_view < 0);
1236 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1237 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1238 * the current position can be calculated as follows:
1240 // a well placed oofus attack can still throw this off.
1241 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1242 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1245 /* last motion event was also over a time axis view */
1246 _last_pointer_time_axis_view += delta_time_axis_view;
1247 assert(_last_pointer_time_axis_view >= 0);
1252 /* the pointer is not over a time axis view */
1253 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1254 _pdropzone += delta_time_axis_view - delta_skip;
1255 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1258 _last_pointer_layer += delta_layer;
1262 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1264 if (_copy && first_move) {
1265 if (_x_constrained && !_brushing) {
1266 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1267 } else if (!_brushing) {
1268 _editor->begin_reversible_command (Operations::region_copy);
1269 } else if (_brushing) {
1270 _editor->begin_reversible_command (Operations::drag_region_brush);
1272 /* duplicate the regionview(s) and region(s) */
1274 list<DraggingView> new_regionviews;
1276 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1278 RegionView* rv = i->view;
1279 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1280 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1282 const boost::shared_ptr<const Region> original = rv->region();
1283 boost::shared_ptr<Region> region_copy;
1285 region_copy = RegionFactory::create (original, true);
1287 /* need to set this so that the drop zone code can work. This doesn't
1288 actually put the region into the playlist, but just sets a weak pointer
1291 region_copy->set_playlist (original->playlist());
1295 boost::shared_ptr<AudioRegion> audioregion_copy
1296 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1298 nrv = new AudioRegionView (*arv, audioregion_copy);
1300 boost::shared_ptr<MidiRegion> midiregion_copy
1301 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1302 nrv = new MidiRegionView (*mrv, midiregion_copy);
1307 nrv->get_canvas_group()->show ();
1308 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1310 /* swap _primary to the copy */
1312 if (rv == _primary) {
1316 /* ..and deselect the one we copied */
1318 rv->set_selected (false);
1321 if (!new_regionviews.empty()) {
1323 /* reflect the fact that we are dragging the copies */
1325 _views = new_regionviews;
1327 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1330 } else if (!_copy && first_move) {
1331 if (_x_constrained && !_brushing) {
1332 _editor->begin_reversible_command (_("fixed time region drag"));
1333 } else if (!_brushing) {
1334 _editor->begin_reversible_command (Operations::region_drag);
1335 } else if (_brushing) {
1336 _editor->begin_reversible_command (Operations::drag_region_brush);
1339 RegionMotionDrag::motion (event, first_move);
1343 RegionMotionDrag::finished (GdkEvent *, bool)
1345 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1346 if (!(*i)->view()) {
1350 if ((*i)->view()->layer_display() == Expanded) {
1351 (*i)->view()->set_layer_display (Stacked);
1357 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1359 RegionMotionDrag::finished (ev, movement_occurred);
1361 if (!movement_occurred) {
1365 if (was_double_click() && !_views.empty()) {
1366 DraggingView dv = _views.front();
1367 _editor->edit_region (dv.view);
1373 assert (!_views.empty ());
1375 /* We might have hidden region views so that they weren't visible during the drag
1376 (when they have been reparented). Now everything can be shown again, as region
1377 views are back in their track parent groups.
1379 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1380 i->view->get_canvas_group()->show ();
1383 bool const changed_position = (_last_position.frame != _primary->region()->position());
1384 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1408 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1410 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1413 TimeAxisView* tav = 0;
1415 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1416 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1417 uint32_t output_chan = region->n_channels();
1418 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1419 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1421 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1422 tav =_editor->axis_view_from_stripable (audio_tracks.front());
1424 ChanCount one_midi_port (DataType::MIDI, 1);
1425 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1426 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1427 Config->get_strict_io () || Profile->get_mixbus (),
1428 boost::shared_ptr<ARDOUR::PluginInfo>(),
1429 (ARDOUR::Plugin::PresetRecord*) 0,
1430 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1431 tav = _editor->axis_view_from_stripable (midi_tracks.front());
1435 tav->set_height (original->current_height());
1438 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1441 return dynamic_cast<RouteTimeAxisView*> (tav);
1445 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, MusicFrame last_position, int32_t const ev_state)
1447 RegionSelection new_views;
1448 PlaylistSet modified_playlists;
1449 RouteTimeAxisView* new_time_axis_view = 0;
1450 framecnt_t const drag_delta = _primary->region()->position() - _last_position.frame;
1452 TempoMap& tmap (_editor->session()->tempo_map());
1453 const double last_pos_qn = tmap.exact_qn_at_frame (last_position.frame, last_position.division);
1454 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1457 /* all changes were made during motion event handlers */
1459 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1463 _editor->commit_reversible_command ();
1467 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1468 PlaylistMapping playlist_mapping;
1470 /* insert the regions into their new playlists */
1471 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1473 RouteTimeAxisView* dest_rtv = 0;
1475 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1479 MusicFrame where (0, 0);
1480 double quarter_note;
1482 if (changed_position && !_x_constrained) {
1483 where.set (i->view->region()->position() - drag_delta, 0);
1484 quarter_note = i->view->region()->quarter_note() - qn_delta;
1486 /* region has not moved - divisor will not affect musical pos */
1487 where.set (i->view->region()->position(), 0);
1488 quarter_note = i->view->region()->quarter_note();
1491 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1492 /* dragged to drop zone */
1494 PlaylistMapping::iterator pm;
1496 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1497 /* first region from this original playlist: create a new track */
1498 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1499 if(!new_time_axis_view) {
1503 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1504 dest_rtv = new_time_axis_view;
1506 /* we already created a new track for regions from this playlist, use it */
1507 dest_rtv = pm->second;
1510 /* destination time axis view is the one we dragged to */
1511 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1514 if (dest_rtv != 0) {
1515 RegionView* new_view;
1516 if (i->view == _primary && !_x_constrained) {
1517 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, last_position, last_pos_qn,
1518 modified_playlists, true);
1520 if (i->view->region()->position_lock_style() == AudioTime) {
1521 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1522 modified_playlists);
1524 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1525 modified_playlists, true);
1529 if (new_view != 0) {
1530 new_views.push_back (new_view);
1534 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1535 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1538 list<DraggingView>::const_iterator next = i;
1544 /* If we've created new regions either by copying or moving
1545 to a new track, we want to replace the old selection with the new ones
1548 if (new_views.size() > 0) {
1549 _editor->selection->set (new_views);
1552 /* write commands for the accumulated diffs for all our modified playlists */
1553 add_stateful_diff_commands_for_playlists (modified_playlists);
1555 _editor->commit_reversible_command ();
1559 RegionMoveDrag::finished_no_copy (
1560 bool const changed_position,
1561 bool const changed_tracks,
1562 MusicFrame last_position,
1563 int32_t const ev_state
1566 RegionSelection new_views;
1567 PlaylistSet modified_playlists;
1568 PlaylistSet frozen_playlists;
1569 set<RouteTimeAxisView*> views_to_update;
1570 RouteTimeAxisView* new_time_axis_view = 0;
1571 framecnt_t const drag_delta = _primary->region()->position() - _last_position.frame;
1573 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1574 PlaylistMapping playlist_mapping;
1576 TempoMap& tmap (_editor->session()->tempo_map());
1577 const double last_pos_qn = tmap.exact_qn_at_frame (last_position.frame, last_position.division);
1578 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1580 std::set<boost::shared_ptr<const Region> > uniq;
1581 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1583 RegionView* rv = i->view;
1584 RouteTimeAxisView* dest_rtv = 0;
1586 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1591 if (uniq.find (rv->region()) != uniq.end()) {
1592 /* prevent duplicate moves when selecting regions from shared playlists */
1596 uniq.insert(rv->region());
1598 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1599 /* dragged to drop zone */
1601 PlaylistMapping::iterator pm;
1603 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1604 /* first region from this original playlist: create a new track */
1605 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1606 if(!new_time_axis_view) { // New track creation failed
1610 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1611 dest_rtv = new_time_axis_view;
1613 /* we already created a new track for regions from this playlist, use it */
1614 dest_rtv = pm->second;
1618 /* destination time axis view is the one we dragged to */
1619 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1624 double const dest_layer = i->layer;
1626 views_to_update.insert (dest_rtv);
1628 MusicFrame where (0, 0);
1629 double quarter_note;
1631 if (changed_position && !_x_constrained) {
1632 where.set (rv->region()->position() - drag_delta, 0);
1633 quarter_note = i->view->region()->quarter_note() - qn_delta;
1635 where.set (rv->region()->position(), 0);
1636 quarter_note = i->view->region()->quarter_note();
1639 if (changed_tracks) {
1641 /* insert into new playlist */
1642 RegionView* new_view;
1643 if (rv == _primary) {
1644 new_view = insert_region_into_playlist (
1645 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, last_position, last_pos_qn,
1646 modified_playlists, true
1649 if (rv->region()->position_lock_style() == AudioTime) {
1651 new_view = insert_region_into_playlist (
1652 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1656 new_view = insert_region_into_playlist (
1657 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1658 modified_playlists, true
1663 if (new_view == 0) {
1668 new_views.push_back (new_view);
1670 /* remove from old playlist */
1672 /* the region that used to be in the old playlist is not
1673 moved to the new one - we use a copy of it. as a result,
1674 any existing editor for the region should no longer be
1677 rv->hide_region_editor();
1680 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1684 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1686 /* this movement may result in a crossfade being modified, or a layering change,
1687 so we need to get undo data from the playlist as well as the region.
1690 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1692 playlist->clear_changes ();
1695 rv->region()->clear_changes ();
1698 motion on the same track. plonk the previously reparented region
1699 back to its original canvas group (its streamview).
1700 No need to do anything for copies as they are fake regions which will be deleted.
1703 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1704 rv->get_canvas_group()->set_y_position (i->initial_y);
1707 /* just change the model */
1708 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1709 playlist->set_layer (rv->region(), dest_layer);
1712 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1714 r = frozen_playlists.insert (playlist);
1717 playlist->freeze ();
1719 if (rv == _primary) {
1720 rv->region()->set_position (where.frame, last_position.division);
1722 if (rv->region()->position_lock_style() == AudioTime) {
1723 /* move by frame offset */
1724 rv->region()->set_position (where.frame, 0);
1726 /* move by music offset */
1727 rv->region()->set_position_music (rv->region()->quarter_note() - qn_delta);
1730 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1733 if (changed_tracks) {
1735 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1736 was selected in all of them, then removing it from a playlist will have removed all
1737 trace of it from _views (i.e. there were N regions selected, we removed 1,
1738 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1739 corresponding regionview, and _views is now empty).
1741 This could have invalidated any and all iterators into _views.
1743 The heuristic we use here is: if the region selection is empty, break out of the loop
1744 here. if the region selection is not empty, then restart the loop because we know that
1745 we must have removed at least the region(view) we've just been working on as well as any
1746 that we processed on previous iterations.
1748 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1749 we can just iterate.
1753 if (_views.empty()) {
1764 /* If we've created new regions either by copying or moving
1765 to a new track, we want to replace the old selection with the new ones
1768 if (new_views.size() > 0) {
1769 _editor->selection->set (new_views);
1772 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1776 /* write commands for the accumulated diffs for all our modified playlists */
1777 add_stateful_diff_commands_for_playlists (modified_playlists);
1778 /* applies to _brushing */
1779 _editor->commit_reversible_command ();
1781 /* We have futzed with the layering of canvas items on our streamviews.
1782 If any region changed layer, this will have resulted in the stream
1783 views being asked to set up their region views, and all will be well.
1784 If not, we might now have badly-ordered region views. Ask the StreamViews
1785 involved to sort themselves out, just in case.
1788 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1789 (*i)->view()->playlist_layered ((*i)->track ());
1793 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1794 * @param region Region to remove.
1795 * @param playlist playlist To remove from.
1796 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1797 * that clear_changes () is only called once per playlist.
1800 RegionMoveDrag::remove_region_from_playlist (
1801 boost::shared_ptr<Region> region,
1802 boost::shared_ptr<Playlist> playlist,
1803 PlaylistSet& modified_playlists
1806 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1809 playlist->clear_changes ();
1812 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1816 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1817 * clearing the playlist's diff history first if necessary.
1818 * @param region Region to insert.
1819 * @param dest_rtv Destination RouteTimeAxisView.
1820 * @param dest_layer Destination layer.
1821 * @param where Destination position.
1822 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1823 * that clear_changes () is only called once per playlist.
1824 * @return New RegionView, or 0 if no insert was performed.
1827 RegionMoveDrag::insert_region_into_playlist (
1828 boost::shared_ptr<Region> region,
1829 RouteTimeAxisView* dest_rtv,
1832 double quarter_note,
1833 PlaylistSet& modified_playlists,
1837 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1838 if (!dest_playlist) {
1842 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1843 _new_region_view = 0;
1844 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1846 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1847 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1849 dest_playlist->clear_changes ();
1852 dest_playlist->add_region (region, where.frame, 1.0, false, where.division, quarter_note, true);
1854 dest_playlist->add_region (region, where.frame, 1.0, false, where.division);
1857 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1858 dest_playlist->set_layer (region, dest_layer);
1863 assert (_new_region_view);
1865 return _new_region_view;
1869 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1871 _new_region_view = rv;
1875 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1877 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1878 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1880 _editor->session()->add_command (c);
1889 RegionMoveDrag::aborted (bool movement_occurred)
1893 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1894 list<DraggingView>::const_iterator next = i;
1903 RegionMotionDrag::aborted (movement_occurred);
1908 RegionMotionDrag::aborted (bool)
1910 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1912 StreamView* sview = (*i)->view();
1915 if (sview->layer_display() == Expanded) {
1916 sview->set_layer_display (Stacked);
1921 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1922 RegionView* rv = i->view;
1923 TimeAxisView* tv = &(rv->get_time_axis_view ());
1924 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1926 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1927 rv->get_canvas_group()->set_y_position (0);
1929 rv->move (-_total_x_delta, 0);
1930 rv->set_height (rtv->view()->child_height ());
1934 /** @param b true to brush, otherwise false.
1935 * @param c true to make copies of the regions being moved, otherwise false.
1937 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1938 : RegionMotionDrag (e, i, p, v, b)
1940 , _new_region_view (0)
1942 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1945 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1946 if (rtv && rtv->is_track()) {
1947 speed = rtv->track()->speed ();
1950 _last_position = MusicFrame (static_cast<framepos_t> (_primary->region()->position() / speed), 0);
1954 RegionMoveDrag::setup_pointer_frame_offset ()
1956 _pointer_frame_offset = raw_grab_frame() - _last_position.frame;
1959 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1960 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1962 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1964 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1965 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1967 _primary = v->view()->create_region_view (r, false, false);
1969 _primary->get_canvas_group()->show ();
1970 _primary->set_position (pos, 0);
1971 _views.push_back (DraggingView (_primary, this, v));
1973 _last_position = MusicFrame (pos, 0);
1975 _item = _primary->get_canvas_group ();
1979 RegionInsertDrag::finished (GdkEvent * event, bool)
1981 int pos = _views.front().time_axis_view;
1982 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1984 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1986 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1987 _primary->get_canvas_group()->set_y_position (0);
1989 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1991 _editor->begin_reversible_command (Operations::insert_region);
1992 playlist->clear_changes ();
1993 _editor->snap_to_with_modifier (_last_position, event);
1995 playlist->add_region (_primary->region (), _last_position.frame, 1.0, false, _last_position.division);
1997 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
1998 if (Config->get_edit_mode() == Ripple) {
1999 playlist->ripple (_last_position.frame, _primary->region()->length(), _primary->region());
2002 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2003 _editor->commit_reversible_command ();
2011 RegionInsertDrag::aborted (bool)
2018 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2019 : RegionMoveDrag (e, i, p, v, false, false)
2021 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2024 struct RegionSelectionByPosition {
2025 bool operator() (RegionView*a, RegionView* b) {
2026 return a->region()->position () < b->region()->position();
2031 RegionSpliceDrag::motion (GdkEvent* event, bool)
2033 /* Which trackview is this ? */
2035 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2036 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2038 /* The region motion is only processed if the pointer is over
2042 if (!tv || !tv->is_track()) {
2043 /* To make sure we hide the verbose canvas cursor when the mouse is
2044 not held over an audio track.
2046 _editor->verbose_cursor()->hide ();
2049 _editor->verbose_cursor()->show ();
2054 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2060 RegionSelection copy;
2061 _editor->selection->regions.by_position(copy);
2063 framepos_t const pf = adjusted_current_frame (event);
2065 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2067 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2073 boost::shared_ptr<Playlist> playlist;
2075 if ((playlist = atv->playlist()) == 0) {
2079 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2084 if (pf < (*i)->region()->last_frame() + 1) {
2088 if (pf > (*i)->region()->first_frame()) {
2094 playlist->shuffle ((*i)->region(), dir);
2099 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2101 RegionMoveDrag::finished (event, movement_occurred);
2105 RegionSpliceDrag::aborted (bool)
2115 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2118 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
2120 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2121 RegionSelection to_ripple;
2122 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2123 if ((*i)->position() >= where) {
2124 to_ripple.push_back (rtv->view()->find_view(*i));
2128 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2129 if (!exclude.contains (*i)) {
2130 // the selection has already been added to _views
2132 if (drag_in_progress) {
2133 // do the same things that RegionMotionDrag::motion does when
2134 // first_move is true, for the region views that we're adding
2135 // to _views this time
2138 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2139 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2140 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2141 rvg->reparent (_editor->_drag_motion_group);
2143 // we only need to move in the y direction
2144 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2149 _views.push_back (DraggingView (*i, this, tav));
2155 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2158 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2159 // we added all the regions after the selection
2161 std::list<DraggingView>::iterator to_erase = i++;
2162 if (!_editor->selection->regions.contains (to_erase->view)) {
2163 // restore the non-selected regions to their original playlist & positions,
2164 // and then ripple them back by the length of the regions that were dragged away
2165 // do the same things as RegionMotionDrag::aborted
2167 RegionView *rv = to_erase->view;
2168 TimeAxisView* tv = &(rv->get_time_axis_view ());
2169 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2172 // plonk them back onto their own track
2173 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2174 rv->get_canvas_group()->set_y_position (0);
2178 // move the underlying region to match the view
2179 rv->region()->set_position (rv->region()->position() + amount);
2181 // restore the view to match the underlying region's original position
2182 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2185 rv->set_height (rtv->view()->child_height ());
2186 _views.erase (to_erase);
2192 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2194 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2196 return allow_moves_across_tracks;
2204 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2205 : RegionMoveDrag (e, i, p, v, false, false)
2207 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2208 // compute length of selection
2209 RegionSelection selected_regions = _editor->selection->regions;
2210 selection_length = selected_regions.end_frame() - selected_regions.start();
2212 // we'll only allow dragging to another track in ripple mode if all the regions
2213 // being dragged start off on the same track
2214 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2217 exclude = new RegionList;
2218 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2219 exclude->push_back((*i)->region());
2222 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2223 RegionSelection copy;
2224 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2226 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2227 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2229 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2230 // find ripple start point on each applicable playlist
2231 RegionView *first_selected_on_this_track = NULL;
2232 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2233 if ((*i)->region()->playlist() == (*pi)) {
2234 // region is on this playlist - it's the first, because they're sorted
2235 first_selected_on_this_track = *i;
2239 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2240 add_all_after_to_views (
2241 &first_selected_on_this_track->get_time_axis_view(),
2242 first_selected_on_this_track->region()->position(),
2243 selected_regions, false);
2246 if (allow_moves_across_tracks) {
2247 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2255 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2257 /* Which trackview is this ? */
2259 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2260 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2262 /* The region motion is only processed if the pointer is over
2266 if (!tv || !tv->is_track()) {
2267 /* To make sure we hide the verbose canvas cursor when the mouse is
2268 not held over an audiotrack.
2270 _editor->verbose_cursor()->hide ();
2274 framepos_t where = adjusted_current_frame (event);
2275 assert (where >= 0);
2276 MusicFrame after (0, 0);
2277 double delta = compute_x_delta (event, &after);
2279 framecnt_t amount = _editor->pixel_to_sample (delta);
2281 if (allow_moves_across_tracks) {
2282 // all the originally selected regions were on the same track
2284 framecnt_t adjust = 0;
2285 if (prev_tav && tv != prev_tav) {
2286 // dragged onto a different track
2287 // remove the unselected regions from _views, restore them to their original positions
2288 // and add the regions after the drop point on the new playlist to _views instead.
2289 // undo the effect of rippling the previous playlist, and include the effect of removing
2290 // the dragged region(s) from this track
2292 remove_unselected_from_views (prev_amount, false);
2293 // ripple previous playlist according to the regions that have been removed onto the new playlist
2294 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2297 // move just the selected regions
2298 RegionMoveDrag::motion(event, first_move);
2300 // ensure that the ripple operation on the new playlist inserts selection_length time
2301 adjust = selection_length;
2302 // ripple the new current playlist
2303 tv->playlist()->ripple (where, amount+adjust, exclude);
2305 // add regions after point where drag entered this track to subsequent ripples
2306 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2309 // motion on same track
2310 RegionMoveDrag::motion(event, first_move);
2314 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2315 prev_position = where;
2317 // selection encompasses multiple tracks - just drag
2318 // cross-track drags are forbidden
2319 RegionMoveDrag::motion(event, first_move);
2322 if (!_x_constrained) {
2323 prev_amount += amount;
2326 _last_position = after;
2330 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2332 if (!movement_occurred) {
2336 if (was_double_click() && !_views.empty()) {
2337 DraggingView dv = _views.front();
2338 _editor->edit_region (dv.view);
2344 _editor->begin_reversible_command(_("Ripple drag"));
2346 // remove the regions being rippled from the dragging view, updating them to
2347 // their new positions
2348 remove_unselected_from_views (prev_amount, true);
2350 if (allow_moves_across_tracks) {
2352 // if regions were dragged across tracks, we've rippled any later
2353 // regions on the track the regions were dragged off, so we need
2354 // to add the original track to the undo record
2355 orig_tav->playlist()->clear_changes();
2356 vector<Command*> cmds;
2357 orig_tav->playlist()->rdiff (cmds);
2358 _editor->session()->add_commands (cmds);
2360 if (prev_tav && prev_tav != orig_tav) {
2361 prev_tav->playlist()->clear_changes();
2362 vector<Command*> cmds;
2363 prev_tav->playlist()->rdiff (cmds);
2364 _editor->session()->add_commands (cmds);
2367 // selection spanned multiple tracks - all will need adding to undo record
2369 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2370 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2372 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2373 (*pi)->clear_changes();
2374 vector<Command*> cmds;
2375 (*pi)->rdiff (cmds);
2376 _editor->session()->add_commands (cmds);
2380 // other modified playlists are added to undo by RegionMoveDrag::finished()
2381 RegionMoveDrag::finished (event, movement_occurred);
2382 _editor->commit_reversible_command();
2386 RegionRippleDrag::aborted (bool movement_occurred)
2388 RegionMoveDrag::aborted (movement_occurred);
2393 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2395 _view (dynamic_cast<MidiTimeAxisView*> (v))
2397 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2403 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2406 _editor->begin_reversible_command (_("create region"));
2407 _region = add_midi_region (_view, false);
2408 _view->playlist()->freeze ();
2411 framepos_t const f = adjusted_current_frame (event);
2412 if (f < grab_frame()) {
2413 _region->set_initial_position (f);
2416 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2417 so that if this region is duplicated, its duplicate starts on
2418 a snap point rather than 1 frame after a snap point. Otherwise things get
2419 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2420 place snapped notes at the start of the region.
2423 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2424 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2430 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2432 if (!movement_occurred) {
2433 add_midi_region (_view, true);
2435 _view->playlist()->thaw ();
2436 _editor->commit_reversible_command();
2441 RegionCreateDrag::aborted (bool)
2444 _view->playlist()->thaw ();
2450 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2455 , _was_selected (false)
2458 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2462 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2464 Gdk::Cursor* cursor;
2465 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2467 float x_fraction = cnote->mouse_x_fraction ();
2469 if (x_fraction > 0.0 && x_fraction < 0.25) {
2470 cursor = _editor->cursors()->left_side_trim;
2473 cursor = _editor->cursors()->right_side_trim;
2477 Drag::start_grab (event, cursor);
2479 region = &cnote->region_view();
2482 temp = region->snap_to_pixel (cnote->x0 (), true);
2483 _snap_delta = temp - cnote->x0 ();
2487 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2492 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2493 if (ms.size() > 1) {
2494 /* has to be relative, may make no sense otherwise */
2498 if (!(_was_selected = cnote->selected())) {
2500 /* tertiary-click means extend selection - we'll do that on button release,
2501 so don't add it here, because otherwise we make it hard to figure
2502 out the "extend-to" range.
2505 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2508 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2511 region->note_selected (cnote, true);
2513 _editor->get_selection().clear_points();
2514 region->unique_select (cnote);
2521 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2523 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2525 _editor->begin_reversible_command (_("resize notes"));
2527 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2528 MidiRegionSelection::iterator next;
2531 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2533 mrv->begin_resizing (at_front);
2539 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2540 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2542 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2546 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2548 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2549 if (_editor->snap_mode () != SnapOff) {
2553 if (_editor->snap_mode () == SnapOff) {
2555 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2556 if (apply_snap_delta) {
2562 if (apply_snap_delta) {
2566 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2572 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2574 if (!movement_occurred) {
2575 /* no motion - select note */
2576 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2577 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2578 _editor->current_mouse_mode() == Editing::MouseDraw) {
2580 bool changed = false;
2582 if (_was_selected) {
2583 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2585 region->note_deselected (cnote);
2588 _editor->get_selection().clear_points();
2589 region->unique_select (cnote);
2593 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2594 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2596 if (!extend && !add && region->selection_size() > 1) {
2597 _editor->get_selection().clear_points();
2598 region->unique_select (cnote);
2600 } else if (extend) {
2601 region->note_selected (cnote, true, true);
2604 /* it was added during button press */
2610 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2611 _editor->commit_reversible_selection_op();
2618 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2619 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2620 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2622 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2625 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2627 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2628 if (_editor->snap_mode () != SnapOff) {
2632 if (_editor->snap_mode () == SnapOff) {
2634 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2635 if (apply_snap_delta) {
2641 if (apply_snap_delta) {
2645 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2649 _editor->commit_reversible_command ();
2653 NoteResizeDrag::aborted (bool)
2655 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2656 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2657 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2659 mrv->abort_resizing ();
2664 AVDraggingView::AVDraggingView (RegionView* v)
2667 initial_position = v->region()->position ();
2670 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2673 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2676 TrackViewList empty;
2678 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2679 std::list<RegionView*> views = rs.by_layer();
2682 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2683 RegionView* rv = (*i);
2684 if (!rv->region()->video_locked()) {
2687 if (rv->region()->locked()) {
2690 _views.push_back (AVDraggingView (rv));
2695 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2697 Drag::start_grab (event);
2698 if (_editor->session() == 0) {
2702 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2708 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2712 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2713 _max_backwards_drag = (
2714 ARDOUR_UI::instance()->video_timeline->get_duration()
2715 + ARDOUR_UI::instance()->video_timeline->get_offset()
2716 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2719 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2720 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2721 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2724 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2727 Timecode::Time timecode;
2728 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2729 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);
2730 show_verbose_cursor_text (buf);
2734 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2736 if (_editor->session() == 0) {
2739 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2743 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2747 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2748 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2750 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2751 dt = - _max_backwards_drag;
2754 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2755 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2757 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2758 RegionView* rv = i->view;
2759 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2762 rv->region()->clear_changes ();
2763 rv->region()->suspend_property_changes();
2765 rv->region()->set_position(i->initial_position + dt);
2766 rv->region_changed(ARDOUR::Properties::position);
2769 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2770 Timecode::Time timecode;
2771 Timecode::Time timediff;
2773 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2774 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2775 snprintf (buf, sizeof (buf),
2776 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2777 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2778 , _("Video Start:"),
2779 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2781 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2783 show_verbose_cursor_text (buf);
2787 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2789 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2796 if (!movement_occurred || ! _editor->session()) {
2800 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2802 _editor->begin_reversible_command (_("Move Video"));
2804 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2805 ARDOUR_UI::instance()->video_timeline->save_undo();
2806 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2807 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2809 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2810 i->view->drag_end();
2811 i->view->region()->resume_property_changes ();
2813 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2816 _editor->session()->maybe_update_session_range(
2817 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2818 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2822 _editor->commit_reversible_command ();
2826 VideoTimeLineDrag::aborted (bool)
2828 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2831 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2832 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2834 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2835 i->view->region()->resume_property_changes ();
2836 i->view->region()->set_position(i->initial_position);
2840 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2841 : RegionDrag (e, i, p, v)
2842 , _operation (StartTrim)
2843 , _preserve_fade_anchor (preserve_fade_anchor)
2844 , _jump_position_when_done (false)
2846 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2850 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2853 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2854 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2856 if (tv && tv->is_track()) {
2857 speed = tv->track()->speed();
2860 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2861 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2862 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2864 framepos_t const pf = adjusted_current_frame (event);
2865 setup_snap_delta (region_start);
2867 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2868 /* Move the contents of the region around without changing the region bounds */
2869 _operation = ContentsTrim;
2870 Drag::start_grab (event, _editor->cursors()->trimmer);
2872 /* These will get overridden for a point trim.*/
2873 if (pf < (region_start + region_length/2)) {
2874 /* closer to front */
2875 _operation = StartTrim;
2876 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2877 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2879 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2883 _operation = EndTrim;
2884 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2885 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2887 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2891 /* jump trim disabled for now
2892 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2893 _jump_position_when_done = true;
2897 switch (_operation) {
2899 show_verbose_cursor_time (region_start);
2902 show_verbose_cursor_duration (region_start, region_end);
2905 show_verbose_cursor_time (pf);
2909 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2910 i->view->region()->suspend_property_changes ();
2915 TrimDrag::motion (GdkEvent* event, bool first_move)
2917 RegionView* rv = _primary;
2920 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2921 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2922 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2923 frameoffset_t frame_delta = 0;
2925 if (tv && tv->is_track()) {
2926 speed = tv->track()->speed();
2928 MusicFrame adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
2929 framecnt_t dt = adj_frame.frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state);
2935 switch (_operation) {
2937 trim_type = "Region start trim";
2940 trim_type = "Region end trim";
2943 trim_type = "Region content trim";
2950 _editor->begin_reversible_command (trim_type);
2952 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2953 RegionView* rv = i->view;
2954 rv->region()->playlist()->clear_owned_changes ();
2956 if (_operation == StartTrim) {
2957 rv->trim_front_starting ();
2960 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2963 arv->temporarily_hide_envelope ();
2967 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2968 insert_result = _editor->motion_frozen_playlists.insert (pl);
2970 if (insert_result.second) {
2976 bool non_overlap_trim = false;
2978 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
2979 non_overlap_trim = true;
2982 /* contstrain trim to fade length */
2983 if (_preserve_fade_anchor) {
2984 switch (_operation) {
2986 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2987 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2989 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2990 if (ar->locked()) continue;
2991 framecnt_t len = ar->fade_in()->back()->when;
2992 if (len < dt) dt = min(dt, len);
2996 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2997 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2999 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3000 if (ar->locked()) continue;
3001 framecnt_t len = ar->fade_out()->back()->when;
3002 if (len < -dt) dt = max(dt, -len);
3010 switch (_operation) {
3012 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3013 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3014 , adj_frame.division);
3016 if (changed && _preserve_fade_anchor) {
3017 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3019 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3020 framecnt_t len = ar->fade_in()->back()->when;
3021 framecnt_t diff = ar->first_frame() - i->initial_position;
3022 framepos_t new_length = len - diff;
3023 i->anchored_fade_length = min (ar->length(), new_length);
3024 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3025 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3032 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3033 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, adj_frame.division);
3034 if (changed && _preserve_fade_anchor) {
3035 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3037 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3038 framecnt_t len = ar->fade_out()->back()->when;
3039 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
3040 framepos_t new_length = len + diff;
3041 i->anchored_fade_length = min (ar->length(), new_length);
3042 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3043 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3051 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
3053 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3054 i->view->move_contents (frame_delta);
3060 switch (_operation) {
3062 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
3065 show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
3068 // show_verbose_cursor_time (frame_delta);
3074 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3076 if (movement_occurred) {
3077 motion (event, false);
3079 if (_operation == StartTrim) {
3080 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3082 /* This must happen before the region's StatefulDiffCommand is created, as it may
3083 `correct' (ahem) the region's _start from being negative to being zero. It
3084 needs to be zero in the undo record.
3086 i->view->trim_front_ending ();
3088 if (_preserve_fade_anchor) {
3089 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3091 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3092 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3093 ar->set_fade_in_length(i->anchored_fade_length);
3094 ar->set_fade_in_active(true);
3097 if (_jump_position_when_done) {
3098 i->view->region()->set_position (i->initial_position);
3101 } else if (_operation == EndTrim) {
3102 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3103 if (_preserve_fade_anchor) {
3104 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3106 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3107 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3108 ar->set_fade_out_length(i->anchored_fade_length);
3109 ar->set_fade_out_active(true);
3112 if (_jump_position_when_done) {
3113 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3118 if (!_editor->selection->selected (_primary)) {
3119 _primary->thaw_after_trim ();
3122 set<boost::shared_ptr<Playlist> > diffed_playlists;
3124 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3125 i->view->thaw_after_trim ();
3126 i->view->enable_display (true);
3128 /* Trimming one region may affect others on the playlist, so we need
3129 to get undo Commands from the whole playlist rather than just the
3130 region. Use diffed_playlists to make sure we don't diff a given
3131 playlist more than once.
3133 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
3134 if (diffed_playlists.find (p) == diffed_playlists.end()) {
3135 vector<Command*> cmds;
3137 _editor->session()->add_commands (cmds);
3138 diffed_playlists.insert (p);
3143 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3147 _editor->motion_frozen_playlists.clear ();
3148 _editor->commit_reversible_command();
3151 /* no mouse movement */
3152 if (adjusted_current_frame (event) != adjusted_frame (_drags->current_pointer_frame(), event, false).frame) {
3153 _editor->point_trim (event, adjusted_current_frame (event));
3157 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3158 i->view->region()->resume_property_changes ();
3163 TrimDrag::aborted (bool movement_occurred)
3165 /* Our motion method is changing model state, so use the Undo system
3166 to cancel. Perhaps not ideal, as this will leave an Undo point
3167 behind which may be slightly odd from the user's point of view.
3171 finished (&ev, true);
3173 if (movement_occurred) {
3174 _editor->session()->undo (1);
3177 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3178 i->view->region()->resume_property_changes ();
3183 TrimDrag::setup_pointer_frame_offset ()
3185 list<DraggingView>::iterator i = _views.begin ();
3186 while (i != _views.end() && i->view != _primary) {
3190 if (i == _views.end()) {
3194 switch (_operation) {
3196 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
3199 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
3206 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3209 , _old_snap_type (e->snap_type())
3210 , _old_snap_mode (e->snap_mode())
3213 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3214 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3216 _real_section = &_marker->meter();
3221 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3223 Drag::start_grab (event, cursor);
3224 show_verbose_cursor_time (adjusted_current_frame(event));
3228 MeterMarkerDrag::setup_pointer_frame_offset ()
3230 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
3234 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3237 // create a dummy marker to catch events, then hide it.
3240 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3242 _marker = new MeterMarker (
3244 *_editor->meter_group,
3245 UIConfiguration::instance().color ("meter marker"),
3247 *new MeterSection (_marker->meter())
3250 /* use the new marker for the grab */
3251 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3254 TempoMap& map (_editor->session()->tempo_map());
3255 /* get current state */
3256 before_state = &map.get_state();
3259 _editor->begin_reversible_command (_("move meter mark"));
3261 _editor->begin_reversible_command (_("copy meter mark"));
3263 Timecode::BBT_Time bbt = _real_section->bbt();
3265 /* we can't add a meter where one currently exists */
3266 if (_real_section->frame() < adjusted_current_frame (event, false)) {
3271 const double beat = map.beat_at_bbt (bbt);
3272 const framepos_t frame = map.frame_at_beat (beat);
3273 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3274 , beat, bbt, frame, _real_section->position_lock_style());
3275 if (!_real_section) {
3281 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3282 if (_real_section->position_lock_style() != AudioTime) {
3283 _editor->set_snap_to (SnapToBar);
3284 _editor->set_snap_mode (SnapNormal);
3288 framepos_t pf = adjusted_current_frame (event);
3290 if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3291 /* never snap to music for audio locked */
3292 pf = adjusted_current_frame (event, false);
3295 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3297 /* fake marker meeds to stay under the mouse, unlike the real one. */
3298 _marker->set_position (adjusted_current_frame (event, false));
3300 show_verbose_cursor_time (_real_section->frame());
3304 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3306 if (!movement_occurred) {
3307 if (was_double_click()) {
3308 _editor->edit_meter_marker (*_marker);
3313 /* reinstate old snap setting */
3314 _editor->set_snap_to (_old_snap_type);
3315 _editor->set_snap_mode (_old_snap_mode);
3317 TempoMap& map (_editor->session()->tempo_map());
3319 XMLNode &after = map.get_state();
3320 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3321 _editor->commit_reversible_command ();
3323 // delete the dummy marker we used for visual representation while moving.
3324 // a new visual marker will show up automatically.
3329 MeterMarkerDrag::aborted (bool moved)
3331 _marker->set_position (_marker->meter().frame ());
3333 /* reinstate old snap setting */
3334 _editor->set_snap_to (_old_snap_type);
3335 _editor->set_snap_mode (_old_snap_mode);
3337 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3338 // delete the dummy marker we used for visual representation while moving.
3339 // a new visual marker will show up automatically.
3344 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3350 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3352 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3353 _real_section = &_marker->tempo();
3354 _movable = !_real_section->initial();
3355 _grab_bpm = _real_section->note_types_per_minute();
3360 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3362 Drag::start_grab (event, cursor);
3363 if (!_real_section->active()) {
3364 show_verbose_cursor_text (_("inactive"));
3366 show_verbose_cursor_time (adjusted_current_frame (event));
3371 TempoMarkerDrag::setup_pointer_frame_offset ()
3373 _pointer_frame_offset = raw_grab_frame() - _real_section->frame();
3377 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3379 if (!_real_section->active()) {
3385 // mvc drag - create a dummy marker to catch events, hide it.
3388 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3390 TempoSection section (_marker->tempo());
3392 _marker = new TempoMarker (
3394 *_editor->tempo_group,
3395 UIConfiguration::instance().color ("tempo marker"),
3397 *new TempoSection (_marker->tempo())
3400 /* use the new marker for the grab */
3401 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3404 TempoMap& map (_editor->session()->tempo_map());
3405 /* get current state */
3406 before_state = &map.get_state();
3409 _editor->begin_reversible_command (_("move tempo mark"));
3412 const Tempo tempo (_marker->tempo());
3413 const framepos_t frame = adjusted_current_frame (event) + 1;
3414 const TempoSection::Type type = _real_section->type();
3416 _editor->begin_reversible_command (_("copy tempo mark"));
3418 if (_real_section->position_lock_style() == MusicTime) {
3419 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3420 _real_section = map.add_tempo (tempo, map.exact_qn_at_frame (frame, divisions), 0, type, MusicTime);
3422 _real_section = map.add_tempo (tempo, 0.0, frame, type, AudioTime);
3425 if (!_real_section) {
3433 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3434 /* use vertical movement to alter tempo .. should be log */
3435 double new_bpm = max (1.5, _grab_bpm + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3437 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type()));
3439 show_verbose_cursor_text (strs.str());
3441 } else if (_movable && !_real_section->locked_to_meter()) {
3444 if (_editor->snap_musical()) {
3445 /* we can't snap to a grid that we are about to move.
3446 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3448 pf = adjusted_current_frame (event, false);
3450 pf = adjusted_current_frame (event);
3453 TempoMap& map (_editor->session()->tempo_map());
3455 /* snap to beat is 1, snap to bar is -1 (sorry) */
3456 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3458 map.gui_set_tempo_position (_real_section, pf, sub_num);
3460 show_verbose_cursor_time (_real_section->frame());
3462 _marker->set_position (adjusted_current_frame (event, false));
3466 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3468 if (!_real_section->active()) {
3471 if (!movement_occurred) {
3472 if (was_double_click()) {
3473 _editor->edit_tempo_marker (*_marker);
3478 TempoMap& map (_editor->session()->tempo_map());
3480 XMLNode &after = map.get_state();
3481 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3482 _editor->commit_reversible_command ();
3484 // delete the dummy marker we used for visual representation while moving.
3485 // a new visual marker will show up automatically.
3490 TempoMarkerDrag::aborted (bool moved)
3492 _marker->set_position (_marker->tempo().frame());
3494 TempoMap& map (_editor->session()->tempo_map());
3495 map.set_state (*before_state, Stateful::current_state_version);
3496 // delete the dummy (hidden) marker we used for events while moving.
3501 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3507 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3512 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3514 Drag::start_grab (event, cursor);
3515 TempoMap& map (_editor->session()->tempo_map());
3516 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
3519 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).note_types_per_minute() << "\n";
3520 sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3521 show_verbose_cursor_text (sstr.str());
3522 finished (event, false);
3526 BBTRulerDrag::setup_pointer_frame_offset ()
3528 TempoMap& map (_editor->session()->tempo_map());
3529 const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame()));
3530 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3533 if (divisions > 0) {
3534 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions);
3536 /* while it makes some sense for the user to determine the division to 'grab',
3537 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3538 and the result over steep tempo curves. Use sixteenths.
3540 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4);
3543 _grab_qn = map.quarter_note_at_beat (beat);
3545 _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn);
3550 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3552 TempoMap& map (_editor->session()->tempo_map());
3555 /* get current state */
3556 before_state = &map.get_state();
3557 _editor->begin_reversible_command (_("stretch tempo"));
3562 if (_editor->snap_musical()) {
3563 pf = adjusted_current_frame (event, false);
3565 pf = adjusted_current_frame (event);
3568 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3569 /* adjust previous tempo to match pointer frame */
3570 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.frame_at_quarter_note (_grab_qn), pf);
3573 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute() << "\n";
3574 sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3575 show_verbose_cursor_text (sstr.str());
3579 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3581 if (!movement_occurred) {
3585 TempoMap& map (_editor->session()->tempo_map());
3587 XMLNode &after = map.get_state();
3588 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3589 _editor->commit_reversible_command ();
3593 BBTRulerDrag::aborted (bool moved)
3596 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3601 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3602 : Drag (e, &c.track_canvas_item(), false)
3607 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3610 /** Do all the things we do when dragging the playhead to make it look as though
3611 * we have located, without actually doing the locate (because that would cause
3612 * the diskstream buffers to be refilled, which is too slow).
3615 CursorDrag::fake_locate (framepos_t t)
3617 if (_editor->session () == 0) {
3621 _editor->playhead_cursor->set_position (t);
3623 Session* s = _editor->session ();
3624 if (s->timecode_transmission_suspended ()) {
3625 framepos_t const f = _editor->playhead_cursor->current_frame ();
3626 /* This is asynchronous so it will be sent "now"
3628 s->send_mmc_locate (f);
3629 /* These are synchronous and will be sent during the next
3632 s->queue_full_time_code ();
3633 s->queue_song_position_pointer ();
3636 show_verbose_cursor_time (t);
3637 _editor->UpdateAllTransportClocks (t);
3641 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3643 Drag::start_grab (event, c);
3644 setup_snap_delta (_editor->playhead_cursor->current_frame());
3646 _grab_zoom = _editor->samples_per_pixel;
3648 MusicFrame where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3650 _editor->snap_to_with_modifier (where, event);
3651 _editor->_dragging_playhead = true;
3653 Session* s = _editor->session ();
3655 /* grab the track canvas item as well */
3657 _cursor.track_canvas_item().grab();
3660 if (_was_rolling && _stop) {
3664 if (s->is_auditioning()) {
3665 s->cancel_audition ();
3669 if (AudioEngine::instance()->connected()) {
3671 /* do this only if we're the engine is connected
3672 * because otherwise this request will never be
3673 * serviced and we'll busy wait forever. likewise,
3674 * notice if we are disconnected while waiting for the
3675 * request to be serviced.
3678 s->request_suspend_timecode_transmission ();
3679 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3680 /* twiddle our thumbs */
3685 fake_locate (where.frame - snap_delta (event->button.state));
3689 CursorDrag::motion (GdkEvent* event, bool)
3691 MusicFrame where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3693 _editor->snap_to_with_modifier (where, event);
3695 if (where.frame != last_pointer_frame()) {
3696 fake_locate (where.frame - snap_delta (event->button.state));
3701 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3703 _editor->_dragging_playhead = false;
3705 _cursor.track_canvas_item().ungrab();
3707 if (!movement_occurred && _stop) {
3711 motion (event, false);
3713 Session* s = _editor->session ();
3715 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3716 _editor->_pending_locate_request = true;
3717 s->request_resume_timecode_transmission ();
3722 CursorDrag::aborted (bool)
3724 _cursor.track_canvas_item().ungrab();
3726 if (_editor->_dragging_playhead) {
3727 _editor->session()->request_resume_timecode_transmission ();
3728 _editor->_dragging_playhead = false;
3731 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false).frame);
3734 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3735 : RegionDrag (e, i, p, v)
3737 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3741 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3743 Drag::start_grab (event, cursor);
3745 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3746 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3747 setup_snap_delta (r->position());
3749 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3753 FadeInDrag::setup_pointer_frame_offset ()
3755 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3756 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3757 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3761 FadeInDrag::motion (GdkEvent* event, bool)
3763 framecnt_t fade_length;
3765 MusicFrame pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3766 _editor->snap_to_with_modifier (pos, event);
3768 pos.frame -= snap_delta (event->button.state);
3770 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3772 if (pos.frame < (region->position() + 64)) {
3773 fade_length = 64; // this should be a minimum defined somewhere
3774 } else if (pos.frame > region->position() + region->length() - region->fade_out()->back()->when) {
3775 fade_length = region->length() - region->fade_out()->back()->when - 1;
3777 fade_length = pos.frame - region->position();
3780 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3782 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3788 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3791 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3795 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3797 if (!movement_occurred) {
3801 framecnt_t fade_length;
3802 MusicFrame pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3804 _editor->snap_to_with_modifier (pos, event);
3805 pos.frame -= snap_delta (event->button.state);
3807 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3809 if (pos.frame < (region->position() + 64)) {
3810 fade_length = 64; // this should be a minimum defined somewhere
3811 } else if (pos.frame >= region->position() + region->length() - region->fade_out()->back()->when) {
3812 fade_length = region->length() - region->fade_out()->back()->when - 1;
3814 fade_length = pos.frame - region->position();
3817 bool in_command = false;
3819 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3821 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3827 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3828 XMLNode &before = alist->get_state();
3830 tmp->audio_region()->set_fade_in_length (fade_length);
3831 tmp->audio_region()->set_fade_in_active (true);
3834 _editor->begin_reversible_command (_("change fade in length"));
3837 XMLNode &after = alist->get_state();
3838 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3842 _editor->commit_reversible_command ();
3847 FadeInDrag::aborted (bool)
3849 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3850 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3856 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3860 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3861 : RegionDrag (e, i, p, v)
3863 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3867 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3869 Drag::start_grab (event, cursor);
3871 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3872 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3873 setup_snap_delta (r->last_frame());
3875 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3879 FadeOutDrag::setup_pointer_frame_offset ()
3881 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3882 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3883 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3887 FadeOutDrag::motion (GdkEvent* event, bool)
3889 framecnt_t fade_length;
3890 MusicFrame pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3892 _editor->snap_to_with_modifier (pos, event);
3893 pos.frame -= snap_delta (event->button.state);
3895 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3897 if (pos.frame > (region->last_frame() - 64)) {
3898 fade_length = 64; // this should really be a minimum fade defined somewhere
3899 } else if (pos.frame <= region->position() + region->fade_in()->back()->when) {
3900 fade_length = region->length() - region->fade_in()->back()->when - 1;
3902 fade_length = region->last_frame() - pos.frame;
3905 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3907 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3913 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3916 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3920 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3922 if (!movement_occurred) {
3926 framecnt_t fade_length;
3927 MusicFrame pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3929 _editor->snap_to_with_modifier (pos, event);
3930 pos.frame -= snap_delta (event->button.state);
3932 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3934 if (pos.frame > (region->last_frame() - 64)) {
3935 fade_length = 64; // this should really be a minimum fade defined somewhere
3936 } else if (pos.frame <= region->position() + region->fade_in()->back()->when) {
3937 fade_length = region->length() - region->fade_in()->back()->when - 1;
3939 fade_length = region->last_frame() - pos.frame;
3942 bool in_command = false;
3944 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3946 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3952 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3953 XMLNode &before = alist->get_state();
3955 tmp->audio_region()->set_fade_out_length (fade_length);
3956 tmp->audio_region()->set_fade_out_active (true);
3959 _editor->begin_reversible_command (_("change fade out length"));
3962 XMLNode &after = alist->get_state();
3963 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3967 _editor->commit_reversible_command ();
3972 FadeOutDrag::aborted (bool)
3974 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3975 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3981 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3985 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3987 , _selection_changed (false)
3989 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3990 Gtk::Window* toplevel = _editor->current_toplevel();
3991 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
3995 _points.push_back (ArdourCanvas::Duple (0, 0));
3997 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4000 MarkerDrag::~MarkerDrag ()
4002 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4007 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4009 location = new Location (*l);
4010 markers.push_back (m);
4015 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4017 Drag::start_grab (event, cursor);
4021 Location *location = _editor->find_location_from_marker (_marker, is_start);
4022 _editor->_dragging_edit_point = true;
4024 update_item (location);
4026 // _drag_line->show();
4027 // _line->raise_to_top();
4030 show_verbose_cursor_time (location->start());
4032 show_verbose_cursor_time (location->end());
4034 setup_snap_delta (is_start ? location->start() : location->end());
4036 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4039 case Selection::Toggle:
4040 /* we toggle on the button release */
4042 case Selection::Set:
4043 if (!_editor->selection->selected (_marker)) {
4044 _editor->selection->set (_marker);
4045 _selection_changed = true;
4048 case Selection::Extend:
4050 Locations::LocationList ll;
4051 list<ArdourMarker*> to_add;
4053 _editor->selection->markers.range (s, e);
4054 s = min (_marker->position(), s);
4055 e = max (_marker->position(), e);
4058 if (e < max_framepos) {
4061 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4062 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4063 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4066 to_add.push_back (lm->start);
4069 to_add.push_back (lm->end);
4073 if (!to_add.empty()) {
4074 _editor->selection->add (to_add);
4075 _selection_changed = true;
4079 case Selection::Add:
4080 _editor->selection->add (_marker);
4081 _selection_changed = true;
4086 /* Set up copies for us to manipulate during the drag
4089 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4091 Location* l = _editor->find_location_from_marker (*i, is_start);
4098 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4100 /* range: check that the other end of the range isn't
4103 CopiedLocationInfo::iterator x;
4104 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4105 if (*(*x).location == *l) {
4109 if (x == _copied_locations.end()) {
4110 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4112 (*x).markers.push_back (*i);
4113 (*x).move_both = true;
4121 MarkerDrag::setup_pointer_frame_offset ()
4124 Location *location = _editor->find_location_from_marker (_marker, is_start);
4125 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
4129 MarkerDrag::motion (GdkEvent* event, bool)
4131 framecnt_t f_delta = 0;
4133 bool move_both = false;
4134 Location *real_location;
4135 Location *copy_location = 0;
4136 framecnt_t const sd = snap_delta (event->button.state);
4138 framecnt_t const newframe = adjusted_frame (_drags->current_pointer_frame () + sd, event, true).frame - sd;
4139 framepos_t next = newframe;
4141 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4145 CopiedLocationInfo::iterator x;
4147 /* find the marker we're dragging, and compute the delta */
4149 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4151 copy_location = (*x).location;
4153 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4155 /* this marker is represented by this
4156 * CopiedLocationMarkerInfo
4159 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4164 if (real_location->is_mark()) {
4165 f_delta = newframe - copy_location->start();
4169 switch (_marker->type()) {
4170 case ArdourMarker::SessionStart:
4171 case ArdourMarker::RangeStart:
4172 case ArdourMarker::LoopStart:
4173 case ArdourMarker::PunchIn:
4174 f_delta = newframe - copy_location->start();
4177 case ArdourMarker::SessionEnd:
4178 case ArdourMarker::RangeEnd:
4179 case ArdourMarker::LoopEnd:
4180 case ArdourMarker::PunchOut:
4181 f_delta = newframe - copy_location->end();
4184 /* what kind of marker is this ? */
4193 if (x == _copied_locations.end()) {
4194 /* hmm, impossible - we didn't find the dragged marker */
4198 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4200 /* now move them all */
4202 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4204 copy_location = x->location;
4206 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4210 if (real_location->locked()) {
4214 if (copy_location->is_mark()) {
4217 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4221 framepos_t new_start = copy_location->start() + f_delta;
4222 framepos_t new_end = copy_location->end() + f_delta;
4224 if (is_start) { // start-of-range marker
4226 if (move_both || (*x).move_both) {
4227 copy_location->set_start (new_start, false, true, divisions);
4228 copy_location->set_end (new_end, false, true, divisions);
4229 } else if (new_start < copy_location->end()) {
4230 copy_location->set_start (new_start, false, true, divisions);
4231 } else if (newframe > 0) {
4232 //_editor->snap_to (next, RoundUpAlways, true);
4233 copy_location->set_end (next, false, true, divisions);
4234 copy_location->set_start (newframe, false, true, divisions);
4237 } else { // end marker
4239 if (move_both || (*x).move_both) {
4240 copy_location->set_end (new_end, divisions);
4241 copy_location->set_start (new_start, false, true, divisions);
4242 } else if (new_end > copy_location->start()) {
4243 copy_location->set_end (new_end, false, true, divisions);
4244 } else if (newframe > 0) {
4245 //_editor->snap_to (next, RoundDownAlways, true);
4246 copy_location->set_start (next, false, true, divisions);
4247 copy_location->set_end (newframe, false, true, divisions);
4252 update_item (copy_location);
4254 /* now lookup the actual GUI items used to display this
4255 * location and move them to wherever the copy of the location
4256 * is now. This means that the logic in ARDOUR::Location is
4257 * still enforced, even though we are not (yet) modifying
4258 * the real Location itself.
4261 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4264 lm->set_position (copy_location->start(), copy_location->end());
4269 assert (!_copied_locations.empty());
4271 show_verbose_cursor_time (newframe);
4275 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4277 if (!movement_occurred) {
4279 if (was_double_click()) {
4280 _editor->rename_marker (_marker);
4284 /* just a click, do nothing but finish
4285 off the selection process
4288 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4290 case Selection::Set:
4291 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4292 _editor->selection->set (_marker);
4293 _selection_changed = true;
4297 case Selection::Toggle:
4298 /* we toggle on the button release, click only */
4299 _editor->selection->toggle (_marker);
4300 _selection_changed = true;
4304 case Selection::Extend:
4305 case Selection::Add:
4309 if (_selection_changed) {
4310 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4311 _editor->commit_reversible_selection_op();
4317 _editor->_dragging_edit_point = false;
4319 XMLNode &before = _editor->session()->locations()->get_state();
4320 bool in_command = false;
4322 MarkerSelection::iterator i;
4323 CopiedLocationInfo::iterator x;
4324 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4327 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4328 x != _copied_locations.end() && i != _editor->selection->markers.end();
4331 Location * location = _editor->find_location_from_marker (*i, is_start);
4335 if (location->locked()) {
4339 _editor->begin_reversible_command ( _("move marker") );
4342 if (location->is_mark()) {
4343 location->set_start (((*x).location)->start(), false, true, divisions);
4345 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4348 if (location->is_session_range()) {
4349 _editor->session()->set_end_is_free (false);
4355 XMLNode &after = _editor->session()->locations()->get_state();
4356 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4357 _editor->commit_reversible_command ();
4362 MarkerDrag::aborted (bool movement_occurred)
4364 if (!movement_occurred) {
4368 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4370 /* move all markers to their original location */
4373 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4376 Location * location = _editor->find_location_from_marker (*m, is_start);
4379 (*m)->set_position (is_start ? location->start() : location->end());
4386 MarkerDrag::update_item (Location*)
4391 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4393 , _fixed_grab_x (0.0)
4394 , _fixed_grab_y (0.0)
4395 , _cumulative_x_drag (0.0)
4396 , _cumulative_y_drag (0.0)
4400 if (_zero_gain_fraction < 0.0) {
4401 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4404 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4406 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4412 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4414 Drag::start_grab (event, _editor->cursors()->fader);
4416 // start the grab at the center of the control point so
4417 // the point doesn't 'jump' to the mouse after the first drag
4418 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4419 _fixed_grab_y = _point->get_y();
4421 setup_snap_delta (_editor->pixel_to_sample (_fixed_grab_x));
4423 float const fraction = 1 - (_point->get_y() / _point->line().height());
4424 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4426 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4428 if (!_point->can_slide ()) {
4429 _x_constrained = true;
4434 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4436 double dx = _drags->current_pointer_x() - last_pointer_x();
4437 double dy = current_pointer_y() - last_pointer_y();
4438 bool need_snap = true;
4440 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4446 /* coordinate in pixels relative to the start of the region (for region-based automation)
4447 or track (for track-based automation) */
4448 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4449 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4451 // calculate zero crossing point. back off by .01 to stay on the
4452 // positive side of zero
4453 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4455 if (_x_constrained) {
4458 if (_y_constrained) {
4462 _cumulative_x_drag = cx - _fixed_grab_x;
4463 _cumulative_y_drag = cy - _fixed_grab_y;
4467 cy = min ((double) _point->line().height(), cy);
4469 // make sure we hit zero when passing through
4470 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4474 MusicFrame cx_mf (_editor->pixel_to_sample (cx) + snap_delta (event->button.state), 0);
4476 if (!_x_constrained && need_snap) {
4477 _editor->snap_to_with_modifier (cx_mf, event);
4480 cx_mf.frame -= snap_delta (event->button.state);
4481 cx_mf.frame = min (cx_mf.frame, _point->line().maximum_time() + _point->line().offset());
4483 float const fraction = 1.0 - (cy / _point->line().height());
4486 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4487 _editor->begin_reversible_command (_("automation event move"));
4488 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4490 pair<double, float> result;
4491 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_mf.frame), fraction, false, _pushing, _final_index);
4493 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (result.second));
4497 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4499 if (!movement_occurred) {
4502 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4503 _editor->reset_point_selection ();
4507 _point->line().end_drag (_pushing, _final_index);
4508 _editor->commit_reversible_command ();
4513 ControlPointDrag::aborted (bool)
4515 _point->line().reset ();
4519 ControlPointDrag::active (Editing::MouseMode m)
4521 if (m == Editing::MouseDraw) {
4522 /* always active in mouse draw */
4526 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4527 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4530 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4533 , _fixed_grab_x (0.0)
4534 , _fixed_grab_y (0.0)
4535 , _cumulative_y_drag (0)
4539 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4543 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4545 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4548 _item = &_line->grab_item ();
4550 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4551 origin, and ditto for y.
4554 double mx = event->button.x;
4555 double my = event->button.y;
4557 _line->grab_item().canvas_to_item (mx, my);
4559 framecnt_t const frame_within_region = (framecnt_t) floor (mx * _editor->samples_per_pixel);
4561 if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
4562 /* no adjacent points */
4566 Drag::start_grab (event, _editor->cursors()->fader);
4568 /* store grab start in item frame */
4569 double const bx = _line->nth (_before)->get_x();
4570 double const ax = _line->nth (_after)->get_x();
4571 double const click_ratio = (ax - mx) / (ax - bx);
4573 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4578 double fraction = 1.0 - (cy / _line->height());
4580 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4584 LineDrag::motion (GdkEvent* event, bool first_move)
4586 double dy = current_pointer_y() - last_pointer_y();
4588 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4592 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4594 _cumulative_y_drag = cy - _fixed_grab_y;
4597 cy = min ((double) _line->height(), cy);
4599 double const fraction = 1.0 - (cy / _line->height());
4603 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4605 _editor->begin_reversible_command (_("automation range move"));
4606 _line->start_drag_line (_before, _after, initial_fraction);
4609 /* we are ignoring x position for this drag, so we can just pass in anything */
4610 pair<double, float> result;
4612 result = _line->drag_motion (0, fraction, true, false, ignored);
4613 show_verbose_cursor_text (_line->get_verbose_cursor_string (result.second));
4617 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4619 if (movement_occurred) {
4620 motion (event, false);
4621 _line->end_drag (false, 0);
4622 _editor->commit_reversible_command ();
4624 /* add a new control point on the line */
4626 AutomationTimeAxisView* atv;
4628 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4629 framepos_t where = grab_frame ();
4632 double cy = _fixed_grab_y;
4634 _line->grab_item().item_to_canvas (cx, cy);
4636 atv->add_automation_event (event, where, cy, false);
4637 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4638 AudioRegionView* arv;
4640 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4641 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4648 LineDrag::aborted (bool)
4653 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4657 _region_view_grab_x (0.0),
4658 _cumulative_x_drag (0),
4662 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4666 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4668 Drag::start_grab (event);
4670 _line = reinterpret_cast<Line*> (_item);
4673 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4675 double cx = event->button.x;
4676 double cy = event->button.y;
4678 _item->parent()->canvas_to_item (cx, cy);
4680 /* store grab start in parent frame */
4681 _region_view_grab_x = cx;
4683 _before = *(float*) _item->get_data ("position");
4685 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4687 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4691 FeatureLineDrag::motion (GdkEvent*, bool)
4693 double dx = _drags->current_pointer_x() - last_pointer_x();
4695 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4697 _cumulative_x_drag += dx;
4699 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4708 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4710 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4712 float *pos = new float;
4715 _line->set_data ("position", pos);
4721 FeatureLineDrag::finished (GdkEvent*, bool)
4723 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4724 _arv->update_transient(_before, _before);
4728 FeatureLineDrag::aborted (bool)
4733 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4735 , _vertical_only (false)
4737 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4741 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4743 Drag::start_grab (event);
4744 show_verbose_cursor_time (adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
4748 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4754 framepos_t const pf = adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
4755 MusicFrame grab (grab_frame (), 0);
4757 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4758 _editor->snap_to_with_modifier (grab, event);
4760 grab.frame = raw_grab_frame ();
4763 /* base start and end on initial click position */
4765 if (pf < grab.frame) {
4773 if (current_pointer_y() < grab_y()) {
4774 y1 = current_pointer_y();
4777 y2 = current_pointer_y();
4781 if (start != end || y1 != y2) {
4783 double x1 = _editor->sample_to_pixel (start);
4784 double x2 = _editor->sample_to_pixel (end);
4785 const double min_dimension = 2.0;
4787 if (_vertical_only) {
4788 /* fixed 10 pixel width */
4792 x2 = min (x1 - min_dimension, x2);
4794 x2 = max (x1 + min_dimension, x2);
4799 y2 = min (y1 - min_dimension, y2);
4801 y2 = max (y1 + min_dimension, y2);
4804 /* translate rect into item space and set */
4806 ArdourCanvas::Rect r (x1, y1, x2, y2);
4808 /* this drag is a _trackview_only == true drag, so the y1 and
4809 * y2 (computed using current_pointer_y() and grab_y()) will be
4810 * relative to the top of the trackview group). The
4811 * rubberband rect has the same parent/scroll offset as the
4812 * the trackview group, so we can use the "r" rect directly
4813 * to set the shape of the rubberband.
4816 _editor->rubberband_rect->set (r);
4817 _editor->rubberband_rect->show();
4818 _editor->rubberband_rect->raise_to_top();
4820 show_verbose_cursor_time (pf);
4822 do_select_things (event, true);
4827 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4831 framepos_t grab = grab_frame ();
4832 framepos_t lpf = last_pointer_frame ();
4834 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4835 grab = raw_grab_frame ();
4836 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4850 if (current_pointer_y() < grab_y()) {
4851 y1 = current_pointer_y();
4854 y2 = current_pointer_y();
4858 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4862 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4864 if (movement_occurred) {
4866 motion (event, false);
4867 do_select_things (event, false);
4873 bool do_deselect = true;
4874 MidiTimeAxisView* mtv;
4876 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4878 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4879 /* nothing selected */
4880 add_midi_region (mtv, true);
4881 do_deselect = false;
4885 /* do not deselect if Primary or Tertiary (toggle-select or
4886 * extend-select are pressed.
4889 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4890 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4897 _editor->rubberband_rect->hide();
4901 RubberbandSelectDrag::aborted (bool)
4903 _editor->rubberband_rect->hide ();
4906 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4907 : RegionDrag (e, i, p, v)
4909 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4913 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4915 Drag::start_grab (event, cursor);
4917 _editor->get_selection().add (_primary);
4919 framepos_t where = _primary->region()->position();
4920 setup_snap_delta (where);
4922 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4926 TimeFXDrag::motion (GdkEvent* event, bool)
4928 RegionView* rv = _primary;
4929 StreamView* cv = rv->get_time_axis_view().view ();
4930 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4931 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4932 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4933 MusicFrame pf (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4935 _editor->snap_to_with_modifier (pf, event);
4936 pf.frame -= snap_delta (event->button.state);
4938 if (pf.frame > rv->region()->position()) {
4939 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf.frame, layers, layer);
4942 show_verbose_cursor_duration (_primary->region()->position(), pf.frame, 0);
4946 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
4948 /* this may have been a single click, no drag. We still want the dialog
4949 to show up in that case, so that the user can manually edit the
4950 parameters for the timestretch.
4953 float fraction = 1.0;
4955 if (movement_occurred) {
4957 motion (event, false);
4959 _primary->get_time_axis_view().hide_timestretch ();
4961 framepos_t adjusted_frame_pos = adjusted_current_frame (event);
4963 if (adjusted_frame_pos < _primary->region()->position()) {
4964 /* backwards drag of the left edge - not usable */
4968 framecnt_t newlen = adjusted_frame_pos - _primary->region()->position();
4970 fraction = (double) newlen / (double) _primary->region()->length();
4972 #ifndef USE_RUBBERBAND
4973 // Soundtouch uses fraction / 100 instead of normal (/ 1)
4974 if (_primary->region()->data_type() == DataType::AUDIO) {
4975 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4980 if (!_editor->get_selection().regions.empty()) {
4981 /* primary will already be included in the selection, and edit
4982 group shared editing will propagate selection across
4983 equivalent regions, so just use the current region
4987 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
4988 error << _("An error occurred while executing time stretch operation") << endmsg;
4994 TimeFXDrag::aborted (bool)
4996 _primary->get_time_axis_view().hide_timestretch ();
4999 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5002 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5006 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5008 Drag::start_grab (event);
5012 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5014 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
5018 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5020 if (movement_occurred && _editor->session()) {
5021 /* make sure we stop */
5022 _editor->session()->request_transport_speed (0.0);
5027 ScrubDrag::aborted (bool)
5032 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5036 , _track_selection_at_start (e)
5037 , _time_selection_at_start (!_editor->get_selection().time.empty())
5039 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5041 if (_time_selection_at_start) {
5042 start_at_start = _editor->get_selection().time.start();
5043 end_at_start = _editor->get_selection().time.end_frame();
5048 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5050 if (_editor->session() == 0) {
5054 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5056 switch (_operation) {
5057 case CreateSelection:
5058 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5063 cursor = _editor->cursors()->selector;
5064 Drag::start_grab (event, cursor);
5067 case SelectionStartTrim:
5068 if (_editor->clicked_axisview) {
5069 _editor->clicked_axisview->order_selection_trims (_item, true);
5071 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5074 case SelectionEndTrim:
5075 if (_editor->clicked_axisview) {
5076 _editor->clicked_axisview->order_selection_trims (_item, false);
5078 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5082 Drag::start_grab (event, cursor);
5085 case SelectionExtend:
5086 Drag::start_grab (event, cursor);
5090 if (_operation == SelectionMove) {
5091 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5093 show_verbose_cursor_time (adjusted_current_frame (event));
5098 SelectionDrag::setup_pointer_frame_offset ()
5100 switch (_operation) {
5101 case CreateSelection:
5102 _pointer_frame_offset = 0;
5105 case SelectionStartTrim:
5107 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
5110 case SelectionEndTrim:
5111 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
5114 case SelectionExtend:
5120 SelectionDrag::motion (GdkEvent* event, bool first_move)
5122 framepos_t start = 0;
5124 framecnt_t length = 0;
5125 framecnt_t distance = 0;
5126 MusicFrame start_mf (0, 0);
5127 framepos_t const pending_position = adjusted_current_frame (event);
5129 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
5134 _track_selection_at_start = _editor->selection->tracks;
5137 switch (_operation) {
5138 case CreateSelection:
5140 MusicFrame grab (grab_frame (), 0);
5142 grab.frame = adjusted_current_frame (event, false);
5143 if (grab.frame < pending_position) {
5144 _editor->snap_to (grab, RoundDownMaybe);
5146 _editor->snap_to (grab, RoundUpMaybe);
5150 if (pending_position < grab.frame) {
5151 start = pending_position;
5154 end = pending_position;
5158 /* first drag: Either add to the selection
5159 or create a new selection
5166 /* adding to the selection */
5167 _editor->set_selected_track_as_side_effect (Selection::Add);
5168 _editor->clicked_selection = _editor->selection->add (start, end);
5175 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5176 _editor->set_selected_track_as_side_effect (Selection::Set);
5179 _editor->clicked_selection = _editor->selection->set (start, end);
5183 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5184 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5185 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5187 _editor->selection->add (atest);
5191 /* select all tracks within the rectangle that we've marked out so far */
5192 TrackViewList new_selection;
5193 TrackViewList& all_tracks (_editor->track_views);
5195 ArdourCanvas::Coord const top = grab_y();
5196 ArdourCanvas::Coord const bottom = current_pointer_y();
5198 if (top >= 0 && bottom >= 0) {
5200 //first, find the tracks that are covered in the y range selection
5201 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5202 if ((*i)->covered_by_y_range (top, bottom)) {
5203 new_selection.push_back (*i);
5207 //now compare our list with the current selection, and add as necessary
5208 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5209 TrackViewList tracks_to_add;
5210 TrackViewList tracks_to_remove;
5213 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5214 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5215 tracks_to_remove.push_back (*i);
5220 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5221 if (!_editor->selection->tracks.contains (*i)) {
5222 tracks_to_add.push_back (*i);
5226 _editor->selection->add (tracks_to_add);
5228 if (!tracks_to_remove.empty()) {
5229 _editor->selection->remove (tracks_to_remove);
5235 case SelectionStartTrim:
5237 end = _editor->selection->time[_editor->clicked_selection].end;
5239 if (pending_position > end) {
5242 start = pending_position;
5246 case SelectionEndTrim:
5248 start = _editor->selection->time[_editor->clicked_selection].start;
5250 if (pending_position < start) {
5253 end = pending_position;
5260 start = _editor->selection->time[_editor->clicked_selection].start;
5261 end = _editor->selection->time[_editor->clicked_selection].end;
5263 length = end - start;
5264 distance = pending_position - start;
5265 start = pending_position;
5267 start_mf.frame = start;
5268 _editor->snap_to (start_mf);
5270 end = start_mf.frame + length;
5274 case SelectionExtend:
5279 switch (_operation) {
5281 if (_time_selection_at_start) {
5282 _editor->selection->move_time (distance);
5286 _editor->selection->replace (_editor->clicked_selection, start, end);
5290 if (_operation == SelectionMove) {
5291 show_verbose_cursor_time(start);
5293 show_verbose_cursor_time(pending_position);
5298 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5300 Session* s = _editor->session();
5302 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5303 if (movement_occurred) {
5304 motion (event, false);
5305 /* XXX this is not object-oriented programming at all. ick */
5306 if (_editor->selection->time.consolidate()) {
5307 _editor->selection->TimeChanged ();
5310 /* XXX what if its a music time selection? */
5312 if (s->get_play_range() && s->transport_rolling()) {
5313 s->request_play_range (&_editor->selection->time, true);
5314 } else if (!s->config.get_external_sync()) {
5315 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5316 s->request_locate (_editor->get_selection().time.start());
5320 if (_editor->get_selection().time.length() != 0) {
5321 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame());
5323 s->clear_range_selection ();
5328 /* just a click, no pointer movement.
5331 if (was_double_click()) {
5332 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5333 _editor->temporal_zoom_selection (Both);
5338 if (_operation == SelectionExtend) {
5339 if (_time_selection_at_start) {
5340 framepos_t pos = adjusted_current_frame (event, false);
5341 framepos_t start = min (pos, start_at_start);
5342 framepos_t end = max (pos, end_at_start);
5343 _editor->selection->set (start, end);
5346 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5347 if (_editor->clicked_selection) {
5348 _editor->selection->remove (_editor->clicked_selection);
5351 if (!_editor->clicked_selection) {
5352 _editor->selection->clear_time();
5357 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5358 _editor->selection->set (_editor->clicked_axisview);
5361 if (s && s->get_play_range () && s->transport_rolling()) {
5362 s->request_stop (false, false);
5367 _editor->stop_canvas_autoscroll ();
5368 _editor->clicked_selection = 0;
5369 _editor->commit_reversible_selection_op ();
5373 SelectionDrag::aborted (bool)
5378 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5379 : Drag (e, i, false),
5383 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5385 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5386 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5387 physical_screen_height (_editor->current_toplevel()->get_window())));
5388 _drag_rect->hide ();
5390 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5391 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5394 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5396 /* normal canvas items will be cleaned up when their parent group is deleted. But
5397 this item is created as the child of a long-lived parent group, and so we
5398 need to explicitly delete it.
5404 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5406 if (_editor->session() == 0) {
5410 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5412 if (!_editor->temp_location) {
5413 _editor->temp_location = new Location (*_editor->session());
5416 switch (_operation) {
5417 case CreateSkipMarker:
5418 case CreateRangeMarker:
5419 case CreateTransportMarker:
5420 case CreateCDMarker:
5422 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5427 cursor = _editor->cursors()->selector;
5431 Drag::start_grab (event, cursor);
5433 show_verbose_cursor_time (adjusted_current_frame (event));
5437 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5439 framepos_t start = 0;
5441 ArdourCanvas::Rectangle *crect;
5443 switch (_operation) {
5444 case CreateSkipMarker:
5445 crect = _editor->range_bar_drag_rect;
5447 case CreateRangeMarker:
5448 crect = _editor->range_bar_drag_rect;
5450 case CreateTransportMarker:
5451 crect = _editor->transport_bar_drag_rect;
5453 case CreateCDMarker:
5454 crect = _editor->cd_marker_bar_drag_rect;
5457 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5462 framepos_t const pf = adjusted_current_frame (event);
5464 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5465 MusicFrame grab (grab_frame (), 0);
5466 _editor->snap_to (grab);
5468 if (pf < grab_frame()) {
5476 /* first drag: Either add to the selection
5477 or create a new selection.
5482 _editor->temp_location->set (start, end);
5486 update_item (_editor->temp_location);
5488 //_drag_rect->raise_to_top();
5494 _editor->temp_location->set (start, end);
5496 double x1 = _editor->sample_to_pixel (start);
5497 double x2 = _editor->sample_to_pixel (end);
5501 update_item (_editor->temp_location);
5504 show_verbose_cursor_time (pf);
5509 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5511 Location * newloc = 0;
5515 if (movement_occurred) {
5516 motion (event, false);
5519 switch (_operation) {
5520 case CreateSkipMarker:
5521 case CreateRangeMarker:
5522 case CreateCDMarker:
5524 XMLNode &before = _editor->session()->locations()->get_state();
5525 if (_operation == CreateSkipMarker) {
5526 _editor->begin_reversible_command (_("new skip marker"));
5527 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5528 flags = Location::IsRangeMarker | Location::IsSkip;
5529 _editor->range_bar_drag_rect->hide();
5530 } else if (_operation == CreateCDMarker) {
5531 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5532 _editor->begin_reversible_command (_("new CD marker"));
5533 flags = Location::IsRangeMarker | Location::IsCDMarker;
5534 _editor->cd_marker_bar_drag_rect->hide();
5536 _editor->begin_reversible_command (_("new skip marker"));
5537 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5538 flags = Location::IsRangeMarker;
5539 _editor->range_bar_drag_rect->hide();
5541 newloc = new Location (
5542 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5543 , _editor->get_grid_music_divisions (event->button.state));
5545 _editor->session()->locations()->add (newloc, true);
5546 XMLNode &after = _editor->session()->locations()->get_state();
5547 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5548 _editor->commit_reversible_command ();
5552 case CreateTransportMarker:
5553 // popup menu to pick loop or punch
5554 _editor->new_transport_marker_context_menu (&event->button, _item);
5560 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5562 if (_operation == CreateTransportMarker) {
5564 /* didn't drag, so just locate */
5566 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5568 } else if (_operation == CreateCDMarker) {
5570 /* didn't drag, but mark is already created so do
5573 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5578 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5580 if (end == max_framepos) {
5581 end = _editor->session()->current_end_frame ();
5584 if (start == max_framepos) {
5585 start = _editor->session()->current_start_frame ();
5588 switch (_editor->mouse_mode) {
5590 /* find the two markers on either side and then make the selection from it */
5591 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5595 /* find the two markers on either side of the click and make the range out of it */
5596 _editor->selection->set (start, end);
5605 _editor->stop_canvas_autoscroll ();
5609 RangeMarkerBarDrag::aborted (bool movement_occurred)
5611 if (movement_occurred) {
5612 _drag_rect->hide ();
5617 RangeMarkerBarDrag::update_item (Location* location)
5619 double const x1 = _editor->sample_to_pixel (location->start());
5620 double const x2 = _editor->sample_to_pixel (location->end());
5622 _drag_rect->set_x0 (x1);
5623 _drag_rect->set_x1 (x2);
5626 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5628 , _cumulative_dx (0)
5629 , _cumulative_dy (0)
5630 , _was_selected (false)
5633 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5635 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5637 _region = &_primary->region_view ();
5638 _note_height = _region->midi_stream_view()->note_height ();
5642 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5644 Drag::start_grab (event);
5646 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5652 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5654 if (!(_was_selected = _primary->selected())) {
5656 /* tertiary-click means extend selection - we'll do that on button release,
5657 so don't add it here, because otherwise we make it hard to figure
5658 out the "extend-to" range.
5661 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5664 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5667 _region->note_selected (_primary, true);
5669 _editor->get_selection().clear_points();
5670 _region->unique_select (_primary);
5676 /** @return Current total drag x change in frames */
5678 NoteDrag::total_dx (const guint state) const
5680 if (_x_constrained) {
5683 TempoMap& map (_editor->session()->tempo_map());
5686 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5688 /* primary note time */
5689 double const quarter_note_start = _region->region()->quarter_note() - _region->midi_region()->start_beats();
5690 frameoffset_t const n = map.frame_at_quarter_note (quarter_note_start + _primary->note()->time().to_double());
5692 /* new time of the primary note in session frames */
5693 frameoffset_t st = n + dx + snap_delta (state);
5695 framepos_t const rp = _region->region()->position ();
5697 /* prevent the note being dragged earlier than the region's position */
5700 /* possibly snap and return corresponding delta */
5704 if (ArdourKeyboard::indicates_snap (state)) {
5705 if (_editor->snap_mode () != SnapOff) {
5709 if (_editor->snap_mode () == SnapOff) {
5711 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
5712 if (ArdourKeyboard::indicates_snap_delta (state)) {
5720 bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
5721 ret = _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
5723 ret = st - n - snap_delta (state);
5728 /** @return Current total drag y change in note number */
5730 NoteDrag::total_dy () const
5732 if (_y_constrained) {
5736 double const y = _region->midi_view()->y_position ();
5737 /* new current note */
5738 uint8_t n = _region->y_to_note (current_pointer_y () - y);
5740 MidiStreamView* msv = _region->midi_stream_view ();
5741 n = max (msv->lowest_note(), n);
5742 n = min (msv->highest_note(), n);
5743 /* and work out delta */
5744 return n - _region->y_to_note (grab_y() - y);
5748 NoteDrag::motion (GdkEvent * event, bool first_move)
5750 if (_copy && first_move) {
5751 /* make copies of all the selected notes */
5752 _primary = _region->copy_selection ();
5755 /* Total change in x and y since the start of the drag */
5756 frameoffset_t const dx = total_dx (event->button.state);
5757 int8_t const dy = total_dy ();
5759 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5760 double const tdx = _x_constrained ? 0 : _editor->sample_to_pixel (dx) - _cumulative_dx;
5761 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
5764 _cumulative_dx += tdx;
5765 _cumulative_dy += tdy;
5767 int8_t note_delta = total_dy();
5771 _region->move_copies (tdx, tdy, note_delta);
5773 _region->move_selection (tdx, tdy, note_delta);
5776 /* the new note value may be the same as the old one, but we
5777 * don't know what that means because the selection may have
5778 * involved more than one note and we might be doing something
5779 * odd with them. so show the note value anyway, always.
5782 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5784 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
5790 NoteDrag::finished (GdkEvent* ev, bool moved)
5793 /* no motion - select note */
5795 if (_editor->current_mouse_mode() == Editing::MouseContent ||
5796 _editor->current_mouse_mode() == Editing::MouseDraw) {
5798 bool changed = false;
5800 if (_was_selected) {
5801 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5803 _region->note_deselected (_primary);
5806 _editor->get_selection().clear_points();
5807 _region->unique_select (_primary);
5811 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5812 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5814 if (!extend && !add && _region->selection_size() > 1) {
5815 _editor->get_selection().clear_points();
5816 _region->unique_select (_primary);
5818 } else if (extend) {
5819 _region->note_selected (_primary, true, true);
5822 /* it was added during button press */
5829 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5830 _editor->commit_reversible_selection_op();
5834 _region->note_dropped (_primary, total_dx (ev->button.state), total_dy(), _copy);
5839 NoteDrag::aborted (bool)
5844 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5845 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5846 : Drag (editor, atv->base_item ())
5848 , _y_origin (atv->y_position())
5849 , _nothing_to_drag (false)
5851 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5852 setup (atv->lines ());
5855 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5856 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5857 : Drag (editor, rv->get_canvas_group ())
5859 , _y_origin (rv->get_time_axis_view().y_position())
5860 , _nothing_to_drag (false)
5863 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5865 list<boost::shared_ptr<AutomationLine> > lines;
5867 AudioRegionView* audio_view;
5868 AutomationRegionView* automation_view;
5869 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5870 lines.push_back (audio_view->get_gain_line ());
5871 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5872 lines.push_back (automation_view->line ());
5875 error << _("Automation range drag created for invalid region type") << endmsg;
5881 /** @param lines AutomationLines to drag.
5882 * @param offset Offset from the session start to the points in the AutomationLines.
5885 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5887 /* find the lines that overlap the ranges being dragged */
5888 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5889 while (i != lines.end ()) {
5890 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5893 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5895 /* check this range against all the AudioRanges that we are using */
5896 list<AudioRange>::const_iterator k = _ranges.begin ();
5897 while (k != _ranges.end()) {
5898 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5904 /* add it to our list if it overlaps at all */
5905 if (k != _ranges.end()) {
5910 _lines.push_back (n);
5916 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5920 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5922 return 1.0 - ((global_y - _y_origin) / line->height());
5926 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5928 const double v = list->eval(x);
5929 return _integral ? rint(v) : v;
5933 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5935 Drag::start_grab (event, cursor);
5937 /* Get line states before we start changing things */
5938 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5939 i->state = &i->line->get_state ();
5940 i->original_fraction = y_fraction (i->line, current_pointer_y());
5943 if (_ranges.empty()) {
5945 /* No selected time ranges: drag all points */
5946 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5947 uint32_t const N = i->line->npoints ();
5948 for (uint32_t j = 0; j < N; ++j) {
5949 i->points.push_back (i->line->nth (j));
5955 if (_nothing_to_drag) {
5961 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
5963 if (_nothing_to_drag && !first_move) {
5968 _editor->begin_reversible_command (_("automation range move"));
5970 if (!_ranges.empty()) {
5972 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5974 framecnt_t const half = (i->start + i->end) / 2;
5976 /* find the line that this audio range starts in */
5977 list<Line>::iterator j = _lines.begin();
5978 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5982 if (j != _lines.end()) {
5983 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5985 /* j is the line that this audio range starts in; fade into it;
5986 64 samples length plucked out of thin air.
5989 framepos_t a = i->start + 64;
5994 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5995 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5997 XMLNode &before = the_list->get_state();
5998 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5999 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6001 if (add_p || add_q) {
6002 _editor->session()->add_command (
6003 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6007 /* same thing for the end */
6010 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
6014 if (j != _lines.end()) {
6015 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6017 /* j is the line that this audio range starts in; fade out of it;
6018 64 samples length plucked out of thin air.
6021 framepos_t b = i->end - 64;
6026 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6027 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6029 XMLNode &before = the_list->get_state();
6030 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6031 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6033 if (add_p || add_q) {
6034 _editor->session()->add_command (
6035 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6040 _nothing_to_drag = true;
6042 /* Find all the points that should be dragged and put them in the relevant
6043 points lists in the Line structs.
6046 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6048 uint32_t const N = i->line->npoints ();
6049 for (uint32_t j = 0; j < N; ++j) {
6051 /* here's a control point on this line */
6052 ControlPoint* p = i->line->nth (j);
6053 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6055 /* see if it's inside a range */
6056 list<AudioRange>::const_iterator k = _ranges.begin ();
6057 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6061 if (k != _ranges.end()) {
6062 /* dragging this point */
6063 _nothing_to_drag = false;
6064 i->points.push_back (p);
6070 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6071 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6075 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6076 float const f = y_fraction (l->line, current_pointer_y());
6077 /* we are ignoring x position for this drag, so we can just pass in anything */
6078 pair<double, float> result;
6080 result = l->line->drag_motion (0, f, true, false, ignored);
6081 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, result.second));
6086 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6088 if (_nothing_to_drag || !motion_occurred) {
6092 motion (event, false);
6093 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6094 i->line->end_drag (false, 0);
6097 _editor->commit_reversible_command ();
6101 AutomationRangeDrag::aborted (bool)
6103 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6108 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6110 , initial_time_axis_view (itav)
6112 /* note that time_axis_view may be null if the regionview was created
6113 * as part of a copy operation.
6115 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6116 layer = v->region()->layer ();
6117 initial_y = v->get_canvas_group()->position().y;
6118 initial_playlist = v->region()->playlist ();
6119 initial_position = v->region()->position ();
6120 initial_end = v->region()->position () + v->region()->length ();
6123 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6124 : Drag (e, i->canvas_item ())
6127 , _cumulative_dx (0)
6129 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6130 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
6135 PatchChangeDrag::motion (GdkEvent* ev, bool)
6137 framepos_t f = adjusted_current_frame (ev);
6138 boost::shared_ptr<Region> r = _region_view->region ();
6139 f = max (f, r->position ());
6140 f = min (f, r->last_frame ());
6142 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
6143 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6144 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6145 _cumulative_dx = dxu;
6149 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6151 if (!movement_occurred) {
6152 if (was_double_click()) {
6153 _region_view->edit_patch_change (_patch_change);
6158 boost::shared_ptr<Region> r (_region_view->region ());
6159 framepos_t f = adjusted_current_frame (ev);
6160 f = max (f, r->position ());
6161 f = min (f, r->last_frame ());
6163 _region_view->move_patch_change (
6165 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
6170 PatchChangeDrag::aborted (bool)
6172 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6176 PatchChangeDrag::setup_pointer_frame_offset ()
6178 boost::shared_ptr<Region> region = _region_view->region ();
6179 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
6182 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6183 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6190 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6192 _region_view->update_drag_selection (
6194 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6198 MidiRubberbandSelectDrag::deselect_things ()
6203 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6204 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6207 _vertical_only = true;
6211 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6213 double const y = _region_view->midi_view()->y_position ();
6215 y1 = max (0.0, y1 - y);
6216 y2 = max (0.0, y2 - y);
6218 _region_view->update_vertical_drag_selection (
6221 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6226 MidiVerticalSelectDrag::deselect_things ()
6231 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6232 : RubberbandSelectDrag (e, i)
6238 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
6240 if (drag_in_progress) {
6241 /* We just want to select things at the end of the drag, not during it */
6245 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6247 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6249 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6251 _editor->commit_reversible_selection_op ();
6255 EditorRubberbandSelectDrag::deselect_things ()
6257 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6259 _editor->selection->clear_tracks();
6260 _editor->selection->clear_regions();
6261 _editor->selection->clear_points ();
6262 _editor->selection->clear_lines ();
6263 _editor->selection->clear_midi_notes ();
6265 _editor->commit_reversible_selection_op();
6268 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6273 _note[0] = _note[1] = 0;
6276 NoteCreateDrag::~NoteCreateDrag ()
6282 NoteCreateDrag::grid_frames (framepos_t t) const
6285 const Evoral::Beats grid_beats = _region_view->get_grid_beats (t);
6286 const Evoral::Beats t_beats = _region_view->region_frames_to_region_beats (t);
6288 return _region_view->region_beats_to_region_frames (t_beats + grid_beats)
6289 - _region_view->region_beats_to_region_frames (t_beats);
6293 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6295 Drag::start_grab (event, cursor);
6297 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6298 TempoMap& map (_editor->session()->tempo_map());
6300 const framepos_t pf = _drags->current_pointer_frame ();
6301 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6303 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6305 double eqaf = map.exact_qn_at_frame (pf, divisions);
6307 if (divisions != 0) {
6309 const double qaf = map.quarter_note_at_frame (pf);
6311 /* Hack so that we always snap to the note that we are over, instead of snapping
6312 to the next one if we're more than halfway through the one we're over.
6315 const double rem = eqaf - qaf;
6317 eqaf -= grid_beats.to_double();
6321 _note[0] = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6322 /* minimum initial length is grid beats */
6323 _note[1] = map.frame_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6325 double const x0 = _editor->sample_to_pixel (_note[0]);
6326 double const x1 = _editor->sample_to_pixel (_note[1]);
6327 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6329 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6330 _drag_rect->set_outline_all ();
6331 _drag_rect->set_outline_color (0xffffff99);
6332 _drag_rect->set_fill_color (0xffffff66);
6336 NoteCreateDrag::motion (GdkEvent* event, bool)
6338 TempoMap& map (_editor->session()->tempo_map());
6339 const framepos_t pf = _drags->current_pointer_frame ();
6340 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6341 double eqaf = map.exact_qn_at_frame (pf, divisions);
6343 if (divisions != 0) {
6345 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6347 const double qaf = map.quarter_note_at_frame (pf);
6348 /* Hack so that we always snap to the note that we are over, instead of snapping
6349 to the next one if we're more than halfway through the one we're over.
6352 const double rem = eqaf - qaf;
6354 eqaf -= grid_beats.to_double();
6357 eqaf += grid_beats.to_double();
6359 _note[1] = max ((framepos_t)0, map.frame_at_quarter_note (eqaf) - _region_view->region()->position ());
6361 double const x0 = _editor->sample_to_pixel (_note[0]);
6362 double const x1 = _editor->sample_to_pixel (_note[1]);
6363 _drag_rect->set_x0 (std::min(x0, x1));
6364 _drag_rect->set_x1 (std::max(x0, x1));
6368 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6370 /* we create a note even if there was no movement */
6371 framepos_t const start = min (_note[0], _note[1]);
6372 framepos_t const start_sess_rel = start + _region_view->region()->position();
6373 framecnt_t length = max (_editor->pixel_to_sample (1.0), (framecnt_t) fabs ((double)(_note[0] - _note[1])));
6374 framecnt_t const g = grid_frames (start);
6376 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6380 TempoMap& map (_editor->session()->tempo_map());
6381 const double qn_length = map.quarter_notes_between_frames (start_sess_rel, start_sess_rel + length);
6382 Evoral::Beats qn_length_beats = max (Evoral::Beats::ticks(1), Evoral::Beats (qn_length));
6384 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6388 NoteCreateDrag::y_to_region (double y) const
6391 _region_view->get_canvas_group()->canvas_to_item (x, y);
6396 NoteCreateDrag::aborted (bool)
6401 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6409 HitCreateDrag::~HitCreateDrag ()
6414 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6416 Drag::start_grab (event, cursor);
6418 TempoMap& map (_editor->session()->tempo_map());
6420 const framepos_t pf = _drags->current_pointer_frame ();
6421 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6423 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6425 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6427 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6431 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6432 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6434 Evoral::Beats length = _region_view->get_grid_beats (pf);
6436 _region_view->create_note_at (start, y, length, event->button.state, false);
6443 HitCreateDrag::motion (GdkEvent* event, bool)
6445 TempoMap& map (_editor->session()->tempo_map());
6447 const framepos_t pf = _drags->current_pointer_frame ();
6448 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6450 if (divisions == 0) {
6454 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6455 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position ();
6456 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6458 if (_last_pos == start && y == _last_y) {
6462 Evoral::Beats length = _region_view->get_grid_beats (pf);
6464 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6465 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6469 _region_view->create_note_at (start, y, length, event->button.state, false);
6476 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6482 HitCreateDrag::y_to_region (double y) const
6485 _region_view->get_canvas_group()->canvas_to_item (x, y);
6490 HitCreateDrag::aborted (bool)
6495 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6500 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6504 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6506 Drag::start_grab (event, cursor);
6510 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6516 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6519 distance = _drags->current_pointer_x() - grab_x();
6520 len = ar->fade_in()->back()->when;
6522 distance = grab_x() - _drags->current_pointer_x();
6523 len = ar->fade_out()->back()->when;
6526 /* how long should it be ? */
6528 new_length = len + _editor->pixel_to_sample (distance);
6530 /* now check with the region that this is legal */
6532 new_length = ar->verify_xfade_bounds (new_length, start);
6535 arv->reset_fade_in_shape_width (ar, new_length);
6537 arv->reset_fade_out_shape_width (ar, new_length);
6542 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6548 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6551 distance = _drags->current_pointer_x() - grab_x();
6552 len = ar->fade_in()->back()->when;
6554 distance = grab_x() - _drags->current_pointer_x();
6555 len = ar->fade_out()->back()->when;
6558 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6560 _editor->begin_reversible_command ("xfade trim");
6561 ar->playlist()->clear_owned_changes ();
6564 ar->set_fade_in_length (new_length);
6566 ar->set_fade_out_length (new_length);
6569 /* Adjusting the xfade may affect other regions in the playlist, so we need
6570 to get undo Commands from the whole playlist rather than just the
6574 vector<Command*> cmds;
6575 ar->playlist()->rdiff (cmds);
6576 _editor->session()->add_commands (cmds);
6577 _editor->commit_reversible_command ();
6582 CrossfadeEdgeDrag::aborted (bool)
6585 // arv->redraw_start_xfade ();
6587 // arv->redraw_end_xfade ();
6591 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
6592 : Drag (e, item, true)
6593 , line (new EditorCursor (*e))
6595 line->set_position (pos);
6597 line->track_canvas_item().reparent (_editor->_drag_motion_group);
6600 RegionCutDrag::~RegionCutDrag ()
6606 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6608 Drag::start_grab (event, c);
6609 motion (event, false);
6613 RegionCutDrag::motion (GdkEvent* event, bool)
6615 MusicFrame pos (_drags->current_pointer_frame(), 0);
6616 _editor->snap_to_with_modifier (pos, event);
6618 line->set_position (pos.frame);
6622 RegionCutDrag::finished (GdkEvent* event, bool)
6624 _editor->get_track_canvas()->canvas()->re_enter();
6627 MusicFrame pos (_drags->current_pointer_frame(), 0);
6628 _editor->snap_to_with_modifier (pos, event);
6631 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos.frame);
6637 _editor->split_regions_at (pos, rs, false);
6641 RegionCutDrag::aborted (bool)
6645 RulerZoomDrag::RulerZoomDrag (Editor* e, ArdourCanvas::Item* item)
6646 : Drag (e, item, true)
6651 RulerZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6653 Drag::start_grab (event, c);
6655 framepos_t where = _editor->canvas_event_sample(event);
6657 _editor->_dragging_playhead = true;
6659 _editor->playhead_cursor->set_position (where);
6663 RulerZoomDrag::motion (GdkEvent* event, bool)
6665 framepos_t where = _editor->canvas_event_sample(event);
6667 _editor->playhead_cursor->set_position (where);
6669 const double movement_limit = 20.0;
6670 const double scale = 1.08;
6671 const double y_delta = last_pointer_y() - current_pointer_y();
6673 if (y_delta > 0 && y_delta < movement_limit) {
6674 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
6675 } else if (y_delta < 0 && y_delta > -movement_limit) {
6676 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
6681 RulerZoomDrag::finished (GdkEvent*, bool)
6683 _editor->_dragging_playhead = false;
6685 Session* s = _editor->session ();
6687 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
6688 _editor->_pending_locate_request = true;
6694 RulerZoomDrag::aborted (bool)