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 (guint state) const
342 if (ArdourKeyboard::indicates_snap_delta (state)) {
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 (temp, ARDOUR::RoundNearest, false, true);
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() != Lock) {
405 _x_constrained = true;
406 _y_constrained = false;
408 _initially_vertical = true;
410 if ((event->motion.state & Gdk::BUTTON2_MASK) && Config->get_edit_mode() != Lock) {
411 _x_constrained = false;
412 _y_constrained = true;
414 _initially_vertical = false;
417 if (Config->get_edit_mode() == Lock) {
418 if (event->button.state & Gdk::BUTTON2_MASK) {
419 _x_constrained = false;
421 _x_constrained = true;
423 _y_constrained = false;
427 if (!from_autoscroll) {
428 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
431 if (!_editor->autoscroll_active() || from_autoscroll) {
434 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
436 motion (event, first_move && !_starting_point_passed);
438 if (first_move && !_starting_point_passed) {
439 _starting_point_passed = true;
442 _last_pointer_x = _drags->current_pointer_x ();
443 _last_pointer_y = current_pointer_y ();
444 _last_pointer_frame = adjusted_current_frame (event);
454 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
462 aborted (_move_threshold_passed);
464 _editor->stop_canvas_autoscroll ();
465 _editor->verbose_cursor()->hide ();
469 Drag::show_verbose_cursor_time (framepos_t frame)
471 _editor->verbose_cursor()->set_time (frame);
472 _editor->verbose_cursor()->show ();
476 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
478 _editor->verbose_cursor()->set_duration (start, end);
479 _editor->verbose_cursor()->show ();
483 Drag::show_verbose_cursor_text (string const & text)
485 _editor->verbose_cursor()->set (text);
486 _editor->verbose_cursor()->show ();
489 boost::shared_ptr<Region>
490 Drag::add_midi_region (MidiTimeAxisView* view)
492 if (_editor->session()) {
493 const TempoMap& map (_editor->session()->tempo_map());
494 framecnt_t pos = grab_frame();
495 const Meter& m = map.meter_at (pos);
496 /* not that the frame rate used here can be affected by pull up/down which
499 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
500 return view->add_region (grab_frame(), len, true);
503 return boost::shared_ptr<Region>();
506 struct EditorOrderTimeAxisViewSorter {
507 bool operator() (TimeAxisView* a, TimeAxisView* b) {
508 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
509 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
511 return ra->route()->order_key () < rb->route()->order_key ();
515 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
520 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
522 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
523 as some of the regions we are dragging may be on such tracks.
526 TrackViewList track_views = _editor->track_views;
527 track_views.sort (EditorOrderTimeAxisViewSorter ());
529 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
530 _time_axis_views.push_back (*i);
532 TimeAxisView::Children children_list = (*i)->get_child_list ();
533 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
534 _time_axis_views.push_back (j->get());
538 /* the list of views can be empty at this point if this is a region list-insert drag
541 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
542 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
545 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
549 RegionDrag::region_going_away (RegionView* v)
551 list<DraggingView>::iterator i = _views.begin ();
552 while (i != _views.end() && i->view != v) {
556 if (i != _views.end()) {
561 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
562 * or -1 if it is not found.
565 RegionDrag::find_time_axis_view (TimeAxisView* t) const
568 int const N = _time_axis_views.size ();
569 while (i < N && _time_axis_views[i] != t) {
573 if (_time_axis_views[i] != t) {
580 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
581 : RegionDrag (e, i, p, v)
584 , _last_pointer_time_axis_view (0)
585 , _last_pointer_layer (0)
590 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
594 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
596 Drag::start_grab (event, cursor);
597 setup_snap_delta (_last_frame_position);
599 show_verbose_cursor_time (_last_frame_position);
601 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
603 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
604 assert(_last_pointer_time_axis_view >= 0);
605 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
610 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
612 /* compute the amount of pointer motion in frames, and where
613 the region would be if we moved it by that much.
615 *pending_region_position = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
617 framepos_t sync_frame;
618 framecnt_t sync_offset;
621 sync_offset = _primary->region()->sync_offset (sync_dir);
623 /* we don't handle a sync point that lies before zero.
625 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
627 sync_frame = *pending_region_position + (sync_dir * sync_offset);
629 _editor->snap_to_with_modifier (sync_frame, event);
631 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - snap_delta (event->button.state);
634 *pending_region_position = _last_frame_position;
637 if (*pending_region_position > max_framepos - _primary->region()->length()) {
638 *pending_region_position = _last_frame_position;
643 bool const x_move_allowed = !_x_constrained;
645 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
647 /* x movement since last time (in pixels) */
648 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
650 /* total x movement */
651 framecnt_t total_dx = *pending_region_position;
652 if (regions_came_from_canvas()) {
653 total_dx = total_dx - grab_frame ();
656 /* check that no regions have gone off the start of the session */
657 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
658 if ((i->view->region()->position() + total_dx) < 0) {
660 *pending_region_position = _last_frame_position;
671 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
677 const int tavsize = _time_axis_views.size();
678 const int dt = delta > 0 ? +1 : -1;
680 int target = start + delta - skip;
682 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
683 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
685 while (current >= 0 && current != target) {
687 if (current < 0 && dt < 0) {
690 if (current >= tavsize && dt > 0) {
693 if (current < 0 || current >= tavsize) {
697 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
698 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
702 if (distance_only && current == start + delta) {
710 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
712 if (_y_constrained) {
716 const int tavsize = _time_axis_views.size();
717 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
718 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
719 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
721 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
722 /* already in the drop zone */
723 if (delta_track >= 0) {
724 /* downward motion - OK if others are still not in the dropzone */
733 } else if (n >= tavsize) {
734 /* downward motion into drop zone. That's fine. */
738 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
739 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
740 /* not a track, or the wrong type */
744 double const l = i->layer + delta_layer;
746 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
747 mode to allow the user to place a region below another on layer 0.
749 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
750 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
751 If it has, the layers will be munged later anyway, so it's ok.
757 /* all regions being dragged are ok with this change */
761 struct DraggingViewSorter {
762 bool operator() (const DraggingView& a, const DraggingView& b) {
763 return a.time_axis_view < b.time_axis_view;
768 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
770 double delta_layer = 0;
771 int delta_time_axis_view = 0;
772 int current_pointer_time_axis_view = -1;
774 assert (!_views.empty ());
776 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
778 /* Find the TimeAxisView that the pointer is now over */
779 const double cur_y = current_pointer_y ();
780 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
781 TimeAxisView* tv = r.first;
783 if (!tv && cur_y < 0) {
784 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
788 /* find drop-zone y-position */
789 Coord last_track_bottom_edge;
790 last_track_bottom_edge = 0;
791 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
792 if (!(*t)->hidden()) {
793 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
798 if (tv && tv->view()) {
799 /* the mouse is over a track */
800 double layer = r.second;
802 if (first_move && tv->view()->layer_display() == Stacked) {
803 tv->view()->set_layer_display (Expanded);
806 /* Here's the current pointer position in terms of time axis view and layer */
807 current_pointer_time_axis_view = find_time_axis_view (tv);
808 assert(current_pointer_time_axis_view >= 0);
810 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
812 /* Work out the change in y */
814 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
815 if (!rtv || !rtv->is_track()) {
816 /* ignore busses early on. we can't move any regions on them */
817 } else if (_last_pointer_time_axis_view < 0) {
818 /* Was in the drop-zone, now over a track.
819 * Hence it must be an upward move (from the bottom)
821 * track_index is still -1, so delta must be set to
822 * move up the correct number of tracks from the bottom.
824 * This is necessary because steps may be skipped if
825 * the bottom-most track is not a valid target and/or
826 * if there are hidden tracks at the bottom.
827 * Hence the initial offset (_ddropzone) as well as the
828 * last valid pointer position (_pdropzone) need to be
829 * taken into account.
831 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
833 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
836 /* TODO needs adjustment per DraggingView,
838 * e.g. select one region on the top-layer of a track
839 * and one region which is at the bottom-layer of another track
842 * Indicated drop-zones and layering is wrong.
843 * and may infer additional layers on the target-track
844 * (depending how many layers the original track had).
846 * Or select two regions (different layers) on a same track,
847 * move across a non-layer track.. -> layering info is lost.
848 * on drop either of the regions may be on top.
850 * Proposed solution: screw it :) well,
851 * don't use delta_layer, use an absolute value
852 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
853 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
854 * 3) iterate over all DraggingView, find the one that is over the track with most layers
855 * 4) proportionally scale layer to layers available on target
857 delta_layer = current_pointer_layer - _last_pointer_layer;
860 /* for automation lanes, there is a TimeAxisView but no ->view()
861 * if (!tv) -> dropzone
863 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
864 /* Moving into the drop-zone.. */
865 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
866 /* delta_time_axis_view may not be sufficient to move into the DZ
867 * the mouse may enter it, but it may not be a valid move due to
870 * -> remember the delta needed to move into the dropzone
872 _ddropzone = delta_time_axis_view;
873 /* ..but subtract hidden tracks (or routes) at the bottom.
874 * we silently move mover them
876 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
877 - _time_axis_views.size();
879 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
880 /* move around inside the zone.
881 * This allows to move further down until all regions are in the zone.
883 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
884 assert(ptr_y >= last_track_bottom_edge);
885 assert(_ddropzone > 0);
887 /* calculate mouse position in 'tracks' below last track. */
888 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
889 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
891 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
893 delta_time_axis_view = dzpos - _pdropzone;
894 } else if (dzpos < _pdropzone && _ndropzone > 0) {
895 // move up inside the DZ
896 delta_time_axis_view = dzpos - _pdropzone;
900 /* Work out the change in x */
901 framepos_t pending_region_position;
902 double const x_delta = compute_x_delta (event, &pending_region_position);
903 _last_frame_position = pending_region_position;
905 /* calculate hidden tracks in current y-axis delta */
907 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
908 /* The mouse is more than one track below the dropzone.
909 * distance calculation is not needed (and would not work, either
910 * because the dropzone is "packed").
912 * Except when [partially] moving regions out of dropzone in a large step.
913 * (the mouse may or may not remain in the DZ)
914 * Hidden tracks at the bottom of the TAV need to be skipped.
916 * This also handles the case if the mouse entered the DZ
917 * in a large step (exessive delta), either due to fast-movement,
918 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
920 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
921 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
923 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
924 -_time_axis_views.size() - dt;
927 else if (_last_pointer_time_axis_view < 0) {
928 /* Moving out of the zone. Check for hidden tracks at the bottom. */
929 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
930 -_time_axis_views.size() - delta_time_axis_view;
932 /* calculate hidden tracks that are skipped by the pointer movement */
933 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
934 - _last_pointer_time_axis_view
935 - delta_time_axis_view;
938 /* Verify change in y */
939 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
940 /* this y movement is not allowed, so do no y movement this time */
941 delta_time_axis_view = 0;
946 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
947 /* haven't reached next snap point, and we're not switching
948 trackviews nor layers. nothing to do.
953 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
954 PlaylistDropzoneMap playlist_dropzone_map;
955 _ndropzone = 0; // number of elements currently in the dropzone
958 /* sort views by time_axis.
959 * This retains track order in the dropzone, regardless
960 * of actual selection order
962 _views.sort (DraggingViewSorter());
964 /* count number of distinct tracks of all regions
965 * being dragged, used for dropzone.
968 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
969 if (i->time_axis_view != prev_track) {
970 prev_track = i->time_axis_view;
976 _views.back().time_axis_view -
977 _views.front().time_axis_view;
979 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
980 - _views.back().time_axis_view;
982 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
986 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
988 RegionView* rv = i->view;
993 if (rv->region()->locked() || rv->region()->video_locked()) {
1000 /* reparent the regionview into a group above all
1004 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1005 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1006 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1007 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1008 /* move the item so that it continues to appear at the
1009 same location now that its parent has changed.
1011 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1014 /* If we have moved tracks, we'll fudge the layer delta so that the
1015 region gets moved back onto layer 0 on its new track; this avoids
1016 confusion when dragging regions from non-zero layers onto different
1019 double this_delta_layer = delta_layer;
1020 if (delta_time_axis_view != 0) {
1021 this_delta_layer = - i->layer;
1024 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1026 int track_index = i->time_axis_view + this_delta_time_axis_view;
1027 assert(track_index >= 0);
1029 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1030 /* Track is in the Dropzone */
1032 i->time_axis_view = track_index;
1033 assert(i->time_axis_view >= (int) _time_axis_views.size());
1036 double yposition = 0;
1037 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1038 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1041 /* store index of each new playlist as a negative count, starting at -1 */
1043 if (pdz == playlist_dropzone_map.end()) {
1044 /* compute where this new track (which doesn't exist yet) will live
1047 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1049 /* How high is this region view ? */
1051 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1052 ArdourCanvas::Rect bbox;
1055 bbox = obbox.get ();
1058 last_track_bottom_edge += bbox.height();
1060 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1063 yposition = pdz->second;
1066 /* values are zero or negative, hence the use of min() */
1067 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1072 /* The TimeAxisView that this region is now over */
1073 TimeAxisView* current_tv = _time_axis_views[track_index];
1075 /* Ensure it is moved from stacked -> expanded if appropriate */
1076 if (current_tv->view()->layer_display() == Stacked) {
1077 current_tv->view()->set_layer_display (Expanded);
1080 /* We're only allowed to go -ve in layer on Expanded views */
1081 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1082 this_delta_layer = - i->layer;
1086 rv->set_height (current_tv->view()->child_height ());
1088 /* Update show/hidden status as the region view may have come from a hidden track,
1089 or have moved to one.
1091 if (current_tv->hidden ()) {
1092 rv->get_canvas_group()->hide ();
1094 rv->get_canvas_group()->show ();
1097 /* Update the DraggingView */
1098 i->time_axis_view = track_index;
1099 i->layer += this_delta_layer;
1102 _editor->mouse_brush_insert_region (rv, pending_region_position);
1106 /* Get the y coordinate of the top of the track that this region is now over */
1107 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1109 /* And adjust for the layer that it should be on */
1110 StreamView* cv = current_tv->view ();
1111 switch (cv->layer_display ()) {
1115 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1118 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1122 /* need to get the parent of the regionview
1123 * canvas group and get its position in
1124 * equivalent coordinate space as the trackview
1125 * we are now dragging over.
1128 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1133 /* Now move the region view */
1134 rv->move (x_delta, y_delta);
1136 } /* foreach region */
1138 _total_x_delta += x_delta;
1140 if (x_delta != 0 && !_brushing) {
1141 show_verbose_cursor_time (_last_frame_position);
1144 /* keep track of pointer movement */
1146 /* the pointer is currently over a time axis view */
1148 if (_last_pointer_time_axis_view < 0) {
1149 /* last motion event was not over a time axis view
1150 * or last y-movement out of the dropzone was not valid
1153 if (delta_time_axis_view < 0) {
1154 /* in the drop zone, moving up */
1156 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1157 * We do not use negative _last_pointer_time_axis_view because
1158 * the dropzone is "packed" (the actual track offset is ignored)
1160 * As opposed to the actual number
1161 * of elements in the dropzone (_ndropzone)
1162 * _pdropzone is not constrained. This is necessary
1163 * to allow moving multiple regions with y-distance
1166 * There can be 0 elements in the dropzone,
1167 * even though the drag-pointer is inside the DZ.
1170 * [ Audio-track, Midi-track, Audio-track, DZ ]
1171 * move regions from both audio tracks at the same time into the
1172 * DZ by grabbing the region in the bottom track.
1174 assert(current_pointer_time_axis_view >= 0);
1175 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1179 /* only move out of the zone if the movement is OK */
1180 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1181 assert(delta_time_axis_view < 0);
1182 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1183 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1184 * the current position can be calculated as follows:
1186 // a well placed oofus attack can still throw this off.
1187 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1188 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1191 /* last motion event was also over a time axis view */
1192 _last_pointer_time_axis_view += delta_time_axis_view;
1193 assert(_last_pointer_time_axis_view >= 0);
1198 /* the pointer is not over a time axis view */
1199 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1200 _pdropzone += delta_time_axis_view - delta_skip;
1201 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1204 _last_pointer_layer += delta_layer;
1208 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1210 if (_copy && first_move) {
1212 if (_x_constrained) {
1213 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1215 _editor->begin_reversible_command (Operations::region_copy);
1218 /* duplicate the regionview(s) and region(s) */
1220 list<DraggingView> new_regionviews;
1222 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1224 RegionView* rv = i->view;
1225 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1226 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1228 const boost::shared_ptr<const Region> original = rv->region();
1229 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
1230 region_copy->set_position (original->position());
1231 /* need to set this so that the drop zone code can work. This doesn't
1232 actually put the region into the playlist, but just sets a weak pointer
1235 region_copy->set_playlist (original->playlist());
1239 boost::shared_ptr<AudioRegion> audioregion_copy
1240 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1242 nrv = new AudioRegionView (*arv, audioregion_copy);
1244 boost::shared_ptr<MidiRegion> midiregion_copy
1245 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1246 nrv = new MidiRegionView (*mrv, midiregion_copy);
1251 nrv->get_canvas_group()->show ();
1252 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1254 /* swap _primary to the copy */
1256 if (rv == _primary) {
1260 /* ..and deselect the one we copied */
1262 rv->set_selected (false);
1265 if (!new_regionviews.empty()) {
1267 /* reflect the fact that we are dragging the copies */
1269 _views = new_regionviews;
1271 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1274 } else if (!_copy && first_move) {
1276 if (_x_constrained) {
1277 _editor->begin_reversible_command (_("fixed time region drag"));
1279 _editor->begin_reversible_command (Operations::region_drag);
1283 RegionMotionDrag::motion (event, first_move);
1287 RegionMotionDrag::finished (GdkEvent *, bool)
1289 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1290 if (!(*i)->view()) {
1294 if ((*i)->view()->layer_display() == Expanded) {
1295 (*i)->view()->set_layer_display (Stacked);
1301 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1303 RegionMotionDrag::finished (ev, movement_occurred);
1305 if (!movement_occurred) {
1309 if (was_double_click() && !_views.empty()) {
1310 DraggingView dv = _views.front();
1311 dv.view->show_region_editor ();
1318 assert (!_views.empty ());
1320 /* We might have hidden region views so that they weren't visible during the drag
1321 (when they have been reparented). Now everything can be shown again, as region
1322 views are back in their track parent groups.
1324 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1325 i->view->get_canvas_group()->show ();
1328 bool const changed_position = (_last_frame_position != _primary->region()->position());
1329 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1330 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1350 _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
1354 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1356 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1361 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1362 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1363 uint32_t output_chan = region->n_channels();
1364 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1365 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1367 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, ARDOUR::Normal, 0, 1, region->name());
1368 RouteTimeAxisView* rtav = _editor->axis_view_from_route (audio_tracks.front());
1370 rtav->set_height (original->current_height());
1374 ChanCount one_midi_port (DataType::MIDI, 1);
1375 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1376 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port, boost::shared_ptr<ARDOUR::PluginInfo>(), ARDOUR::Normal, 0, 1, region->name());
1377 RouteTimeAxisView* rtav = _editor->axis_view_from_route (midi_tracks.front());
1379 rtav->set_height (original->current_height());
1384 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1390 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
1392 RegionSelection new_views;
1393 PlaylistSet modified_playlists;
1394 RouteTimeAxisView* new_time_axis_view = 0;
1397 /* all changes were made during motion event handlers */
1399 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1403 _editor->commit_reversible_command ();
1407 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1408 PlaylistMapping playlist_mapping;
1410 /* insert the regions into their new playlists */
1411 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1413 RouteTimeAxisView* dest_rtv = 0;
1415 if (i->view->region()->locked() || i->view->region()->video_locked()) {
1421 if (changed_position && !_x_constrained) {
1422 where = i->view->region()->position() - drag_delta;
1424 where = i->view->region()->position();
1427 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1428 /* dragged to drop zone */
1430 PlaylistMapping::iterator pm;
1432 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1433 /* first region from this original playlist: create a new track */
1434 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1435 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1436 dest_rtv = new_time_axis_view;
1438 /* we already created a new track for regions from this playlist, use it */
1439 dest_rtv = pm->second;
1442 /* destination time axis view is the one we dragged to */
1443 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1446 if (dest_rtv != 0) {
1447 RegionView* new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, modified_playlists);
1448 if (new_view != 0) {
1449 new_views.push_back (new_view);
1453 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1454 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1457 list<DraggingView>::const_iterator next = i;
1463 /* If we've created new regions either by copying or moving
1464 to a new track, we want to replace the old selection with the new ones
1467 if (new_views.size() > 0) {
1468 _editor->selection->set (new_views);
1471 /* write commands for the accumulated diffs for all our modified playlists */
1472 add_stateful_diff_commands_for_playlists (modified_playlists);
1474 _editor->commit_reversible_command ();
1478 RegionMoveDrag::finished_no_copy (
1479 bool const changed_position,
1480 bool const changed_tracks,
1481 framecnt_t const drag_delta
1484 RegionSelection new_views;
1485 PlaylistSet modified_playlists;
1486 PlaylistSet frozen_playlists;
1487 set<RouteTimeAxisView*> views_to_update;
1488 RouteTimeAxisView* new_time_axis_view = 0;
1491 /* all changes were made during motion event handlers */
1492 _editor->commit_reversible_command ();
1496 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1497 PlaylistMapping playlist_mapping;
1499 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1501 RegionView* rv = i->view;
1502 RouteTimeAxisView* dest_rtv = 0;
1504 if (rv->region()->locked() || rv->region()->video_locked()) {
1509 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1510 /* dragged to drop zone */
1512 PlaylistMapping::iterator pm;
1514 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1515 /* first region from this original playlist: create a new track */
1516 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1517 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1518 dest_rtv = new_time_axis_view;
1520 /* we already created a new track for regions from this playlist, use it */
1521 dest_rtv = pm->second;
1525 /* destination time axis view is the one we dragged to */
1526 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1531 double const dest_layer = i->layer;
1533 views_to_update.insert (dest_rtv);
1537 if (changed_position && !_x_constrained) {
1538 where = rv->region()->position() - drag_delta;
1540 where = rv->region()->position();
1543 if (changed_tracks) {
1545 /* insert into new playlist */
1547 RegionView* new_view = insert_region_into_playlist (
1548 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
1551 if (new_view == 0) {
1556 new_views.push_back (new_view);
1558 /* remove from old playlist */
1560 /* the region that used to be in the old playlist is not
1561 moved to the new one - we use a copy of it. as a result,
1562 any existing editor for the region should no longer be
1565 rv->hide_region_editor();
1568 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1572 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1574 /* this movement may result in a crossfade being modified, or a layering change,
1575 so we need to get undo data from the playlist as well as the region.
1578 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1580 playlist->clear_changes ();
1583 rv->region()->clear_changes ();
1586 motion on the same track. plonk the previously reparented region
1587 back to its original canvas group (its streamview).
1588 No need to do anything for copies as they are fake regions which will be deleted.
1591 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1592 rv->get_canvas_group()->set_y_position (i->initial_y);
1595 /* just change the model */
1596 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1597 playlist->set_layer (rv->region(), dest_layer);
1600 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1602 r = frozen_playlists.insert (playlist);
1605 playlist->freeze ();
1608 rv->region()->set_position (where);
1610 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1613 if (changed_tracks) {
1615 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1616 was selected in all of them, then removing it from a playlist will have removed all
1617 trace of it from _views (i.e. there were N regions selected, we removed 1,
1618 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1619 corresponding regionview, and _views is now empty).
1621 This could have invalidated any and all iterators into _views.
1623 The heuristic we use here is: if the region selection is empty, break out of the loop
1624 here. if the region selection is not empty, then restart the loop because we know that
1625 we must have removed at least the region(view) we've just been working on as well as any
1626 that we processed on previous iterations.
1628 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1629 we can just iterate.
1633 if (_views.empty()) {
1644 /* If we've created new regions either by copying or moving
1645 to a new track, we want to replace the old selection with the new ones
1648 if (new_views.size() > 0) {
1649 _editor->selection->set (new_views);
1652 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1656 /* write commands for the accumulated diffs for all our modified playlists */
1657 add_stateful_diff_commands_for_playlists (modified_playlists);
1659 _editor->commit_reversible_command ();
1661 /* We have futzed with the layering of canvas items on our streamviews.
1662 If any region changed layer, this will have resulted in the stream
1663 views being asked to set up their region views, and all will be well.
1664 If not, we might now have badly-ordered region views. Ask the StreamViews
1665 involved to sort themselves out, just in case.
1668 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1669 (*i)->view()->playlist_layered ((*i)->track ());
1673 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1674 * @param region Region to remove.
1675 * @param playlist playlist To remove from.
1676 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1677 * that clear_changes () is only called once per playlist.
1680 RegionMoveDrag::remove_region_from_playlist (
1681 boost::shared_ptr<Region> region,
1682 boost::shared_ptr<Playlist> playlist,
1683 PlaylistSet& modified_playlists
1686 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1689 playlist->clear_changes ();
1692 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1696 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1697 * clearing the playlist's diff history first if necessary.
1698 * @param region Region to insert.
1699 * @param dest_rtv Destination RouteTimeAxisView.
1700 * @param dest_layer Destination layer.
1701 * @param where Destination position.
1702 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1703 * that clear_changes () is only called once per playlist.
1704 * @return New RegionView, or 0 if no insert was performed.
1707 RegionMoveDrag::insert_region_into_playlist (
1708 boost::shared_ptr<Region> region,
1709 RouteTimeAxisView* dest_rtv,
1712 PlaylistSet& modified_playlists
1715 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1716 if (!dest_playlist) {
1720 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1721 _new_region_view = 0;
1722 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1724 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1725 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1727 dest_playlist->clear_changes ();
1730 dest_playlist->add_region (region, where);
1732 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1733 dest_playlist->set_layer (region, dest_layer);
1738 assert (_new_region_view);
1740 return _new_region_view;
1744 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1746 _new_region_view = rv;
1750 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1752 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1753 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1755 _editor->session()->add_command (c);
1764 RegionMoveDrag::aborted (bool movement_occurred)
1768 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1769 list<DraggingView>::const_iterator next = i;
1778 RegionMotionDrag::aborted (movement_occurred);
1783 RegionMotionDrag::aborted (bool)
1785 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1787 StreamView* sview = (*i)->view();
1790 if (sview->layer_display() == Expanded) {
1791 sview->set_layer_display (Stacked);
1796 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1797 RegionView* rv = i->view;
1798 TimeAxisView* tv = &(rv->get_time_axis_view ());
1799 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1801 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1802 rv->get_canvas_group()->set_y_position (0);
1804 rv->move (-_total_x_delta, 0);
1805 rv->set_height (rtv->view()->child_height ());
1809 /** @param b true to brush, otherwise false.
1810 * @param c true to make copies of the regions being moved, otherwise false.
1812 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1813 : RegionMotionDrag (e, i, p, v, b)
1816 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1819 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1820 if (rtv && rtv->is_track()) {
1821 speed = rtv->track()->speed ();
1824 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1828 RegionMoveDrag::setup_pointer_frame_offset ()
1830 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1833 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1834 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1836 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1838 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1839 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1841 _primary = v->view()->create_region_view (r, false, false);
1843 _primary->get_canvas_group()->show ();
1844 _primary->set_position (pos, 0);
1845 _views.push_back (DraggingView (_primary, this, v));
1847 _last_frame_position = pos;
1849 _item = _primary->get_canvas_group ();
1853 RegionInsertDrag::finished (GdkEvent *, bool)
1855 int pos = _views.front().time_axis_view;
1856 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1858 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1860 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1861 _primary->get_canvas_group()->set_y_position (0);
1863 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1865 _editor->begin_reversible_command (Operations::insert_region);
1866 playlist->clear_changes ();
1867 playlist->add_region (_primary->region (), _last_frame_position);
1869 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
1870 if (Config->get_edit_mode() == Ripple) {
1871 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
1874 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1875 _editor->commit_reversible_command ();
1883 RegionInsertDrag::aborted (bool)
1890 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1891 : RegionMoveDrag (e, i, p, v, false, false)
1893 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1896 struct RegionSelectionByPosition {
1897 bool operator() (RegionView*a, RegionView* b) {
1898 return a->region()->position () < b->region()->position();
1903 RegionSpliceDrag::motion (GdkEvent* event, bool)
1905 /* Which trackview is this ? */
1907 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
1908 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1910 /* The region motion is only processed if the pointer is over
1914 if (!tv || !tv->is_track()) {
1915 /* To make sure we hide the verbose canvas cursor when the mouse is
1916 not held over an audio track.
1918 _editor->verbose_cursor()->hide ();
1921 _editor->verbose_cursor()->show ();
1926 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1932 RegionSelection copy;
1933 _editor->selection->regions.by_position(copy);
1935 framepos_t const pf = adjusted_current_frame (event);
1937 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1939 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1945 boost::shared_ptr<Playlist> playlist;
1947 if ((playlist = atv->playlist()) == 0) {
1951 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1956 if (pf < (*i)->region()->last_frame() + 1) {
1960 if (pf > (*i)->region()->first_frame()) {
1966 playlist->shuffle ((*i)->region(), dir);
1971 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1973 RegionMoveDrag::finished (event, movement_occurred);
1977 RegionSpliceDrag::aborted (bool)
1987 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
1990 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
1992 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
1993 RegionSelection to_ripple;
1994 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
1995 if ((*i)->position() >= where) {
1996 to_ripple.push_back (rtv->view()->find_view(*i));
2000 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2001 if (!exclude.contains (*i)) {
2002 // the selection has already been added to _views
2004 if (drag_in_progress) {
2005 // do the same things that RegionMotionDrag::motion does when
2006 // first_move is true, for the region views that we're adding
2007 // to _views this time
2010 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2011 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2012 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2013 rvg->reparent (_editor->_drag_motion_group);
2015 // we only need to move in the y direction
2016 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2021 _views.push_back (DraggingView (*i, this, tav));
2027 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2030 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2031 // we added all the regions after the selection
2033 std::list<DraggingView>::iterator to_erase = i++;
2034 if (!_editor->selection->regions.contains (to_erase->view)) {
2035 // restore the non-selected regions to their original playlist & positions,
2036 // and then ripple them back by the length of the regions that were dragged away
2037 // do the same things as RegionMotionDrag::aborted
2039 RegionView *rv = to_erase->view;
2040 TimeAxisView* tv = &(rv->get_time_axis_view ());
2041 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2044 // plonk them back onto their own track
2045 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2046 rv->get_canvas_group()->set_y_position (0);
2050 // move the underlying region to match the view
2051 rv->region()->set_position (rv->region()->position() + amount);
2053 // restore the view to match the underlying region's original position
2054 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2057 rv->set_height (rtv->view()->child_height ());
2058 _views.erase (to_erase);
2064 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2066 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2068 return allow_moves_across_tracks;
2076 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2077 : RegionMoveDrag (e, i, p, v, false, false)
2079 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2080 // compute length of selection
2081 RegionSelection selected_regions = _editor->selection->regions;
2082 selection_length = selected_regions.end_frame() - selected_regions.start();
2084 // we'll only allow dragging to another track in ripple mode if all the regions
2085 // being dragged start off on the same track
2086 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2089 exclude = new RegionList;
2090 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2091 exclude->push_back((*i)->region());
2094 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2095 RegionSelection copy;
2096 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2098 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2099 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2101 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2102 // find ripple start point on each applicable playlist
2103 RegionView *first_selected_on_this_track = NULL;
2104 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2105 if ((*i)->region()->playlist() == (*pi)) {
2106 // region is on this playlist - it's the first, because they're sorted
2107 first_selected_on_this_track = *i;
2111 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2112 add_all_after_to_views (
2113 &first_selected_on_this_track->get_time_axis_view(),
2114 first_selected_on_this_track->region()->position(),
2115 selected_regions, false);
2118 if (allow_moves_across_tracks) {
2119 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2127 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2129 /* Which trackview is this ? */
2131 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2132 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2134 /* The region motion is only processed if the pointer is over
2138 if (!tv || !tv->is_track()) {
2139 /* To make sure we hide the verbose canvas cursor when the mouse is
2140 not held over an audiotrack.
2142 _editor->verbose_cursor()->hide ();
2146 framepos_t where = adjusted_current_frame (event);
2147 assert (where >= 0);
2149 double delta = compute_x_delta (event, &after);
2151 framecnt_t amount = _editor->pixel_to_sample (delta);
2153 if (allow_moves_across_tracks) {
2154 // all the originally selected regions were on the same track
2156 framecnt_t adjust = 0;
2157 if (prev_tav && tv != prev_tav) {
2158 // dragged onto a different track
2159 // remove the unselected regions from _views, restore them to their original positions
2160 // and add the regions after the drop point on the new playlist to _views instead.
2161 // undo the effect of rippling the previous playlist, and include the effect of removing
2162 // the dragged region(s) from this track
2164 remove_unselected_from_views (prev_amount, false);
2165 // ripple previous playlist according to the regions that have been removed onto the new playlist
2166 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2169 // move just the selected regions
2170 RegionMoveDrag::motion(event, first_move);
2172 // ensure that the ripple operation on the new playlist inserts selection_length time
2173 adjust = selection_length;
2174 // ripple the new current playlist
2175 tv->playlist()->ripple (where, amount+adjust, exclude);
2177 // add regions after point where drag entered this track to subsequent ripples
2178 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2181 // motion on same track
2182 RegionMoveDrag::motion(event, first_move);
2186 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2187 prev_position = where;
2189 // selection encompasses multiple tracks - just drag
2190 // cross-track drags are forbidden
2191 RegionMoveDrag::motion(event, first_move);
2194 if (!_x_constrained) {
2195 prev_amount += amount;
2198 _last_frame_position = after;
2202 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2204 if (!movement_occurred) {
2208 if (was_double_click() && !_views.empty()) {
2209 DraggingView dv = _views.front();
2210 dv.view->show_region_editor ();
2217 _editor->begin_reversible_command(_("Ripple drag"));
2219 // remove the regions being rippled from the dragging view, updating them to
2220 // their new positions
2221 remove_unselected_from_views (prev_amount, true);
2223 if (allow_moves_across_tracks) {
2225 // if regions were dragged across tracks, we've rippled any later
2226 // regions on the track the regions were dragged off, so we need
2227 // to add the original track to the undo record
2228 orig_tav->playlist()->clear_changes();
2229 vector<Command*> cmds;
2230 orig_tav->playlist()->rdiff (cmds);
2231 _editor->session()->add_commands (cmds);
2233 if (prev_tav && prev_tav != orig_tav) {
2234 prev_tav->playlist()->clear_changes();
2235 vector<Command*> cmds;
2236 prev_tav->playlist()->rdiff (cmds);
2237 _editor->session()->add_commands (cmds);
2240 // selection spanned multiple tracks - all will need adding to undo record
2242 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2243 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2245 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2246 (*pi)->clear_changes();
2247 vector<Command*> cmds;
2248 (*pi)->rdiff (cmds);
2249 _editor->session()->add_commands (cmds);
2253 // other modified playlists are added to undo by RegionMoveDrag::finished()
2254 RegionMoveDrag::finished (event, movement_occurred);
2255 _editor->commit_reversible_command();
2259 RegionRippleDrag::aborted (bool movement_occurred)
2261 RegionMoveDrag::aborted (movement_occurred);
2266 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2268 _view (dynamic_cast<MidiTimeAxisView*> (v))
2270 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2276 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2279 _region = add_midi_region (_view);
2280 _view->playlist()->freeze ();
2283 framepos_t const f = adjusted_current_frame (event);
2284 if (f < grab_frame()) {
2285 _region->set_position (f);
2288 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2289 so that if this region is duplicated, its duplicate starts on
2290 a snap point rather than 1 frame after a snap point. Otherwise things get
2291 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2292 place snapped notes at the start of the region.
2295 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2296 _region->set_length (len < 1 ? 1 : len);
2302 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
2304 if (!movement_occurred) {
2305 add_midi_region (_view);
2307 _view->playlist()->thaw ();
2312 RegionCreateDrag::aborted (bool)
2315 _view->playlist()->thaw ();
2321 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2326 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2330 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2332 Gdk::Cursor* cursor;
2333 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2335 float x_fraction = cnote->mouse_x_fraction ();
2337 if (x_fraction > 0.0 && x_fraction < 0.25) {
2338 cursor = _editor->cursors()->left_side_trim;
2341 cursor = _editor->cursors()->right_side_trim;
2345 Drag::start_grab (event, cursor);
2347 region = &cnote->region_view();
2350 temp = region->snap_to_pixel (cnote->x0 (), true);
2351 _snap_delta = temp - cnote->x0 ();
2355 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2361 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2363 if (ms.size() > 1) {
2364 /* has to be relative, may make no sense otherwise */
2368 /* select this note; if it is already selected, preserve the existing selection,
2369 otherwise make this note the only one selected.
2371 region->note_selected (cnote, cnote->selected ());
2373 _editor->begin_reversible_command (_("resize notes"));
2375 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2376 MidiRegionSelection::iterator next;
2379 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2381 mrv->begin_resizing (at_front);
2388 NoteResizeDrag::motion (GdkEvent* event, bool /*first_move*/)
2390 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2391 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2392 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2394 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2397 if (!ArdourKeyboard::indicates_snap_delta (event->button.state)) {
2400 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd);
2406 NoteResizeDrag::finished (GdkEvent* event, bool /*movement_occurred*/)
2408 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2409 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2410 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2412 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2414 if (!ArdourKeyboard::indicates_snap_delta (event->button.state)) {
2418 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd);
2422 _editor->commit_reversible_command ();
2426 NoteResizeDrag::aborted (bool)
2428 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2429 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2430 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2432 mrv->abort_resizing ();
2437 AVDraggingView::AVDraggingView (RegionView* v)
2440 initial_position = v->region()->position ();
2443 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2446 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2449 TrackViewList empty;
2451 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2452 std::list<RegionView*> views = rs.by_layer();
2454 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2455 RegionView* rv = (*i);
2456 if (!rv->region()->video_locked()) {
2459 _views.push_back (AVDraggingView (rv));
2464 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2466 Drag::start_grab (event);
2467 if (_editor->session() == 0) {
2471 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2472 _max_backwards_drag = (
2473 ARDOUR_UI::instance()->video_timeline->get_duration()
2474 + ARDOUR_UI::instance()->video_timeline->get_offset()
2475 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2478 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2479 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2480 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2483 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2486 Timecode::Time timecode;
2487 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2488 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);
2489 show_verbose_cursor_text (buf);
2493 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2495 if (_editor->session() == 0) {
2498 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2502 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2503 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2505 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2506 dt = - _max_backwards_drag;
2509 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2510 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2512 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2513 RegionView* rv = i->view;
2514 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2517 rv->region()->clear_changes ();
2518 rv->region()->suspend_property_changes();
2520 rv->region()->set_position(i->initial_position + dt);
2521 rv->region_changed(ARDOUR::Properties::position);
2524 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2525 Timecode::Time timecode;
2526 Timecode::Time timediff;
2528 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2529 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2530 snprintf (buf, sizeof (buf),
2531 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2532 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2533 , _("Video Start:"),
2534 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2536 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2538 show_verbose_cursor_text (buf);
2542 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2544 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2548 if (!movement_occurred || ! _editor->session()) {
2552 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2554 _editor->begin_reversible_command (_("Move Video"));
2556 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2557 ARDOUR_UI::instance()->video_timeline->save_undo();
2558 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2559 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2561 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2562 i->view->drag_end();
2563 i->view->region()->resume_property_changes ();
2565 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2568 _editor->session()->maybe_update_session_range(
2569 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2570 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2574 _editor->commit_reversible_command ();
2578 VideoTimeLineDrag::aborted (bool)
2580 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2583 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2584 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2586 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2587 i->view->region()->resume_property_changes ();
2588 i->view->region()->set_position(i->initial_position);
2592 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2593 : RegionDrag (e, i, p, v)
2594 , _preserve_fade_anchor (preserve_fade_anchor)
2595 , _jump_position_when_done (false)
2597 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2601 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2604 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2605 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2607 if (tv && tv->is_track()) {
2608 speed = tv->track()->speed();
2611 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2612 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2613 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2615 framepos_t const pf = adjusted_current_frame (event);
2616 setup_snap_delta (region_start);
2618 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2619 /* Move the contents of the region around without changing the region bounds */
2620 _operation = ContentsTrim;
2621 Drag::start_grab (event, _editor->cursors()->trimmer);
2623 /* These will get overridden for a point trim.*/
2624 if (pf < (region_start + region_length/2)) {
2625 /* closer to front */
2626 _operation = StartTrim;
2627 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2628 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2630 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2634 _operation = EndTrim;
2635 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2636 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2638 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2642 /* jump trim disabled for now
2643 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2644 _jump_position_when_done = true;
2648 switch (_operation) {
2650 show_verbose_cursor_time (region_start);
2651 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2652 i->view->trim_front_starting ();
2656 show_verbose_cursor_duration (region_start, region_end);
2659 show_verbose_cursor_time (pf);
2663 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2664 i->view->region()->suspend_property_changes ();
2669 TrimDrag::motion (GdkEvent* event, bool first_move)
2671 RegionView* rv = _primary;
2674 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2675 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2676 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2677 frameoffset_t frame_delta = 0;
2679 if (tv && tv->is_track()) {
2680 speed = tv->track()->speed();
2682 framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
2683 framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state);
2689 switch (_operation) {
2691 trim_type = "Region start trim";
2694 trim_type = "Region end trim";
2697 trim_type = "Region content trim";
2704 _editor->begin_reversible_command (trim_type);
2706 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2707 RegionView* rv = i->view;
2708 rv->enable_display (false);
2709 rv->region()->playlist()->clear_owned_changes ();
2711 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2714 arv->temporarily_hide_envelope ();
2718 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2719 insert_result = _editor->motion_frozen_playlists.insert (pl);
2721 if (insert_result.second) {
2727 bool non_overlap_trim = false;
2729 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
2730 non_overlap_trim = true;
2733 /* contstrain trim to fade length */
2734 if (_preserve_fade_anchor) {
2735 switch (_operation) {
2737 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2738 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2740 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2741 if (ar->locked()) continue;
2742 framecnt_t len = ar->fade_in()->back()->when;
2743 if (len < dt) dt = min(dt, len);
2747 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2748 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2750 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2751 if (ar->locked()) continue;
2752 framecnt_t len = ar->fade_out()->back()->when;
2753 if (len < -dt) dt = max(dt, -len);
2762 switch (_operation) {
2764 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2765 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim);
2766 if (changed && _preserve_fade_anchor) {
2767 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2769 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2770 framecnt_t len = ar->fade_in()->back()->when;
2771 framecnt_t diff = ar->first_frame() - i->initial_position;
2772 framepos_t new_length = len - diff;
2773 i->anchored_fade_length = min (ar->length(), new_length);
2774 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
2775 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
2782 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2783 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim);
2784 if (changed && _preserve_fade_anchor) {
2785 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2787 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2788 framecnt_t len = ar->fade_out()->back()->when;
2789 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
2790 framepos_t new_length = len + diff;
2791 i->anchored_fade_length = min (ar->length(), new_length);
2792 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
2793 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
2801 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
2803 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2804 i->view->move_contents (frame_delta);
2810 switch (_operation) {
2812 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
2815 show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
2818 // show_verbose_cursor_time (frame_delta);
2824 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
2826 if (movement_occurred) {
2827 motion (event, false);
2829 if (_operation == StartTrim) {
2830 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2832 /* This must happen before the region's StatefulDiffCommand is created, as it may
2833 `correct' (ahem) the region's _start from being negative to being zero. It
2834 needs to be zero in the undo record.
2836 i->view->trim_front_ending ();
2838 if (_preserve_fade_anchor) {
2839 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2841 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2842 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
2843 ar->set_fade_in_length(i->anchored_fade_length);
2844 ar->set_fade_in_active(true);
2847 if (_jump_position_when_done) {
2848 i->view->region()->set_position (i->initial_position);
2851 } else if (_operation == EndTrim) {
2852 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2853 if (_preserve_fade_anchor) {
2854 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2856 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2857 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
2858 ar->set_fade_out_length(i->anchored_fade_length);
2859 ar->set_fade_out_active(true);
2862 if (_jump_position_when_done) {
2863 i->view->region()->set_position (i->initial_end - i->view->region()->length());
2868 if (!_views.empty()) {
2869 if (_operation == StartTrim) {
2870 _editor->maybe_locate_with_edit_preroll(
2871 _views.begin()->view->region()->position());
2873 if (_operation == EndTrim) {
2874 _editor->maybe_locate_with_edit_preroll(
2875 _views.begin()->view->region()->position() +
2876 _views.begin()->view->region()->length());
2880 if (!_editor->selection->selected (_primary)) {
2881 _primary->thaw_after_trim ();
2884 set<boost::shared_ptr<Playlist> > diffed_playlists;
2886 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2887 i->view->thaw_after_trim ();
2888 i->view->enable_display (true);
2890 /* Trimming one region may affect others on the playlist, so we need
2891 to get undo Commands from the whole playlist rather than just the
2892 region. Use diffed_playlists to make sure we don't diff a given
2893 playlist more than once.
2895 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
2896 if (diffed_playlists.find (p) == diffed_playlists.end()) {
2897 vector<Command*> cmds;
2899 _editor->session()->add_commands (cmds);
2900 diffed_playlists.insert (p);
2905 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
2909 _editor->motion_frozen_playlists.clear ();
2910 _editor->commit_reversible_command();
2913 /* no mouse movement */
2914 _editor->point_trim (event, adjusted_current_frame (event));
2917 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2918 if (_operation == StartTrim) {
2919 i->view->trim_front_ending ();
2922 i->view->region()->resume_property_changes ();
2927 TrimDrag::aborted (bool movement_occurred)
2929 /* Our motion method is changing model state, so use the Undo system
2930 to cancel. Perhaps not ideal, as this will leave an Undo point
2931 behind which may be slightly odd from the user's point of view.
2936 if (movement_occurred) {
2940 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2941 i->view->region()->resume_property_changes ();
2946 TrimDrag::setup_pointer_frame_offset ()
2948 list<DraggingView>::iterator i = _views.begin ();
2949 while (i != _views.end() && i->view != _primary) {
2953 if (i == _views.end()) {
2957 switch (_operation) {
2959 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
2962 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
2969 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
2973 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
2974 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
2979 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2981 Drag::start_grab (event, cursor);
2982 show_verbose_cursor_time (adjusted_current_frame(event));
2986 MeterMarkerDrag::setup_pointer_frame_offset ()
2988 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
2992 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
2994 if (!_marker->meter().movable()) {
3000 // create a dummy marker for visual representation of moving the
3001 // section, because whether its a copy or not, we're going to
3002 // leave or lose the original marker (leave if its a copy; lose if its
3003 // not, because we'll remove it from the map).
3005 MeterSection section (_marker->meter());
3007 if (!section.movable()) {
3012 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3014 _marker = new MeterMarker (
3016 *_editor->meter_group,
3017 ARDOUR_UI::config()->color ("meter marker"),
3019 *new MeterSection (_marker->meter())
3022 /* use the new marker for the grab */
3023 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3026 TempoMap& map (_editor->session()->tempo_map());
3027 /* get current state */
3028 before_state = &map.get_state();
3029 /* remove the section while we drag it */
3030 map.remove_meter (section, true);
3034 framepos_t const pf = adjusted_current_frame (event);
3036 _marker->set_position (pf);
3037 show_verbose_cursor_time (pf);
3041 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3043 if (!movement_occurred) {
3044 if (was_double_click()) {
3045 _editor->edit_meter_marker (*_marker);
3050 if (!_marker->meter().movable()) {
3054 motion (event, false);
3056 Timecode::BBT_Time when;
3058 TempoMap& map (_editor->session()->tempo_map());
3059 map.bbt_time (last_pointer_frame(), when);
3061 if (_copy == true) {
3062 _editor->begin_reversible_command (_("copy meter mark"));
3063 XMLNode &before = map.get_state();
3064 map.add_meter (_marker->meter(), when);
3065 XMLNode &after = map.get_state();
3066 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
3067 _editor->commit_reversible_command ();
3070 _editor->begin_reversible_command (_("move meter mark"));
3072 /* we removed it before, so add it back now */
3074 map.add_meter (_marker->meter(), when);
3075 XMLNode &after = map.get_state();
3076 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3077 _editor->commit_reversible_command ();
3080 // delete the dummy marker we used for visual representation while moving.
3081 // a new visual marker will show up automatically.
3086 MeterMarkerDrag::aborted (bool moved)
3088 _marker->set_position (_marker->meter().frame ());
3091 TempoMap& map (_editor->session()->tempo_map());
3092 /* we removed it before, so add it back now */
3093 map.add_meter (_marker->meter(), _marker->meter().frame());
3094 // delete the dummy marker we used for visual representation while moving.
3095 // a new visual marker will show up automatically.
3100 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3104 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3106 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3111 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3113 Drag::start_grab (event, cursor);
3114 show_verbose_cursor_time (adjusted_current_frame (event));
3118 TempoMarkerDrag::setup_pointer_frame_offset ()
3120 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
3124 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3126 if (!_marker->tempo().movable()) {
3132 // create a dummy marker for visual representation of moving the
3133 // section, because whether its a copy or not, we're going to
3134 // leave or lose the original marker (leave if its a copy; lose if its
3135 // not, because we'll remove it from the map).
3137 // create a dummy marker for visual representation of moving the copy.
3138 // The actual copying is not done before we reach the finish callback.
3141 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
3143 TempoSection section (_marker->tempo());
3145 _marker = new TempoMarker (
3147 *_editor->tempo_group,
3148 ARDOUR_UI::config()->color ("tempo marker"),
3150 *new TempoSection (_marker->tempo())
3153 /* use the new marker for the grab */
3154 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3157 TempoMap& map (_editor->session()->tempo_map());
3158 /* get current state */
3159 before_state = &map.get_state();
3160 /* remove the section while we drag it */
3161 map.remove_tempo (section, true);
3165 framepos_t const pf = adjusted_current_frame (event);
3166 _marker->set_position (pf);
3167 show_verbose_cursor_time (pf);
3171 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3173 if (!movement_occurred) {
3174 if (was_double_click()) {
3175 _editor->edit_tempo_marker (*_marker);
3180 if (!_marker->tempo().movable()) {
3184 motion (event, false);
3186 TempoMap& map (_editor->session()->tempo_map());
3187 framepos_t beat_time = map.round_to_beat (last_pointer_frame(), RoundNearest);
3188 Timecode::BBT_Time when;
3190 map.bbt_time (beat_time, when);
3192 if (_copy == true) {
3193 _editor->begin_reversible_command (_("copy tempo mark"));
3194 XMLNode &before = map.get_state();
3195 map.add_tempo (_marker->tempo(), when);
3196 XMLNode &after = map.get_state();
3197 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
3198 _editor->commit_reversible_command ();
3201 _editor->begin_reversible_command (_("move tempo mark"));
3202 /* we removed it before, so add it back now */
3203 map.add_tempo (_marker->tempo(), when);
3204 XMLNode &after = map.get_state();
3205 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3206 _editor->commit_reversible_command ();
3209 // delete the dummy marker we used for visual representation while moving.
3210 // a new visual marker will show up automatically.
3215 TempoMarkerDrag::aborted (bool moved)
3217 _marker->set_position (_marker->tempo().frame());
3219 TempoMap& map (_editor->session()->tempo_map());
3220 /* we removed it before, so add it back now */
3221 map.add_tempo (_marker->tempo(), _marker->tempo().start());
3222 // delete the dummy marker we used for visual representation while moving.
3223 // a new visual marker will show up automatically.
3228 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3229 : Drag (e, &c.track_canvas_item(), false)
3233 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3236 /** Do all the things we do when dragging the playhead to make it look as though
3237 * we have located, without actually doing the locate (because that would cause
3238 * the diskstream buffers to be refilled, which is too slow).
3241 CursorDrag::fake_locate (framepos_t t)
3243 _editor->playhead_cursor->set_position (t);
3245 Session* s = _editor->session ();
3246 if (s->timecode_transmission_suspended ()) {
3247 framepos_t const f = _editor->playhead_cursor->current_frame ();
3248 /* This is asynchronous so it will be sent "now"
3250 s->send_mmc_locate (f);
3251 /* These are synchronous and will be sent during the next
3254 s->queue_full_time_code ();
3255 s->queue_song_position_pointer ();
3258 show_verbose_cursor_time (t);
3259 _editor->UpdateAllTransportClocks (t);
3263 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3265 Drag::start_grab (event, c);
3266 setup_snap_delta (_editor->playhead_cursor->current_frame ());
3268 _grab_zoom = _editor->samples_per_pixel;
3270 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3272 _editor->snap_to_with_modifier (where, event);
3274 _editor->_dragging_playhead = true;
3276 Session* s = _editor->session ();
3278 /* grab the track canvas item as well */
3280 _cursor.track_canvas_item().grab();
3283 if (_was_rolling && _stop) {
3287 if (s->is_auditioning()) {
3288 s->cancel_audition ();
3292 if (AudioEngine::instance()->connected()) {
3294 /* do this only if we're the engine is connected
3295 * because otherwise this request will never be
3296 * serviced and we'll busy wait forever. likewise,
3297 * notice if we are disconnected while waiting for the
3298 * request to be serviced.
3301 s->request_suspend_timecode_transmission ();
3302 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3303 /* twiddle our thumbs */
3308 fake_locate (where - snap_delta (event->button.state));
3312 CursorDrag::motion (GdkEvent* event, bool)
3314 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3315 _editor->snap_to_with_modifier (where, event);
3316 if (where != last_pointer_frame()) {
3317 fake_locate (where - snap_delta (event->button.state));
3322 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3324 _editor->_dragging_playhead = false;
3326 _cursor.track_canvas_item().ungrab();
3328 if (!movement_occurred && _stop) {
3332 motion (event, false);
3334 Session* s = _editor->session ();
3336 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3337 _editor->_pending_locate_request = true;
3338 s->request_resume_timecode_transmission ();
3343 CursorDrag::aborted (bool)
3345 _cursor.track_canvas_item().ungrab();
3347 if (_editor->_dragging_playhead) {
3348 _editor->session()->request_resume_timecode_transmission ();
3349 _editor->_dragging_playhead = false;
3352 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3355 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3356 : RegionDrag (e, i, p, v)
3358 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3362 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3364 Drag::start_grab (event, cursor);
3366 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3367 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3368 setup_snap_delta (r->position ());
3370 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3374 FadeInDrag::setup_pointer_frame_offset ()
3376 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3377 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3378 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3382 FadeInDrag::motion (GdkEvent* event, bool)
3384 framecnt_t fade_length;
3386 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3387 _editor->snap_to_with_modifier (pos, event);
3388 pos -= snap_delta (event->button.state);
3390 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3392 if (pos < (region->position() + 64)) {
3393 fade_length = 64; // this should be a minimum defined somewhere
3394 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3395 fade_length = region->length() - region->fade_out()->back()->when - 1;
3397 fade_length = pos - region->position();
3400 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3402 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3408 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3411 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3415 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3417 if (!movement_occurred) {
3421 framecnt_t fade_length;
3422 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3423 _editor->snap_to_with_modifier (pos, event);
3424 pos -= snap_delta (event->button.state);
3426 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3428 if (pos < (region->position() + 64)) {
3429 fade_length = 64; // this should be a minimum defined somewhere
3430 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3431 fade_length = region->length() - region->fade_out()->back()->when - 1;
3433 fade_length = pos - region->position();
3436 _editor->begin_reversible_command (_("change fade in length"));
3438 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3440 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3446 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3447 XMLNode &before = alist->get_state();
3449 tmp->audio_region()->set_fade_in_length (fade_length);
3450 tmp->audio_region()->set_fade_in_active (true);
3452 XMLNode &after = alist->get_state();
3453 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3456 _editor->commit_reversible_command ();
3460 FadeInDrag::aborted (bool)
3462 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3463 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3469 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3473 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3474 : RegionDrag (e, i, p, v)
3476 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3480 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3482 Drag::start_grab (event, cursor);
3484 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3485 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3486 setup_snap_delta (r->last_frame ());
3488 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3492 FadeOutDrag::setup_pointer_frame_offset ()
3494 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3495 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3496 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3500 FadeOutDrag::motion (GdkEvent* event, bool)
3502 framecnt_t fade_length;
3504 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3505 _editor->snap_to_with_modifier (pos, event);
3506 pos -= snap_delta (event->button.state);
3508 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3510 if (pos > (region->last_frame() - 64)) {
3511 fade_length = 64; // this should really be a minimum fade defined somewhere
3512 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3513 fade_length = region->length() - region->fade_in()->back()->when - 1;
3515 fade_length = region->last_frame() - pos;
3518 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3520 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3526 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3529 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3533 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3535 if (!movement_occurred) {
3539 framecnt_t fade_length;
3541 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3542 _editor->snap_to_with_modifier (pos, event);
3543 pos -= snap_delta (event->button.state);
3545 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3547 if (pos > (region->last_frame() - 64)) {
3548 fade_length = 64; // this should really be a minimum fade defined somewhere
3549 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3550 fade_length = region->length() - region->fade_in()->back()->when - 1;
3552 fade_length = region->last_frame() - pos;
3555 _editor->begin_reversible_command (_("change fade out length"));
3557 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3559 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3565 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3566 XMLNode &before = alist->get_state();
3568 tmp->audio_region()->set_fade_out_length (fade_length);
3569 tmp->audio_region()->set_fade_out_active (true);
3571 XMLNode &after = alist->get_state();
3572 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3575 _editor->commit_reversible_command ();
3579 FadeOutDrag::aborted (bool)
3581 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3582 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3588 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3592 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3595 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3597 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
3600 _points.push_back (ArdourCanvas::Duple (0, 0));
3601 _points.push_back (ArdourCanvas::Duple (0, physical_screen_height (_editor->get_window())));
3604 MarkerDrag::~MarkerDrag ()
3606 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
3611 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, Marker* m)
3613 location = new Location (*l);
3614 markers.push_back (m);
3619 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3621 Drag::start_grab (event, cursor);
3625 Location *location = _editor->find_location_from_marker (_marker, is_start);
3626 _editor->_dragging_edit_point = true;
3628 update_item (location);
3630 // _drag_line->show();
3631 // _line->raise_to_top();
3634 show_verbose_cursor_time (location->start());
3636 show_verbose_cursor_time (location->end());
3639 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3642 case Selection::Toggle:
3643 /* we toggle on the button release */
3645 case Selection::Set:
3646 if (!_editor->selection->selected (_marker)) {
3647 _editor->selection->set (_marker);
3650 case Selection::Extend:
3652 Locations::LocationList ll;
3653 list<Marker*> to_add;
3655 _editor->selection->markers.range (s, e);
3656 s = min (_marker->position(), s);
3657 e = max (_marker->position(), e);
3660 if (e < max_framepos) {
3663 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
3664 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
3665 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
3668 to_add.push_back (lm->start);
3671 to_add.push_back (lm->end);
3675 if (!to_add.empty()) {
3676 _editor->selection->add (to_add);
3680 case Selection::Add:
3681 _editor->selection->add (_marker);
3685 /* Set up copies for us to manipulate during the drag
3688 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
3690 Location* l = _editor->find_location_from_marker (*i, is_start);
3697 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
3699 /* range: check that the other end of the range isn't
3702 CopiedLocationInfo::iterator x;
3703 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3704 if (*(*x).location == *l) {
3708 if (x == _copied_locations.end()) {
3709 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
3711 (*x).markers.push_back (*i);
3712 (*x).move_both = true;
3720 MarkerDrag::setup_pointer_frame_offset ()
3723 Location *location = _editor->find_location_from_marker (_marker, is_start);
3724 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
3728 MarkerDrag::motion (GdkEvent* event, bool)
3730 framecnt_t f_delta = 0;
3732 bool move_both = false;
3733 Location *real_location;
3734 Location *copy_location = 0;
3736 framepos_t const newframe = adjusted_current_frame (event);
3737 framepos_t next = newframe;
3739 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
3743 CopiedLocationInfo::iterator x;
3745 /* find the marker we're dragging, and compute the delta */
3747 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3749 copy_location = (*x).location;
3751 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
3753 /* this marker is represented by this
3754 * CopiedLocationMarkerInfo
3757 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
3762 if (real_location->is_mark()) {
3763 f_delta = newframe - copy_location->start();
3767 switch (_marker->type()) {
3768 case Marker::SessionStart:
3769 case Marker::RangeStart:
3770 case Marker::LoopStart:
3771 case Marker::PunchIn:
3772 f_delta = newframe - copy_location->start();
3775 case Marker::SessionEnd:
3776 case Marker::RangeEnd:
3777 case Marker::LoopEnd:
3778 case Marker::PunchOut:
3779 f_delta = newframe - copy_location->end();
3782 /* what kind of marker is this ? */
3791 if (x == _copied_locations.end()) {
3792 /* hmm, impossible - we didn't find the dragged marker */
3796 /* now move them all */
3798 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3800 copy_location = x->location;
3802 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
3806 if (real_location->locked()) {
3810 if (copy_location->is_mark()) {
3814 copy_location->set_start (copy_location->start() + f_delta);
3818 framepos_t new_start = copy_location->start() + f_delta;
3819 framepos_t new_end = copy_location->end() + f_delta;
3821 if (is_start) { // start-of-range marker
3823 if (move_both || (*x).move_both) {
3824 copy_location->set_start (new_start);
3825 copy_location->set_end (new_end);
3826 } else if (new_start < copy_location->end()) {
3827 copy_location->set_start (new_start);
3828 } else if (newframe > 0) {
3829 _editor->snap_to (next, RoundUpAlways, true);
3830 copy_location->set_end (next);
3831 copy_location->set_start (newframe);
3834 } else { // end marker
3836 if (move_both || (*x).move_both) {
3837 copy_location->set_end (new_end);
3838 copy_location->set_start (new_start);
3839 } else if (new_end > copy_location->start()) {
3840 copy_location->set_end (new_end);
3841 } else if (newframe > 0) {
3842 _editor->snap_to (next, RoundDownAlways, true);
3843 copy_location->set_start (next);
3844 copy_location->set_end (newframe);
3849 update_item (copy_location);
3851 /* now lookup the actual GUI items used to display this
3852 * location and move them to wherever the copy of the location
3853 * is now. This means that the logic in ARDOUR::Location is
3854 * still enforced, even though we are not (yet) modifying
3855 * the real Location itself.
3858 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
3861 lm->set_position (copy_location->start(), copy_location->end());
3866 assert (!_copied_locations.empty());
3868 show_verbose_cursor_time (newframe);
3872 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3874 if (!movement_occurred) {
3876 if (was_double_click()) {
3877 _editor->rename_marker (_marker);
3881 /* just a click, do nothing but finish
3882 off the selection process
3885 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3888 case Selection::Set:
3889 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
3890 _editor->selection->set (_marker);
3894 case Selection::Toggle:
3895 /* we toggle on the button release, click only */
3896 _editor->selection->toggle (_marker);
3899 case Selection::Extend:
3900 case Selection::Add:
3907 _editor->_dragging_edit_point = false;
3909 _editor->begin_reversible_command ( _("move marker") );
3910 XMLNode &before = _editor->session()->locations()->get_state();
3912 MarkerSelection::iterator i;
3913 CopiedLocationInfo::iterator x;
3916 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
3917 x != _copied_locations.end() && i != _editor->selection->markers.end();
3920 Location * location = _editor->find_location_from_marker (*i, is_start);
3924 if (location->locked()) {
3928 if (location->is_mark()) {
3929 location->set_start (((*x).location)->start());
3931 location->set (((*x).location)->start(), ((*x).location)->end());
3936 XMLNode &after = _editor->session()->locations()->get_state();
3937 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3938 _editor->commit_reversible_command ();
3942 MarkerDrag::aborted (bool movement_occured)
3944 if (!movement_occured) {
3948 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3950 /* move all markers to their original location */
3953 for (vector<Marker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
3956 Location * location = _editor->find_location_from_marker (*m, is_start);
3959 (*m)->set_position (is_start ? location->start() : location->end());
3966 MarkerDrag::update_item (Location*)
3971 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
3973 _cumulative_x_drag (0),
3974 _cumulative_y_drag (0)
3976 if (_zero_gain_fraction < 0.0) {
3977 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
3980 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
3982 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
3988 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
3990 Drag::start_grab (event, _editor->cursors()->fader);
3992 // start the grab at the center of the control point so
3993 // the point doesn't 'jump' to the mouse after the first drag
3994 _fixed_grab_x = _point->get_x();
3995 _fixed_grab_y = _point->get_y();
3997 framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
3998 setup_snap_delta (pos);
4000 float const fraction = 1 - (_point->get_y() / _point->line().height());
4002 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
4004 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4006 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4008 if (!_point->can_slide ()) {
4009 _x_constrained = true;
4014 ControlPointDrag::motion (GdkEvent* event, bool)
4016 double dx = _drags->current_pointer_x() - last_pointer_x();
4017 double dy = current_pointer_y() - last_pointer_y();
4019 if (event->button.state & ArdourKeyboard::fine_adjust_modifier ()) {
4024 /* coordinate in pixels relative to the start of the region (for region-based automation)
4025 or track (for track-based automation) */
4026 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4027 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4029 // calculate zero crossing point. back off by .01 to stay on the
4030 // positive side of zero
4031 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4033 // make sure we hit zero when passing through
4034 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4038 if (_x_constrained) {
4041 if (_y_constrained) {
4045 _cumulative_x_drag = cx - _fixed_grab_x;
4046 _cumulative_y_drag = cy - _fixed_grab_y;
4050 cy = min ((double) _point->line().height(), cy);
4052 framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state);
4054 if (!_x_constrained) {
4055 _editor->snap_to_with_modifier (cx_frames, event);
4058 cx_frames -= snap_delta (event->button.state);
4059 cx_frames = min (cx_frames, _point->line().maximum_time());
4061 float const fraction = 1.0 - (cy / _point->line().height());
4063 _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4065 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4069 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4071 if (!movement_occurred) {
4074 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4075 _editor->reset_point_selection ();
4079 motion (event, false);
4082 _point->line().end_drag (_pushing, _final_index);
4083 _editor->commit_reversible_command ();
4087 ControlPointDrag::aborted (bool)
4089 _point->line().reset ();
4093 ControlPointDrag::active (Editing::MouseMode m)
4095 if (m == Editing::MouseDraw) {
4096 /* always active in mouse draw */
4100 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4101 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4104 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4107 , _cumulative_y_drag (0)
4109 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4113 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4115 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4118 _item = &_line->grab_item ();
4120 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4121 origin, and ditto for y.
4124 double cx = event->button.x;
4125 double cy = event->button.y;
4127 _line->parent_group().canvas_to_item (cx, cy);
4129 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->samples_per_pixel);
4134 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
4135 /* no adjacent points */
4139 Drag::start_grab (event, _editor->cursors()->fader);
4141 /* store grab start in parent frame */
4146 double fraction = 1.0 - (cy / _line->height());
4148 _line->start_drag_line (before, after, fraction);
4150 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4154 LineDrag::motion (GdkEvent* event, bool)
4156 double dy = current_pointer_y() - last_pointer_y();
4158 if (event->button.state & ArdourKeyboard::fine_adjust_modifier ()) {
4162 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4164 _cumulative_y_drag = cy - _fixed_grab_y;
4167 cy = min ((double) _line->height(), cy);
4169 double const fraction = 1.0 - (cy / _line->height());
4172 /* we are ignoring x position for this drag, so we can just pass in anything */
4173 _line->drag_motion (0, fraction, true, false, ignored);
4175 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4179 LineDrag::finished (GdkEvent* event, bool movement_occured)
4181 if (movement_occured) {
4182 motion (event, false);
4183 _line->end_drag (false, 0);
4185 /* add a new control point on the line */
4187 AutomationTimeAxisView* atv;
4189 _line->end_drag (false, 0);
4191 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4192 framepos_t where = _editor->window_event_sample (event, 0, 0);
4193 atv->add_automation_event (event, where, event->button.y, false);
4197 _editor->commit_reversible_command ();
4201 LineDrag::aborted (bool)
4206 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4209 _cumulative_x_drag (0)
4211 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4215 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4217 Drag::start_grab (event);
4219 _line = reinterpret_cast<Line*> (_item);
4222 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4224 double cx = event->button.x;
4225 double cy = event->button.y;
4227 _item->parent()->canvas_to_item (cx, cy);
4229 /* store grab start in parent frame */
4230 _region_view_grab_x = cx;
4232 _before = *(float*) _item->get_data ("position");
4234 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4236 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4240 FeatureLineDrag::motion (GdkEvent*, bool)
4242 double dx = _drags->current_pointer_x() - last_pointer_x();
4244 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4246 _cumulative_x_drag += dx;
4248 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4257 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4259 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4261 float *pos = new float;
4264 _line->set_data ("position", pos);
4270 FeatureLineDrag::finished (GdkEvent*, bool)
4272 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4273 _arv->update_transient(_before, _before);
4277 FeatureLineDrag::aborted (bool)
4282 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4284 , _vertical_only (false)
4286 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4290 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4292 Drag::start_grab (event);
4293 show_verbose_cursor_time (adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid()));
4297 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4304 framepos_t const pf = adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid());
4306 framepos_t grab = grab_frame ();
4307 if (ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) {
4308 _editor->snap_to_with_modifier (grab, event);
4310 grab = raw_grab_frame ();
4313 /* base start and end on initial click position */
4323 if (current_pointer_y() < grab_y()) {
4324 y1 = current_pointer_y();
4327 y2 = current_pointer_y();
4331 if (start != end || y1 != y2) {
4333 double x1 = _editor->sample_to_pixel (start);
4334 double x2 = _editor->sample_to_pixel (end);
4335 const double min_dimension = 2.0;
4337 if (_vertical_only) {
4338 /* fixed 10 pixel width */
4342 x2 = min (x1 - min_dimension, x2);
4344 x2 = max (x1 + min_dimension, x2);
4349 y2 = min (y1 - min_dimension, y2);
4351 y2 = max (y1 + min_dimension, y2);
4354 /* translate rect into item space and set */
4356 ArdourCanvas::Rect r (x1, y1, x2, y2);
4358 /* this drag is a _trackview_only == true drag, so the y1 and
4359 * y2 (computed using current_pointer_y() and grab_y()) will be
4360 * relative to the top of the trackview group). The
4361 * rubberband rect has the same parent/scroll offset as the
4362 * the trackview group, so we can use the "r" rect directly
4363 * to set the shape of the rubberband.
4366 _editor->rubberband_rect->set (r);
4367 _editor->rubberband_rect->show();
4368 _editor->rubberband_rect->raise_to_top();
4370 show_verbose_cursor_time (pf);
4372 do_select_things (event, true);
4377 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4381 framepos_t grab = grab_frame ();
4382 framepos_t lpf = last_pointer_frame ();
4384 if (!ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) {
4385 grab = raw_grab_frame ();
4386 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4400 if (current_pointer_y() < grab_y()) {
4401 y1 = current_pointer_y();
4404 y2 = current_pointer_y();
4408 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4412 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4414 if (movement_occurred) {
4416 motion (event, false);
4417 do_select_things (event, false);
4423 bool do_deselect = true;
4424 MidiTimeAxisView* mtv;
4426 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4428 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4429 /* nothing selected */
4430 add_midi_region (mtv);
4431 do_deselect = false;
4435 /* do not deselect if Primary or Tertiary (toggle-select or
4436 * extend-select are pressed.
4439 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4440 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4447 _editor->rubberband_rect->hide();
4451 RubberbandSelectDrag::aborted (bool)
4453 _editor->rubberband_rect->hide ();
4456 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4457 : RegionDrag (e, i, p, v)
4459 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4463 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4465 Drag::start_grab (event, cursor);
4467 _editor->get_selection().add (_primary);
4469 framepos_t where = _primary->region()->position();
4470 setup_snap_delta (where);
4472 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4476 TimeFXDrag::motion (GdkEvent* event, bool)
4478 RegionView* rv = _primary;
4479 StreamView* cv = rv->get_time_axis_view().view ();
4481 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4482 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4483 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4484 framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
4485 _editor->snap_to_with_modifier (pf, event);
4486 pf -= snap_delta (event->button.state);
4488 if (pf > rv->region()->position()) {
4489 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4492 show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4496 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4498 _primary->get_time_axis_view().hide_timestretch ();
4500 if (!movement_occurred) {
4504 if (last_pointer_frame() < _primary->region()->position()) {
4505 /* backwards drag of the left edge - not usable */
4509 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
4511 float percentage = (double) newlen / (double) _primary->region()->length();
4513 #ifndef USE_RUBBERBAND
4514 // Soundtouch uses percentage / 100 instead of normal (/ 1)
4515 if (_primary->region()->data_type() == DataType::AUDIO) {
4516 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4520 if (!_editor->get_selection().regions.empty()) {
4521 /* primary will already be included in the selection, and edit
4522 group shared editing will propagate selection across
4523 equivalent regions, so just use the current region
4527 if (_editor->time_stretch (_editor->get_selection().regions, percentage) == -1) {
4528 error << _("An error occurred while executing time stretch operation") << endmsg;
4534 TimeFXDrag::aborted (bool)
4536 _primary->get_time_axis_view().hide_timestretch ();
4539 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
4542 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
4546 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4548 Drag::start_grab (event);
4552 ScrubDrag::motion (GdkEvent* /*event*/, bool)
4554 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
4558 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4560 if (movement_occurred && _editor->session()) {
4561 /* make sure we stop */
4562 _editor->session()->request_transport_speed (0.0);
4567 ScrubDrag::aborted (bool)
4572 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4576 , _time_selection_at_start (!_editor->get_selection().time.empty())
4578 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
4580 if (_time_selection_at_start) {
4581 start_at_start = _editor->get_selection().time.start();
4582 end_at_start = _editor->get_selection().time.end_frame();
4587 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
4589 if (_editor->session() == 0) {
4593 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4595 switch (_operation) {
4596 case CreateSelection:
4597 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4602 cursor = _editor->cursors()->selector;
4603 Drag::start_grab (event, cursor);
4606 case SelectionStartTrim:
4607 if (_editor->clicked_axisview) {
4608 _editor->clicked_axisview->order_selection_trims (_item, true);
4610 Drag::start_grab (event, _editor->cursors()->left_side_trim);
4613 case SelectionEndTrim:
4614 if (_editor->clicked_axisview) {
4615 _editor->clicked_axisview->order_selection_trims (_item, false);
4617 Drag::start_grab (event, _editor->cursors()->right_side_trim);
4621 Drag::start_grab (event, cursor);
4624 case SelectionExtend:
4625 Drag::start_grab (event, cursor);
4629 if (_operation == SelectionMove) {
4630 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
4632 show_verbose_cursor_time (adjusted_current_frame (event));
4637 SelectionDrag::setup_pointer_frame_offset ()
4639 switch (_operation) {
4640 case CreateSelection:
4641 _pointer_frame_offset = 0;
4644 case SelectionStartTrim:
4646 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
4649 case SelectionEndTrim:
4650 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
4653 case SelectionExtend:
4659 SelectionDrag::motion (GdkEvent* event, bool first_move)
4661 framepos_t start = 0;
4663 framecnt_t length = 0;
4664 framecnt_t distance = 0;
4666 framepos_t const pending_position = adjusted_current_frame (event);
4668 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
4672 switch (_operation) {
4673 case CreateSelection:
4675 framepos_t grab = grab_frame ();
4678 grab = adjusted_current_frame (event, false);
4679 if (grab < pending_position) {
4680 _editor->snap_to (grab, RoundDownMaybe);
4682 _editor->snap_to (grab, RoundUpMaybe);
4686 if (pending_position < grab) {
4687 start = pending_position;
4690 end = pending_position;
4694 /* first drag: Either add to the selection
4695 or create a new selection
4702 /* adding to the selection */
4703 _editor->set_selected_track_as_side_effect (Selection::Add);
4704 _editor->clicked_selection = _editor->selection->add (start, end);
4711 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
4712 _editor->set_selected_track_as_side_effect (Selection::Set);
4715 _editor->clicked_selection = _editor->selection->set (start, end);
4719 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
4720 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
4721 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
4723 _editor->selection->add (atest);
4727 /* select all tracks within the rectangle that we've marked out so far */
4728 TrackViewList new_selection;
4729 TrackViewList& all_tracks (_editor->track_views);
4731 ArdourCanvas::Coord const top = grab_y();
4732 ArdourCanvas::Coord const bottom = current_pointer_y();
4734 if (top >= 0 && bottom >= 0) {
4736 //first, find the tracks that are covered in the y range selection
4737 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
4738 if ((*i)->covered_by_y_range (top, bottom)) {
4739 new_selection.push_back (*i);
4743 //now find any tracks that are GROUPED with the tracks we selected
4744 TrackViewList grouped_add = new_selection;
4745 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
4746 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
4747 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::select.property_id) ) {
4748 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
4749 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
4750 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
4751 grouped_add.push_back (*j);
4756 //now compare our list with the current selection, and add or remove as necessary
4757 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
4758 TrackViewList tracks_to_add;
4759 TrackViewList tracks_to_remove;
4760 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
4761 if ( !_editor->selection->tracks.contains ( *i ) )
4762 tracks_to_add.push_back ( *i );
4763 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
4764 if ( !grouped_add.contains ( *i ) )
4765 tracks_to_remove.push_back ( *i );
4766 _editor->selection->add(tracks_to_add);
4767 _editor->selection->remove(tracks_to_remove);
4773 case SelectionStartTrim:
4775 start = _editor->selection->time[_editor->clicked_selection].start;
4776 end = _editor->selection->time[_editor->clicked_selection].end;
4778 if (pending_position > end) {
4781 start = pending_position;
4785 case SelectionEndTrim:
4787 start = _editor->selection->time[_editor->clicked_selection].start;
4788 end = _editor->selection->time[_editor->clicked_selection].end;
4790 if (pending_position < start) {
4793 end = pending_position;
4800 start = _editor->selection->time[_editor->clicked_selection].start;
4801 end = _editor->selection->time[_editor->clicked_selection].end;
4803 length = end - start;
4804 distance = pending_position - start;
4805 start = pending_position;
4806 _editor->snap_to (start);
4808 end = start + length;
4812 case SelectionExtend:
4817 switch (_operation) {
4819 if (_time_selection_at_start) {
4820 _editor->selection->move_time (distance);
4824 _editor->selection->replace (_editor->clicked_selection, start, end);
4828 if (_operation == SelectionMove) {
4829 show_verbose_cursor_time(start);
4831 show_verbose_cursor_time(pending_position);
4836 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
4838 Session* s = _editor->session();
4840 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
4841 if (movement_occurred) {
4842 motion (event, false);
4843 /* XXX this is not object-oriented programming at all. ick */
4844 if (_editor->selection->time.consolidate()) {
4845 _editor->selection->TimeChanged ();
4848 /* XXX what if its a music time selection? */
4850 if ( s->get_play_range() && s->transport_rolling() ) {
4851 s->request_play_range (&_editor->selection->time, true);
4853 if (ARDOUR_UI::config()->get_follow_edits() && !s->transport_rolling()) {
4854 if (_operation == SelectionEndTrim)
4855 _editor->maybe_locate_with_edit_preroll( _editor->get_selection().time.end_frame());
4857 s->request_locate (_editor->get_selection().time.start());
4863 /* just a click, no pointer movement.
4866 if (_operation == SelectionExtend) {
4867 if (_time_selection_at_start) {
4868 framepos_t pos = adjusted_current_frame (event, false);
4869 framepos_t start = min (pos, start_at_start);
4870 framepos_t end = max (pos, end_at_start);
4871 _editor->selection->set (start, end);
4874 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4875 if (_editor->clicked_selection) {
4876 _editor->selection->remove (_editor->clicked_selection);
4879 if (!_editor->clicked_selection) {
4880 _editor->selection->clear_time();
4885 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
4886 _editor->selection->set (_editor->clicked_axisview);
4889 if (s && s->get_play_range () && s->transport_rolling()) {
4890 s->request_stop (false, false);
4895 _editor->stop_canvas_autoscroll ();
4896 _editor->clicked_selection = 0;
4897 _editor->commit_reversible_selection_op ();
4901 SelectionDrag::aborted (bool)
4906 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4907 : Drag (e, i, false),
4911 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
4913 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
4914 ArdourCanvas::Rect (0.0, 0.0, 0.0,
4915 physical_screen_height (_editor->get_window())));
4916 _drag_rect->hide ();
4918 _drag_rect->set_fill_color (ARDOUR_UI::config()->color ("range drag rect"));
4919 _drag_rect->set_outline_color (ARDOUR_UI::config()->color ("range drag rect"));
4922 RangeMarkerBarDrag::~RangeMarkerBarDrag()
4924 /* normal canvas items will be cleaned up when their parent group is deleted. But
4925 this item is created as the child of a long-lived parent group, and so we
4926 need to explicitly delete it.
4932 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4934 if (_editor->session() == 0) {
4938 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4940 if (!_editor->temp_location) {
4941 _editor->temp_location = new Location (*_editor->session());
4944 switch (_operation) {
4945 case CreateSkipMarker:
4946 case CreateRangeMarker:
4947 case CreateTransportMarker:
4948 case CreateCDMarker:
4950 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4955 cursor = _editor->cursors()->selector;
4959 Drag::start_grab (event, cursor);
4961 show_verbose_cursor_time (adjusted_current_frame (event));
4965 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
4967 framepos_t start = 0;
4969 ArdourCanvas::Rectangle *crect;
4971 switch (_operation) {
4972 case CreateSkipMarker:
4973 crect = _editor->range_bar_drag_rect;
4975 case CreateRangeMarker:
4976 crect = _editor->range_bar_drag_rect;
4978 case CreateTransportMarker:
4979 crect = _editor->transport_bar_drag_rect;
4981 case CreateCDMarker:
4982 crect = _editor->cd_marker_bar_drag_rect;
4985 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
4990 framepos_t const pf = adjusted_current_frame (event);
4992 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
4993 framepos_t grab = grab_frame ();
4994 _editor->snap_to (grab);
4996 if (pf < grab_frame()) {
5004 /* first drag: Either add to the selection
5005 or create a new selection.
5010 _editor->temp_location->set (start, end);
5014 update_item (_editor->temp_location);
5016 //_drag_rect->raise_to_top();
5022 _editor->temp_location->set (start, end);
5024 double x1 = _editor->sample_to_pixel (start);
5025 double x2 = _editor->sample_to_pixel (end);
5029 update_item (_editor->temp_location);
5032 show_verbose_cursor_time (pf);
5037 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5039 Location * newloc = 0;
5043 if (movement_occurred) {
5044 motion (event, false);
5047 switch (_operation) {
5048 case CreateSkipMarker:
5049 case CreateRangeMarker:
5050 case CreateCDMarker:
5052 XMLNode &before = _editor->session()->locations()->get_state();
5053 if (_operation == CreateSkipMarker) {
5054 _editor->begin_reversible_command (_("new skip marker"));
5055 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5056 flags = Location::IsRangeMarker | Location::IsSkip;
5057 _editor->range_bar_drag_rect->hide();
5058 } else if (_operation == CreateCDMarker) {
5059 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5060 _editor->begin_reversible_command (_("new CD marker"));
5061 flags = Location::IsRangeMarker | Location::IsCDMarker;
5062 _editor->cd_marker_bar_drag_rect->hide();
5064 _editor->begin_reversible_command (_("new skip marker"));
5065 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5066 flags = Location::IsRangeMarker;
5067 _editor->range_bar_drag_rect->hide();
5069 newloc = new Location (
5070 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5073 _editor->session()->locations()->add (newloc, true);
5074 XMLNode &after = _editor->session()->locations()->get_state();
5075 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5076 _editor->commit_reversible_command ();
5080 case CreateTransportMarker:
5081 // popup menu to pick loop or punch
5082 _editor->new_transport_marker_context_menu (&event->button, _item);
5088 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5090 if (_operation == CreateTransportMarker) {
5092 /* didn't drag, so just locate */
5094 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5096 } else if (_operation == CreateCDMarker) {
5098 /* didn't drag, but mark is already created so do
5101 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5106 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5108 if (end == max_framepos) {
5109 end = _editor->session()->current_end_frame ();
5112 if (start == max_framepos) {
5113 start = _editor->session()->current_start_frame ();
5116 switch (_editor->mouse_mode) {
5118 /* find the two markers on either side and then make the selection from it */
5119 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5123 /* find the two markers on either side of the click and make the range out of it */
5124 _editor->selection->set (start, end);
5133 _editor->stop_canvas_autoscroll ();
5137 RangeMarkerBarDrag::aborted (bool movement_occured)
5139 if (movement_occured) {
5140 _drag_rect->hide ();
5145 RangeMarkerBarDrag::update_item (Location* location)
5147 double const x1 = _editor->sample_to_pixel (location->start());
5148 double const x2 = _editor->sample_to_pixel (location->end());
5150 _drag_rect->set_x0 (x1);
5151 _drag_rect->set_x1 (x2);
5154 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5156 , _cumulative_dx (0)
5157 , _cumulative_dy (0)
5159 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5161 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5163 _region = &_primary->region_view ();
5164 _note_height = _region->midi_stream_view()->note_height ();
5168 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5170 Drag::start_grab (event);
5171 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5173 if (!(_was_selected = _primary->selected())) {
5175 /* tertiary-click means extend selection - we'll do that on button release,
5176 so don't add it here, because otherwise we make it hard to figure
5177 out the "extend-to" range.
5180 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5183 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5186 _region->note_selected (_primary, true);
5188 _region->unique_select (_primary);
5191 _editor->begin_reversible_selection_op(X_("Select Note Press"));
5192 _editor->commit_reversible_selection_op();
5197 /** @return Current total drag x change in frames */
5199 NoteDrag::total_dx (GdkEvent const * event) const
5202 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5204 /* primary note time */
5205 frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
5207 /* new time of the primary note in session frames */
5208 frameoffset_t st = n + dx + snap_delta (event->button.state);
5210 framepos_t const rp = _region->region()->position ();
5212 /* prevent the note being dragged earlier than the region's position */
5215 /* snap and return corresponding delta */
5216 return _region->snap_frame_to_frame (st - rp) + rp - n - snap_delta (event->button.state);
5219 /** @return Current total drag y change in note number */
5221 NoteDrag::total_dy () const
5223 MidiStreamView* msv = _region->midi_stream_view ();
5224 double const y = _region->midi_view()->y_position ();
5225 /* new current note */
5226 uint8_t n = msv->y_to_note (current_pointer_y () - y);
5228 n = max (msv->lowest_note(), n);
5229 n = min (msv->highest_note(), n);
5230 /* and work out delta */
5231 return n - msv->y_to_note (grab_y() - y);
5235 NoteDrag::motion (GdkEvent * event, bool)
5237 /* Total change in x and y since the start of the drag */
5238 frameoffset_t const dx = total_dx (event);
5239 int8_t const dy = total_dy ();
5241 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5242 double const tdx = _editor->sample_to_pixel (dx) - _cumulative_dx;
5243 double const tdy = -dy * _note_height - _cumulative_dy;
5246 _cumulative_dx += tdx;
5247 _cumulative_dy += tdy;
5249 int8_t note_delta = total_dy();
5251 _region->move_selection (tdx, tdy, note_delta);
5253 /* the new note value may be the same as the old one, but we
5254 * don't know what that means because the selection may have
5255 * involved more than one note and we might be doing something
5256 * odd with them. so show the note value anyway, always.
5260 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5262 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
5263 (int) floor ((double)new_note));
5265 show_verbose_cursor_text (buf);
5270 NoteDrag::finished (GdkEvent* ev, bool moved)
5273 /* no motion - select note */
5275 if (_editor->current_mouse_mode() == Editing::MouseObject ||
5276 _editor->current_mouse_mode() == Editing::MouseDraw) {
5278 bool changed = false;
5280 if (_was_selected) {
5281 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5283 _region->note_deselected (_primary);
5287 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5288 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5290 if (!extend && !add && _region->selection_size() > 1) {
5291 _region->unique_select (_primary);
5293 } else if (extend) {
5294 _region->note_selected (_primary, true, true);
5297 /* it was added during button press */
5302 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5303 _editor->commit_reversible_selection_op();
5307 _region->note_dropped (_primary, total_dx (ev), total_dy());
5312 NoteDrag::aborted (bool)
5317 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5318 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5319 : Drag (editor, atv->base_item ())
5321 , _y_origin (atv->y_position())
5322 , _nothing_to_drag (false)
5324 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5325 setup (atv->lines ());
5328 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5329 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5330 : Drag (editor, rv->get_canvas_group ())
5332 , _y_origin (rv->get_time_axis_view().y_position())
5333 , _nothing_to_drag (false)
5336 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5338 list<boost::shared_ptr<AutomationLine> > lines;
5340 AudioRegionView* audio_view;
5341 AutomationRegionView* automation_view;
5342 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5343 lines.push_back (audio_view->get_gain_line ());
5344 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5345 lines.push_back (automation_view->line ());
5348 error << _("Automation range drag created for invalid region type") << endmsg;
5354 /** @param lines AutomationLines to drag.
5355 * @param offset Offset from the session start to the points in the AutomationLines.
5358 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5360 /* find the lines that overlap the ranges being dragged */
5361 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5362 while (i != lines.end ()) {
5363 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5366 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5368 /* check this range against all the AudioRanges that we are using */
5369 list<AudioRange>::const_iterator k = _ranges.begin ();
5370 while (k != _ranges.end()) {
5371 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5377 /* add it to our list if it overlaps at all */
5378 if (k != _ranges.end()) {
5383 _lines.push_back (n);
5389 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5393 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5395 return 1.0 - ((global_y - _y_origin) / line->height());
5399 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5401 const double v = list->eval(x);
5402 return _integral ? rint(v) : v;
5406 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5408 Drag::start_grab (event, cursor);
5410 /* Get line states before we start changing things */
5411 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5412 i->state = &i->line->get_state ();
5413 i->original_fraction = y_fraction (i->line, current_pointer_y());
5416 if (_ranges.empty()) {
5418 /* No selected time ranges: drag all points */
5419 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5420 uint32_t const N = i->line->npoints ();
5421 for (uint32_t j = 0; j < N; ++j) {
5422 i->points.push_back (i->line->nth (j));
5428 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5430 framecnt_t const half = (i->start + i->end) / 2;
5432 /* find the line that this audio range starts in */
5433 list<Line>::iterator j = _lines.begin();
5434 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5438 if (j != _lines.end()) {
5439 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5441 /* j is the line that this audio range starts in; fade into it;
5442 64 samples length plucked out of thin air.
5445 framepos_t a = i->start + 64;
5450 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5451 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5453 the_list->editor_add (p, value (the_list, p));
5454 the_list->editor_add (q, value (the_list, q));
5457 /* same thing for the end */
5460 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5464 if (j != _lines.end()) {
5465 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5467 /* j is the line that this audio range starts in; fade out of it;
5468 64 samples length plucked out of thin air.
5471 framepos_t b = i->end - 64;
5476 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
5477 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
5479 the_list->editor_add (p, value (the_list, p));
5480 the_list->editor_add (q, value (the_list, q));
5484 _nothing_to_drag = true;
5486 /* Find all the points that should be dragged and put them in the relevant
5487 points lists in the Line structs.
5490 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5492 uint32_t const N = i->line->npoints ();
5493 for (uint32_t j = 0; j < N; ++j) {
5495 /* here's a control point on this line */
5496 ControlPoint* p = i->line->nth (j);
5497 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
5499 /* see if it's inside a range */
5500 list<AudioRange>::const_iterator k = _ranges.begin ();
5501 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
5505 if (k != _ranges.end()) {
5506 /* dragging this point */
5507 _nothing_to_drag = false;
5508 i->points.push_back (p);
5514 if (_nothing_to_drag) {
5518 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5519 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
5524 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
5526 if (_nothing_to_drag) {
5530 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
5531 float const f = y_fraction (l->line, current_pointer_y());
5532 /* we are ignoring x position for this drag, so we can just pass in anything */
5534 l->line->drag_motion (0, f, true, false, ignored);
5535 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, f));
5540 AutomationRangeDrag::finished (GdkEvent* event, bool)
5542 if (_nothing_to_drag) {
5546 motion (event, false);
5547 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5548 i->line->end_drag (false, 0);
5551 _editor->commit_reversible_command ();
5555 AutomationRangeDrag::aborted (bool)
5557 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5562 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
5564 , initial_time_axis_view (itav)
5566 /* note that time_axis_view may be null if the regionview was created
5567 * as part of a copy operation.
5569 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
5570 layer = v->region()->layer ();
5571 initial_y = v->get_canvas_group()->position().y;
5572 initial_playlist = v->region()->playlist ();
5573 initial_position = v->region()->position ();
5574 initial_end = v->region()->position () + v->region()->length ();
5577 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
5578 : Drag (e, i->canvas_item ())
5581 , _cumulative_dx (0)
5583 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
5584 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
5589 PatchChangeDrag::motion (GdkEvent* ev, bool)
5591 framepos_t f = adjusted_current_frame (ev);
5592 boost::shared_ptr<Region> r = _region_view->region ();
5593 f = max (f, r->position ());
5594 f = min (f, r->last_frame ());
5596 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
5597 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
5598 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
5599 _cumulative_dx = dxu;
5603 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
5605 if (!movement_occurred) {
5609 boost::shared_ptr<Region> r (_region_view->region ());
5610 framepos_t f = adjusted_current_frame (ev);
5611 f = max (f, r->position ());
5612 f = min (f, r->last_frame ());
5614 _region_view->move_patch_change (
5616 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
5621 PatchChangeDrag::aborted (bool)
5623 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
5627 PatchChangeDrag::setup_pointer_frame_offset ()
5629 boost::shared_ptr<Region> region = _region_view->region ();
5630 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
5633 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
5634 : RubberbandSelectDrag (e, rv->get_canvas_group ())
5641 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
5643 _region_view->update_drag_selection (
5645 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
5649 MidiRubberbandSelectDrag::deselect_things ()
5654 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
5655 : RubberbandSelectDrag (e, rv->get_canvas_group ())
5658 _vertical_only = true;
5662 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
5664 double const y = _region_view->midi_view()->y_position ();
5666 y1 = max (0.0, y1 - y);
5667 y2 = max (0.0, y2 - y);
5669 _region_view->update_vertical_drag_selection (
5672 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
5677 MidiVerticalSelectDrag::deselect_things ()
5682 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5683 : RubberbandSelectDrag (e, i)
5689 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
5691 if (drag_in_progress) {
5692 /* We just want to select things at the end of the drag, not during it */
5696 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
5698 _editor->begin_reversible_selection_op (X_("rubberband selection"));
5700 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
5702 _editor->commit_reversible_selection_op ();
5706 EditorRubberbandSelectDrag::deselect_things ()
5708 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
5710 _editor->selection->clear_tracks();
5711 _editor->selection->clear_regions();
5712 _editor->selection->clear_points ();
5713 _editor->selection->clear_lines ();
5714 _editor->selection->clear_midi_notes ();
5716 _editor->commit_reversible_selection_op();
5719 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
5724 _note[0] = _note[1] = 0;
5727 NoteCreateDrag::~NoteCreateDrag ()
5733 NoteCreateDrag::grid_frames (framepos_t t) const
5736 Evoral::Beats grid_beats = _editor->get_grid_type_as_beats (success, t);
5738 grid_beats = Evoral::Beats(1);
5741 return _region_view->region_beats_to_region_frames (grid_beats);
5745 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5747 Drag::start_grab (event, cursor);
5749 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
5751 framepos_t pf = _drags->current_pointer_frame ();
5752 framecnt_t const g = grid_frames (pf);
5754 /* Hack so that we always snap to the note that we are over, instead of snapping
5755 to the next one if we're more than halfway through the one we're over.
5757 if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
5761 _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
5762 _note[1] = _note[0];
5764 MidiStreamView* sv = _region_view->midi_stream_view ();
5765 double const x = _editor->sample_to_pixel (_note[0]);
5766 double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
5768 _drag_rect->set (ArdourCanvas::Rect (x, y, x, y + floor (_region_view->midi_stream_view()->note_height ())));
5769 _drag_rect->set_outline_all ();
5770 _drag_rect->set_outline_color (0xffffff99);
5771 _drag_rect->set_fill_color (0xffffff66);
5775 NoteCreateDrag::motion (GdkEvent* event, bool)
5777 _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
5778 double const x0 = _editor->sample_to_pixel (_note[0]);
5779 double const x1 = _editor->sample_to_pixel (_note[1]);
5780 _drag_rect->set_x0 (std::min(x0, x1));
5781 _drag_rect->set_x1 (std::max(x0, x1));
5785 NoteCreateDrag::finished (GdkEvent*, bool had_movement)
5787 if (!had_movement) {
5791 framepos_t const start = min (_note[0], _note[1]);
5792 framecnt_t length = (framecnt_t) fabs ((double)(_note[0] - _note[1]));
5794 framecnt_t const g = grid_frames (start);
5795 Evoral::Beats const one_tick = Evoral::Beats::ticks(1);
5797 if (_editor->snap_mode() == SnapNormal && length < g) {
5801 Evoral::Beats length_beats = max (
5802 one_tick, _region_view->region_frames_to_region_beats (length) - one_tick);
5804 _region_view->create_note_at (start, _drag_rect->y0(), length_beats, false);
5808 NoteCreateDrag::y_to_region (double y) const
5811 _region_view->get_canvas_group()->canvas_to_item (x, y);
5816 NoteCreateDrag::aborted (bool)
5821 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
5826 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
5830 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
5832 Drag::start_grab (event, cursor);
5836 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
5842 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
5845 distance = _drags->current_pointer_x() - grab_x();
5846 len = ar->fade_in()->back()->when;
5848 distance = grab_x() - _drags->current_pointer_x();
5849 len = ar->fade_out()->back()->when;
5852 /* how long should it be ? */
5854 new_length = len + _editor->pixel_to_sample (distance);
5856 /* now check with the region that this is legal */
5858 new_length = ar->verify_xfade_bounds (new_length, start);
5861 arv->reset_fade_in_shape_width (ar, new_length);
5863 arv->reset_fade_out_shape_width (ar, new_length);
5868 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
5874 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
5877 distance = _drags->current_pointer_x() - grab_x();
5878 len = ar->fade_in()->back()->when;
5880 distance = grab_x() - _drags->current_pointer_x();
5881 len = ar->fade_out()->back()->when;
5884 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
5886 _editor->begin_reversible_command ("xfade trim");
5887 ar->playlist()->clear_owned_changes ();
5890 ar->set_fade_in_length (new_length);
5892 ar->set_fade_out_length (new_length);
5895 /* Adjusting the xfade may affect other regions in the playlist, so we need
5896 to get undo Commands from the whole playlist rather than just the
5900 vector<Command*> cmds;
5901 ar->playlist()->rdiff (cmds);
5902 _editor->session()->add_commands (cmds);
5903 _editor->commit_reversible_command ();
5908 CrossfadeEdgeDrag::aborted (bool)
5911 // arv->redraw_start_xfade ();
5913 // arv->redraw_end_xfade ();
5917 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
5918 : Drag (e, item, true)
5919 , line (new EditorCursor (*e))
5921 line->set_position (pos);
5925 RegionCutDrag::~RegionCutDrag ()
5931 RegionCutDrag::motion (GdkEvent*, bool)
5933 framepos_t where = _drags->current_pointer_frame();
5934 _editor->snap_to (where);
5936 line->set_position (where);
5940 RegionCutDrag::finished (GdkEvent*, bool)
5942 _editor->get_track_canvas()->canvas()->re_enter();
5944 framepos_t pos = _drags->current_pointer_frame();
5948 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
5954 _editor->split_regions_at (pos, rs);
5958 RegionCutDrag::aborted (bool)