2 Copyright (C) 2009 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include "gtk2ardour-config.h"
27 #include "pbd/memento_command.h"
28 #include "pbd/basename.h"
29 #include "pbd/stateful_diff_command.h"
31 #include "gtkmm2ext/utils.h"
33 #include "ardour/audioengine.h"
34 #include "ardour/audioregion.h"
35 #include "ardour/audio_track.h"
36 #include "ardour/dB.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_track.h"
39 #include "ardour/operations.h"
40 #include "ardour/region_factory.h"
41 #include "ardour/session.h"
43 #include "canvas/canvas.h"
44 #include "canvas/scroll_group.h"
49 #include "audio_region_view.h"
50 #include "automation_region_view.h"
51 #include "midi_region_view.h"
52 #include "ardour_ui.h"
53 #include "gui_thread.h"
54 #include "control_point.h"
55 #include "region_gain_line.h"
56 #include "editor_drag.h"
57 #include "audio_time_axis.h"
58 #include "midi_time_axis.h"
59 #include "selection.h"
60 #include "midi_selection.h"
61 #include "automation_time_axis.h"
63 #include "editor_cursors.h"
64 #include "mouse_cursors.h"
65 #include "note_base.h"
66 #include "patch_change.h"
67 #include "verbose_cursor.h"
70 using namespace ARDOUR;
73 using namespace Gtkmm2ext;
74 using namespace Editing;
75 using namespace ArdourCanvas;
77 using Gtkmm2ext::Keyboard;
79 double ControlPointDrag::_zero_gain_fraction = -1.0;
81 DragManager::DragManager (Editor* e)
84 , _current_pointer_frame (0)
88 DragManager::~DragManager ()
93 /** Call abort for each active drag */
99 cerr << "Aborting drag\n";
101 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
106 if (!_drags.empty ()) {
107 _editor->set_follow_playhead (_old_follow_playhead, false);
111 _editor->abort_reversible_command();
117 DragManager::add (Drag* d)
119 d->set_manager (this);
120 _drags.push_back (d);
124 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
126 d->set_manager (this);
127 _drags.push_back (d);
132 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
134 /* Prevent follow playhead during the drag to be nice to the user */
135 _old_follow_playhead = _editor->follow_playhead ();
136 _editor->set_follow_playhead (false);
138 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
140 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
141 (*i)->start_grab (e, c);
145 /** Call end_grab for each active drag.
146 * @return true if any drag reported movement having occurred.
149 DragManager::end_grab (GdkEvent* e)
154 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
155 bool const t = (*i)->end_grab (e);
166 _editor->set_follow_playhead (_old_follow_playhead, false);
172 DragManager::mark_double_click ()
174 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
175 (*i)->set_double_click (true);
180 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
184 /* calling this implies that we expect the event to have canvas
187 * Can we guarantee that this is true?
190 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
192 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
193 bool const t = (*i)->motion_handler (e, from_autoscroll);
194 /* run all handlers; return true if at least one of them
195 returns true (indicating that the event has been handled).
207 DragManager::have_item (ArdourCanvas::Item* i) const
209 list<Drag*>::const_iterator j = _drags.begin ();
210 while (j != _drags.end() && (*j)->item () != i) {
214 return j != _drags.end ();
217 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
220 , _pointer_frame_offset (0)
221 , _trackview_only (trackview_only)
222 , _move_threshold_passed (false)
223 , _starting_point_passed (false)
224 , _initially_vertical (false)
225 , _was_double_click (false)
226 , _raw_grab_frame (0)
228 , _last_pointer_frame (0)
235 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
241 _cursor_ctx = CursorContext::create (*_editor, cursor);
243 _cursor_ctx->change (cursor);
250 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
252 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
254 /* we set up x/y dragging constraints on first move */
255 _x_constrained = false;
256 _y_constrained = false;
258 _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
260 setup_pointer_frame_offset ();
261 _grab_frame = adjusted_frame (_raw_grab_frame, event);
262 _last_pointer_frame = _grab_frame;
263 _last_pointer_x = _grab_x;
265 if (_trackview_only) {
266 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
269 _last_pointer_y = _grab_y;
273 if (!_editor->cursors()->is_invalid (cursor)) {
274 /* CAIROCANVAS need a variant here that passes *cursor */
275 _cursor_ctx = CursorContext::create (*_editor, cursor);
278 if (_editor->session() && _editor->session()->transport_rolling()) {
281 _was_rolling = false;
284 switch (_editor->snap_type()) {
285 case SnapToRegionStart:
286 case SnapToRegionEnd:
287 case SnapToRegionSync:
288 case SnapToRegionBoundary:
289 _editor->build_region_boundary_cache ();
296 /** Call to end a drag `successfully'. Ungrabs item and calls
297 * subclass' finished() method.
299 * @param event GDK event, or 0.
300 * @return true if some movement occurred, otherwise false.
303 Drag::end_grab (GdkEvent* event)
305 _editor->stop_canvas_autoscroll ();
309 finished (event, _move_threshold_passed);
311 _editor->verbose_cursor()->hide ();
314 return _move_threshold_passed;
318 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
322 if (f > _pointer_frame_offset) {
323 pos = f - _pointer_frame_offset;
327 _editor->snap_to_with_modifier (pos, event);
334 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
336 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
340 Drag::snap_delta (GdkEvent const * event) const
342 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::snap_delta_modifier())) {
350 Drag::current_pointer_x() const
352 return _drags->current_pointer_x ();
356 Drag::current_pointer_y () const
358 if (!_trackview_only) {
359 return _drags->current_pointer_y ();
362 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
366 Drag::setup_snap_delta (framepos_t pos)
368 framepos_t temp = pos;
369 _editor->snap_to_no_magnets (temp);
370 _snap_delta = temp - pos;
374 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
376 /* check to see if we have moved in any way that matters since the last motion event */
377 if (_move_threshold_passed &&
378 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
379 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
383 pair<framecnt_t, int> const threshold = move_threshold ();
385 bool const old_move_threshold_passed = _move_threshold_passed;
387 if (!_move_threshold_passed) {
389 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
390 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
392 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
395 if (active (_editor->mouse_mode) && _move_threshold_passed) {
397 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
399 if (old_move_threshold_passed != _move_threshold_passed) {
403 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
404 if ((event->motion.state & Gdk::BUTTON2_MASK) || Config->get_edit_mode() == Constrained) {
405 _x_constrained = true;
406 _y_constrained = false;
408 _initially_vertical = true;
410 if ((event->motion.state & Gdk::BUTTON2_MASK) || Config->get_edit_mode() == Constrained) {
411 _x_constrained = false;
412 _y_constrained = true;
414 _initially_vertical = false;
417 if ((event->motion.state & Gdk::BUTTON2_MASK) && Config->get_edit_mode() == Constrained) {
418 _x_constrained = false;
419 _y_constrained = false;
423 if (!from_autoscroll) {
424 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
427 if (!_editor->autoscroll_active() || from_autoscroll) {
430 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
432 motion (event, first_move && !_starting_point_passed);
434 if (first_move && !_starting_point_passed) {
435 _starting_point_passed = true;
438 _last_pointer_x = _drags->current_pointer_x ();
439 _last_pointer_y = current_pointer_y ();
440 _last_pointer_frame = adjusted_current_frame (event);
450 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
458 aborted (_move_threshold_passed);
460 _editor->stop_canvas_autoscroll ();
461 _editor->verbose_cursor()->hide ();
465 Drag::show_verbose_cursor_time (framepos_t frame)
467 _editor->verbose_cursor()->set_time (frame);
468 _editor->verbose_cursor()->show ();
472 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
474 _editor->verbose_cursor()->set_duration (start, end);
475 _editor->verbose_cursor()->show ();
479 Drag::show_verbose_cursor_text (string const & text)
481 _editor->verbose_cursor()->set (text);
482 _editor->verbose_cursor()->show ();
485 boost::shared_ptr<Region>
486 Drag::add_midi_region (MidiTimeAxisView* view)
488 if (_editor->session()) {
489 const TempoMap& map (_editor->session()->tempo_map());
490 framecnt_t pos = grab_frame();
491 const Meter& m = map.meter_at (pos);
492 /* not that the frame rate used here can be affected by pull up/down which
495 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
496 return view->add_region (grab_frame(), len, true);
499 return boost::shared_ptr<Region>();
502 struct EditorOrderTimeAxisViewSorter {
503 bool operator() (TimeAxisView* a, TimeAxisView* b) {
504 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
505 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
507 return ra->route()->order_key () < rb->route()->order_key ();
511 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
516 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
518 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
519 as some of the regions we are dragging may be on such tracks.
522 TrackViewList track_views = _editor->track_views;
523 track_views.sort (EditorOrderTimeAxisViewSorter ());
525 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
526 _time_axis_views.push_back (*i);
528 TimeAxisView::Children children_list = (*i)->get_child_list ();
529 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
530 _time_axis_views.push_back (j->get());
534 /* the list of views can be empty at this point if this is a region list-insert drag
537 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
538 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
541 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
545 RegionDrag::region_going_away (RegionView* v)
547 list<DraggingView>::iterator i = _views.begin ();
548 while (i != _views.end() && i->view != v) {
552 if (i != _views.end()) {
557 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
558 * or -1 if it is not found.
561 RegionDrag::find_time_axis_view (TimeAxisView* t) const
564 int const N = _time_axis_views.size ();
565 while (i < N && _time_axis_views[i] != t) {
569 if (_time_axis_views[i] != t) {
576 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
577 : RegionDrag (e, i, p, v)
580 , _last_pointer_time_axis_view (0)
581 , _last_pointer_layer (0)
586 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
590 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
592 Drag::start_grab (event, cursor);
593 setup_snap_delta (_last_frame_position);
595 show_verbose_cursor_time (_last_frame_position);
597 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
599 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
600 assert(_last_pointer_time_axis_view >= 0);
601 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
606 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
608 /* compute the amount of pointer motion in frames, and where
609 the region would be if we moved it by that much.
611 *pending_region_position = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event), event, true);
613 framepos_t sync_frame;
614 framecnt_t sync_offset;
617 sync_offset = _primary->region()->sync_offset (sync_dir);
619 /* we don't handle a sync point that lies before zero.
621 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
623 sync_frame = *pending_region_position + (sync_dir * sync_offset);
625 _editor->snap_to_with_modifier (sync_frame, event);
627 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - snap_delta (event);
630 *pending_region_position = _last_frame_position;
633 if (*pending_region_position > max_framepos - _primary->region()->length()) {
634 *pending_region_position = _last_frame_position;
638 bool x_move_allowed = !_x_constrained;
640 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
642 /* x movement since last time (in pixels) */
643 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
645 /* total x movement */
646 framecnt_t total_dx = *pending_region_position;
647 if (regions_came_from_canvas()) {
648 total_dx = total_dx - grab_frame ();
651 /* check that no regions have gone off the start of the session */
652 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
653 if ((i->view->region()->position() + total_dx) < 0) {
655 *pending_region_position = _last_frame_position;
666 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
672 const int tavsize = _time_axis_views.size();
673 const int dt = delta > 0 ? +1 : -1;
675 int target = start + delta - skip;
677 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
678 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
680 while (current >= 0 && current != target) {
682 if (current < 0 && dt < 0) {
685 if (current >= tavsize && dt > 0) {
688 if (current < 0 || current >= tavsize) {
692 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
693 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
697 if (distance_only && current == start + delta) {
705 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
707 if (_y_constrained) {
711 const int tavsize = _time_axis_views.size();
712 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
713 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
714 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
716 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
717 /* already in the drop zone */
718 if (delta_track >= 0) {
719 /* downward motion - OK if others are still not in the dropzone */
728 } else if (n >= tavsize) {
729 /* downward motion into drop zone. That's fine. */
733 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
734 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
735 /* not a track, or the wrong type */
739 double const l = i->layer + delta_layer;
741 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
742 mode to allow the user to place a region below another on layer 0.
744 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
745 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
746 If it has, the layers will be munged later anyway, so it's ok.
752 /* all regions being dragged are ok with this change */
756 struct DraggingViewSorter {
757 bool operator() (const DraggingView& a, const DraggingView& b) {
758 return a.time_axis_view < b.time_axis_view;
763 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
765 double delta_layer = 0;
766 int delta_time_axis_view = 0;
767 int current_pointer_time_axis_view = -1;
769 assert (!_views.empty ());
771 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
773 /* Find the TimeAxisView that the pointer is now over */
774 const double cur_y = current_pointer_y ();
775 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
776 TimeAxisView* tv = r.first;
778 if (!tv && cur_y < 0) {
779 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
783 /* find drop-zone y-position */
784 Coord last_track_bottom_edge;
785 last_track_bottom_edge = 0;
786 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
787 if (!(*t)->hidden()) {
788 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
793 if (tv && tv->view()) {
794 /* the mouse is over a track */
795 double layer = r.second;
797 if (first_move && tv->view()->layer_display() == Stacked) {
798 tv->view()->set_layer_display (Expanded);
801 /* Here's the current pointer position in terms of time axis view and layer */
802 current_pointer_time_axis_view = find_time_axis_view (tv);
803 assert(current_pointer_time_axis_view >= 0);
805 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
807 /* Work out the change in y */
809 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
810 if (!rtv || !rtv->is_track()) {
811 /* ignore busses early on. we can't move any regions on them */
812 } else if (_last_pointer_time_axis_view < 0) {
813 /* Was in the drop-zone, now over a track.
814 * Hence it must be an upward move (from the bottom)
816 * track_index is still -1, so delta must be set to
817 * move up the correct number of tracks from the bottom.
819 * This is necessary because steps may be skipped if
820 * the bottom-most track is not a valid target and/or
821 * if there are hidden tracks at the bottom.
822 * Hence the initial offset (_ddropzone) as well as the
823 * last valid pointer position (_pdropzone) need to be
824 * taken into account.
826 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
828 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
831 /* TODO needs adjustment per DraggingView,
833 * e.g. select one region on the top-layer of a track
834 * and one region which is at the bottom-layer of another track
837 * Indicated drop-zones and layering is wrong.
838 * and may infer additional layers on the target-track
839 * (depending how many layers the original track had).
841 * Or select two regions (different layers) on a same track,
842 * move across a non-layer track.. -> layering info is lost.
843 * on drop either of the regions may be on top.
845 * Proposed solution: screw it :) well,
846 * don't use delta_layer, use an absolute value
847 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
848 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
849 * 3) iterate over all DraggingView, find the one that is over the track with most layers
850 * 4) proportionally scale layer to layers available on target
852 delta_layer = current_pointer_layer - _last_pointer_layer;
855 /* for automation lanes, there is a TimeAxisView but no ->view()
856 * if (!tv) -> dropzone
858 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
859 /* Moving into the drop-zone.. */
860 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
861 /* delta_time_axis_view may not be sufficient to move into the DZ
862 * the mouse may enter it, but it may not be a valid move due to
865 * -> remember the delta needed to move into the dropzone
867 _ddropzone = delta_time_axis_view;
868 /* ..but subtract hidden tracks (or routes) at the bottom.
869 * we silently move mover them
871 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
872 - _time_axis_views.size();
874 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
875 /* move around inside the zone.
876 * This allows to move further down until all regions are in the zone.
878 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
879 assert(ptr_y >= last_track_bottom_edge);
880 assert(_ddropzone > 0);
882 /* calculate mouse position in 'tracks' below last track. */
883 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
884 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
886 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
888 delta_time_axis_view = dzpos - _pdropzone;
889 } else if (dzpos < _pdropzone && _ndropzone > 0) {
890 // move up inside the DZ
891 delta_time_axis_view = dzpos - _pdropzone;
895 /* Work out the change in x */
896 framepos_t pending_region_position;
897 double const x_delta = compute_x_delta (event, &pending_region_position);
898 _last_frame_position = pending_region_position;
900 /* calculate hidden tracks in current y-axis delta */
902 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
903 /* The mouse is more than one track below the dropzone.
904 * distance calculation is not needed (and would not work, either
905 * because the dropzone is "packed").
907 * Except when [partially] moving regions out of dropzone in a large step.
908 * (the mouse may or may not remain in the DZ)
909 * Hidden tracks at the bottom of the TAV need to be skipped.
911 * This also handles the case if the mouse entered the DZ
912 * in a large step (exessive delta), either due to fast-movement,
913 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
915 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
916 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
918 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
919 -_time_axis_views.size() - dt;
922 else if (_last_pointer_time_axis_view < 0) {
923 /* Moving out of the zone. Check for hidden tracks at the bottom. */
924 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
925 -_time_axis_views.size() - delta_time_axis_view;
927 /* calculate hidden tracks that are skipped by the pointer movement */
928 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
929 - _last_pointer_time_axis_view
930 - delta_time_axis_view;
933 /* Verify change in y */
934 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
935 /* this y movement is not allowed, so do no y movement this time */
936 delta_time_axis_view = 0;
941 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
942 /* haven't reached next snap point, and we're not switching
943 trackviews nor layers. nothing to do.
948 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
949 PlaylistDropzoneMap playlist_dropzone_map;
950 _ndropzone = 0; // number of elements currently in the dropzone
953 /* sort views by time_axis.
954 * This retains track order in the dropzone, regardless
955 * of actual selection order
957 _views.sort (DraggingViewSorter());
959 /* count number of distinct tracks of all regions
960 * being dragged, used for dropzone.
963 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
964 if (i->time_axis_view != prev_track) {
965 prev_track = i->time_axis_view;
971 _views.back().time_axis_view -
972 _views.front().time_axis_view;
974 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
975 - _views.back().time_axis_view;
977 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
981 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
983 RegionView* rv = i->view;
988 if (rv->region()->locked() || rv->region()->video_locked()) {
995 /* reparent the regionview into a group above all
999 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1000 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1001 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1002 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1003 /* move the item so that it continues to appear at the
1004 same location now that its parent has changed.
1006 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1009 /* If we have moved tracks, we'll fudge the layer delta so that the
1010 region gets moved back onto layer 0 on its new track; this avoids
1011 confusion when dragging regions from non-zero layers onto different
1014 double this_delta_layer = delta_layer;
1015 if (delta_time_axis_view != 0) {
1016 this_delta_layer = - i->layer;
1019 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1021 int track_index = i->time_axis_view + this_delta_time_axis_view;
1022 assert(track_index >= 0);
1024 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1025 /* Track is in the Dropzone */
1027 i->time_axis_view = track_index;
1028 assert(i->time_axis_view >= (int) _time_axis_views.size());
1031 double yposition = 0;
1032 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1033 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1036 /* store index of each new playlist as a negative count, starting at -1 */
1038 if (pdz == playlist_dropzone_map.end()) {
1039 /* compute where this new track (which doesn't exist yet) will live
1042 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1044 /* How high is this region view ? */
1046 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1047 ArdourCanvas::Rect bbox;
1050 bbox = obbox.get ();
1053 last_track_bottom_edge += bbox.height();
1055 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1058 yposition = pdz->second;
1061 /* values are zero or negative, hence the use of min() */
1062 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1067 /* The TimeAxisView that this region is now over */
1068 TimeAxisView* current_tv = _time_axis_views[track_index];
1070 /* Ensure it is moved from stacked -> expanded if appropriate */
1071 if (current_tv->view()->layer_display() == Stacked) {
1072 current_tv->view()->set_layer_display (Expanded);
1075 /* We're only allowed to go -ve in layer on Expanded views */
1076 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1077 this_delta_layer = - i->layer;
1081 rv->set_height (current_tv->view()->child_height ());
1083 /* Update show/hidden status as the region view may have come from a hidden track,
1084 or have moved to one.
1086 if (current_tv->hidden ()) {
1087 rv->get_canvas_group()->hide ();
1089 rv->get_canvas_group()->show ();
1092 /* Update the DraggingView */
1093 i->time_axis_view = track_index;
1094 i->layer += this_delta_layer;
1097 _editor->mouse_brush_insert_region (rv, pending_region_position);
1101 /* Get the y coordinate of the top of the track that this region is now over */
1102 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1104 /* And adjust for the layer that it should be on */
1105 StreamView* cv = current_tv->view ();
1106 switch (cv->layer_display ()) {
1110 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1113 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1117 /* need to get the parent of the regionview
1118 * canvas group and get its position in
1119 * equivalent coordinate space as the trackview
1120 * we are now dragging over.
1123 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1128 /* Now move the region view */
1129 rv->move (x_delta, y_delta);
1131 } /* foreach region */
1133 _total_x_delta += x_delta;
1135 if (x_delta != 0 && !_brushing) {
1136 show_verbose_cursor_time (_last_frame_position);
1139 /* keep track of pointer movement */
1141 /* the pointer is currently over a time axis view */
1143 if (_last_pointer_time_axis_view < 0) {
1144 /* last motion event was not over a time axis view
1145 * or last y-movement out of the dropzone was not valid
1148 if (delta_time_axis_view < 0) {
1149 /* in the drop zone, moving up */
1151 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1152 * We do not use negative _last_pointer_time_axis_view because
1153 * the dropzone is "packed" (the actual track offset is ignored)
1155 * As opposed to the actual number
1156 * of elements in the dropzone (_ndropzone)
1157 * _pdropzone is not constrained. This is necessary
1158 * to allow moving multiple regions with y-distance
1161 * There can be 0 elements in the dropzone,
1162 * even though the drag-pointer is inside the DZ.
1165 * [ Audio-track, Midi-track, Audio-track, DZ ]
1166 * move regions from both audio tracks at the same time into the
1167 * DZ by grabbing the region in the bottom track.
1169 assert(current_pointer_time_axis_view >= 0);
1170 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1174 /* only move out of the zone if the movement is OK */
1175 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1176 assert(delta_time_axis_view < 0);
1177 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1178 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1179 * the current position can be calculated as follows:
1181 // a well placed oofus attack can still throw this off.
1182 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1183 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1186 /* last motion event was also over a time axis view */
1187 _last_pointer_time_axis_view += delta_time_axis_view;
1188 assert(_last_pointer_time_axis_view >= 0);
1193 /* the pointer is not over a time axis view */
1194 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1195 _pdropzone += delta_time_axis_view - delta_skip;
1196 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1199 _last_pointer_layer += delta_layer;
1203 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1205 if (_copy && first_move) {
1207 if (_x_constrained) {
1208 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1210 _editor->begin_reversible_command (Operations::region_copy);
1213 /* duplicate the regionview(s) and region(s) */
1215 list<DraggingView> new_regionviews;
1217 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1219 RegionView* rv = i->view;
1220 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1221 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1223 const boost::shared_ptr<const Region> original = rv->region();
1224 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
1225 region_copy->set_position (original->position());
1226 /* need to set this so that the drop zone code can work. This doesn't
1227 actually put the region into the playlist, but just sets a weak pointer
1230 region_copy->set_playlist (original->playlist());
1234 boost::shared_ptr<AudioRegion> audioregion_copy
1235 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1237 nrv = new AudioRegionView (*arv, audioregion_copy);
1239 boost::shared_ptr<MidiRegion> midiregion_copy
1240 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1241 nrv = new MidiRegionView (*mrv, midiregion_copy);
1246 nrv->get_canvas_group()->show ();
1247 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1249 /* swap _primary to the copy */
1251 if (rv == _primary) {
1255 /* ..and deselect the one we copied */
1257 rv->set_selected (false);
1260 if (!new_regionviews.empty()) {
1262 /* reflect the fact that we are dragging the copies */
1264 _views = new_regionviews;
1266 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1269 } else if (!_copy && first_move) {
1271 if (_x_constrained) {
1272 _editor->begin_reversible_command (_("fixed time region drag"));
1274 _editor->begin_reversible_command (Operations::region_drag);
1278 RegionMotionDrag::motion (event, first_move);
1282 RegionMotionDrag::finished (GdkEvent *, bool)
1284 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1285 if (!(*i)->view()) {
1289 if ((*i)->view()->layer_display() == Expanded) {
1290 (*i)->view()->set_layer_display (Stacked);
1296 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1298 RegionMotionDrag::finished (ev, movement_occurred);
1300 if (!movement_occurred) {
1304 if (was_double_click() && !_views.empty()) {
1305 DraggingView dv = _views.front();
1306 dv.view->show_region_editor ();
1313 assert (!_views.empty ());
1315 /* We might have hidden region views so that they weren't visible during the drag
1316 (when they have been reparented). Now everything can be shown again, as region
1317 views are back in their track parent groups.
1319 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1320 i->view->get_canvas_group()->show ();
1323 bool const changed_position = (_last_frame_position != _primary->region()->position());
1324 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1325 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1345 _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
1349 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1351 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1356 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1357 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1358 uint32_t output_chan = region->n_channels();
1359 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1360 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1362 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, ARDOUR::Normal, 0, 1, region->name());
1363 RouteTimeAxisView* rtav = _editor->axis_view_from_route (audio_tracks.front());
1365 rtav->set_height (original->current_height());
1369 ChanCount one_midi_port (DataType::MIDI, 1);
1370 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1371 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port, boost::shared_ptr<ARDOUR::PluginInfo>(), ARDOUR::Normal, 0, 1, region->name());
1372 RouteTimeAxisView* rtav = _editor->axis_view_from_route (midi_tracks.front());
1374 rtav->set_height (original->current_height());
1379 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1385 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
1387 RegionSelection new_views;
1388 PlaylistSet modified_playlists;
1389 RouteTimeAxisView* new_time_axis_view = 0;
1392 /* all changes were made during motion event handlers */
1394 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1398 _editor->commit_reversible_command ();
1402 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1403 PlaylistMapping playlist_mapping;
1405 /* insert the regions into their new playlists */
1406 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1408 RouteTimeAxisView* dest_rtv = 0;
1410 if (i->view->region()->locked() || i->view->region()->video_locked()) {
1416 if (changed_position && !_x_constrained) {
1417 where = i->view->region()->position() - drag_delta;
1419 where = i->view->region()->position();
1422 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1423 /* dragged to drop zone */
1425 PlaylistMapping::iterator pm;
1427 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1428 /* first region from this original playlist: create a new track */
1429 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1430 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1431 dest_rtv = new_time_axis_view;
1433 /* we already created a new track for regions from this playlist, use it */
1434 dest_rtv = pm->second;
1437 /* destination time axis view is the one we dragged to */
1438 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1441 if (dest_rtv != 0) {
1442 RegionView* new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, modified_playlists);
1443 if (new_view != 0) {
1444 new_views.push_back (new_view);
1448 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1449 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1452 list<DraggingView>::const_iterator next = i;
1458 /* If we've created new regions either by copying or moving
1459 to a new track, we want to replace the old selection with the new ones
1462 if (new_views.size() > 0) {
1463 _editor->selection->set (new_views);
1466 /* write commands for the accumulated diffs for all our modified playlists */
1467 add_stateful_diff_commands_for_playlists (modified_playlists);
1469 _editor->commit_reversible_command ();
1473 RegionMoveDrag::finished_no_copy (
1474 bool const changed_position,
1475 bool const changed_tracks,
1476 framecnt_t const drag_delta
1479 RegionSelection new_views;
1480 PlaylistSet modified_playlists;
1481 PlaylistSet frozen_playlists;
1482 set<RouteTimeAxisView*> views_to_update;
1483 RouteTimeAxisView* new_time_axis_view = 0;
1486 /* all changes were made during motion event handlers */
1487 _editor->commit_reversible_command ();
1491 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1492 PlaylistMapping playlist_mapping;
1494 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1496 RegionView* rv = i->view;
1497 RouteTimeAxisView* dest_rtv = 0;
1499 if (rv->region()->locked() || rv->region()->video_locked()) {
1504 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1505 /* dragged to drop zone */
1507 PlaylistMapping::iterator pm;
1509 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1510 /* first region from this original playlist: create a new track */
1511 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1512 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1513 dest_rtv = new_time_axis_view;
1515 /* we already created a new track for regions from this playlist, use it */
1516 dest_rtv = pm->second;
1520 /* destination time axis view is the one we dragged to */
1521 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1526 double const dest_layer = i->layer;
1528 views_to_update.insert (dest_rtv);
1532 if (changed_position && !_x_constrained) {
1533 where = rv->region()->position() - drag_delta;
1535 where = rv->region()->position();
1538 if (changed_tracks) {
1540 /* insert into new playlist */
1542 RegionView* new_view = insert_region_into_playlist (
1543 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
1546 if (new_view == 0) {
1551 new_views.push_back (new_view);
1553 /* remove from old playlist */
1555 /* the region that used to be in the old playlist is not
1556 moved to the new one - we use a copy of it. as a result,
1557 any existing editor for the region should no longer be
1560 rv->hide_region_editor();
1563 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1567 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1569 /* this movement may result in a crossfade being modified, or a layering change,
1570 so we need to get undo data from the playlist as well as the region.
1573 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1575 playlist->clear_changes ();
1578 rv->region()->clear_changes ();
1581 motion on the same track. plonk the previously reparented region
1582 back to its original canvas group (its streamview).
1583 No need to do anything for copies as they are fake regions which will be deleted.
1586 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1587 rv->get_canvas_group()->set_y_position (i->initial_y);
1590 /* just change the model */
1591 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1592 playlist->set_layer (rv->region(), dest_layer);
1595 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1597 r = frozen_playlists.insert (playlist);
1600 playlist->freeze ();
1603 rv->region()->set_position (where);
1605 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1608 if (changed_tracks) {
1610 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1611 was selected in all of them, then removing it from a playlist will have removed all
1612 trace of it from _views (i.e. there were N regions selected, we removed 1,
1613 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1614 corresponding regionview, and _views is now empty).
1616 This could have invalidated any and all iterators into _views.
1618 The heuristic we use here is: if the region selection is empty, break out of the loop
1619 here. if the region selection is not empty, then restart the loop because we know that
1620 we must have removed at least the region(view) we've just been working on as well as any
1621 that we processed on previous iterations.
1623 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1624 we can just iterate.
1628 if (_views.empty()) {
1639 /* If we've created new regions either by copying or moving
1640 to a new track, we want to replace the old selection with the new ones
1643 if (new_views.size() > 0) {
1644 _editor->selection->set (new_views);
1647 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1651 /* write commands for the accumulated diffs for all our modified playlists */
1652 add_stateful_diff_commands_for_playlists (modified_playlists);
1654 _editor->commit_reversible_command ();
1656 /* We have futzed with the layering of canvas items on our streamviews.
1657 If any region changed layer, this will have resulted in the stream
1658 views being asked to set up their region views, and all will be well.
1659 If not, we might now have badly-ordered region views. Ask the StreamViews
1660 involved to sort themselves out, just in case.
1663 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1664 (*i)->view()->playlist_layered ((*i)->track ());
1668 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1669 * @param region Region to remove.
1670 * @param playlist playlist To remove from.
1671 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1672 * that clear_changes () is only called once per playlist.
1675 RegionMoveDrag::remove_region_from_playlist (
1676 boost::shared_ptr<Region> region,
1677 boost::shared_ptr<Playlist> playlist,
1678 PlaylistSet& modified_playlists
1681 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1684 playlist->clear_changes ();
1687 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1691 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1692 * clearing the playlist's diff history first if necessary.
1693 * @param region Region to insert.
1694 * @param dest_rtv Destination RouteTimeAxisView.
1695 * @param dest_layer Destination layer.
1696 * @param where Destination position.
1697 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1698 * that clear_changes () is only called once per playlist.
1699 * @return New RegionView, or 0 if no insert was performed.
1702 RegionMoveDrag::insert_region_into_playlist (
1703 boost::shared_ptr<Region> region,
1704 RouteTimeAxisView* dest_rtv,
1707 PlaylistSet& modified_playlists
1710 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1711 if (!dest_playlist) {
1715 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1716 _new_region_view = 0;
1717 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1719 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1720 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1722 dest_playlist->clear_changes ();
1725 dest_playlist->add_region (region, where);
1727 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1728 dest_playlist->set_layer (region, dest_layer);
1733 assert (_new_region_view);
1735 return _new_region_view;
1739 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1741 _new_region_view = rv;
1745 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1747 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1748 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1750 _editor->session()->add_command (c);
1759 RegionMoveDrag::aborted (bool movement_occurred)
1763 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1764 list<DraggingView>::const_iterator next = i;
1773 RegionMotionDrag::aborted (movement_occurred);
1778 RegionMotionDrag::aborted (bool)
1780 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1782 StreamView* sview = (*i)->view();
1785 if (sview->layer_display() == Expanded) {
1786 sview->set_layer_display (Stacked);
1791 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1792 RegionView* rv = i->view;
1793 TimeAxisView* tv = &(rv->get_time_axis_view ());
1794 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1796 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1797 rv->get_canvas_group()->set_y_position (0);
1799 rv->move (-_total_x_delta, 0);
1800 rv->set_height (rtv->view()->child_height ());
1804 /** @param b true to brush, otherwise false.
1805 * @param c true to make copies of the regions being moved, otherwise false.
1807 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1808 : RegionMotionDrag (e, i, p, v, b)
1811 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1814 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1815 if (rtv && rtv->is_track()) {
1816 speed = rtv->track()->speed ();
1819 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1823 RegionMoveDrag::setup_pointer_frame_offset ()
1825 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1828 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1829 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1831 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1833 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1834 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1836 _primary = v->view()->create_region_view (r, false, false);
1838 _primary->get_canvas_group()->show ();
1839 _primary->set_position (pos, 0);
1840 _views.push_back (DraggingView (_primary, this, v));
1842 _last_frame_position = pos;
1844 _item = _primary->get_canvas_group ();
1848 RegionInsertDrag::finished (GdkEvent *, bool)
1850 int pos = _views.front().time_axis_view;
1851 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1853 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1855 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1856 _primary->get_canvas_group()->set_y_position (0);
1858 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1860 _editor->begin_reversible_command (Operations::insert_region);
1861 playlist->clear_changes ();
1862 playlist->add_region (_primary->region (), _last_frame_position);
1864 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
1865 if (Config->get_edit_mode() == Ripple) {
1866 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
1869 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1870 _editor->commit_reversible_command ();
1878 RegionInsertDrag::aborted (bool)
1885 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1886 : RegionMoveDrag (e, i, p, v, false, false)
1888 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1891 struct RegionSelectionByPosition {
1892 bool operator() (RegionView*a, RegionView* b) {
1893 return a->region()->position () < b->region()->position();
1898 RegionSpliceDrag::motion (GdkEvent* event, bool)
1900 /* Which trackview is this ? */
1902 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
1903 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1905 /* The region motion is only processed if the pointer is over
1909 if (!tv || !tv->is_track()) {
1910 /* To make sure we hide the verbose canvas cursor when the mouse is
1911 not held over an audio track.
1913 _editor->verbose_cursor()->hide ();
1916 _editor->verbose_cursor()->show ();
1921 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1927 RegionSelection copy;
1928 _editor->selection->regions.by_position(copy);
1930 framepos_t const pf = adjusted_current_frame (event);
1932 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1934 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1940 boost::shared_ptr<Playlist> playlist;
1942 if ((playlist = atv->playlist()) == 0) {
1946 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1951 if (pf < (*i)->region()->last_frame() + 1) {
1955 if (pf > (*i)->region()->first_frame()) {
1961 playlist->shuffle ((*i)->region(), dir);
1966 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1968 RegionMoveDrag::finished (event, movement_occurred);
1972 RegionSpliceDrag::aborted (bool)
1982 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
1985 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
1987 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
1988 RegionSelection to_ripple;
1989 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
1990 if ((*i)->position() >= where) {
1991 to_ripple.push_back (rtv->view()->find_view(*i));
1995 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
1996 if (!exclude.contains (*i)) {
1997 // the selection has already been added to _views
1999 if (drag_in_progress) {
2000 // do the same things that RegionMotionDrag::motion does when
2001 // first_move is true, for the region views that we're adding
2002 // to _views this time
2005 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2006 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2007 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2008 rvg->reparent (_editor->_drag_motion_group);
2010 // we only need to move in the y direction
2011 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2016 _views.push_back (DraggingView (*i, this, tav));
2022 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2025 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2026 // we added all the regions after the selection
2028 std::list<DraggingView>::iterator to_erase = i++;
2029 if (!_editor->selection->regions.contains (to_erase->view)) {
2030 // restore the non-selected regions to their original playlist & positions,
2031 // and then ripple them back by the length of the regions that were dragged away
2032 // do the same things as RegionMotionDrag::aborted
2034 RegionView *rv = to_erase->view;
2035 TimeAxisView* tv = &(rv->get_time_axis_view ());
2036 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2039 // plonk them back onto their own track
2040 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2041 rv->get_canvas_group()->set_y_position (0);
2045 // move the underlying region to match the view
2046 rv->region()->set_position (rv->region()->position() + amount);
2048 // restore the view to match the underlying region's original position
2049 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2052 rv->set_height (rtv->view()->child_height ());
2053 _views.erase (to_erase);
2059 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2061 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2063 return allow_moves_across_tracks;
2071 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2072 : RegionMoveDrag (e, i, p, v, false, false)
2074 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2075 // compute length of selection
2076 RegionSelection selected_regions = _editor->selection->regions;
2077 selection_length = selected_regions.end_frame() - selected_regions.start();
2079 // we'll only allow dragging to another track in ripple mode if all the regions
2080 // being dragged start off on the same track
2081 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2084 exclude = new RegionList;
2085 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2086 exclude->push_back((*i)->region());
2089 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2090 RegionSelection copy;
2091 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2093 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2094 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2096 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2097 // find ripple start point on each applicable playlist
2098 RegionView *first_selected_on_this_track = NULL;
2099 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2100 if ((*i)->region()->playlist() == (*pi)) {
2101 // region is on this playlist - it's the first, because they're sorted
2102 first_selected_on_this_track = *i;
2106 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2107 add_all_after_to_views (
2108 &first_selected_on_this_track->get_time_axis_view(),
2109 first_selected_on_this_track->region()->position(),
2110 selected_regions, false);
2113 if (allow_moves_across_tracks) {
2114 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2122 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2124 /* Which trackview is this ? */
2126 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2127 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2129 /* The region motion is only processed if the pointer is over
2133 if (!tv || !tv->is_track()) {
2134 /* To make sure we hide the verbose canvas cursor when the mouse is
2135 not held over an audiotrack.
2137 _editor->verbose_cursor()->hide ();
2141 framepos_t where = adjusted_current_frame (event);
2142 assert (where >= 0);
2144 double delta = compute_x_delta (event, &after);
2146 framecnt_t amount = _editor->pixel_to_sample (delta);
2148 if (allow_moves_across_tracks) {
2149 // all the originally selected regions were on the same track
2151 framecnt_t adjust = 0;
2152 if (prev_tav && tv != prev_tav) {
2153 // dragged onto a different track
2154 // remove the unselected regions from _views, restore them to their original positions
2155 // and add the regions after the drop point on the new playlist to _views instead.
2156 // undo the effect of rippling the previous playlist, and include the effect of removing
2157 // the dragged region(s) from this track
2159 remove_unselected_from_views (prev_amount, false);
2160 // ripple previous playlist according to the regions that have been removed onto the new playlist
2161 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2164 // move just the selected regions
2165 RegionMoveDrag::motion(event, first_move);
2167 // ensure that the ripple operation on the new playlist inserts selection_length time
2168 adjust = selection_length;
2169 // ripple the new current playlist
2170 tv->playlist()->ripple (where, amount+adjust, exclude);
2172 // add regions after point where drag entered this track to subsequent ripples
2173 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2176 // motion on same track
2177 RegionMoveDrag::motion(event, first_move);
2181 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2182 prev_position = where;
2184 // selection encompasses multiple tracks - just drag
2185 // cross-track drags are forbidden
2186 RegionMoveDrag::motion(event, first_move);
2189 if (!_x_constrained) {
2190 prev_amount += amount;
2193 _last_frame_position = after;
2197 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2199 if (!movement_occurred) {
2203 if (was_double_click() && !_views.empty()) {
2204 DraggingView dv = _views.front();
2205 dv.view->show_region_editor ();
2212 _editor->begin_reversible_command(_("Ripple drag"));
2214 // remove the regions being rippled from the dragging view, updating them to
2215 // their new positions
2216 remove_unselected_from_views (prev_amount, true);
2218 if (allow_moves_across_tracks) {
2220 // if regions were dragged across tracks, we've rippled any later
2221 // regions on the track the regions were dragged off, so we need
2222 // to add the original track to the undo record
2223 orig_tav->playlist()->clear_changes();
2224 vector<Command*> cmds;
2225 orig_tav->playlist()->rdiff (cmds);
2226 _editor->session()->add_commands (cmds);
2228 if (prev_tav && prev_tav != orig_tav) {
2229 prev_tav->playlist()->clear_changes();
2230 vector<Command*> cmds;
2231 prev_tav->playlist()->rdiff (cmds);
2232 _editor->session()->add_commands (cmds);
2235 // selection spanned multiple tracks - all will need adding to undo record
2237 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2238 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2240 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2241 (*pi)->clear_changes();
2242 vector<Command*> cmds;
2243 (*pi)->rdiff (cmds);
2244 _editor->session()->add_commands (cmds);
2248 // other modified playlists are added to undo by RegionMoveDrag::finished()
2249 RegionMoveDrag::finished (event, movement_occurred);
2250 _editor->commit_reversible_command();
2254 RegionRippleDrag::aborted (bool movement_occurred)
2256 RegionMoveDrag::aborted (movement_occurred);
2261 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2263 _view (dynamic_cast<MidiTimeAxisView*> (v))
2265 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2271 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2274 _region = add_midi_region (_view);
2275 _view->playlist()->freeze ();
2278 framepos_t const f = adjusted_current_frame (event);
2279 if (f < grab_frame()) {
2280 _region->set_position (f);
2283 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2284 so that if this region is duplicated, its duplicate starts on
2285 a snap point rather than 1 frame after a snap point. Otherwise things get
2286 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2287 place snapped notes at the start of the region.
2290 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2291 _region->set_length (len < 1 ? 1 : len);
2297 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
2299 if (!movement_occurred) {
2300 add_midi_region (_view);
2302 _view->playlist()->thaw ();
2307 RegionCreateDrag::aborted (bool)
2310 _view->playlist()->thaw ();
2316 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2321 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2325 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2327 Gdk::Cursor* cursor;
2328 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2330 float x_fraction = cnote->mouse_x_fraction ();
2332 if (x_fraction > 0.0 && x_fraction < 0.25) {
2333 cursor = _editor->cursors()->left_side_trim;
2336 cursor = _editor->cursors()->right_side_trim;
2340 Drag::start_grab (event, cursor);
2342 region = &cnote->region_view();
2345 temp = region->snap_to_pixel_no_magnets (cnote->x0 ());
2346 _snap_delta = temp - cnote->x0 ();
2350 if (event->motion.state & Keyboard::PrimaryModifier) {
2356 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2358 if (ms.size() > 1) {
2359 /* has to be relative, may make no sense otherwise */
2363 /* select this note; if it is already selected, preserve the existing selection,
2364 otherwise make this note the only one selected.
2366 region->note_selected (cnote, cnote->selected ());
2368 _editor->begin_reversible_command (_("resize notes"));
2370 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2371 MidiRegionSelection::iterator next;
2374 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2376 mrv->begin_resizing (at_front);
2383 NoteResizeDrag::motion (GdkEvent* event, bool /*first_move*/)
2385 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2386 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2387 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2389 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2392 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
2395 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd);
2401 NoteResizeDrag::finished (GdkEvent* event, bool /*movement_occurred*/)
2403 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2404 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2405 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2407 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2409 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
2413 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd);
2417 _editor->commit_reversible_command ();
2421 NoteResizeDrag::aborted (bool)
2423 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2424 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2425 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2427 mrv->abort_resizing ();
2432 AVDraggingView::AVDraggingView (RegionView* v)
2435 initial_position = v->region()->position ();
2438 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2441 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2444 TrackViewList empty;
2446 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2447 std::list<RegionView*> views = rs.by_layer();
2449 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2450 RegionView* rv = (*i);
2451 if (!rv->region()->video_locked()) {
2454 _views.push_back (AVDraggingView (rv));
2459 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2461 Drag::start_grab (event);
2462 if (_editor->session() == 0) {
2466 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2467 _max_backwards_drag = (
2468 ARDOUR_UI::instance()->video_timeline->get_duration()
2469 + ARDOUR_UI::instance()->video_timeline->get_offset()
2470 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2473 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2474 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2475 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2478 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2481 Timecode::Time timecode;
2482 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2483 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);
2484 show_verbose_cursor_text (buf);
2488 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2490 if (_editor->session() == 0) {
2493 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2497 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2498 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2500 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2501 dt = - _max_backwards_drag;
2504 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2505 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2507 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2508 RegionView* rv = i->view;
2509 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2512 rv->region()->clear_changes ();
2513 rv->region()->suspend_property_changes();
2515 rv->region()->set_position(i->initial_position + dt);
2516 rv->region_changed(ARDOUR::Properties::position);
2519 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2520 Timecode::Time timecode;
2521 Timecode::Time timediff;
2523 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2524 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2525 snprintf (buf, sizeof (buf),
2526 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2527 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2528 , _("Video Start:"),
2529 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2531 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2533 show_verbose_cursor_text (buf);
2537 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2539 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2543 if (!movement_occurred || ! _editor->session()) {
2547 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2549 _editor->begin_reversible_command (_("Move Video"));
2551 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2552 ARDOUR_UI::instance()->video_timeline->save_undo();
2553 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2554 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2556 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2557 i->view->drag_end();
2558 i->view->region()->resume_property_changes ();
2560 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2563 _editor->session()->maybe_update_session_range(
2564 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2565 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2569 _editor->commit_reversible_command ();
2573 VideoTimeLineDrag::aborted (bool)
2575 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2578 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2579 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2581 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2582 i->view->region()->resume_property_changes ();
2583 i->view->region()->set_position(i->initial_position);
2587 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2588 : RegionDrag (e, i, p, v)
2589 , _preserve_fade_anchor (preserve_fade_anchor)
2591 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2595 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2598 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2599 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2601 if (tv && tv->is_track()) {
2602 speed = tv->track()->speed();
2605 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2606 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2607 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2609 framepos_t const pf = adjusted_current_frame (event);
2610 setup_snap_delta (region_start);
2612 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
2613 /* Move the contents of the region around without changing the region bounds */
2614 _operation = ContentsTrim;
2615 Drag::start_grab (event, _editor->cursors()->trimmer);
2617 /* These will get overridden for a point trim.*/
2618 if (pf < (region_start + region_length/2)) {
2619 /* closer to front */
2620 _operation = StartTrim;
2621 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2622 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2624 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2628 _operation = EndTrim;
2629 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2630 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2632 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2637 switch (_operation) {
2639 show_verbose_cursor_time (region_start);
2640 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2641 i->view->trim_front_starting ();
2645 show_verbose_cursor_duration (region_start, region_end);
2648 show_verbose_cursor_time (pf);
2652 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2653 i->view->region()->suspend_property_changes ();
2658 TrimDrag::motion (GdkEvent* event, bool first_move)
2660 RegionView* rv = _primary;
2663 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2664 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2665 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2666 frameoffset_t frame_delta = 0;
2668 if (tv && tv->is_track()) {
2669 speed = tv->track()->speed();
2671 framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event), event, true);
2672 framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event);
2678 switch (_operation) {
2680 trim_type = "Region start trim";
2683 trim_type = "Region end trim";
2686 trim_type = "Region content trim";
2693 _editor->begin_reversible_command (trim_type);
2695 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2696 RegionView* rv = i->view;
2697 rv->enable_display (false);
2698 rv->region()->playlist()->clear_owned_changes ();
2700 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2703 arv->temporarily_hide_envelope ();
2707 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2708 insert_result = _editor->motion_frozen_playlists.insert (pl);
2710 if (insert_result.second) {
2716 bool non_overlap_trim = false;
2718 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::PrimaryModifier | Keyboard::TertiaryModifier))) {
2719 non_overlap_trim = true;
2722 /* contstrain trim to fade length */
2723 if (_preserve_fade_anchor) {
2724 switch (_operation) {
2726 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2727 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2729 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2730 if (ar->locked()) continue;
2731 framecnt_t len = ar->fade_in()->back()->when;
2732 if (len < dt) dt = min(dt, len);
2736 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2737 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2739 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2740 if (ar->locked()) continue;
2741 framecnt_t len = ar->fade_out()->back()->when;
2742 if (len < -dt) dt = max(dt, -len);
2751 switch (_operation) {
2753 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2754 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim);
2755 if (changed && _preserve_fade_anchor) {
2756 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2758 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2759 framecnt_t len = ar->fade_in()->back()->when;
2760 framecnt_t diff = ar->first_frame() - i->initial_position;
2761 framepos_t new_length = len - diff;
2762 i->anchored_fade_length = min (ar->length(), new_length);
2763 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
2764 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
2771 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2772 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim);
2773 if (changed && _preserve_fade_anchor) {
2774 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2776 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2777 framecnt_t len = ar->fade_out()->back()->when;
2778 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
2779 framepos_t new_length = len + diff;
2780 i->anchored_fade_length = min (ar->length(), new_length);
2781 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
2782 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
2790 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
2792 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2793 i->view->move_contents (frame_delta);
2799 switch (_operation) {
2801 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
2804 show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
2807 // show_verbose_cursor_time (frame_delta);
2813 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
2815 if (movement_occurred) {
2816 motion (event, false);
2818 if (_operation == StartTrim) {
2819 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2821 /* This must happen before the region's StatefulDiffCommand is created, as it may
2822 `correct' (ahem) the region's _start from being negative to being zero. It
2823 needs to be zero in the undo record.
2825 i->view->trim_front_ending ();
2827 if (_preserve_fade_anchor) {
2828 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2830 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2831 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
2832 ar->set_fade_in_length(i->anchored_fade_length);
2833 ar->set_fade_in_active(true);
2837 } else if (_operation == EndTrim) {
2838 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2839 if (_preserve_fade_anchor) {
2840 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2842 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2843 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
2844 ar->set_fade_out_length(i->anchored_fade_length);
2845 ar->set_fade_out_active(true);
2851 if (!_views.empty()) {
2852 if (_operation == StartTrim) {
2853 _editor->maybe_locate_with_edit_preroll(
2854 _views.begin()->view->region()->position());
2856 if (_operation == EndTrim) {
2857 _editor->maybe_locate_with_edit_preroll(
2858 _views.begin()->view->region()->position() +
2859 _views.begin()->view->region()->length());
2863 if (!_editor->selection->selected (_primary)) {
2864 _primary->thaw_after_trim ();
2867 set<boost::shared_ptr<Playlist> > diffed_playlists;
2869 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2870 i->view->thaw_after_trim ();
2871 i->view->enable_display (true);
2873 /* Trimming one region may affect others on the playlist, so we need
2874 to get undo Commands from the whole playlist rather than just the
2875 region. Use diffed_playlists to make sure we don't diff a given
2876 playlist more than once.
2878 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
2879 if (diffed_playlists.find (p) == diffed_playlists.end()) {
2880 vector<Command*> cmds;
2882 _editor->session()->add_commands (cmds);
2883 diffed_playlists.insert (p);
2888 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
2892 _editor->motion_frozen_playlists.clear ();
2893 _editor->commit_reversible_command();
2896 /* no mouse movement */
2897 _editor->point_trim (event, adjusted_current_frame (event));
2900 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2901 if (_operation == StartTrim) {
2902 i->view->trim_front_ending ();
2905 i->view->region()->resume_property_changes ();
2910 TrimDrag::aborted (bool movement_occurred)
2912 /* Our motion method is changing model state, so use the Undo system
2913 to cancel. Perhaps not ideal, as this will leave an Undo point
2914 behind which may be slightly odd from the user's point of view.
2919 if (movement_occurred) {
2923 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2924 i->view->region()->resume_property_changes ();
2929 TrimDrag::setup_pointer_frame_offset ()
2931 list<DraggingView>::iterator i = _views.begin ();
2932 while (i != _views.end() && i->view != _primary) {
2936 if (i == _views.end()) {
2940 switch (_operation) {
2942 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
2945 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
2952 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
2956 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
2957 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
2962 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2964 Drag::start_grab (event, cursor);
2965 show_verbose_cursor_time (adjusted_current_frame(event));
2969 MeterMarkerDrag::setup_pointer_frame_offset ()
2971 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
2975 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
2977 if (!_marker->meter().movable()) {
2983 // create a dummy marker for visual representation of moving the
2984 // section, because whether its a copy or not, we're going to
2985 // leave or lose the original marker (leave if its a copy; lose if its
2986 // not, because we'll remove it from the map).
2988 MeterSection section (_marker->meter());
2990 if (!section.movable()) {
2995 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
2997 _marker = new MeterMarker (
2999 *_editor->meter_group,
3000 ARDOUR_UI::config()->color ("meter marker"),
3002 *new MeterSection (_marker->meter())
3005 /* use the new marker for the grab */
3006 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3009 TempoMap& map (_editor->session()->tempo_map());
3010 /* get current state */
3011 before_state = &map.get_state();
3012 /* remove the section while we drag it */
3013 map.remove_meter (section, true);
3017 framepos_t const pf = adjusted_current_frame (event);
3019 _marker->set_position (pf);
3020 show_verbose_cursor_time (pf);
3024 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3026 if (!movement_occurred) {
3027 if (was_double_click()) {
3028 _editor->edit_meter_marker (*_marker);
3033 if (!_marker->meter().movable()) {
3037 motion (event, false);
3039 Timecode::BBT_Time when;
3041 TempoMap& map (_editor->session()->tempo_map());
3042 map.bbt_time (last_pointer_frame(), when);
3044 if (_copy == true) {
3045 _editor->begin_reversible_command (_("copy meter mark"));
3046 XMLNode &before = map.get_state();
3047 map.add_meter (_marker->meter(), when);
3048 XMLNode &after = map.get_state();
3049 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
3050 _editor->commit_reversible_command ();
3053 _editor->begin_reversible_command (_("move meter mark"));
3055 /* we removed it before, so add it back now */
3057 map.add_meter (_marker->meter(), when);
3058 XMLNode &after = map.get_state();
3059 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3060 _editor->commit_reversible_command ();
3063 // delete the dummy marker we used for visual representation while moving.
3064 // a new visual marker will show up automatically.
3069 MeterMarkerDrag::aborted (bool moved)
3071 _marker->set_position (_marker->meter().frame ());
3074 TempoMap& map (_editor->session()->tempo_map());
3075 /* we removed it before, so add it back now */
3076 map.add_meter (_marker->meter(), _marker->meter().frame());
3077 // delete the dummy marker we used for visual representation while moving.
3078 // a new visual marker will show up automatically.
3083 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3087 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3089 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3094 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3096 Drag::start_grab (event, cursor);
3097 show_verbose_cursor_time (adjusted_current_frame (event));
3101 TempoMarkerDrag::setup_pointer_frame_offset ()
3103 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
3107 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3109 if (!_marker->tempo().movable()) {
3115 // create a dummy marker for visual representation of moving the
3116 // section, because whether its a copy or not, we're going to
3117 // leave or lose the original marker (leave if its a copy; lose if its
3118 // not, because we'll remove it from the map).
3120 // create a dummy marker for visual representation of moving the copy.
3121 // The actual copying is not done before we reach the finish callback.
3124 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
3126 TempoSection section (_marker->tempo());
3128 _marker = new TempoMarker (
3130 *_editor->tempo_group,
3131 ARDOUR_UI::config()->color ("tempo marker"),
3133 *new TempoSection (_marker->tempo())
3136 /* use the new marker for the grab */
3137 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3140 TempoMap& map (_editor->session()->tempo_map());
3141 /* get current state */
3142 before_state = &map.get_state();
3143 /* remove the section while we drag it */
3144 map.remove_tempo (section, true);
3148 framepos_t const pf = adjusted_current_frame (event);
3149 _marker->set_position (pf);
3150 show_verbose_cursor_time (pf);
3154 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3156 if (!movement_occurred) {
3157 if (was_double_click()) {
3158 _editor->edit_tempo_marker (*_marker);
3163 if (!_marker->tempo().movable()) {
3167 motion (event, false);
3169 TempoMap& map (_editor->session()->tempo_map());
3170 framepos_t beat_time = map.round_to_beat (last_pointer_frame(), RoundNearest);
3171 Timecode::BBT_Time when;
3173 map.bbt_time (beat_time, when);
3175 if (_copy == true) {
3176 _editor->begin_reversible_command (_("copy tempo mark"));
3177 XMLNode &before = map.get_state();
3178 map.add_tempo (_marker->tempo(), when);
3179 XMLNode &after = map.get_state();
3180 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
3181 _editor->commit_reversible_command ();
3184 _editor->begin_reversible_command (_("move tempo mark"));
3185 /* we removed it before, so add it back now */
3186 map.add_tempo (_marker->tempo(), when);
3187 XMLNode &after = map.get_state();
3188 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3189 _editor->commit_reversible_command ();
3192 // delete the dummy marker we used for visual representation while moving.
3193 // a new visual marker will show up automatically.
3198 TempoMarkerDrag::aborted (bool moved)
3200 _marker->set_position (_marker->tempo().frame());
3202 TempoMap& map (_editor->session()->tempo_map());
3203 /* we removed it before, so add it back now */
3204 map.add_tempo (_marker->tempo(), _marker->tempo().start());
3205 // delete the dummy marker we used for visual representation while moving.
3206 // a new visual marker will show up automatically.
3211 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3212 : Drag (e, &c.track_canvas_item(), false)
3216 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3219 /** Do all the things we do when dragging the playhead to make it look as though
3220 * we have located, without actually doing the locate (because that would cause
3221 * the diskstream buffers to be refilled, which is too slow).
3224 CursorDrag::fake_locate (framepos_t t)
3226 _editor->playhead_cursor->set_position (t);
3228 Session* s = _editor->session ();
3229 if (s->timecode_transmission_suspended ()) {
3230 framepos_t const f = _editor->playhead_cursor->current_frame ();
3231 /* This is asynchronous so it will be sent "now"
3233 s->send_mmc_locate (f);
3234 /* These are synchronous and will be sent during the next
3237 s->queue_full_time_code ();
3238 s->queue_song_position_pointer ();
3241 show_verbose_cursor_time (t);
3242 _editor->UpdateAllTransportClocks (t);
3246 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3248 Drag::start_grab (event, c);
3249 setup_snap_delta (_editor->playhead_cursor->current_frame ());
3251 _grab_zoom = _editor->samples_per_pixel;
3253 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event);
3255 _editor->snap_to_with_modifier (where, event);
3257 _editor->_dragging_playhead = true;
3259 Session* s = _editor->session ();
3261 /* grab the track canvas item as well */
3263 _cursor.track_canvas_item().grab();
3266 if (_was_rolling && _stop) {
3270 if (s->is_auditioning()) {
3271 s->cancel_audition ();
3275 if (AudioEngine::instance()->connected()) {
3277 /* do this only if we're the engine is connected
3278 * because otherwise this request will never be
3279 * serviced and we'll busy wait forever. likewise,
3280 * notice if we are disconnected while waiting for the
3281 * request to be serviced.
3284 s->request_suspend_timecode_transmission ();
3285 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3286 /* twiddle our thumbs */
3291 fake_locate (where - snap_delta (event));
3295 CursorDrag::motion (GdkEvent* event, bool)
3297 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event);
3298 _editor->snap_to_with_modifier (where, event);
3299 if (where != last_pointer_frame()) {
3300 fake_locate (where - snap_delta (event));
3305 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3307 _editor->_dragging_playhead = false;
3309 _cursor.track_canvas_item().ungrab();
3311 if (!movement_occurred && _stop) {
3315 motion (event, false);
3317 Session* s = _editor->session ();
3319 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3320 _editor->_pending_locate_request = true;
3321 s->request_resume_timecode_transmission ();
3326 CursorDrag::aborted (bool)
3328 _cursor.track_canvas_item().ungrab();
3330 if (_editor->_dragging_playhead) {
3331 _editor->session()->request_resume_timecode_transmission ();
3332 _editor->_dragging_playhead = false;
3335 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3338 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3339 : RegionDrag (e, i, p, v)
3341 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3345 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3347 Drag::start_grab (event, cursor);
3349 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3350 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3351 setup_snap_delta (r->position ());
3353 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3357 FadeInDrag::setup_pointer_frame_offset ()
3359 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3360 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3361 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3365 FadeInDrag::motion (GdkEvent* event, bool)
3367 framecnt_t fade_length;
3369 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event);
3370 _editor->snap_to_with_modifier (pos, event);
3371 pos -= snap_delta (event);
3373 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3375 if (pos < (region->position() + 64)) {
3376 fade_length = 64; // this should be a minimum defined somewhere
3377 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3378 fade_length = region->length() - region->fade_out()->back()->when - 1;
3380 fade_length = pos - region->position();
3383 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3385 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3391 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3394 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3398 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3400 if (!movement_occurred) {
3404 framecnt_t fade_length;
3405 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event);
3406 _editor->snap_to_with_modifier (pos, event);
3407 pos -= snap_delta (event);
3409 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3411 if (pos < (region->position() + 64)) {
3412 fade_length = 64; // this should be a minimum defined somewhere
3413 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3414 fade_length = region->length() - region->fade_out()->back()->when - 1;
3416 fade_length = pos - region->position();
3419 _editor->begin_reversible_command (_("change fade in length"));
3421 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3423 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3429 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3430 XMLNode &before = alist->get_state();
3432 tmp->audio_region()->set_fade_in_length (fade_length);
3433 tmp->audio_region()->set_fade_in_active (true);
3435 XMLNode &after = alist->get_state();
3436 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3439 _editor->commit_reversible_command ();
3443 FadeInDrag::aborted (bool)
3445 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3446 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3452 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3456 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3457 : RegionDrag (e, i, p, v)
3459 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3463 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3465 Drag::start_grab (event, cursor);
3467 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3468 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3469 setup_snap_delta (r->last_frame ());
3471 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3475 FadeOutDrag::setup_pointer_frame_offset ()
3477 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3478 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3479 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3483 FadeOutDrag::motion (GdkEvent* event, bool)
3485 framecnt_t fade_length;
3487 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event);
3488 _editor->snap_to_with_modifier (pos, event);
3489 pos -= snap_delta (event);
3491 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3493 if (pos > (region->last_frame() - 64)) {
3494 fade_length = 64; // this should really be a minimum fade defined somewhere
3495 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3496 fade_length = region->length() - region->fade_in()->back()->when - 1;
3498 fade_length = region->last_frame() - pos;
3501 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3503 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3509 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3512 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3516 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3518 if (!movement_occurred) {
3522 framecnt_t fade_length;
3524 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event);
3525 _editor->snap_to_with_modifier (pos, event);
3526 pos -= snap_delta (event);
3528 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3530 if (pos > (region->last_frame() - 64)) {
3531 fade_length = 64; // this should really be a minimum fade defined somewhere
3532 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3533 fade_length = region->length() - region->fade_in()->back()->when - 1;
3535 fade_length = region->last_frame() - pos;
3538 _editor->begin_reversible_command (_("change fade out length"));
3540 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3542 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3548 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3549 XMLNode &before = alist->get_state();
3551 tmp->audio_region()->set_fade_out_length (fade_length);
3552 tmp->audio_region()->set_fade_out_active (true);
3554 XMLNode &after = alist->get_state();
3555 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3558 _editor->commit_reversible_command ();
3562 FadeOutDrag::aborted (bool)
3564 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3565 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3571 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3575 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3578 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3580 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
3583 _points.push_back (ArdourCanvas::Duple (0, 0));
3584 _points.push_back (ArdourCanvas::Duple (0, physical_screen_height (_editor->get_window())));
3587 MarkerDrag::~MarkerDrag ()
3589 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
3594 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, Marker* m)
3596 location = new Location (*l);
3597 markers.push_back (m);
3602 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3604 Drag::start_grab (event, cursor);
3608 Location *location = _editor->find_location_from_marker (_marker, is_start);
3609 _editor->_dragging_edit_point = true;
3611 update_item (location);
3613 // _drag_line->show();
3614 // _line->raise_to_top();
3617 show_verbose_cursor_time (location->start());
3619 show_verbose_cursor_time (location->end());
3622 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3625 case Selection::Toggle:
3626 /* we toggle on the button release */
3628 case Selection::Set:
3629 if (!_editor->selection->selected (_marker)) {
3630 _editor->selection->set (_marker);
3633 case Selection::Extend:
3635 Locations::LocationList ll;
3636 list<Marker*> to_add;
3638 _editor->selection->markers.range (s, e);
3639 s = min (_marker->position(), s);
3640 e = max (_marker->position(), e);
3643 if (e < max_framepos) {
3646 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
3647 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
3648 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
3651 to_add.push_back (lm->start);
3654 to_add.push_back (lm->end);
3658 if (!to_add.empty()) {
3659 _editor->selection->add (to_add);
3663 case Selection::Add:
3664 _editor->selection->add (_marker);
3668 /* Set up copies for us to manipulate during the drag
3671 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
3673 Location* l = _editor->find_location_from_marker (*i, is_start);
3680 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
3682 /* range: check that the other end of the range isn't
3685 CopiedLocationInfo::iterator x;
3686 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3687 if (*(*x).location == *l) {
3691 if (x == _copied_locations.end()) {
3692 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
3694 (*x).markers.push_back (*i);
3695 (*x).move_both = true;
3703 MarkerDrag::setup_pointer_frame_offset ()
3706 Location *location = _editor->find_location_from_marker (_marker, is_start);
3707 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
3711 MarkerDrag::motion (GdkEvent* event, bool)
3713 framecnt_t f_delta = 0;
3715 bool move_both = false;
3716 Location *real_location;
3717 Location *copy_location = 0;
3719 framepos_t const newframe = adjusted_current_frame (event);
3720 framepos_t next = newframe;
3722 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
3726 CopiedLocationInfo::iterator x;
3728 /* find the marker we're dragging, and compute the delta */
3730 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3732 copy_location = (*x).location;
3734 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
3736 /* this marker is represented by this
3737 * CopiedLocationMarkerInfo
3740 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
3745 if (real_location->is_mark()) {
3746 f_delta = newframe - copy_location->start();
3750 switch (_marker->type()) {
3751 case Marker::SessionStart:
3752 case Marker::RangeStart:
3753 case Marker::LoopStart:
3754 case Marker::PunchIn:
3755 f_delta = newframe - copy_location->start();
3758 case Marker::SessionEnd:
3759 case Marker::RangeEnd:
3760 case Marker::LoopEnd:
3761 case Marker::PunchOut:
3762 f_delta = newframe - copy_location->end();
3765 /* what kind of marker is this ? */
3774 if (x == _copied_locations.end()) {
3775 /* hmm, impossible - we didn't find the dragged marker */
3779 /* now move them all */
3781 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3783 copy_location = x->location;
3785 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
3789 if (real_location->locked()) {
3793 if (copy_location->is_mark()) {
3797 copy_location->set_start (copy_location->start() + f_delta);
3801 framepos_t new_start = copy_location->start() + f_delta;
3802 framepos_t new_end = copy_location->end() + f_delta;
3804 if (is_start) { // start-of-range marker
3806 if (move_both || (*x).move_both) {
3807 copy_location->set_start (new_start);
3808 copy_location->set_end (new_end);
3809 } else if (new_start < copy_location->end()) {
3810 copy_location->set_start (new_start);
3811 } else if (newframe > 0) {
3812 _editor->snap_to (next, RoundUpAlways, true);
3813 copy_location->set_end (next);
3814 copy_location->set_start (newframe);
3817 } else { // end marker
3819 if (move_both || (*x).move_both) {
3820 copy_location->set_end (new_end);
3821 copy_location->set_start (new_start);
3822 } else if (new_end > copy_location->start()) {
3823 copy_location->set_end (new_end);
3824 } else if (newframe > 0) {
3825 _editor->snap_to (next, RoundDownAlways, true);
3826 copy_location->set_start (next);
3827 copy_location->set_end (newframe);
3832 update_item (copy_location);
3834 /* now lookup the actual GUI items used to display this
3835 * location and move them to wherever the copy of the location
3836 * is now. This means that the logic in ARDOUR::Location is
3837 * still enforced, even though we are not (yet) modifying
3838 * the real Location itself.
3841 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
3844 lm->set_position (copy_location->start(), copy_location->end());
3849 assert (!_copied_locations.empty());
3851 show_verbose_cursor_time (newframe);
3855 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3857 if (!movement_occurred) {
3859 if (was_double_click()) {
3860 _editor->rename_marker (_marker);
3864 /* just a click, do nothing but finish
3865 off the selection process
3868 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3871 case Selection::Set:
3872 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
3873 _editor->selection->set (_marker);
3877 case Selection::Toggle:
3878 /* we toggle on the button release, click only */
3879 _editor->selection->toggle (_marker);
3882 case Selection::Extend:
3883 case Selection::Add:
3890 _editor->_dragging_edit_point = false;
3892 _editor->begin_reversible_command ( _("move marker") );
3893 XMLNode &before = _editor->session()->locations()->get_state();
3895 MarkerSelection::iterator i;
3896 CopiedLocationInfo::iterator x;
3899 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
3900 x != _copied_locations.end() && i != _editor->selection->markers.end();
3903 Location * location = _editor->find_location_from_marker (*i, is_start);
3907 if (location->locked()) {
3911 if (location->is_mark()) {
3912 location->set_start (((*x).location)->start());
3914 location->set (((*x).location)->start(), ((*x).location)->end());
3919 XMLNode &after = _editor->session()->locations()->get_state();
3920 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3921 _editor->commit_reversible_command ();
3925 MarkerDrag::aborted (bool movement_occured)
3927 if (!movement_occured) {
3931 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3933 /* move all markers to their original location */
3936 for (vector<Marker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
3939 Location * location = _editor->find_location_from_marker (*m, is_start);
3942 (*m)->set_position (is_start ? location->start() : location->end());
3949 MarkerDrag::update_item (Location*)
3954 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
3956 _cumulative_x_drag (0),
3957 _cumulative_y_drag (0)
3959 if (_zero_gain_fraction < 0.0) {
3960 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
3963 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
3965 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
3971 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
3973 Drag::start_grab (event, _editor->cursors()->fader);
3975 // start the grab at the center of the control point so
3976 // the point doesn't 'jump' to the mouse after the first drag
3977 _fixed_grab_x = _point->get_x();
3978 _fixed_grab_y = _point->get_y();
3980 framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
3981 setup_snap_delta (pos);
3983 float const fraction = 1 - (_point->get_y() / _point->line().height());
3985 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
3987 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
3989 _pushing = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
3991 if (!_point->can_slide ()) {
3992 _x_constrained = true;
3997 ControlPointDrag::motion (GdkEvent* event, bool)
3999 double dx = _drags->current_pointer_x() - last_pointer_x();
4000 double dy = current_pointer_y() - last_pointer_y();
4002 if (event->button.state & Keyboard::SecondaryModifier) {
4007 /* coordinate in pixels relative to the start of the region (for region-based automation)
4008 or track (for track-based automation) */
4009 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4010 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4012 // calculate zero crossing point. back off by .01 to stay on the
4013 // positive side of zero
4014 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4016 // make sure we hit zero when passing through
4017 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4021 if (_x_constrained) {
4024 if (_y_constrained) {
4028 _cumulative_x_drag = cx - _fixed_grab_x;
4029 _cumulative_y_drag = cy - _fixed_grab_y;
4033 cy = min ((double) _point->line().height(), cy);
4035 framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event);
4037 if (!_x_constrained) {
4038 _editor->snap_to_with_modifier (cx_frames, event);
4041 cx_frames -= snap_delta (event);
4042 cx_frames = min (cx_frames, _point->line().maximum_time());
4044 float const fraction = 1.0 - (cy / _point->line().height());
4046 _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4048 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4052 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4054 if (!movement_occurred) {
4057 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::PrimaryModifier | Keyboard::TertiaryModifier))) {
4058 _editor->reset_point_selection ();
4062 motion (event, false);
4065 _point->line().end_drag (_pushing, _final_index);
4066 _editor->commit_reversible_command ();
4070 ControlPointDrag::aborted (bool)
4072 _point->line().reset ();
4076 ControlPointDrag::active (Editing::MouseMode m)
4078 if (m == Editing::MouseDraw) {
4079 /* always active in mouse draw */
4083 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4084 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4087 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4090 , _cumulative_y_drag (0)
4092 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4096 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4098 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4101 _item = &_line->grab_item ();
4103 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4104 origin, and ditto for y.
4107 double cx = event->button.x;
4108 double cy = event->button.y;
4110 _line->parent_group().canvas_to_item (cx, cy);
4112 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->samples_per_pixel);
4117 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
4118 /* no adjacent points */
4122 Drag::start_grab (event, _editor->cursors()->fader);
4124 /* store grab start in parent frame */
4129 double fraction = 1.0 - (cy / _line->height());
4131 _line->start_drag_line (before, after, fraction);
4133 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4137 LineDrag::motion (GdkEvent* event, bool)
4139 double dy = current_pointer_y() - last_pointer_y();
4141 if (event->button.state & Keyboard::SecondaryModifier) {
4145 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4147 _cumulative_y_drag = cy - _fixed_grab_y;
4150 cy = min ((double) _line->height(), cy);
4152 double const fraction = 1.0 - (cy / _line->height());
4155 /* we are ignoring x position for this drag, so we can just pass in anything */
4156 _line->drag_motion (0, fraction, true, false, ignored);
4158 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4162 LineDrag::finished (GdkEvent* event, bool movement_occured)
4164 if (movement_occured) {
4165 motion (event, false);
4166 _line->end_drag (false, 0);
4168 /* add a new control point on the line */
4170 AutomationTimeAxisView* atv;
4172 _line->end_drag (false, 0);
4174 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4175 framepos_t where = _editor->window_event_sample (event, 0, 0);
4176 atv->add_automation_event (event, where, event->button.y, false);
4180 _editor->commit_reversible_command ();
4184 LineDrag::aborted (bool)
4189 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4192 _cumulative_x_drag (0)
4194 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4198 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4200 Drag::start_grab (event);
4202 _line = reinterpret_cast<Line*> (_item);
4205 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4207 double cx = event->button.x;
4208 double cy = event->button.y;
4210 _item->parent()->canvas_to_item (cx, cy);
4212 /* store grab start in parent frame */
4213 _region_view_grab_x = cx;
4215 _before = *(float*) _item->get_data ("position");
4217 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4219 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4223 FeatureLineDrag::motion (GdkEvent*, bool)
4225 double dx = _drags->current_pointer_x() - last_pointer_x();
4227 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4229 _cumulative_x_drag += dx;
4231 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4240 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4242 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4244 float *pos = new float;
4247 _line->set_data ("position", pos);
4253 FeatureLineDrag::finished (GdkEvent*, bool)
4255 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4256 _arv->update_transient(_before, _before);
4260 FeatureLineDrag::aborted (bool)
4265 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4267 , _vertical_only (false)
4269 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4273 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4275 Drag::start_grab (event);
4276 show_verbose_cursor_time (adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid()));
4280 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4287 framepos_t const pf = adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid());
4289 framepos_t grab = grab_frame ();
4290 if (ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) {
4291 _editor->snap_to_with_modifier (grab, event);
4293 grab = raw_grab_frame ();
4296 /* base start and end on initial click position */
4306 if (current_pointer_y() < grab_y()) {
4307 y1 = current_pointer_y();
4310 y2 = current_pointer_y();
4314 if (start != end || y1 != y2) {
4316 double x1 = _editor->sample_to_pixel (start);
4317 double x2 = _editor->sample_to_pixel (end);
4318 const double min_dimension = 2.0;
4320 if (_vertical_only) {
4321 /* fixed 10 pixel width */
4325 x2 = min (x1 - min_dimension, x2);
4327 x2 = max (x1 + min_dimension, x2);
4332 y2 = min (y1 - min_dimension, y2);
4334 y2 = max (y1 + min_dimension, y2);
4337 /* translate rect into item space and set */
4339 ArdourCanvas::Rect r (x1, y1, x2, y2);
4341 /* this drag is a _trackview_only == true drag, so the y1 and
4342 * y2 (computed using current_pointer_y() and grab_y()) will be
4343 * relative to the top of the trackview group). The
4344 * rubberband rect has the same parent/scroll offset as the
4345 * the trackview group, so we can use the "r" rect directly
4346 * to set the shape of the rubberband.
4349 _editor->rubberband_rect->set (r);
4350 _editor->rubberband_rect->show();
4351 _editor->rubberband_rect->raise_to_top();
4353 show_verbose_cursor_time (pf);
4355 do_select_things (event, true);
4360 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4364 framepos_t grab = grab_frame ();
4365 framepos_t lpf = last_pointer_frame ();
4367 if (!ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) {
4368 grab = raw_grab_frame ();
4369 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4383 if (current_pointer_y() < grab_y()) {
4384 y1 = current_pointer_y();
4387 y2 = current_pointer_y();
4391 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4395 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4397 if (movement_occurred) {
4399 motion (event, false);
4400 do_select_things (event, false);
4406 bool do_deselect = true;
4407 MidiTimeAxisView* mtv;
4409 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4411 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4412 /* nothing selected */
4413 add_midi_region (mtv);
4414 do_deselect = false;
4418 /* do not deselect if Primary or Tertiary (toggle-select or
4419 * extend-select are pressed.
4422 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4423 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4430 _editor->rubberband_rect->hide();
4434 RubberbandSelectDrag::aborted (bool)
4436 _editor->rubberband_rect->hide ();
4439 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4440 : RegionDrag (e, i, p, v)
4442 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4446 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4448 Drag::start_grab (event, cursor);
4450 _editor->get_selection().add (_primary);
4452 framepos_t where = _primary->region()->position();
4453 setup_snap_delta (where);
4455 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4459 TimeFXDrag::motion (GdkEvent* event, bool)
4461 RegionView* rv = _primary;
4462 StreamView* cv = rv->get_time_axis_view().view ();
4464 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4465 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4466 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4467 framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event);
4468 _editor->snap_to_with_modifier (pf, event);
4469 pf -= snap_delta (event);
4471 if (pf > rv->region()->position()) {
4472 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4475 show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4479 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4481 _primary->get_time_axis_view().hide_timestretch ();
4483 if (!movement_occurred) {
4487 if (last_pointer_frame() < _primary->region()->position()) {
4488 /* backwards drag of the left edge - not usable */
4492 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
4494 float percentage = (double) newlen / (double) _primary->region()->length();
4496 #ifndef USE_RUBBERBAND
4497 // Soundtouch uses percentage / 100 instead of normal (/ 1)
4498 if (_primary->region()->data_type() == DataType::AUDIO) {
4499 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4503 if (!_editor->get_selection().regions.empty()) {
4504 /* primary will already be included in the selection, and edit
4505 group shared editing will propagate selection across
4506 equivalent regions, so just use the current region
4510 if (_editor->time_stretch (_editor->get_selection().regions, percentage) == -1) {
4511 error << _("An error occurred while executing time stretch operation") << endmsg;
4517 TimeFXDrag::aborted (bool)
4519 _primary->get_time_axis_view().hide_timestretch ();
4522 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
4525 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
4529 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4531 Drag::start_grab (event);
4535 ScrubDrag::motion (GdkEvent* /*event*/, bool)
4537 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
4541 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4543 if (movement_occurred && _editor->session()) {
4544 /* make sure we stop */
4545 _editor->session()->request_transport_speed (0.0);
4550 ScrubDrag::aborted (bool)
4555 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4559 , _time_selection_at_start (!_editor->get_selection().time.empty())
4561 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
4563 if (_time_selection_at_start) {
4564 start_at_start = _editor->get_selection().time.start();
4565 end_at_start = _editor->get_selection().time.end_frame();
4570 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
4572 if (_editor->session() == 0) {
4576 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4578 switch (_operation) {
4579 case CreateSelection:
4580 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4585 cursor = _editor->cursors()->selector;
4586 Drag::start_grab (event, cursor);
4589 case SelectionStartTrim:
4590 if (_editor->clicked_axisview) {
4591 _editor->clicked_axisview->order_selection_trims (_item, true);
4593 Drag::start_grab (event, _editor->cursors()->left_side_trim);
4596 case SelectionEndTrim:
4597 if (_editor->clicked_axisview) {
4598 _editor->clicked_axisview->order_selection_trims (_item, false);
4600 Drag::start_grab (event, _editor->cursors()->right_side_trim);
4604 Drag::start_grab (event, cursor);
4607 case SelectionExtend:
4608 Drag::start_grab (event, cursor);
4612 if (_operation == SelectionMove) {
4613 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
4615 show_verbose_cursor_time (adjusted_current_frame (event));
4620 SelectionDrag::setup_pointer_frame_offset ()
4622 switch (_operation) {
4623 case CreateSelection:
4624 _pointer_frame_offset = 0;
4627 case SelectionStartTrim:
4629 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
4632 case SelectionEndTrim:
4633 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
4636 case SelectionExtend:
4642 SelectionDrag::motion (GdkEvent* event, bool first_move)
4644 framepos_t start = 0;
4646 framecnt_t length = 0;
4647 framecnt_t distance = 0;
4649 framepos_t const pending_position = adjusted_current_frame (event);
4651 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
4655 switch (_operation) {
4656 case CreateSelection:
4658 framepos_t grab = grab_frame ();
4661 grab = adjusted_current_frame (event, false);
4662 if (grab < pending_position) {
4663 _editor->snap_to (grab, RoundDownMaybe);
4665 _editor->snap_to (grab, RoundUpMaybe);
4669 if (pending_position < grab) {
4670 start = pending_position;
4673 end = pending_position;
4677 /* first drag: Either add to the selection
4678 or create a new selection
4685 /* adding to the selection */
4686 _editor->set_selected_track_as_side_effect (Selection::Add);
4687 _editor->clicked_selection = _editor->selection->add (start, end);
4694 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
4695 _editor->set_selected_track_as_side_effect (Selection::Set);
4698 _editor->clicked_selection = _editor->selection->set (start, end);
4702 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
4703 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
4704 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
4706 _editor->selection->add (atest);
4710 /* select all tracks within the rectangle that we've marked out so far */
4711 TrackViewList new_selection;
4712 TrackViewList& all_tracks (_editor->track_views);
4714 ArdourCanvas::Coord const top = grab_y();
4715 ArdourCanvas::Coord const bottom = current_pointer_y();
4717 if (top >= 0 && bottom >= 0) {
4719 //first, find the tracks that are covered in the y range selection
4720 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
4721 if ((*i)->covered_by_y_range (top, bottom)) {
4722 new_selection.push_back (*i);
4726 //now find any tracks that are GROUPED with the tracks we selected
4727 TrackViewList grouped_add = new_selection;
4728 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
4729 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
4730 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::select.property_id) ) {
4731 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
4732 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
4733 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
4734 grouped_add.push_back (*j);
4739 //now compare our list with the current selection, and add or remove as necessary
4740 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
4741 TrackViewList tracks_to_add;
4742 TrackViewList tracks_to_remove;
4743 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
4744 if ( !_editor->selection->tracks.contains ( *i ) )
4745 tracks_to_add.push_back ( *i );
4746 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
4747 if ( !grouped_add.contains ( *i ) )
4748 tracks_to_remove.push_back ( *i );
4749 _editor->selection->add(tracks_to_add);
4750 _editor->selection->remove(tracks_to_remove);
4756 case SelectionStartTrim:
4758 start = _editor->selection->time[_editor->clicked_selection].start;
4759 end = _editor->selection->time[_editor->clicked_selection].end;
4761 if (pending_position > end) {
4764 start = pending_position;
4768 case SelectionEndTrim:
4770 start = _editor->selection->time[_editor->clicked_selection].start;
4771 end = _editor->selection->time[_editor->clicked_selection].end;
4773 if (pending_position < start) {
4776 end = pending_position;
4783 start = _editor->selection->time[_editor->clicked_selection].start;
4784 end = _editor->selection->time[_editor->clicked_selection].end;
4786 length = end - start;
4787 distance = pending_position - start;
4788 start = pending_position;
4789 _editor->snap_to (start);
4791 end = start + length;
4795 case SelectionExtend:
4800 switch (_operation) {
4802 if (_time_selection_at_start) {
4803 _editor->selection->move_time (distance);
4807 _editor->selection->replace (_editor->clicked_selection, start, end);
4811 if (_operation == SelectionMove) {
4812 show_verbose_cursor_time(start);
4814 show_verbose_cursor_time(pending_position);
4819 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
4821 Session* s = _editor->session();
4823 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
4824 if (movement_occurred) {
4825 motion (event, false);
4826 /* XXX this is not object-oriented programming at all. ick */
4827 if (_editor->selection->time.consolidate()) {
4828 _editor->selection->TimeChanged ();
4831 /* XXX what if its a music time selection? */
4833 if ( s->get_play_range() && s->transport_rolling() ) {
4834 s->request_play_range (&_editor->selection->time, true);
4836 if (ARDOUR_UI::config()->get_follow_edits() && !s->transport_rolling()) {
4837 if (_operation == SelectionEndTrim)
4838 _editor->maybe_locate_with_edit_preroll( _editor->get_selection().time.end_frame());
4840 s->request_locate (_editor->get_selection().time.start());
4846 /* just a click, no pointer movement.
4849 if (_operation == SelectionExtend) {
4850 if (_time_selection_at_start) {
4851 framepos_t pos = adjusted_current_frame (event, false);
4852 framepos_t start = min (pos, start_at_start);
4853 framepos_t end = max (pos, end_at_start);
4854 _editor->selection->set (start, end);
4857 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4858 if (_editor->clicked_selection) {
4859 _editor->selection->remove (_editor->clicked_selection);
4862 if (!_editor->clicked_selection) {
4863 _editor->selection->clear_time();
4868 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
4869 _editor->selection->set (_editor->clicked_axisview);
4872 if (s && s->get_play_range () && s->transport_rolling()) {
4873 s->request_stop (false, false);
4878 _editor->stop_canvas_autoscroll ();
4879 _editor->clicked_selection = 0;
4880 _editor->commit_reversible_selection_op ();
4884 SelectionDrag::aborted (bool)
4889 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4890 : Drag (e, i, false),
4894 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
4896 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
4897 ArdourCanvas::Rect (0.0, 0.0, 0.0,
4898 physical_screen_height (_editor->get_window())));
4899 _drag_rect->hide ();
4901 _drag_rect->set_fill_color (ARDOUR_UI::config()->color ("range drag rect"));
4902 _drag_rect->set_outline_color (ARDOUR_UI::config()->color ("range drag rect"));
4905 RangeMarkerBarDrag::~RangeMarkerBarDrag()
4907 /* normal canvas items will be cleaned up when their parent group is deleted. But
4908 this item is created as the child of a long-lived parent group, and so we
4909 need to explicitly delete it.
4915 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4917 if (_editor->session() == 0) {
4921 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4923 if (!_editor->temp_location) {
4924 _editor->temp_location = new Location (*_editor->session());
4927 switch (_operation) {
4928 case CreateSkipMarker:
4929 case CreateRangeMarker:
4930 case CreateTransportMarker:
4931 case CreateCDMarker:
4933 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
4938 cursor = _editor->cursors()->selector;
4942 Drag::start_grab (event, cursor);
4944 show_verbose_cursor_time (adjusted_current_frame (event));
4948 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
4950 framepos_t start = 0;
4952 ArdourCanvas::Rectangle *crect;
4954 switch (_operation) {
4955 case CreateSkipMarker:
4956 crect = _editor->range_bar_drag_rect;
4958 case CreateRangeMarker:
4959 crect = _editor->range_bar_drag_rect;
4961 case CreateTransportMarker:
4962 crect = _editor->transport_bar_drag_rect;
4964 case CreateCDMarker:
4965 crect = _editor->cd_marker_bar_drag_rect;
4968 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
4973 framepos_t const pf = adjusted_current_frame (event);
4975 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
4976 framepos_t grab = grab_frame ();
4977 _editor->snap_to (grab);
4979 if (pf < grab_frame()) {
4987 /* first drag: Either add to the selection
4988 or create a new selection.
4993 _editor->temp_location->set (start, end);
4997 update_item (_editor->temp_location);
4999 //_drag_rect->raise_to_top();
5005 _editor->temp_location->set (start, end);
5007 double x1 = _editor->sample_to_pixel (start);
5008 double x2 = _editor->sample_to_pixel (end);
5012 update_item (_editor->temp_location);
5015 show_verbose_cursor_time (pf);
5020 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5022 Location * newloc = 0;
5026 if (movement_occurred) {
5027 motion (event, false);
5030 switch (_operation) {
5031 case CreateSkipMarker:
5032 case CreateRangeMarker:
5033 case CreateCDMarker:
5035 XMLNode &before = _editor->session()->locations()->get_state();
5036 if (_operation == CreateSkipMarker) {
5037 _editor->begin_reversible_command (_("new skip marker"));
5038 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5039 flags = Location::IsRangeMarker | Location::IsSkip;
5040 _editor->range_bar_drag_rect->hide();
5041 } else if (_operation == CreateCDMarker) {
5042 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5043 _editor->begin_reversible_command (_("new CD marker"));
5044 flags = Location::IsRangeMarker | Location::IsCDMarker;
5045 _editor->cd_marker_bar_drag_rect->hide();
5047 _editor->begin_reversible_command (_("new skip marker"));
5048 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5049 flags = Location::IsRangeMarker;
5050 _editor->range_bar_drag_rect->hide();
5052 newloc = new Location (
5053 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5056 _editor->session()->locations()->add (newloc, true);
5057 XMLNode &after = _editor->session()->locations()->get_state();
5058 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5059 _editor->commit_reversible_command ();
5063 case CreateTransportMarker:
5064 // popup menu to pick loop or punch
5065 _editor->new_transport_marker_context_menu (&event->button, _item);
5071 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5073 if (_operation == CreateTransportMarker) {
5075 /* didn't drag, so just locate */
5077 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5079 } else if (_operation == CreateCDMarker) {
5081 /* didn't drag, but mark is already created so do
5084 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5089 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5091 if (end == max_framepos) {
5092 end = _editor->session()->current_end_frame ();
5095 if (start == max_framepos) {
5096 start = _editor->session()->current_start_frame ();
5099 switch (_editor->mouse_mode) {
5101 /* find the two markers on either side and then make the selection from it */
5102 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5106 /* find the two markers on either side of the click and make the range out of it */
5107 _editor->selection->set (start, end);
5116 _editor->stop_canvas_autoscroll ();
5120 RangeMarkerBarDrag::aborted (bool movement_occured)
5122 if (movement_occured) {
5123 _drag_rect->hide ();
5128 RangeMarkerBarDrag::update_item (Location* location)
5130 double const x1 = _editor->sample_to_pixel (location->start());
5131 double const x2 = _editor->sample_to_pixel (location->end());
5133 _drag_rect->set_x0 (x1);
5134 _drag_rect->set_x1 (x2);
5137 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5139 , _cumulative_dx (0)
5140 , _cumulative_dy (0)
5142 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5144 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5146 _region = &_primary->region_view ();
5147 _note_height = _region->midi_stream_view()->note_height ();
5151 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5153 Drag::start_grab (event);
5154 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5156 if (!(_was_selected = _primary->selected())) {
5158 /* tertiary-click means extend selection - we'll do that on button release,
5159 so don't add it here, because otherwise we make it hard to figure
5160 out the "extend-to" range.
5163 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5166 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5169 _region->note_selected (_primary, true);
5171 _region->unique_select (_primary);
5174 _editor->begin_reversible_selection_op(X_("Select Note Press"));
5175 _editor->commit_reversible_selection_op();
5180 /** @return Current total drag x change in frames */
5182 NoteDrag::total_dx (GdkEvent const * event) const
5185 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5187 /* primary note time */
5188 frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
5190 /* new time of the primary note in session frames */
5191 frameoffset_t st = n + dx + snap_delta (event);
5193 framepos_t const rp = _region->region()->position ();
5195 /* prevent the note being dragged earlier than the region's position */
5198 /* snap and return corresponding delta */
5199 return _region->snap_frame_to_frame (st - rp) + rp - n - snap_delta (event);
5202 /** @return Current total drag y change in note number */
5204 NoteDrag::total_dy () const
5206 MidiStreamView* msv = _region->midi_stream_view ();
5207 double const y = _region->midi_view()->y_position ();
5208 /* new current note */
5209 uint8_t n = msv->y_to_note (current_pointer_y () - y);
5211 n = max (msv->lowest_note(), n);
5212 n = min (msv->highest_note(), n);
5213 /* and work out delta */
5214 return n - msv->y_to_note (grab_y() - y);
5218 NoteDrag::motion (GdkEvent * event, bool)
5220 /* Total change in x and y since the start of the drag */
5221 frameoffset_t const dx = total_dx (event);
5222 int8_t const dy = total_dy ();
5224 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5225 double const tdx = _editor->sample_to_pixel (dx) - _cumulative_dx;
5226 double const tdy = -dy * _note_height - _cumulative_dy;
5229 _cumulative_dx += tdx;
5230 _cumulative_dy += tdy;
5232 int8_t note_delta = total_dy();
5234 _region->move_selection (tdx, tdy, note_delta);
5236 /* the new note value may be the same as the old one, but we
5237 * don't know what that means because the selection may have
5238 * involved more than one note and we might be doing something
5239 * odd with them. so show the note value anyway, always.
5243 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5245 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
5246 (int) floor ((double)new_note));
5248 show_verbose_cursor_text (buf);
5253 NoteDrag::finished (GdkEvent* ev, bool moved)
5256 /* no motion - select note */
5258 if (_editor->current_mouse_mode() == Editing::MouseObject ||
5259 _editor->current_mouse_mode() == Editing::MouseDraw) {
5261 bool changed = false;
5263 if (_was_selected) {
5264 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5266 _region->note_deselected (_primary);
5270 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5271 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5273 if (!extend && !add && _region->selection_size() > 1) {
5274 _region->unique_select (_primary);
5276 } else if (extend) {
5277 _region->note_selected (_primary, true, true);
5280 /* it was added during button press */
5285 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5286 _editor->commit_reversible_selection_op();
5290 _region->note_dropped (_primary, total_dx (ev), total_dy());
5295 NoteDrag::aborted (bool)
5300 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5301 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5302 : Drag (editor, atv->base_item ())
5304 , _y_origin (atv->y_position())
5305 , _nothing_to_drag (false)
5307 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5308 setup (atv->lines ());
5311 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5312 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5313 : Drag (editor, rv->get_canvas_group ())
5315 , _y_origin (rv->get_time_axis_view().y_position())
5316 , _nothing_to_drag (false)
5319 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5321 list<boost::shared_ptr<AutomationLine> > lines;
5323 AudioRegionView* audio_view;
5324 AutomationRegionView* automation_view;
5325 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5326 lines.push_back (audio_view->get_gain_line ());
5327 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5328 lines.push_back (automation_view->line ());
5331 error << _("Automation range drag created for invalid region type") << endmsg;
5337 /** @param lines AutomationLines to drag.
5338 * @param offset Offset from the session start to the points in the AutomationLines.
5341 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5343 /* find the lines that overlap the ranges being dragged */
5344 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5345 while (i != lines.end ()) {
5346 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5349 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5351 /* check this range against all the AudioRanges that we are using */
5352 list<AudioRange>::const_iterator k = _ranges.begin ();
5353 while (k != _ranges.end()) {
5354 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5360 /* add it to our list if it overlaps at all */
5361 if (k != _ranges.end()) {
5366 _lines.push_back (n);
5372 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5376 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5378 return 1.0 - ((global_y - _y_origin) / line->height());
5382 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5384 const double v = list->eval(x);
5385 return _integral ? rint(v) : v;
5389 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5391 Drag::start_grab (event, cursor);
5393 /* Get line states before we start changing things */
5394 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5395 i->state = &i->line->get_state ();
5396 i->original_fraction = y_fraction (i->line, current_pointer_y());
5399 if (_ranges.empty()) {
5401 /* No selected time ranges: drag all points */
5402 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5403 uint32_t const N = i->line->npoints ();
5404 for (uint32_t j = 0; j < N; ++j) {
5405 i->points.push_back (i->line->nth (j));
5411 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5413 framecnt_t const half = (i->start + i->end) / 2;
5415 /* find the line that this audio range starts in */
5416 list<Line>::iterator j = _lines.begin();
5417 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5421 if (j != _lines.end()) {
5422 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5424 /* j is the line that this audio range starts in; fade into it;
5425 64 samples length plucked out of thin air.
5428 framepos_t a = i->start + 64;
5433 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5434 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5436 the_list->editor_add (p, value (the_list, p));
5437 the_list->editor_add (q, value (the_list, q));
5440 /* same thing for the end */
5443 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5447 if (j != _lines.end()) {
5448 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5450 /* j is the line that this audio range starts in; fade out of it;
5451 64 samples length plucked out of thin air.
5454 framepos_t b = i->end - 64;
5459 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
5460 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
5462 the_list->editor_add (p, value (the_list, p));
5463 the_list->editor_add (q, value (the_list, q));
5467 _nothing_to_drag = true;
5469 /* Find all the points that should be dragged and put them in the relevant
5470 points lists in the Line structs.
5473 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5475 uint32_t const N = i->line->npoints ();
5476 for (uint32_t j = 0; j < N; ++j) {
5478 /* here's a control point on this line */
5479 ControlPoint* p = i->line->nth (j);
5480 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
5482 /* see if it's inside a range */
5483 list<AudioRange>::const_iterator k = _ranges.begin ();
5484 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
5488 if (k != _ranges.end()) {
5489 /* dragging this point */
5490 _nothing_to_drag = false;
5491 i->points.push_back (p);
5497 if (_nothing_to_drag) {
5501 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5502 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
5507 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
5509 if (_nothing_to_drag) {
5513 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
5514 float const f = y_fraction (l->line, current_pointer_y());
5515 /* we are ignoring x position for this drag, so we can just pass in anything */
5517 l->line->drag_motion (0, f, true, false, ignored);
5518 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, f));
5523 AutomationRangeDrag::finished (GdkEvent* event, bool)
5525 if (_nothing_to_drag) {
5529 motion (event, false);
5530 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5531 i->line->end_drag (false, 0);
5534 _editor->commit_reversible_command ();
5538 AutomationRangeDrag::aborted (bool)
5540 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5545 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
5547 , initial_time_axis_view (itav)
5549 /* note that time_axis_view may be null if the regionview was created
5550 * as part of a copy operation.
5552 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
5553 layer = v->region()->layer ();
5554 initial_y = v->get_canvas_group()->position().y;
5555 initial_playlist = v->region()->playlist ();
5556 initial_position = v->region()->position ();
5557 initial_end = v->region()->position () + v->region()->length ();
5560 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
5561 : Drag (e, i->canvas_item ())
5564 , _cumulative_dx (0)
5566 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
5567 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
5572 PatchChangeDrag::motion (GdkEvent* ev, bool)
5574 framepos_t f = adjusted_current_frame (ev);
5575 boost::shared_ptr<Region> r = _region_view->region ();
5576 f = max (f, r->position ());
5577 f = min (f, r->last_frame ());
5579 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
5580 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
5581 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
5582 _cumulative_dx = dxu;
5586 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
5588 if (!movement_occurred) {
5592 boost::shared_ptr<Region> r (_region_view->region ());
5593 framepos_t f = adjusted_current_frame (ev);
5594 f = max (f, r->position ());
5595 f = min (f, r->last_frame ());
5597 _region_view->move_patch_change (
5599 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
5604 PatchChangeDrag::aborted (bool)
5606 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
5610 PatchChangeDrag::setup_pointer_frame_offset ()
5612 boost::shared_ptr<Region> region = _region_view->region ();
5613 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
5616 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
5617 : RubberbandSelectDrag (e, rv->get_canvas_group ())
5624 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
5626 _region_view->update_drag_selection (
5628 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
5632 MidiRubberbandSelectDrag::deselect_things ()
5637 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
5638 : RubberbandSelectDrag (e, rv->get_canvas_group ())
5641 _vertical_only = true;
5645 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
5647 double const y = _region_view->midi_view()->y_position ();
5649 y1 = max (0.0, y1 - y);
5650 y2 = max (0.0, y2 - y);
5652 _region_view->update_vertical_drag_selection (
5655 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
5660 MidiVerticalSelectDrag::deselect_things ()
5665 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5666 : RubberbandSelectDrag (e, i)
5672 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
5674 if (drag_in_progress) {
5675 /* We just want to select things at the end of the drag, not during it */
5679 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
5681 _editor->begin_reversible_selection_op (X_("rubberband selection"));
5683 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
5685 _editor->commit_reversible_selection_op ();
5689 EditorRubberbandSelectDrag::deselect_things ()
5691 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
5693 _editor->selection->clear_tracks();
5694 _editor->selection->clear_regions();
5695 _editor->selection->clear_points ();
5696 _editor->selection->clear_lines ();
5697 _editor->selection->clear_midi_notes ();
5699 _editor->commit_reversible_selection_op();
5702 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
5707 _note[0] = _note[1] = 0;
5710 NoteCreateDrag::~NoteCreateDrag ()
5716 NoteCreateDrag::grid_frames (framepos_t t) const
5719 Evoral::Beats grid_beats = _editor->get_grid_type_as_beats (success, t);
5721 grid_beats = Evoral::Beats(1);
5724 return _region_view->region_beats_to_region_frames (grid_beats);
5728 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5730 Drag::start_grab (event, cursor);
5732 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
5734 framepos_t pf = _drags->current_pointer_frame ();
5735 framecnt_t const g = grid_frames (pf);
5737 /* Hack so that we always snap to the note that we are over, instead of snapping
5738 to the next one if we're more than halfway through the one we're over.
5740 if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
5744 _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
5745 _note[1] = _note[0];
5747 MidiStreamView* sv = _region_view->midi_stream_view ();
5748 double const x = _editor->sample_to_pixel (_note[0]);
5749 double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
5751 _drag_rect->set (ArdourCanvas::Rect (x, y, x, y + floor (_region_view->midi_stream_view()->note_height ())));
5752 _drag_rect->set_outline_all ();
5753 _drag_rect->set_outline_color (0xffffff99);
5754 _drag_rect->set_fill_color (0xffffff66);
5758 NoteCreateDrag::motion (GdkEvent* event, bool)
5760 _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
5761 double const x0 = _editor->sample_to_pixel (_note[0]);
5762 double const x1 = _editor->sample_to_pixel (_note[1]);
5763 _drag_rect->set_x0 (std::min(x0, x1));
5764 _drag_rect->set_x1 (std::max(x0, x1));
5768 NoteCreateDrag::finished (GdkEvent*, bool had_movement)
5770 if (!had_movement) {
5774 framepos_t const start = min (_note[0], _note[1]);
5775 framecnt_t length = (framecnt_t) fabs ((double)(_note[0] - _note[1]));
5777 framecnt_t const g = grid_frames (start);
5778 Evoral::Beats const one_tick = Evoral::Beats::ticks(1);
5780 if (_editor->snap_mode() == SnapNormal && length < g) {
5784 Evoral::Beats length_beats = max (
5785 one_tick, _region_view->region_frames_to_region_beats (length) - one_tick);
5787 _region_view->create_note_at (start, _drag_rect->y0(), length_beats, false);
5791 NoteCreateDrag::y_to_region (double y) const
5794 _region_view->get_canvas_group()->canvas_to_item (x, y);
5799 NoteCreateDrag::aborted (bool)
5804 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
5809 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
5813 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
5815 Drag::start_grab (event, cursor);
5819 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
5825 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
5828 distance = _drags->current_pointer_x() - grab_x();
5829 len = ar->fade_in()->back()->when;
5831 distance = grab_x() - _drags->current_pointer_x();
5832 len = ar->fade_out()->back()->when;
5835 /* how long should it be ? */
5837 new_length = len + _editor->pixel_to_sample (distance);
5839 /* now check with the region that this is legal */
5841 new_length = ar->verify_xfade_bounds (new_length, start);
5844 arv->reset_fade_in_shape_width (ar, new_length);
5846 arv->reset_fade_out_shape_width (ar, new_length);
5851 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
5857 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
5860 distance = _drags->current_pointer_x() - grab_x();
5861 len = ar->fade_in()->back()->when;
5863 distance = grab_x() - _drags->current_pointer_x();
5864 len = ar->fade_out()->back()->when;
5867 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
5869 _editor->begin_reversible_command ("xfade trim");
5870 ar->playlist()->clear_owned_changes ();
5873 ar->set_fade_in_length (new_length);
5875 ar->set_fade_out_length (new_length);
5878 /* Adjusting the xfade may affect other regions in the playlist, so we need
5879 to get undo Commands from the whole playlist rather than just the
5883 vector<Command*> cmds;
5884 ar->playlist()->rdiff (cmds);
5885 _editor->session()->add_commands (cmds);
5886 _editor->commit_reversible_command ();
5891 CrossfadeEdgeDrag::aborted (bool)
5894 // arv->redraw_start_xfade ();
5896 // arv->redraw_end_xfade ();
5900 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
5901 : Drag (e, item, true)
5902 , line (new EditorCursor (*e))
5904 line->set_position (pos);
5908 RegionCutDrag::~RegionCutDrag ()
5914 RegionCutDrag::motion (GdkEvent*, bool)
5916 framepos_t where = _drags->current_pointer_frame();
5917 _editor->snap_to (where);
5919 line->set_position (where);
5923 RegionCutDrag::finished (GdkEvent*, bool)
5925 _editor->get_track_canvas()->canvas()->re_enter();
5927 framepos_t pos = _drags->current_pointer_frame();
5931 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
5937 _editor->split_regions_at (pos, rs);
5941 RegionCutDrag::aborted (bool)