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)
234 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
240 _cursor_ctx = CursorContext::create (*_editor, cursor);
242 _cursor_ctx->change (cursor);
249 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
251 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
253 if (Keyboard::is_button2_event (&event->button)) {
254 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
255 _y_constrained = true;
256 _x_constrained = false;
258 _y_constrained = false;
259 _x_constrained = true;
262 _x_constrained = false;
263 _y_constrained = false;
266 _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
267 setup_pointer_frame_offset ();
268 _grab_frame = adjusted_frame (_raw_grab_frame, event);
269 _last_pointer_frame = _grab_frame;
270 _last_pointer_x = _grab_x;
272 if (_trackview_only) {
273 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
276 _last_pointer_y = _grab_y;
280 if (!_editor->cursors()->is_invalid (cursor)) {
281 /* CAIROCANVAS need a variant here that passes *cursor */
282 _cursor_ctx = CursorContext::create (*_editor, cursor);
285 if (_editor->session() && _editor->session()->transport_rolling()) {
288 _was_rolling = false;
291 switch (_editor->snap_type()) {
292 case SnapToRegionStart:
293 case SnapToRegionEnd:
294 case SnapToRegionSync:
295 case SnapToRegionBoundary:
296 _editor->build_region_boundary_cache ();
303 /** Call to end a drag `successfully'. Ungrabs item and calls
304 * subclass' finished() method.
306 * @param event GDK event, or 0.
307 * @return true if some movement occurred, otherwise false.
310 Drag::end_grab (GdkEvent* event)
312 _editor->stop_canvas_autoscroll ();
316 finished (event, _move_threshold_passed);
318 _editor->verbose_cursor()->hide ();
321 return _move_threshold_passed;
325 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
329 if (f > _pointer_frame_offset) {
330 pos = f - _pointer_frame_offset;
334 _editor->snap_to_with_modifier (pos, event);
341 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
343 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
347 Drag::current_pointer_x() const
349 return _drags->current_pointer_x ();
353 Drag::current_pointer_y () const
355 if (!_trackview_only) {
356 return _drags->current_pointer_y ();
359 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
363 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
365 /* check to see if we have moved in any way that matters since the last motion event */
366 if (_move_threshold_passed &&
367 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
368 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
372 pair<framecnt_t, int> const threshold = move_threshold ();
374 bool const old_move_threshold_passed = _move_threshold_passed;
376 if (!_move_threshold_passed) {
378 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
379 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
381 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
384 if (active (_editor->mouse_mode) && _move_threshold_passed) {
386 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
388 if (old_move_threshold_passed != _move_threshold_passed) {
392 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
393 _initially_vertical = true;
395 _initially_vertical = false;
399 if (!from_autoscroll) {
400 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
403 if (!_editor->autoscroll_active() || from_autoscroll) {
406 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
408 motion (event, first_move && !_starting_point_passed);
410 if (first_move && !_starting_point_passed) {
411 _starting_point_passed = true;
414 _last_pointer_x = _drags->current_pointer_x ();
415 _last_pointer_y = current_pointer_y ();
416 _last_pointer_frame = adjusted_current_frame (event);
426 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
434 aborted (_move_threshold_passed);
436 _editor->stop_canvas_autoscroll ();
437 _editor->verbose_cursor()->hide ();
441 Drag::show_verbose_cursor_time (framepos_t frame)
443 _editor->verbose_cursor()->set_time (frame);
444 _editor->verbose_cursor()->show ();
448 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
450 _editor->verbose_cursor()->set_duration (start, end);
451 _editor->verbose_cursor()->show ();
455 Drag::show_verbose_cursor_text (string const & text)
457 _editor->verbose_cursor()->set (text);
458 _editor->verbose_cursor()->show ();
461 boost::shared_ptr<Region>
462 Drag::add_midi_region (MidiTimeAxisView* view)
464 if (_editor->session()) {
465 const TempoMap& map (_editor->session()->tempo_map());
466 framecnt_t pos = grab_frame();
467 const Meter& m = map.meter_at (pos);
468 /* not that the frame rate used here can be affected by pull up/down which
471 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
472 return view->add_region (grab_frame(), len, true);
475 return boost::shared_ptr<Region>();
478 struct EditorOrderTimeAxisViewSorter {
479 bool operator() (TimeAxisView* a, TimeAxisView* b) {
480 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
481 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
483 return ra->route()->order_key () < rb->route()->order_key ();
487 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
492 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
494 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
495 as some of the regions we are dragging may be on such tracks.
498 TrackViewList track_views = _editor->track_views;
499 track_views.sort (EditorOrderTimeAxisViewSorter ());
501 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
502 _time_axis_views.push_back (*i);
504 TimeAxisView::Children children_list = (*i)->get_child_list ();
505 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
506 _time_axis_views.push_back (j->get());
510 /* the list of views can be empty at this point if this is a region list-insert drag
513 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
514 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
517 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
521 RegionDrag::region_going_away (RegionView* v)
523 list<DraggingView>::iterator i = _views.begin ();
524 while (i != _views.end() && i->view != v) {
528 if (i != _views.end()) {
533 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
534 * or -1 if it is not found.
537 RegionDrag::find_time_axis_view (TimeAxisView* t) const
540 int const N = _time_axis_views.size ();
541 while (i < N && _time_axis_views[i] != t) {
545 if (_time_axis_views[i] != t) {
552 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
553 : RegionDrag (e, i, p, v)
556 , _last_pointer_time_axis_view (0)
557 , _last_pointer_layer (0)
558 , _single_axis (false)
563 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
567 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
569 Drag::start_grab (event, cursor);
571 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier)) {
575 show_verbose_cursor_time (_last_frame_position);
577 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
579 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
580 assert(_last_pointer_time_axis_view >= 0);
581 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
586 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
588 /* compute the amount of pointer motion in frames, and where
589 the region would be if we moved it by that much.
591 *pending_region_position = adjusted_current_frame (event);
593 framepos_t sync_frame;
594 framecnt_t sync_offset;
597 sync_offset = _primary->region()->sync_offset (sync_dir);
599 /* we don't handle a sync point that lies before zero.
601 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
603 sync_frame = *pending_region_position + (sync_dir*sync_offset);
605 _editor->snap_to_with_modifier (sync_frame, event);
607 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
610 *pending_region_position = _last_frame_position;
613 if (*pending_region_position > max_framepos - _primary->region()->length()) {
614 *pending_region_position = _last_frame_position;
619 /* in locked edit mode, reverse the usual meaning of _x_constrained */
620 bool const x_move_allowed = Config->get_edit_mode() == Lock ? _x_constrained : !_x_constrained;
622 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
624 /* x movement since last time (in pixels) */
625 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
627 /* total x movement */
628 framecnt_t total_dx = *pending_region_position;
629 if (regions_came_from_canvas()) {
630 total_dx = total_dx - grab_frame ();
633 /* check that no regions have gone off the start of the session */
634 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
635 if ((i->view->region()->position() + total_dx) < 0) {
637 *pending_region_position = _last_frame_position;
648 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
654 const int dt = delta > 0 ? +1 : -1;
656 int target = start + delta - skip;
658 assert (current < 0 || current >= _time_axis_views.size() || !_time_axis_views[current]->hidden());
659 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
661 while (current >= 0 && current != target) {
663 if (current < 0 && dt < 0) {
666 if (current >= _time_axis_views.size() && dt > 0) {
669 if (current < 0 || current >= _time_axis_views.size()) {
673 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
674 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
678 if (distance_only && current == start + delta) {
686 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
688 if (_y_constrained) {
692 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
693 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
694 assert (n < 0 || n >= _time_axis_views.size() || !_time_axis_views[n]->hidden());
696 if (i->time_axis_view < 0 || i->time_axis_view >= _time_axis_views.size()) {
697 /* already in the drop zone */
698 if (delta_track >= 0) {
699 /* downward motion - OK if others are still not in the dropzone */
708 } else if (n >= int (_time_axis_views.size())) {
709 /* downward motion into drop zone. That's fine. */
713 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
714 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
715 /* not a track, or the wrong type */
719 double const l = i->layer + delta_layer;
721 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
722 mode to allow the user to place a region below another on layer 0.
724 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
725 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
726 If it has, the layers will be munged later anyway, so it's ok.
732 /* all regions being dragged are ok with this change */
736 struct DraggingViewSorter {
737 bool operator() (const DraggingView& a, const DraggingView& b) {
738 return a.time_axis_view < b.time_axis_view;
743 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
745 double delta_layer = 0;
746 int delta_time_axis_view = 0;
747 int current_pointer_time_axis_view = -1;
749 assert (!_views.empty ());
753 if (initially_vertical()) {
754 _y_constrained = false;
755 _x_constrained = true;
757 _y_constrained = true;
758 _x_constrained = false;
763 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
765 /* Find the TimeAxisView that the pointer is now over */
766 const double cur_y = current_pointer_y ();
767 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
768 TimeAxisView* tv = r.first;
770 if (!tv && cur_y < 0) {
771 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
775 /* find drop-zone y-position */
776 Coord last_track_bottom_edge;
777 last_track_bottom_edge = 0;
778 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
779 if (!(*t)->hidden()) {
780 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
785 if (tv && tv->view()) {
786 /* the mouse is over a track */
787 double layer = r.second;
789 if (first_move && tv->view()->layer_display() == Stacked) {
790 tv->view()->set_layer_display (Expanded);
793 /* Here's the current pointer position in terms of time axis view and layer */
794 current_pointer_time_axis_view = find_time_axis_view (tv);
795 assert(current_pointer_time_axis_view >= 0);
797 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
799 /* Work out the change in y */
801 if (_last_pointer_time_axis_view < 0) {
802 /* Was in the drop-zone, now over a track.
803 * Hence it must be an upward move (from the bottom)
805 * track_index is still -1, so delta must be set to
806 * move up the correct number of tracks from the bottom.
808 * This is necessary because steps may be skipped if
809 * the bottom-most track is not a valid target and/or
810 * if there are hidden tracks at the bottom.
811 * Hence the initial offset (_ddropzone) as well as the
812 * last valid pointer position (_pdropzone) need to be
813 * taken into account.
815 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
817 /* ignore busses early on. we can't move any regions on them */
818 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
819 if (rtv && rtv->is_track()) {
820 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
824 /* TODO needs adjustment per DraggingView,
826 * e.g. select one region on the top-layer of a track
827 * and one region which is at the bottom-layer of another track
830 * Indicated drop-zones and layering is wrong.
831 * and may infer additional layers on the target-track
832 * (depending how many layers the original track had).
834 * Or select two regions (different layers) on a same track,
835 * move across a non-layer track.. -> layering info is lost.
836 * on drop either of the regions may be on top.
838 * Proposed solution: screw it :) well,
839 * don't use delta_layer, use an absolute value
840 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
841 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
842 * 3) iterate over all DraggingView, find the one that is over the track with most layers
843 * 4) proportionally scale layer to layers available on target
845 delta_layer = current_pointer_layer - _last_pointer_layer;
848 /* for automation lanes, there is a TimeAxisView but no ->view()
849 * if (!tv) -> dropzone
851 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
852 /* Moving into the drop-zone.. */
853 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
854 /* delta_time_axis_view may not be sufficient to move into the DZ
855 * the mouse may enter it, but it may not be a valid move due to
858 * -> remember the delta needed to move into the dropzone
860 _ddropzone = delta_time_axis_view;
861 /* ..but subtract hidden tracks (or routes) at the bottom.
862 * we silently move mover them
864 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
865 - _time_axis_views.size();
867 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
868 /* move around inside the zone.
869 * This allows to move further down until all regions are in the zone.
871 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
872 assert(ptr_y >= last_track_bottom_edge);
873 assert(_ddropzone > 0);
875 /* calculate mouse position in 'tracks' below last track. */
876 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
877 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
879 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
881 delta_time_axis_view = dzpos - _pdropzone;
882 } else if (dzpos < _pdropzone && _ndropzone > 0) {
883 // move up inside the DZ
884 delta_time_axis_view = dzpos - _pdropzone;
888 /* Work out the change in x */
889 framepos_t pending_region_position;
890 double const x_delta = compute_x_delta (event, &pending_region_position);
891 _last_frame_position = pending_region_position;
893 /* calculate hidden tracks in current y-axis delta */
895 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
896 /* The mouse is more than one track below the dropzone.
897 * distance calculation is not needed (and would not work, either
898 * because the dropzone is "packed").
900 * Except when partially(!) moving regions out of dropzone in a large step.
901 * (the mouse may or may not remain in the DZ)
902 * Hidden tracks at the bottom of the TAV need to be skipped.
904 assert(_pdropzone >= _ddropzone);
905 if (delta_time_axis_view < 0 && -delta_time_axis_view >= _pdropzone - _ddropzone)
907 const int dt = delta_time_axis_view + _pdropzone - _ddropzone;
909 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
910 -_time_axis_views.size() - dt;
913 else if (_last_pointer_time_axis_view < 0) {
914 /* Moving out of the zone. Check for hidden tracks at the bottom. */
915 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
916 -_time_axis_views.size() - delta_time_axis_view;
918 /* calculate hidden tracks that are skipped by the pointer movement */
919 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
920 - _last_pointer_time_axis_view
921 - delta_time_axis_view;
924 /* Verify change in y */
925 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
926 /* this y movement is not allowed, so do no y movement this time */
927 delta_time_axis_view = 0;
932 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
933 /* haven't reached next snap point, and we're not switching
934 trackviews nor layers. nothing to do.
939 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
940 PlaylistDropzoneMap playlist_dropzone_map;
941 _ndropzone = 0; // number of elements currently in the dropzone
944 /* sort views by time_axis.
945 * This retains track order in the dropzone, regardless
946 * of actual selection order
948 _views.sort (DraggingViewSorter());
950 /* count number of distinct tracks of all regions
951 * being dragged, used for dropzone.
954 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
955 if (i->time_axis_view != prev_track) {
956 prev_track = i->time_axis_view;
962 _views.back().time_axis_view -
963 _views.front().time_axis_view;
965 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
966 - _views.back().time_axis_view;
968 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
972 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
974 RegionView* rv = i->view;
979 if (rv->region()->locked() || rv->region()->video_locked()) {
986 /* reparent the regionview into a group above all
990 ArdourCanvas::Item* rvg = rv->get_canvas_group();
991 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
992 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
993 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
994 /* move the item so that it continues to appear at the
995 same location now that its parent has changed.
997 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1000 /* If we have moved tracks, we'll fudge the layer delta so that the
1001 region gets moved back onto layer 0 on its new track; this avoids
1002 confusion when dragging regions from non-zero layers onto different
1005 double this_delta_layer = delta_layer;
1006 if (delta_time_axis_view != 0) {
1007 this_delta_layer = - i->layer;
1010 int this_delta_time_axis_view = delta_time_axis_view;
1011 this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1013 int track_index = i->time_axis_view + this_delta_time_axis_view;
1014 assert(track_index >= 0);
1016 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1017 /* Track is in the Dropzone */
1019 i->time_axis_view = track_index;
1020 assert(i->time_axis_view >= _time_axis_views.size());
1023 double yposition = 0;
1024 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1025 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1028 /* store index of each new playlist as a negative count, starting at -1 */
1030 if (pdz == playlist_dropzone_map.end()) {
1032 int n = playlist_dropzone_map.size() + 1;
1034 /* compute where this new track (which doesn't exist yet) will live
1038 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1040 /* How high is this region view ? */
1042 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1043 ArdourCanvas::Rect bbox;
1046 bbox = obbox.get ();
1049 last_track_bottom_edge += bbox.height();
1051 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1054 yposition = pdz->second;
1057 /* values are zero or negative, hence the use of min() */
1058 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1063 /* The TimeAxisView that this region is now over */
1064 TimeAxisView* current_tv = _time_axis_views[track_index];
1066 /* Ensure it is moved from stacked -> expanded if appropriate */
1067 if (current_tv->view()->layer_display() == Stacked) {
1068 current_tv->view()->set_layer_display (Expanded);
1071 /* We're only allowed to go -ve in layer on Expanded views */
1072 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1073 this_delta_layer = - i->layer;
1077 rv->set_height (current_tv->view()->child_height ());
1079 /* Update show/hidden status as the region view may have come from a hidden track,
1080 or have moved to one.
1082 if (current_tv->hidden ()) {
1083 rv->get_canvas_group()->hide ();
1085 rv->get_canvas_group()->show ();
1088 /* Update the DraggingView */
1089 i->time_axis_view = track_index;
1090 i->layer += this_delta_layer;
1093 _editor->mouse_brush_insert_region (rv, pending_region_position);
1097 /* Get the y coordinate of the top of the track that this region is now over */
1098 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1100 /* And adjust for the layer that it should be on */
1101 StreamView* cv = current_tv->view ();
1102 switch (cv->layer_display ()) {
1106 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1109 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1113 /* need to get the parent of the regionview
1114 * canvas group and get its position in
1115 * equivalent coordinate space as the trackview
1116 * we are now dragging over.
1119 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1124 /* Now move the region view */
1125 rv->move (x_delta, y_delta);
1127 } /* foreach region */
1129 _total_x_delta += x_delta;
1131 if (x_delta != 0 && !_brushing) {
1132 show_verbose_cursor_time (_last_frame_position);
1135 /* keep track of pointer movement */
1137 /* the pointer is currently over a time axis view */
1139 if (_last_pointer_time_axis_view < 0) {
1140 /* last motion event was not over a time axis view
1141 * or last y-movement out of the dropzone was not valid
1144 if (delta_time_axis_view < 0) {
1145 /* in the drop zone, moving up */
1147 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1148 * We do not use negative _last_pointer_time_axis_view because
1149 * the dropzone is "packed" (the actual track offset is ignored)
1151 * As opposed to the actual number
1152 * of elements in the dropzone (_ndropzone)
1153 * _pdropzone is not constrained. This is necessary
1154 * to allow moving multiple regions with y-distance
1157 * There can be 0 elements in the dropzone,
1158 * even though the drag-pointer is inside the DZ.
1161 * [ Audio-track, Midi-track, Audio-track, DZ ]
1162 * move regions from both audio tracks at the same time into the
1163 * DZ by grabbing the region in the bottom track.
1165 assert(current_pointer_time_axis_view >= 0);
1166 dtz = std::min((int)_pdropzone, -delta_time_axis_view);
1170 /* only move out of the zone if the movement is OK */
1171 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1172 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1175 /* last motion event was also over a time axis view */
1176 _last_pointer_time_axis_view += delta_time_axis_view;
1177 assert(_last_pointer_time_axis_view >= 0);
1182 /* the pointer is not over a time axis view */
1183 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1184 _pdropzone += delta_time_axis_view - delta_skip;
1185 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1188 _last_pointer_layer += delta_layer;
1192 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1194 if (_copy && first_move) {
1196 if (_x_constrained) {
1197 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1199 _editor->begin_reversible_command (Operations::region_copy);
1202 /* duplicate the regionview(s) and region(s) */
1204 list<DraggingView> new_regionviews;
1206 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1208 RegionView* rv = i->view;
1209 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1210 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1212 const boost::shared_ptr<const Region> original = rv->region();
1213 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
1214 region_copy->set_position (original->position());
1215 /* need to set this so that the drop zone code can work. This doesn't
1216 actually put the region into the playlist, but just sets a weak pointer
1219 region_copy->set_playlist (original->playlist());
1223 boost::shared_ptr<AudioRegion> audioregion_copy
1224 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1226 nrv = new AudioRegionView (*arv, audioregion_copy);
1228 boost::shared_ptr<MidiRegion> midiregion_copy
1229 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1230 nrv = new MidiRegionView (*mrv, midiregion_copy);
1235 nrv->get_canvas_group()->show ();
1236 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1238 /* swap _primary to the copy */
1240 if (rv == _primary) {
1244 /* ..and deselect the one we copied */
1246 rv->set_selected (false);
1249 if (!new_regionviews.empty()) {
1251 /* reflect the fact that we are dragging the copies */
1253 _views = new_regionviews;
1255 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1258 } else if (!_copy && first_move) {
1260 if (_x_constrained) {
1261 _editor->begin_reversible_command (_("fixed time region drag"));
1263 _editor->begin_reversible_command (Operations::region_drag);
1267 RegionMotionDrag::motion (event, first_move);
1271 RegionMotionDrag::finished (GdkEvent *, bool)
1273 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1274 if (!(*i)->view()) {
1278 if ((*i)->view()->layer_display() == Expanded) {
1279 (*i)->view()->set_layer_display (Stacked);
1285 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1287 RegionMotionDrag::finished (ev, movement_occurred);
1289 if (!movement_occurred) {
1293 if (was_double_click() && !_views.empty()) {
1294 DraggingView dv = _views.front();
1295 dv.view->show_region_editor ();
1302 /* reverse this here so that we have the correct logic to finalize
1306 if (Config->get_edit_mode() == Lock) {
1307 _x_constrained = !_x_constrained;
1310 assert (!_views.empty ());
1312 /* We might have hidden region views so that they weren't visible during the drag
1313 (when they have been reparented). Now everything can be shown again, as region
1314 views are back in their track parent groups.
1316 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1317 i->view->get_canvas_group()->show ();
1320 bool const changed_position = (_last_frame_position != _primary->region()->position());
1321 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1322 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1342 _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start());
1346 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1348 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1353 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1354 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1355 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), region->n_channels(), ARDOUR::Normal, 0, 1, region->name());
1356 RouteTimeAxisView* rtav = _editor->axis_view_from_route (audio_tracks.front());
1358 rtav->set_height (original->current_height());
1362 ChanCount one_midi_port (DataType::MIDI, 1);
1363 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1364 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port, boost::shared_ptr<ARDOUR::PluginInfo>(), ARDOUR::Normal, 0, 1, region->name());
1365 RouteTimeAxisView* rtav = _editor->axis_view_from_route (midi_tracks.front());
1367 rtav->set_height (original->current_height());
1372 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1378 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
1380 RegionSelection new_views;
1381 PlaylistSet modified_playlists;
1382 RouteTimeAxisView* new_time_axis_view = 0;
1385 /* all changes were made during motion event handlers */
1387 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1391 _editor->commit_reversible_command ();
1395 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1396 PlaylistMapping playlist_mapping;
1398 /* insert the regions into their new playlists */
1399 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1401 RouteTimeAxisView* dest_rtv = 0;
1403 if (i->view->region()->locked() || i->view->region()->video_locked()) {
1409 if (changed_position && !_x_constrained) {
1410 where = i->view->region()->position() - drag_delta;
1412 where = i->view->region()->position();
1415 if (i->time_axis_view < 0 || i->time_axis_view >= _time_axis_views.size()) {
1416 /* dragged to drop zone */
1418 PlaylistMapping::iterator pm;
1420 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1421 /* first region from this original playlist: create a new track */
1422 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1423 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1424 dest_rtv = new_time_axis_view;
1426 /* we already created a new track for regions from this playlist, use it */
1427 dest_rtv = pm->second;
1430 /* destination time axis view is the one we dragged to */
1431 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1434 if (dest_rtv != 0) {
1435 RegionView* new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, modified_playlists);
1436 if (new_view != 0) {
1437 new_views.push_back (new_view);
1441 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1442 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1445 list<DraggingView>::const_iterator next = i;
1451 /* If we've created new regions either by copying or moving
1452 to a new track, we want to replace the old selection with the new ones
1455 if (new_views.size() > 0) {
1456 _editor->selection->set (new_views);
1459 /* write commands for the accumulated diffs for all our modified playlists */
1460 add_stateful_diff_commands_for_playlists (modified_playlists);
1462 _editor->commit_reversible_command ();
1466 RegionMoveDrag::finished_no_copy (
1467 bool const changed_position,
1468 bool const changed_tracks,
1469 framecnt_t const drag_delta
1472 RegionSelection new_views;
1473 PlaylistSet modified_playlists;
1474 PlaylistSet frozen_playlists;
1475 set<RouteTimeAxisView*> views_to_update;
1476 RouteTimeAxisView* new_time_axis_view = 0;
1479 /* all changes were made during motion event handlers */
1480 _editor->commit_reversible_command ();
1484 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1485 PlaylistMapping playlist_mapping;
1487 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1489 RegionView* rv = i->view;
1490 RouteTimeAxisView* dest_rtv = 0;
1492 if (rv->region()->locked() || rv->region()->video_locked()) {
1497 if (i->time_axis_view < 0 || i->time_axis_view >= _time_axis_views.size()) {
1498 /* dragged to drop zone */
1500 PlaylistMapping::iterator pm;
1502 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1503 /* first region from this original playlist: create a new track */
1504 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1505 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1506 dest_rtv = new_time_axis_view;
1508 /* we already created a new track for regions from this playlist, use it */
1509 dest_rtv = pm->second;
1513 /* destination time axis view is the one we dragged to */
1514 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1519 double const dest_layer = i->layer;
1521 views_to_update.insert (dest_rtv);
1525 if (changed_position && !_x_constrained) {
1526 where = rv->region()->position() - drag_delta;
1528 where = rv->region()->position();
1531 if (changed_tracks) {
1533 /* insert into new playlist */
1535 RegionView* new_view = insert_region_into_playlist (
1536 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
1539 if (new_view == 0) {
1544 new_views.push_back (new_view);
1546 /* remove from old playlist */
1548 /* the region that used to be in the old playlist is not
1549 moved to the new one - we use a copy of it. as a result,
1550 any existing editor for the region should no longer be
1553 rv->hide_region_editor();
1556 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1560 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1562 /* this movement may result in a crossfade being modified, or a layering change,
1563 so we need to get undo data from the playlist as well as the region.
1566 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1568 playlist->clear_changes ();
1571 rv->region()->clear_changes ();
1574 motion on the same track. plonk the previously reparented region
1575 back to its original canvas group (its streamview).
1576 No need to do anything for copies as they are fake regions which will be deleted.
1579 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1580 rv->get_canvas_group()->set_y_position (i->initial_y);
1583 /* just change the model */
1584 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1585 playlist->set_layer (rv->region(), dest_layer);
1588 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1590 r = frozen_playlists.insert (playlist);
1593 playlist->freeze ();
1596 rv->region()->set_position (where);
1598 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1601 if (changed_tracks) {
1603 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1604 was selected in all of them, then removing it from a playlist will have removed all
1605 trace of it from _views (i.e. there were N regions selected, we removed 1,
1606 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1607 corresponding regionview, and _views is now empty).
1609 This could have invalidated any and all iterators into _views.
1611 The heuristic we use here is: if the region selection is empty, break out of the loop
1612 here. if the region selection is not empty, then restart the loop because we know that
1613 we must have removed at least the region(view) we've just been working on as well as any
1614 that we processed on previous iterations.
1616 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1617 we can just iterate.
1621 if (_views.empty()) {
1632 /* If we've created new regions either by copying or moving
1633 to a new track, we want to replace the old selection with the new ones
1636 if (new_views.size() > 0) {
1637 _editor->selection->set (new_views);
1640 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1644 /* write commands for the accumulated diffs for all our modified playlists */
1645 add_stateful_diff_commands_for_playlists (modified_playlists);
1647 _editor->commit_reversible_command ();
1649 /* We have futzed with the layering of canvas items on our streamviews.
1650 If any region changed layer, this will have resulted in the stream
1651 views being asked to set up their region views, and all will be well.
1652 If not, we might now have badly-ordered region views. Ask the StreamViews
1653 involved to sort themselves out, just in case.
1656 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1657 (*i)->view()->playlist_layered ((*i)->track ());
1661 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1662 * @param region Region to remove.
1663 * @param playlist playlist To remove from.
1664 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1665 * that clear_changes () is only called once per playlist.
1668 RegionMoveDrag::remove_region_from_playlist (
1669 boost::shared_ptr<Region> region,
1670 boost::shared_ptr<Playlist> playlist,
1671 PlaylistSet& modified_playlists
1674 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1677 playlist->clear_changes ();
1680 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1684 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1685 * clearing the playlist's diff history first if necessary.
1686 * @param region Region to insert.
1687 * @param dest_rtv Destination RouteTimeAxisView.
1688 * @param dest_layer Destination layer.
1689 * @param where Destination position.
1690 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1691 * that clear_changes () is only called once per playlist.
1692 * @return New RegionView, or 0 if no insert was performed.
1695 RegionMoveDrag::insert_region_into_playlist (
1696 boost::shared_ptr<Region> region,
1697 RouteTimeAxisView* dest_rtv,
1700 PlaylistSet& modified_playlists
1703 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1704 if (!dest_playlist) {
1708 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1709 _new_region_view = 0;
1710 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1712 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1713 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1715 dest_playlist->clear_changes ();
1718 dest_playlist->add_region (region, where);
1720 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1721 dest_playlist->set_layer (region, dest_layer);
1726 assert (_new_region_view);
1728 return _new_region_view;
1732 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1734 _new_region_view = rv;
1738 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1740 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1741 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1743 _editor->session()->add_command (c);
1752 RegionMoveDrag::aborted (bool movement_occurred)
1756 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1757 list<DraggingView>::const_iterator next = i;
1766 RegionMotionDrag::aborted (movement_occurred);
1771 RegionMotionDrag::aborted (bool)
1773 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1775 StreamView* sview = (*i)->view();
1778 if (sview->layer_display() == Expanded) {
1779 sview->set_layer_display (Stacked);
1784 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1785 RegionView* rv = i->view;
1786 TimeAxisView* tv = &(rv->get_time_axis_view ());
1787 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1789 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1790 rv->get_canvas_group()->set_y_position (0);
1792 rv->move (-_total_x_delta, 0);
1793 rv->set_height (rtv->view()->child_height ());
1797 /** @param b true to brush, otherwise false.
1798 * @param c true to make copies of the regions being moved, otherwise false.
1800 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1801 : RegionMotionDrag (e, i, p, v, b)
1804 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1807 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1808 if (rtv && rtv->is_track()) {
1809 speed = rtv->track()->speed ();
1812 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1816 RegionMoveDrag::setup_pointer_frame_offset ()
1818 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1821 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1822 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1824 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1826 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1827 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1829 _primary = v->view()->create_region_view (r, false, false);
1831 _primary->get_canvas_group()->show ();
1832 _primary->set_position (pos, 0);
1833 _views.push_back (DraggingView (_primary, this, v));
1835 _last_frame_position = pos;
1837 _item = _primary->get_canvas_group ();
1841 RegionInsertDrag::finished (GdkEvent *, bool)
1843 int pos = _views.front().time_axis_view;
1844 assert(pos >= 0 && pos < _time_axis_views.size());
1846 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1848 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1849 _primary->get_canvas_group()->set_y_position (0);
1851 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1853 _editor->begin_reversible_command (Operations::insert_region);
1854 playlist->clear_changes ();
1855 playlist->add_region (_primary->region (), _last_frame_position);
1857 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
1858 if (Config->get_edit_mode() == Ripple) {
1859 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
1862 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1863 _editor->commit_reversible_command ();
1871 RegionInsertDrag::aborted (bool)
1878 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1879 : RegionMoveDrag (e, i, p, v, false, false)
1881 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1884 struct RegionSelectionByPosition {
1885 bool operator() (RegionView*a, RegionView* b) {
1886 return a->region()->position () < b->region()->position();
1891 RegionSpliceDrag::motion (GdkEvent* event, bool)
1893 /* Which trackview is this ? */
1895 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
1896 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1898 /* The region motion is only processed if the pointer is over
1902 if (!tv || !tv->is_track()) {
1903 /* To make sure we hide the verbose canvas cursor when the mouse is
1904 not held over an audio track.
1906 _editor->verbose_cursor()->hide ();
1909 _editor->verbose_cursor()->show ();
1914 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1920 RegionSelection copy;
1921 _editor->selection->regions.by_position(copy);
1923 framepos_t const pf = adjusted_current_frame (event);
1925 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1927 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1933 boost::shared_ptr<Playlist> playlist;
1935 if ((playlist = atv->playlist()) == 0) {
1939 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1944 if (pf < (*i)->region()->last_frame() + 1) {
1948 if (pf > (*i)->region()->first_frame()) {
1954 playlist->shuffle ((*i)->region(), dir);
1959 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1961 RegionMoveDrag::finished (event, movement_occurred);
1965 RegionSpliceDrag::aborted (bool)
1975 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
1978 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
1980 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
1981 RegionSelection to_ripple;
1982 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
1983 if ((*i)->position() >= where) {
1984 to_ripple.push_back (rtv->view()->find_view(*i));
1988 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
1989 if (!exclude.contains (*i)) {
1990 // the selection has already been added to _views
1992 if (drag_in_progress) {
1993 // do the same things that RegionMotionDrag::motion does when
1994 // first_move is true, for the region views that we're adding
1995 // to _views this time
1998 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
1999 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2000 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2001 rvg->reparent (_editor->_drag_motion_group);
2003 // we only need to move in the y direction
2004 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2009 _views.push_back (DraggingView (*i, this, tav));
2015 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2018 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2019 // we added all the regions after the selection
2021 std::list<DraggingView>::iterator to_erase = i++;
2022 if (!_editor->selection->regions.contains (to_erase->view)) {
2023 // restore the non-selected regions to their original playlist & positions,
2024 // and then ripple them back by the length of the regions that were dragged away
2025 // do the same things as RegionMotionDrag::aborted
2027 RegionView *rv = to_erase->view;
2028 TimeAxisView* tv = &(rv->get_time_axis_view ());
2029 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2032 // plonk them back onto their own track
2033 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2034 rv->get_canvas_group()->set_y_position (0);
2038 // move the underlying region to match the view
2039 rv->region()->set_position (rv->region()->position() + amount);
2041 // restore the view to match the underlying region's original position
2042 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2045 rv->set_height (rtv->view()->child_height ());
2046 _views.erase (to_erase);
2052 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2054 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2056 return allow_moves_across_tracks;
2064 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2065 : RegionMoveDrag (e, i, p, v, false, false)
2067 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2068 // compute length of selection
2069 RegionSelection selected_regions = _editor->selection->regions;
2070 selection_length = selected_regions.end_frame() - selected_regions.start();
2072 // we'll only allow dragging to another track in ripple mode if all the regions
2073 // being dragged start off on the same track
2074 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2077 exclude = new RegionList;
2078 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2079 exclude->push_back((*i)->region());
2082 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2083 RegionSelection copy;
2084 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2086 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2087 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2089 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2090 // find ripple start point on each applicable playlist
2091 RegionView *first_selected_on_this_track = NULL;
2092 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2093 if ((*i)->region()->playlist() == (*pi)) {
2094 // region is on this playlist - it's the first, because they're sorted
2095 first_selected_on_this_track = *i;
2099 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2100 add_all_after_to_views (
2101 &first_selected_on_this_track->get_time_axis_view(),
2102 first_selected_on_this_track->region()->position(),
2103 selected_regions, false);
2106 if (allow_moves_across_tracks) {
2107 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2115 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2117 /* Which trackview is this ? */
2119 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2120 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2122 /* The region motion is only processed if the pointer is over
2126 if (!tv || !tv->is_track()) {
2127 /* To make sure we hide the verbose canvas cursor when the mouse is
2128 not held over an audiotrack.
2130 _editor->verbose_cursor()->hide ();
2134 framepos_t where = adjusted_current_frame (event);
2135 assert (where >= 0);
2137 double delta = compute_x_delta (event, &after);
2139 framecnt_t amount = _editor->pixel_to_sample (delta);
2141 if (allow_moves_across_tracks) {
2142 // all the originally selected regions were on the same track
2144 framecnt_t adjust = 0;
2145 if (prev_tav && tv != prev_tav) {
2146 // dragged onto a different track
2147 // remove the unselected regions from _views, restore them to their original positions
2148 // and add the regions after the drop point on the new playlist to _views instead.
2149 // undo the effect of rippling the previous playlist, and include the effect of removing
2150 // the dragged region(s) from this track
2152 remove_unselected_from_views (prev_amount, false);
2153 // ripple previous playlist according to the regions that have been removed onto the new playlist
2154 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2157 // move just the selected regions
2158 RegionMoveDrag::motion(event, first_move);
2160 // ensure that the ripple operation on the new playlist inserts selection_length time
2161 adjust = selection_length;
2162 // ripple the new current playlist
2163 tv->playlist()->ripple (where, amount+adjust, exclude);
2165 // add regions after point where drag entered this track to subsequent ripples
2166 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2169 // motion on same track
2170 RegionMoveDrag::motion(event, first_move);
2174 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2175 prev_position = where;
2177 // selection encompasses multiple tracks - just drag
2178 // cross-track drags are forbidden
2179 RegionMoveDrag::motion(event, first_move);
2182 if (!_x_constrained) {
2183 prev_amount += amount;
2186 _last_frame_position = after;
2190 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2192 if (!movement_occurred) {
2196 if (was_double_click() && !_views.empty()) {
2197 DraggingView dv = _views.front();
2198 dv.view->show_region_editor ();
2205 _editor->begin_reversible_command(_("Ripple drag"));
2207 // remove the regions being rippled from the dragging view, updating them to
2208 // their new positions
2209 remove_unselected_from_views (prev_amount, true);
2211 if (allow_moves_across_tracks) {
2213 // if regions were dragged across tracks, we've rippled any later
2214 // regions on the track the regions were dragged off, so we need
2215 // to add the original track to the undo record
2216 orig_tav->playlist()->clear_changes();
2217 vector<Command*> cmds;
2218 orig_tav->playlist()->rdiff (cmds);
2219 _editor->session()->add_commands (cmds);
2221 if (prev_tav && prev_tav != orig_tav) {
2222 prev_tav->playlist()->clear_changes();
2223 vector<Command*> cmds;
2224 prev_tav->playlist()->rdiff (cmds);
2225 _editor->session()->add_commands (cmds);
2228 // selection spanned multiple tracks - all will need adding to undo record
2230 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2231 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2233 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2234 (*pi)->clear_changes();
2235 vector<Command*> cmds;
2236 (*pi)->rdiff (cmds);
2237 _editor->session()->add_commands (cmds);
2241 // other modified playlists are added to undo by RegionMoveDrag::finished()
2242 RegionMoveDrag::finished (event, movement_occurred);
2243 _editor->commit_reversible_command();
2247 RegionRippleDrag::aborted (bool movement_occurred)
2249 RegionMoveDrag::aborted (movement_occurred);
2254 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2256 _view (dynamic_cast<MidiTimeAxisView*> (v))
2258 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2264 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2267 _region = add_midi_region (_view);
2268 _view->playlist()->freeze ();
2271 framepos_t const f = adjusted_current_frame (event);
2272 if (f < grab_frame()) {
2273 _region->set_position (f);
2276 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2277 so that if this region is duplicated, its duplicate starts on
2278 a snap point rather than 1 frame after a snap point. Otherwise things get
2279 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2280 place snapped notes at the start of the region.
2283 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2284 _region->set_length (len < 1 ? 1 : len);
2290 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
2292 if (!movement_occurred) {
2293 add_midi_region (_view);
2295 _view->playlist()->thaw ();
2300 RegionCreateDrag::aborted (bool)
2303 _view->playlist()->thaw ();
2309 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2313 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2317 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2319 Gdk::Cursor* cursor;
2320 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2322 float x_fraction = cnote->mouse_x_fraction ();
2324 if (x_fraction > 0.0 && x_fraction < 0.25) {
2325 cursor = _editor->cursors()->left_side_trim;
2328 cursor = _editor->cursors()->right_side_trim;
2332 Drag::start_grab (event, cursor);
2334 region = &cnote->region_view();
2338 if (event->motion.state & Keyboard::PrimaryModifier) {
2344 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2346 if (ms.size() > 1) {
2347 /* has to be relative, may make no sense otherwise */
2351 /* select this note; if it is already selected, preserve the existing selection,
2352 otherwise make this note the only one selected.
2354 region->note_selected (cnote, cnote->selected ());
2356 _editor->begin_reversible_command (_("resize notes"));
2358 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2359 MidiRegionSelection::iterator next;
2362 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2364 mrv->begin_resizing (at_front);
2371 NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
2373 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2374 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2375 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2377 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2379 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative);
2385 NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
2387 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2388 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2389 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2391 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2393 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative);
2397 _editor->commit_reversible_command ();
2401 NoteResizeDrag::aborted (bool)
2403 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2404 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2405 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2407 mrv->abort_resizing ();
2412 AVDraggingView::AVDraggingView (RegionView* v)
2415 initial_position = v->region()->position ();
2418 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2421 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2424 TrackViewList empty;
2426 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2427 std::list<RegionView*> views = rs.by_layer();
2429 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2430 RegionView* rv = (*i);
2431 if (!rv->region()->video_locked()) {
2434 _views.push_back (AVDraggingView (rv));
2439 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2441 Drag::start_grab (event);
2442 if (_editor->session() == 0) {
2446 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2447 _max_backwards_drag = (
2448 ARDOUR_UI::instance()->video_timeline->get_duration()
2449 + ARDOUR_UI::instance()->video_timeline->get_offset()
2450 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2453 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2454 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2455 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2458 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2461 Timecode::Time timecode;
2462 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2463 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);
2464 show_verbose_cursor_text (buf);
2468 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2470 if (_editor->session() == 0) {
2473 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2477 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2478 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2480 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2481 dt = - _max_backwards_drag;
2484 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2485 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2487 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2488 RegionView* rv = i->view;
2489 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2492 rv->region()->clear_changes ();
2493 rv->region()->suspend_property_changes();
2495 rv->region()->set_position(i->initial_position + dt);
2496 rv->region_changed(ARDOUR::Properties::position);
2499 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2500 Timecode::Time timecode;
2501 Timecode::Time timediff;
2503 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2504 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2505 snprintf (buf, sizeof (buf),
2506 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2507 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2508 , _("Video Start:"),
2509 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2511 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2513 show_verbose_cursor_text (buf);
2517 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2519 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2523 if (!movement_occurred || ! _editor->session()) {
2527 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2529 _editor->begin_reversible_command (_("Move Video"));
2531 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2532 ARDOUR_UI::instance()->video_timeline->save_undo();
2533 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2534 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2536 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2537 i->view->drag_end();
2538 i->view->region()->resume_property_changes ();
2540 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2543 _editor->session()->maybe_update_session_range(
2544 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2545 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2549 _editor->commit_reversible_command ();
2553 VideoTimeLineDrag::aborted (bool)
2555 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2558 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2559 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2561 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2562 i->view->region()->resume_property_changes ();
2563 i->view->region()->set_position(i->initial_position);
2567 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2568 : RegionDrag (e, i, p, v)
2569 , _preserve_fade_anchor (preserve_fade_anchor)
2570 , _jump_position_when_done (false)
2572 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2576 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2579 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2580 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2582 if (tv && tv->is_track()) {
2583 speed = tv->track()->speed();
2586 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2587 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2588 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2590 framepos_t const pf = adjusted_current_frame (event);
2592 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2593 /* Move the contents of the region around without changing the region bounds */
2594 _operation = ContentsTrim;
2595 Drag::start_grab (event, _editor->cursors()->trimmer);
2597 /* These will get overridden for a point trim.*/
2598 if (pf < (region_start + region_length/2)) {
2599 /* closer to front */
2600 _operation = StartTrim;
2602 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2603 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2605 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2609 _operation = EndTrim;
2610 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2611 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2613 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2618 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2619 _jump_position_when_done = true;
2622 switch (_operation) {
2624 show_verbose_cursor_time (region_start);
2625 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2626 i->view->trim_front_starting ();
2630 show_verbose_cursor_time (region_end);
2633 show_verbose_cursor_time (pf);
2637 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2638 i->view->region()->suspend_property_changes ();
2643 TrimDrag::motion (GdkEvent* event, bool first_move)
2645 RegionView* rv = _primary;
2648 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2649 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2650 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2651 frameoffset_t frame_delta = 0;
2653 if (tv && tv->is_track()) {
2654 speed = tv->track()->speed();
2657 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
2663 switch (_operation) {
2665 trim_type = "Region start trim";
2668 trim_type = "Region end trim";
2671 trim_type = "Region content trim";
2678 _editor->begin_reversible_command (trim_type);
2680 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2681 RegionView* rv = i->view;
2682 rv->enable_display (false);
2683 rv->region()->playlist()->clear_owned_changes ();
2685 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2688 arv->temporarily_hide_envelope ();
2692 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2693 insert_result = _editor->motion_frozen_playlists.insert (pl);
2695 if (insert_result.second) {
2701 bool non_overlap_trim = false;
2703 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2704 non_overlap_trim = true;
2707 /* contstrain trim to fade length */
2708 if (_preserve_fade_anchor) {
2709 switch (_operation) {
2711 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2712 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2714 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2715 if (ar->locked()) continue;
2716 framecnt_t len = ar->fade_in()->back()->when;
2717 if (len < dt) dt = min(dt, len);
2721 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2722 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2724 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2725 if (ar->locked()) continue;
2726 framecnt_t len = ar->fade_out()->back()->when;
2727 if (len < -dt) dt = max(dt, -len);
2736 switch (_operation) {
2738 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2739 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim);
2740 if (changed && _preserve_fade_anchor) {
2741 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2743 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2744 framecnt_t len = ar->fade_in()->back()->when;
2745 framecnt_t diff = ar->first_frame() - i->initial_position;
2746 framepos_t new_length = len - diff;
2747 i->anchored_fade_length = min (ar->length(), new_length);
2748 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
2749 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
2756 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2757 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim);
2758 if (changed && _preserve_fade_anchor) {
2759 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2761 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2762 framecnt_t len = ar->fade_out()->back()->when;
2763 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
2764 framepos_t new_length = len + diff;
2765 i->anchored_fade_length = min (ar->length(), new_length);
2766 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
2767 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
2775 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
2777 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2778 i->view->move_contents (frame_delta);
2784 switch (_operation) {
2786 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
2789 show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed));
2792 // show_verbose_cursor_time (frame_delta);
2799 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
2801 if (movement_occurred) {
2802 motion (event, false);
2804 if (_operation == StartTrim) {
2805 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2807 /* This must happen before the region's StatefulDiffCommand is created, as it may
2808 `correct' (ahem) the region's _start from being negative to being zero. It
2809 needs to be zero in the undo record.
2811 i->view->trim_front_ending ();
2813 if (_preserve_fade_anchor) {
2814 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2816 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2817 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
2818 ar->set_fade_in_length(i->anchored_fade_length);
2819 ar->set_fade_in_active(true);
2822 if (_jump_position_when_done) {
2823 i->view->region()->set_position (i->initial_position);
2826 } else if (_operation == EndTrim) {
2827 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2828 if (_preserve_fade_anchor) {
2829 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2831 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2832 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
2833 ar->set_fade_out_length(i->anchored_fade_length);
2834 ar->set_fade_out_active(true);
2837 if (_jump_position_when_done) {
2838 i->view->region()->set_position (i->initial_end - i->view->region()->length());
2843 if (!_views.empty()) {
2844 if (_operation == StartTrim) {
2845 _editor->maybe_locate_with_edit_preroll(
2846 _views.begin()->view->region()->position());
2848 if (_operation == EndTrim) {
2849 _editor->maybe_locate_with_edit_preroll(
2850 _views.begin()->view->region()->position() +
2851 _views.begin()->view->region()->length());
2855 if (!_editor->selection->selected (_primary)) {
2856 _primary->thaw_after_trim ();
2859 set<boost::shared_ptr<Playlist> > diffed_playlists;
2861 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2862 i->view->thaw_after_trim ();
2863 i->view->enable_display (true);
2865 /* Trimming one region may affect others on the playlist, so we need
2866 to get undo Commands from the whole playlist rather than just the
2867 region. Use diffed_playlists to make sure we don't diff a given
2868 playlist more than once.
2870 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
2871 if (diffed_playlists.find (p) == diffed_playlists.end()) {
2872 vector<Command*> cmds;
2874 _editor->session()->add_commands (cmds);
2875 diffed_playlists.insert (p);
2880 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
2884 _editor->motion_frozen_playlists.clear ();
2885 _editor->commit_reversible_command();
2888 /* no mouse movement */
2889 _editor->point_trim (event, adjusted_current_frame (event));
2892 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2893 if (_operation == StartTrim) {
2894 i->view->trim_front_ending ();
2897 i->view->region()->resume_property_changes ();
2902 TrimDrag::aborted (bool movement_occurred)
2904 /* Our motion method is changing model state, so use the Undo system
2905 to cancel. Perhaps not ideal, as this will leave an Undo point
2906 behind which may be slightly odd from the user's point of view.
2911 if (movement_occurred) {
2915 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2916 i->view->region()->resume_property_changes ();
2921 TrimDrag::setup_pointer_frame_offset ()
2923 list<DraggingView>::iterator i = _views.begin ();
2924 while (i != _views.end() && i->view != _primary) {
2928 if (i == _views.end()) {
2932 switch (_operation) {
2934 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
2937 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
2944 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
2948 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
2949 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
2954 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2956 Drag::start_grab (event, cursor);
2957 show_verbose_cursor_time (adjusted_current_frame(event));
2961 MeterMarkerDrag::setup_pointer_frame_offset ()
2963 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
2967 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
2969 if (!_marker->meter().movable()) {
2975 // create a dummy marker for visual representation of moving the
2976 // section, because whether its a copy or not, we're going to
2977 // leave or lose the original marker (leave if its a copy; lose if its
2978 // not, because we'll remove it from the map).
2980 MeterSection section (_marker->meter());
2982 if (!section.movable()) {
2987 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
2989 _marker = new MeterMarker (
2991 *_editor->meter_group,
2992 ARDOUR_UI::config()->color ("meter marker"),
2994 *new MeterSection (_marker->meter())
2997 /* use the new marker for the grab */
2998 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3001 TempoMap& map (_editor->session()->tempo_map());
3002 /* get current state */
3003 before_state = &map.get_state();
3004 /* remove the section while we drag it */
3005 map.remove_meter (section, true);
3009 framepos_t const pf = adjusted_current_frame (event);
3011 _marker->set_position (pf);
3012 show_verbose_cursor_time (pf);
3016 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3018 if (!movement_occurred) {
3019 if (was_double_click()) {
3020 _editor->edit_meter_marker (*_marker);
3025 if (!_marker->meter().movable()) {
3029 motion (event, false);
3031 Timecode::BBT_Time when;
3033 TempoMap& map (_editor->session()->tempo_map());
3034 map.bbt_time (last_pointer_frame(), when);
3036 if (_copy == true) {
3037 _editor->begin_reversible_command (_("copy meter mark"));
3038 XMLNode &before = map.get_state();
3039 map.add_meter (_marker->meter(), when);
3040 XMLNode &after = map.get_state();
3041 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
3042 _editor->commit_reversible_command ();
3045 _editor->begin_reversible_command (_("move meter mark"));
3047 /* we removed it before, so add it back now */
3049 map.add_meter (_marker->meter(), when);
3050 XMLNode &after = map.get_state();
3051 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3052 _editor->commit_reversible_command ();
3055 // delete the dummy marker we used for visual representation while moving.
3056 // a new visual marker will show up automatically.
3061 MeterMarkerDrag::aborted (bool moved)
3063 _marker->set_position (_marker->meter().frame ());
3066 TempoMap& map (_editor->session()->tempo_map());
3067 /* we removed it before, so add it back now */
3068 map.add_meter (_marker->meter(), _marker->meter().frame());
3069 // delete the dummy marker we used for visual representation while moving.
3070 // a new visual marker will show up automatically.
3075 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3079 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3081 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3086 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3088 Drag::start_grab (event, cursor);
3089 show_verbose_cursor_time (adjusted_current_frame (event));
3093 TempoMarkerDrag::setup_pointer_frame_offset ()
3095 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
3099 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3101 if (!_marker->tempo().movable()) {
3107 // create a dummy marker for visual representation of moving the
3108 // section, because whether its a copy or not, we're going to
3109 // leave or lose the original marker (leave if its a copy; lose if its
3110 // not, because we'll remove it from the map).
3112 // create a dummy marker for visual representation of moving the copy.
3113 // The actual copying is not done before we reach the finish callback.
3116 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
3118 TempoSection section (_marker->tempo());
3120 _marker = new TempoMarker (
3122 *_editor->tempo_group,
3123 ARDOUR_UI::config()->color ("tempo marker"),
3125 *new TempoSection (_marker->tempo())
3128 /* use the new marker for the grab */
3129 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3132 TempoMap& map (_editor->session()->tempo_map());
3133 /* get current state */
3134 before_state = &map.get_state();
3135 /* remove the section while we drag it */
3136 map.remove_tempo (section, true);
3140 framepos_t const pf = adjusted_current_frame (event);
3141 _marker->set_position (pf);
3142 show_verbose_cursor_time (pf);
3146 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3148 if (!movement_occurred) {
3149 if (was_double_click()) {
3150 _editor->edit_tempo_marker (*_marker);
3155 if (!_marker->tempo().movable()) {
3159 motion (event, false);
3161 TempoMap& map (_editor->session()->tempo_map());
3162 framepos_t beat_time = map.round_to_beat (last_pointer_frame(), RoundNearest);
3163 Timecode::BBT_Time when;
3165 map.bbt_time (beat_time, when);
3167 if (_copy == true) {
3168 _editor->begin_reversible_command (_("copy tempo mark"));
3169 XMLNode &before = map.get_state();
3170 map.add_tempo (_marker->tempo(), when);
3171 XMLNode &after = map.get_state();
3172 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
3173 _editor->commit_reversible_command ();
3176 _editor->begin_reversible_command (_("move tempo mark"));
3177 /* we removed it before, so add it back now */
3178 map.add_tempo (_marker->tempo(), when);
3179 XMLNode &after = map.get_state();
3180 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3181 _editor->commit_reversible_command ();
3184 // delete the dummy marker we used for visual representation while moving.
3185 // a new visual marker will show up automatically.
3190 TempoMarkerDrag::aborted (bool moved)
3192 _marker->set_position (_marker->tempo().frame());
3194 TempoMap& map (_editor->session()->tempo_map());
3195 /* we removed it before, so add it back now */
3196 map.add_tempo (_marker->tempo(), _marker->tempo().start());
3197 // delete the dummy marker we used for visual representation while moving.
3198 // a new visual marker will show up automatically.
3203 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3204 : Drag (e, &c.track_canvas_item(), false)
3208 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3211 /** Do all the things we do when dragging the playhead to make it look as though
3212 * we have located, without actually doing the locate (because that would cause
3213 * the diskstream buffers to be refilled, which is too slow).
3216 CursorDrag::fake_locate (framepos_t t)
3218 _editor->playhead_cursor->set_position (t);
3220 Session* s = _editor->session ();
3221 if (s->timecode_transmission_suspended ()) {
3222 framepos_t const f = _editor->playhead_cursor->current_frame ();
3223 /* This is asynchronous so it will be sent "now"
3225 s->send_mmc_locate (f);
3226 /* These are synchronous and will be sent during the next
3229 s->queue_full_time_code ();
3230 s->queue_song_position_pointer ();
3233 show_verbose_cursor_time (t);
3234 _editor->UpdateAllTransportClocks (t);
3238 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3240 Drag::start_grab (event, c);
3242 _grab_zoom = _editor->samples_per_pixel;
3244 framepos_t where = _editor->canvas_event_sample (event);
3246 _editor->snap_to_with_modifier (where, event);
3248 _editor->_dragging_playhead = true;
3250 Session* s = _editor->session ();
3252 /* grab the track canvas item as well */
3254 _cursor.track_canvas_item().grab();
3257 if (_was_rolling && _stop) {
3261 if (s->is_auditioning()) {
3262 s->cancel_audition ();
3266 if (AudioEngine::instance()->connected()) {
3268 /* do this only if we're the engine is connected
3269 * because otherwise this request will never be
3270 * serviced and we'll busy wait forever. likewise,
3271 * notice if we are disconnected while waiting for the
3272 * request to be serviced.
3275 s->request_suspend_timecode_transmission ();
3276 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3277 /* twiddle our thumbs */
3282 fake_locate (where);
3286 CursorDrag::motion (GdkEvent* event, bool)
3288 framepos_t const adjusted_frame = adjusted_current_frame (event);
3289 if (adjusted_frame != last_pointer_frame()) {
3290 fake_locate (adjusted_frame);
3295 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3297 _editor->_dragging_playhead = false;
3299 _cursor.track_canvas_item().ungrab();
3301 if (!movement_occurred && _stop) {
3305 motion (event, false);
3307 Session* s = _editor->session ();
3309 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3310 _editor->_pending_locate_request = true;
3311 s->request_resume_timecode_transmission ();
3316 CursorDrag::aborted (bool)
3318 _cursor.track_canvas_item().ungrab();
3320 if (_editor->_dragging_playhead) {
3321 _editor->session()->request_resume_timecode_transmission ();
3322 _editor->_dragging_playhead = false;
3325 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3328 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3329 : RegionDrag (e, i, p, v)
3331 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3335 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3337 Drag::start_grab (event, cursor);
3339 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3340 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3342 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3346 FadeInDrag::setup_pointer_frame_offset ()
3348 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3349 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3350 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3354 FadeInDrag::motion (GdkEvent* event, bool)
3356 framecnt_t fade_length;
3357 framepos_t const pos = adjusted_current_frame (event);
3358 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3360 if (pos < (region->position() + 64)) {
3361 fade_length = 64; // this should be a minimum defined somewhere
3362 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3363 fade_length = region->length() - region->fade_out()->back()->when - 1;
3365 fade_length = pos - region->position();
3368 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3370 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3376 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3379 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3383 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3385 if (!movement_occurred) {
3389 framecnt_t fade_length;
3391 framepos_t const pos = adjusted_current_frame (event);
3393 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3395 if (pos < (region->position() + 64)) {
3396 fade_length = 64; // this should be a minimum defined somewhere
3397 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3398 fade_length = region->length() - region->fade_out()->back()->when - 1;
3400 fade_length = pos - region->position();
3403 _editor->begin_reversible_command (_("change fade in length"));
3405 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3407 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3413 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3414 XMLNode &before = alist->get_state();
3416 tmp->audio_region()->set_fade_in_length (fade_length);
3417 tmp->audio_region()->set_fade_in_active (true);
3419 XMLNode &after = alist->get_state();
3420 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3423 _editor->commit_reversible_command ();
3427 FadeInDrag::aborted (bool)
3429 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3430 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3436 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3440 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3441 : RegionDrag (e, i, p, v)
3443 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3447 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3449 Drag::start_grab (event, cursor);
3451 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3452 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3454 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3458 FadeOutDrag::setup_pointer_frame_offset ()
3460 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3461 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3462 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3466 FadeOutDrag::motion (GdkEvent* event, bool)
3468 framecnt_t fade_length;
3470 framepos_t const pos = adjusted_current_frame (event);
3472 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3474 if (pos > (region->last_frame() - 64)) {
3475 fade_length = 64; // this should really be a minimum fade defined somewhere
3476 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3477 fade_length = region->length() - region->fade_in()->back()->when - 1;
3479 fade_length = region->last_frame() - pos;
3482 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3484 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3490 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3493 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3497 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3499 if (!movement_occurred) {
3503 framecnt_t fade_length;
3505 framepos_t const pos = adjusted_current_frame (event);
3507 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3509 if (pos > (region->last_frame() - 64)) {
3510 fade_length = 64; // this should really be a minimum fade defined somewhere
3511 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3512 fade_length = region->length() - region->fade_in()->back()->when - 1;
3514 fade_length = region->last_frame() - pos;
3517 _editor->begin_reversible_command (_("change fade out length"));
3519 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3521 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3527 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3528 XMLNode &before = alist->get_state();
3530 tmp->audio_region()->set_fade_out_length (fade_length);
3531 tmp->audio_region()->set_fade_out_active (true);
3533 XMLNode &after = alist->get_state();
3534 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3537 _editor->commit_reversible_command ();
3541 FadeOutDrag::aborted (bool)
3543 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3544 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3550 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3554 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3557 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3559 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
3562 _points.push_back (ArdourCanvas::Duple (0, 0));
3563 _points.push_back (ArdourCanvas::Duple (0, physical_screen_height (_editor->get_window())));
3566 MarkerDrag::~MarkerDrag ()
3568 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
3573 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, Marker* m)
3575 location = new Location (*l);
3576 markers.push_back (m);
3581 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3583 Drag::start_grab (event, cursor);
3587 Location *location = _editor->find_location_from_marker (_marker, is_start);
3588 _editor->_dragging_edit_point = true;
3590 update_item (location);
3592 // _drag_line->show();
3593 // _line->raise_to_top();
3596 show_verbose_cursor_time (location->start());
3598 show_verbose_cursor_time (location->end());
3601 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3604 case Selection::Toggle:
3605 /* we toggle on the button release */
3607 case Selection::Set:
3608 if (!_editor->selection->selected (_marker)) {
3609 _editor->selection->set (_marker);
3612 case Selection::Extend:
3614 Locations::LocationList ll;
3615 list<Marker*> to_add;
3617 _editor->selection->markers.range (s, e);
3618 s = min (_marker->position(), s);
3619 e = max (_marker->position(), e);
3622 if (e < max_framepos) {
3625 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
3626 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
3627 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
3630 to_add.push_back (lm->start);
3633 to_add.push_back (lm->end);
3637 if (!to_add.empty()) {
3638 _editor->selection->add (to_add);
3642 case Selection::Add:
3643 _editor->selection->add (_marker);
3647 /* Set up copies for us to manipulate during the drag
3650 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
3652 Location* l = _editor->find_location_from_marker (*i, is_start);
3659 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
3661 /* range: check that the other end of the range isn't
3664 CopiedLocationInfo::iterator x;
3665 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3666 if (*(*x).location == *l) {
3670 if (x == _copied_locations.end()) {
3671 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
3673 (*x).markers.push_back (*i);
3674 (*x).move_both = true;
3682 MarkerDrag::setup_pointer_frame_offset ()
3685 Location *location = _editor->find_location_from_marker (_marker, is_start);
3686 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
3690 MarkerDrag::motion (GdkEvent* event, bool)
3692 framecnt_t f_delta = 0;
3694 bool move_both = false;
3695 Location *real_location;
3696 Location *copy_location = 0;
3698 framepos_t const newframe = adjusted_current_frame (event);
3699 framepos_t next = newframe;
3701 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
3705 CopiedLocationInfo::iterator x;
3707 /* find the marker we're dragging, and compute the delta */
3709 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3711 copy_location = (*x).location;
3713 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
3715 /* this marker is represented by this
3716 * CopiedLocationMarkerInfo
3719 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
3724 if (real_location->is_mark()) {
3725 f_delta = newframe - copy_location->start();
3729 switch (_marker->type()) {
3730 case Marker::SessionStart:
3731 case Marker::RangeStart:
3732 case Marker::LoopStart:
3733 case Marker::PunchIn:
3734 f_delta = newframe - copy_location->start();
3737 case Marker::SessionEnd:
3738 case Marker::RangeEnd:
3739 case Marker::LoopEnd:
3740 case Marker::PunchOut:
3741 f_delta = newframe - copy_location->end();
3744 /* what kind of marker is this ? */
3753 if (x == _copied_locations.end()) {
3754 /* hmm, impossible - we didn't find the dragged marker */
3758 /* now move them all */
3760 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3762 copy_location = x->location;
3764 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
3768 if (real_location->locked()) {
3772 if (copy_location->is_mark()) {
3776 copy_location->set_start (copy_location->start() + f_delta);
3780 framepos_t new_start = copy_location->start() + f_delta;
3781 framepos_t new_end = copy_location->end() + f_delta;
3783 if (is_start) { // start-of-range marker
3785 if (move_both || (*x).move_both) {
3786 copy_location->set_start (new_start);
3787 copy_location->set_end (new_end);
3788 } else if (new_start < copy_location->end()) {
3789 copy_location->set_start (new_start);
3790 } else if (newframe > 0) {
3791 _editor->snap_to (next, RoundUpAlways, true);
3792 copy_location->set_end (next);
3793 copy_location->set_start (newframe);
3796 } else { // end marker
3798 if (move_both || (*x).move_both) {
3799 copy_location->set_end (new_end);
3800 copy_location->set_start (new_start);
3801 } else if (new_end > copy_location->start()) {
3802 copy_location->set_end (new_end);
3803 } else if (newframe > 0) {
3804 _editor->snap_to (next, RoundDownAlways, true);
3805 copy_location->set_start (next);
3806 copy_location->set_end (newframe);
3811 update_item (copy_location);
3813 /* now lookup the actual GUI items used to display this
3814 * location and move them to wherever the copy of the location
3815 * is now. This means that the logic in ARDOUR::Location is
3816 * still enforced, even though we are not (yet) modifying
3817 * the real Location itself.
3820 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
3823 lm->set_position (copy_location->start(), copy_location->end());
3828 assert (!_copied_locations.empty());
3830 show_verbose_cursor_time (newframe);
3834 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3836 if (!movement_occurred) {
3838 if (was_double_click()) {
3839 _editor->rename_marker (_marker);
3843 /* just a click, do nothing but finish
3844 off the selection process
3847 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3850 case Selection::Set:
3851 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
3852 _editor->selection->set (_marker);
3856 case Selection::Toggle:
3857 /* we toggle on the button release, click only */
3858 _editor->selection->toggle (_marker);
3861 case Selection::Extend:
3862 case Selection::Add:
3869 _editor->_dragging_edit_point = false;
3871 _editor->begin_reversible_command ( _("move marker") );
3872 XMLNode &before = _editor->session()->locations()->get_state();
3874 MarkerSelection::iterator i;
3875 CopiedLocationInfo::iterator x;
3878 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
3879 x != _copied_locations.end() && i != _editor->selection->markers.end();
3882 Location * location = _editor->find_location_from_marker (*i, is_start);
3886 if (location->locked()) {
3890 if (location->is_mark()) {
3891 location->set_start (((*x).location)->start());
3893 location->set (((*x).location)->start(), ((*x).location)->end());
3898 XMLNode &after = _editor->session()->locations()->get_state();
3899 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3900 _editor->commit_reversible_command ();
3904 MarkerDrag::aborted (bool movement_occured)
3906 if (!movement_occured) {
3910 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
3912 /* move all markers to their original location */
3915 for (vector<Marker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
3918 Location * location = _editor->find_location_from_marker (*m, is_start);
3921 (*m)->set_position (is_start ? location->start() : location->end());
3928 MarkerDrag::update_item (Location*)
3933 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
3935 _cumulative_x_drag (0),
3936 _cumulative_y_drag (0)
3938 if (_zero_gain_fraction < 0.0) {
3939 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
3942 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
3944 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
3950 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
3952 Drag::start_grab (event, _editor->cursors()->fader);
3954 // start the grab at the center of the control point so
3955 // the point doesn't 'jump' to the mouse after the first drag
3956 _fixed_grab_x = _point->get_x();
3957 _fixed_grab_y = _point->get_y();
3959 float const fraction = 1 - (_point->get_y() / _point->line().height());
3961 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
3963 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
3965 _pushing = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
3967 if (!_point->can_slide ()) {
3968 _x_constrained = true;
3973 ControlPointDrag::motion (GdkEvent* event, bool)
3975 double dx = _drags->current_pointer_x() - last_pointer_x();
3976 double dy = current_pointer_y() - last_pointer_y();
3978 if (event->button.state & Keyboard::SecondaryModifier) {
3983 /* coordinate in pixels relative to the start of the region (for region-based automation)
3984 or track (for track-based automation) */
3985 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
3986 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
3988 // calculate zero crossing point. back off by .01 to stay on the
3989 // positive side of zero
3990 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
3992 // make sure we hit zero when passing through
3993 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
3997 if (_x_constrained) {
4000 if (_y_constrained) {
4004 _cumulative_x_drag = cx - _fixed_grab_x;
4005 _cumulative_y_drag = cy - _fixed_grab_y;
4009 cy = min ((double) _point->line().height(), cy);
4011 framepos_t cx_frames = _editor->pixel_to_sample (cx);
4013 if (!_x_constrained) {
4014 _editor->snap_to_with_modifier (cx_frames, event);
4017 cx_frames = min (cx_frames, _point->line().maximum_time());
4019 float const fraction = 1.0 - (cy / _point->line().height());
4021 _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4023 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4027 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4029 if (!movement_occurred) {
4033 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
4034 _editor->reset_point_selection ();
4038 motion (event, false);
4041 _point->line().end_drag (_pushing, _final_index);
4042 _editor->commit_reversible_command ();
4046 ControlPointDrag::aborted (bool)
4048 _point->line().reset ();
4052 ControlPointDrag::active (Editing::MouseMode m)
4054 if (m == Editing::MouseDraw) {
4055 /* always active in mouse draw */
4059 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4060 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4063 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4066 _cumulative_y_drag (0)
4068 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4072 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4074 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4077 _item = &_line->grab_item ();
4079 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4080 origin, and ditto for y.
4083 double cx = event->button.x;
4084 double cy = event->button.y;
4086 _line->parent_group().canvas_to_item (cx, cy);
4088 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->samples_per_pixel);
4093 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
4094 /* no adjacent points */
4098 Drag::start_grab (event, _editor->cursors()->fader);
4100 /* store grab start in parent frame */
4105 double fraction = 1.0 - (cy / _line->height());
4107 _line->start_drag_line (before, after, fraction);
4109 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4113 LineDrag::motion (GdkEvent* event, bool)
4115 double dy = current_pointer_y() - last_pointer_y();
4117 if (event->button.state & Keyboard::SecondaryModifier) {
4121 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4123 _cumulative_y_drag = cy - _fixed_grab_y;
4126 cy = min ((double) _line->height(), cy);
4128 double const fraction = 1.0 - (cy / _line->height());
4131 /* we are ignoring x position for this drag, so we can just pass in anything */
4132 _line->drag_motion (0, fraction, true, false, ignored);
4134 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4138 LineDrag::finished (GdkEvent* event, bool movement_occured)
4140 if (movement_occured) {
4141 motion (event, false);
4142 _line->end_drag (false, 0);
4144 /* add a new control point on the line */
4146 AutomationTimeAxisView* atv;
4148 _line->end_drag (false, 0);
4150 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4151 framepos_t where = _editor->window_event_sample (event, 0, 0);
4152 atv->add_automation_event (event, where, event->button.y, false);
4156 _editor->commit_reversible_command ();
4160 LineDrag::aborted (bool)
4165 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4168 _cumulative_x_drag (0)
4170 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4174 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4176 Drag::start_grab (event);
4178 _line = reinterpret_cast<Line*> (_item);
4181 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4183 double cx = event->button.x;
4184 double cy = event->button.y;
4186 _item->parent()->canvas_to_item (cx, cy);
4188 /* store grab start in parent frame */
4189 _region_view_grab_x = cx;
4191 _before = *(float*) _item->get_data ("position");
4193 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4195 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4199 FeatureLineDrag::motion (GdkEvent*, bool)
4201 double dx = _drags->current_pointer_x() - last_pointer_x();
4203 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4205 _cumulative_x_drag += dx;
4207 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4216 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4218 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4220 float *pos = new float;
4223 _line->set_data ("position", pos);
4229 FeatureLineDrag::finished (GdkEvent*, bool)
4231 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4232 _arv->update_transient(_before, _before);
4236 FeatureLineDrag::aborted (bool)
4241 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4243 , _vertical_only (false)
4245 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4249 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4251 Drag::start_grab (event);
4252 show_verbose_cursor_time (adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid()));
4256 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4263 framepos_t const pf = adjusted_current_frame (event, ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid());
4265 framepos_t grab = grab_frame ();
4266 if (ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) {
4267 _editor->snap_to_with_modifier (grab, event);
4269 grab = raw_grab_frame ();
4272 /* base start and end on initial click position */
4282 if (current_pointer_y() < grab_y()) {
4283 y1 = current_pointer_y();
4286 y2 = current_pointer_y();
4290 if (start != end || y1 != y2) {
4292 double x1 = _editor->sample_to_pixel (start);
4293 double x2 = _editor->sample_to_pixel (end);
4294 const double min_dimension = 2.0;
4296 if (_vertical_only) {
4297 /* fixed 10 pixel width */
4301 x2 = min (x1 - min_dimension, x2);
4303 x2 = max (x1 + min_dimension, x2);
4308 y2 = min (y1 - min_dimension, y2);
4310 y2 = max (y1 + min_dimension, y2);
4313 /* translate rect into item space and set */
4315 ArdourCanvas::Rect r (x1, y1, x2, y2);
4317 /* this drag is a _trackview_only == true drag, so the y1 and
4318 * y2 (computed using current_pointer_y() and grab_y()) will be
4319 * relative to the top of the trackview group). The
4320 * rubberband rect has the same parent/scroll offset as the
4321 * the trackview group, so we can use the "r" rect directly
4322 * to set the shape of the rubberband.
4325 _editor->rubberband_rect->set (r);
4326 _editor->rubberband_rect->show();
4327 _editor->rubberband_rect->raise_to_top();
4329 show_verbose_cursor_time (pf);
4331 do_select_things (event, true);
4336 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4340 framepos_t grab = grab_frame ();
4341 framepos_t lpf = last_pointer_frame ();
4343 if (!ARDOUR_UI::config()->get_rubberbanding_snaps_to_grid ()) {
4344 grab = raw_grab_frame ();
4345 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4359 if (current_pointer_y() < grab_y()) {
4360 y1 = current_pointer_y();
4363 y2 = current_pointer_y();
4367 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4371 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4373 if (movement_occurred) {
4375 motion (event, false);
4376 do_select_things (event, false);
4382 bool do_deselect = true;
4383 MidiTimeAxisView* mtv;
4385 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4387 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4388 /* nothing selected */
4389 add_midi_region (mtv);
4390 do_deselect = false;
4394 /* do not deselect if Primary or Tertiary (toggle-select or
4395 * extend-select are pressed.
4398 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4399 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4406 _editor->rubberband_rect->hide();
4410 RubberbandSelectDrag::aborted (bool)
4412 _editor->rubberband_rect->hide ();
4415 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4416 : RegionDrag (e, i, p, v)
4418 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4422 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4424 Drag::start_grab (event, cursor);
4426 show_verbose_cursor_time (adjusted_current_frame (event));
4430 TimeFXDrag::motion (GdkEvent* event, bool)
4432 RegionView* rv = _primary;
4433 StreamView* cv = rv->get_time_axis_view().view ();
4435 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4436 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4437 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4439 framepos_t const pf = adjusted_current_frame (event);
4441 if (pf > rv->region()->position()) {
4442 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4445 show_verbose_cursor_time (pf);
4449 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4451 _primary->get_time_axis_view().hide_timestretch ();
4453 if (!movement_occurred) {
4457 if (last_pointer_frame() < _primary->region()->position()) {
4458 /* backwards drag of the left edge - not usable */
4462 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
4464 float percentage = (double) newlen / (double) _primary->region()->length();
4466 #ifndef USE_RUBBERBAND
4467 // Soundtouch uses percentage / 100 instead of normal (/ 1)
4468 if (_primary->region()->data_type() == DataType::AUDIO) {
4469 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4473 if (!_editor->get_selection().regions.empty()) {
4474 /* primary will already be included in the selection, and edit
4475 group shared editing will propagate selection across
4476 equivalent regions, so just use the current region
4480 if (_editor->time_stretch (_editor->get_selection().regions, percentage) == -1) {
4481 error << _("An error occurred while executing time stretch operation") << endmsg;
4487 TimeFXDrag::aborted (bool)
4489 _primary->get_time_axis_view().hide_timestretch ();
4492 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
4495 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
4499 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4501 Drag::start_grab (event);
4505 ScrubDrag::motion (GdkEvent* /*event*/, bool)
4507 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
4511 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
4513 if (movement_occurred && _editor->session()) {
4514 /* make sure we stop */
4515 _editor->session()->request_transport_speed (0.0);
4520 ScrubDrag::aborted (bool)
4525 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4529 , _time_selection_at_start (!_editor->get_selection().time.empty())
4531 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
4533 if (_time_selection_at_start) {
4534 start_at_start = _editor->get_selection().time.start();
4535 end_at_start = _editor->get_selection().time.end_frame();
4540 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
4542 if (_editor->session() == 0) {
4546 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4548 switch (_operation) {
4549 case CreateSelection:
4550 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4555 cursor = _editor->cursors()->selector;
4556 Drag::start_grab (event, cursor);
4559 case SelectionStartTrim:
4560 if (_editor->clicked_axisview) {
4561 _editor->clicked_axisview->order_selection_trims (_item, true);
4563 Drag::start_grab (event, _editor->cursors()->left_side_trim);
4566 case SelectionEndTrim:
4567 if (_editor->clicked_axisview) {
4568 _editor->clicked_axisview->order_selection_trims (_item, false);
4570 Drag::start_grab (event, _editor->cursors()->right_side_trim);
4574 Drag::start_grab (event, cursor);
4577 case SelectionExtend:
4578 Drag::start_grab (event, cursor);
4582 if (_operation == SelectionMove) {
4583 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
4585 show_verbose_cursor_time (adjusted_current_frame (event));
4590 SelectionDrag::setup_pointer_frame_offset ()
4592 switch (_operation) {
4593 case CreateSelection:
4594 _pointer_frame_offset = 0;
4597 case SelectionStartTrim:
4599 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
4602 case SelectionEndTrim:
4603 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
4606 case SelectionExtend:
4612 SelectionDrag::motion (GdkEvent* event, bool first_move)
4614 framepos_t start = 0;
4616 framecnt_t length = 0;
4617 framecnt_t distance = 0;
4619 framepos_t const pending_position = adjusted_current_frame (event);
4621 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
4625 switch (_operation) {
4626 case CreateSelection:
4628 framepos_t grab = grab_frame ();
4631 grab = adjusted_current_frame (event, false);
4632 if (grab < pending_position) {
4633 _editor->snap_to (grab, RoundDownMaybe);
4635 _editor->snap_to (grab, RoundUpMaybe);
4639 if (pending_position < grab) {
4640 start = pending_position;
4643 end = pending_position;
4647 /* first drag: Either add to the selection
4648 or create a new selection
4655 /* adding to the selection */
4656 _editor->set_selected_track_as_side_effect (Selection::Add);
4657 _editor->clicked_selection = _editor->selection->add (start, end);
4664 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
4665 _editor->set_selected_track_as_side_effect (Selection::Set);
4668 _editor->clicked_selection = _editor->selection->set (start, end);
4672 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
4673 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
4674 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
4676 _editor->selection->add (atest);
4680 /* select all tracks within the rectangle that we've marked out so far */
4681 TrackViewList new_selection;
4682 TrackViewList& all_tracks (_editor->track_views);
4684 ArdourCanvas::Coord const top = grab_y();
4685 ArdourCanvas::Coord const bottom = current_pointer_y();
4687 if (top >= 0 && bottom >= 0) {
4689 //first, find the tracks that are covered in the y range selection
4690 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
4691 if ((*i)->covered_by_y_range (top, bottom)) {
4692 new_selection.push_back (*i);
4696 //now find any tracks that are GROUPED with the tracks we selected
4697 TrackViewList grouped_add = new_selection;
4698 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
4699 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
4700 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::select.property_id) ) {
4701 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
4702 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
4703 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
4704 grouped_add.push_back (*j);
4709 //now compare our list with the current selection, and add or remove as necessary
4710 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
4711 TrackViewList tracks_to_add;
4712 TrackViewList tracks_to_remove;
4713 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
4714 if ( !_editor->selection->tracks.contains ( *i ) )
4715 tracks_to_add.push_back ( *i );
4716 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
4717 if ( !grouped_add.contains ( *i ) )
4718 tracks_to_remove.push_back ( *i );
4719 _editor->selection->add(tracks_to_add);
4720 _editor->selection->remove(tracks_to_remove);
4726 case SelectionStartTrim:
4728 start = _editor->selection->time[_editor->clicked_selection].start;
4729 end = _editor->selection->time[_editor->clicked_selection].end;
4731 if (pending_position > end) {
4734 start = pending_position;
4738 case SelectionEndTrim:
4740 start = _editor->selection->time[_editor->clicked_selection].start;
4741 end = _editor->selection->time[_editor->clicked_selection].end;
4743 if (pending_position < start) {
4746 end = pending_position;
4753 start = _editor->selection->time[_editor->clicked_selection].start;
4754 end = _editor->selection->time[_editor->clicked_selection].end;
4756 length = end - start;
4757 distance = pending_position - start;
4758 start = pending_position;
4759 _editor->snap_to (start);
4761 end = start + length;
4765 case SelectionExtend:
4770 switch (_operation) {
4772 if (_time_selection_at_start) {
4773 _editor->selection->move_time (distance);
4777 _editor->selection->replace (_editor->clicked_selection, start, end);
4781 if (_operation == SelectionMove) {
4782 show_verbose_cursor_time(start);
4784 show_verbose_cursor_time(pending_position);
4789 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
4791 Session* s = _editor->session();
4793 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
4794 if (movement_occurred) {
4795 motion (event, false);
4796 /* XXX this is not object-oriented programming at all. ick */
4797 if (_editor->selection->time.consolidate()) {
4798 _editor->selection->TimeChanged ();
4801 /* XXX what if its a music time selection? */
4803 if ( s->get_play_range() && s->transport_rolling() ) {
4804 s->request_play_range (&_editor->selection->time, true);
4806 if (ARDOUR_UI::config()->get_follow_edits() && !s->transport_rolling()) {
4807 if (_operation == SelectionEndTrim)
4808 _editor->maybe_locate_with_edit_preroll( _editor->get_selection().time.end_frame());
4810 s->request_locate (_editor->get_selection().time.start());
4816 /* just a click, no pointer movement.
4819 if (_operation == SelectionExtend) {
4820 if (_time_selection_at_start) {
4821 framepos_t pos = adjusted_current_frame (event, false);
4822 framepos_t start = min (pos, start_at_start);
4823 framepos_t end = max (pos, end_at_start);
4824 _editor->selection->set (start, end);
4827 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
4828 if (_editor->clicked_selection) {
4829 _editor->selection->remove (_editor->clicked_selection);
4832 if (!_editor->clicked_selection) {
4833 _editor->selection->clear_time();
4838 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
4839 _editor->selection->set (_editor->clicked_axisview);
4842 if (s && s->get_play_range () && s->transport_rolling()) {
4843 s->request_stop (false, false);
4848 _editor->stop_canvas_autoscroll ();
4849 _editor->clicked_selection = 0;
4850 _editor->commit_reversible_selection_op ();
4854 SelectionDrag::aborted (bool)
4859 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
4860 : Drag (e, i, false),
4864 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
4866 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
4867 ArdourCanvas::Rect (0.0, 0.0, 0.0,
4868 physical_screen_height (_editor->get_window())));
4869 _drag_rect->hide ();
4871 _drag_rect->set_fill_color (ARDOUR_UI::config()->color ("range drag rect"));
4872 _drag_rect->set_outline_color (ARDOUR_UI::config()->color ("range drag rect"));
4875 RangeMarkerBarDrag::~RangeMarkerBarDrag()
4877 /* normal canvas items will be cleaned up when their parent group is deleted. But
4878 this item is created as the child of a long-lived parent group, and so we
4879 need to explicitly delete it.
4885 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4887 if (_editor->session() == 0) {
4891 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
4893 if (!_editor->temp_location) {
4894 _editor->temp_location = new Location (*_editor->session());
4897 switch (_operation) {
4898 case CreateSkipMarker:
4899 case CreateRangeMarker:
4900 case CreateTransportMarker:
4901 case CreateCDMarker:
4903 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
4908 cursor = _editor->cursors()->selector;
4912 Drag::start_grab (event, cursor);
4914 show_verbose_cursor_time (adjusted_current_frame (event));
4918 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
4920 framepos_t start = 0;
4922 ArdourCanvas::Rectangle *crect;
4924 switch (_operation) {
4925 case CreateSkipMarker:
4926 crect = _editor->range_bar_drag_rect;
4928 case CreateRangeMarker:
4929 crect = _editor->range_bar_drag_rect;
4931 case CreateTransportMarker:
4932 crect = _editor->transport_bar_drag_rect;
4934 case CreateCDMarker:
4935 crect = _editor->cd_marker_bar_drag_rect;
4938 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
4943 framepos_t const pf = adjusted_current_frame (event);
4945 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
4946 framepos_t grab = grab_frame ();
4947 _editor->snap_to (grab);
4949 if (pf < grab_frame()) {
4957 /* first drag: Either add to the selection
4958 or create a new selection.
4963 _editor->temp_location->set (start, end);
4967 update_item (_editor->temp_location);
4969 //_drag_rect->raise_to_top();
4975 _editor->temp_location->set (start, end);
4977 double x1 = _editor->sample_to_pixel (start);
4978 double x2 = _editor->sample_to_pixel (end);
4982 update_item (_editor->temp_location);
4985 show_verbose_cursor_time (pf);
4990 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
4992 Location * newloc = 0;
4996 if (movement_occurred) {
4997 motion (event, false);
5000 switch (_operation) {
5001 case CreateSkipMarker:
5002 case CreateRangeMarker:
5003 case CreateCDMarker:
5005 XMLNode &before = _editor->session()->locations()->get_state();
5006 if (_operation == CreateSkipMarker) {
5007 _editor->begin_reversible_command (_("new skip marker"));
5008 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5009 flags = Location::IsRangeMarker | Location::IsSkip;
5010 _editor->range_bar_drag_rect->hide();
5011 } else if (_operation == CreateCDMarker) {
5012 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5013 _editor->begin_reversible_command (_("new CD marker"));
5014 flags = Location::IsRangeMarker | Location::IsCDMarker;
5015 _editor->cd_marker_bar_drag_rect->hide();
5017 _editor->begin_reversible_command (_("new skip marker"));
5018 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5019 flags = Location::IsRangeMarker;
5020 _editor->range_bar_drag_rect->hide();
5022 newloc = new Location (
5023 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5026 _editor->session()->locations()->add (newloc, true);
5027 XMLNode &after = _editor->session()->locations()->get_state();
5028 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5029 _editor->commit_reversible_command ();
5033 case CreateTransportMarker:
5034 // popup menu to pick loop or punch
5035 _editor->new_transport_marker_context_menu (&event->button, _item);
5041 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5043 if (_operation == CreateTransportMarker) {
5045 /* didn't drag, so just locate */
5047 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5049 } else if (_operation == CreateCDMarker) {
5051 /* didn't drag, but mark is already created so do
5054 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5059 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5061 if (end == max_framepos) {
5062 end = _editor->session()->current_end_frame ();
5065 if (start == max_framepos) {
5066 start = _editor->session()->current_start_frame ();
5069 switch (_editor->mouse_mode) {
5071 /* find the two markers on either side and then make the selection from it */
5072 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5076 /* find the two markers on either side of the click and make the range out of it */
5077 _editor->selection->set (start, end);
5086 _editor->stop_canvas_autoscroll ();
5090 RangeMarkerBarDrag::aborted (bool movement_occured)
5092 if (movement_occured) {
5093 _drag_rect->hide ();
5098 RangeMarkerBarDrag::update_item (Location* location)
5100 double const x1 = _editor->sample_to_pixel (location->start());
5101 double const x2 = _editor->sample_to_pixel (location->end());
5103 _drag_rect->set_x0 (x1);
5104 _drag_rect->set_x1 (x2);
5107 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5109 , _cumulative_dx (0)
5110 , _cumulative_dy (0)
5112 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5114 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5116 _region = &_primary->region_view ();
5117 _note_height = _region->midi_stream_view()->note_height ();
5121 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5123 Drag::start_grab (event);
5125 if (!(_was_selected = _primary->selected())) {
5127 /* tertiary-click means extend selection - we'll do that on button release,
5128 so don't add it here, because otherwise we make it hard to figure
5129 out the "extend-to" range.
5132 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5135 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5138 _region->note_selected (_primary, true);
5140 _region->unique_select (_primary);
5143 _editor->begin_reversible_selection_op(X_("Select Note Press"));
5144 _editor->commit_reversible_selection_op();
5149 /** @return Current total drag x change in frames */
5151 NoteDrag::total_dx () const
5154 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5156 /* primary note time */
5157 frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
5159 /* new time of the primary note in session frames */
5160 frameoffset_t st = n + dx;
5162 framepos_t const rp = _region->region()->position ();
5164 /* prevent the note being dragged earlier than the region's position */
5167 /* snap and return corresponding delta */
5168 return _region->snap_frame_to_frame (st - rp) + rp - n;
5171 /** @return Current total drag y change in note number */
5173 NoteDrag::total_dy () const
5175 MidiStreamView* msv = _region->midi_stream_view ();
5176 double const y = _region->midi_view()->y_position ();
5177 /* new current note */
5178 uint8_t n = msv->y_to_note (current_pointer_y () - y);
5180 n = max (msv->lowest_note(), n);
5181 n = min (msv->highest_note(), n);
5182 /* and work out delta */
5183 return n - msv->y_to_note (grab_y() - y);
5187 NoteDrag::motion (GdkEvent *, bool)
5189 /* Total change in x and y since the start of the drag */
5190 frameoffset_t const dx = total_dx ();
5191 int8_t const dy = total_dy ();
5193 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5194 double const tdx = _editor->sample_to_pixel (dx) - _cumulative_dx;
5195 double const tdy = -dy * _note_height - _cumulative_dy;
5198 _cumulative_dx += tdx;
5199 _cumulative_dy += tdy;
5201 int8_t note_delta = total_dy();
5203 _region->move_selection (tdx, tdy, note_delta);
5205 /* the new note value may be the same as the old one, but we
5206 * don't know what that means because the selection may have
5207 * involved more than one note and we might be doing something
5208 * odd with them. so show the note value anyway, always.
5212 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5214 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
5215 (int) floor ((double)new_note));
5217 show_verbose_cursor_text (buf);
5222 NoteDrag::finished (GdkEvent* ev, bool moved)
5225 /* no motion - select note */
5227 if (_editor->current_mouse_mode() == Editing::MouseObject ||
5228 _editor->current_mouse_mode() == Editing::MouseDraw) {
5230 bool changed = false;
5232 if (_was_selected) {
5233 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5235 _region->note_deselected (_primary);
5239 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5240 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5242 if (!extend && !add && _region->selection_size() > 1) {
5243 _region->unique_select (_primary);
5245 } else if (extend) {
5246 _region->note_selected (_primary, true, true);
5249 /* it was added during button press */
5254 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5255 _editor->commit_reversible_selection_op();
5259 _region->note_dropped (_primary, total_dx(), total_dy());
5264 NoteDrag::aborted (bool)
5269 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5270 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5271 : Drag (editor, atv->base_item ())
5273 , _y_origin (atv->y_position())
5274 , _nothing_to_drag (false)
5276 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5277 setup (atv->lines ());
5280 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5281 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5282 : Drag (editor, rv->get_canvas_group ())
5284 , _y_origin (rv->get_time_axis_view().y_position())
5285 , _nothing_to_drag (false)
5288 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5290 list<boost::shared_ptr<AutomationLine> > lines;
5292 AudioRegionView* audio_view;
5293 AutomationRegionView* automation_view;
5294 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5295 lines.push_back (audio_view->get_gain_line ());
5296 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5297 lines.push_back (automation_view->line ());
5300 error << _("Automation range drag created for invalid region type") << endmsg;
5306 /** @param lines AutomationLines to drag.
5307 * @param offset Offset from the session start to the points in the AutomationLines.
5310 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5312 /* find the lines that overlap the ranges being dragged */
5313 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5314 while (i != lines.end ()) {
5315 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5318 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5320 /* check this range against all the AudioRanges that we are using */
5321 list<AudioRange>::const_iterator k = _ranges.begin ();
5322 while (k != _ranges.end()) {
5323 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5329 /* add it to our list if it overlaps at all */
5330 if (k != _ranges.end()) {
5335 _lines.push_back (n);
5341 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5345 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5347 return 1.0 - ((global_y - _y_origin) / line->height());
5351 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5353 const double v = list->eval(x);
5354 return _integral ? rint(v) : v;
5358 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5360 Drag::start_grab (event, cursor);
5362 /* Get line states before we start changing things */
5363 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5364 i->state = &i->line->get_state ();
5365 i->original_fraction = y_fraction (i->line, current_pointer_y());
5368 if (_ranges.empty()) {
5370 /* No selected time ranges: drag all points */
5371 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5372 uint32_t const N = i->line->npoints ();
5373 for (uint32_t j = 0; j < N; ++j) {
5374 i->points.push_back (i->line->nth (j));
5380 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5382 framecnt_t const half = (i->start + i->end) / 2;
5384 /* find the line that this audio range starts in */
5385 list<Line>::iterator j = _lines.begin();
5386 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5390 if (j != _lines.end()) {
5391 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5393 /* j is the line that this audio range starts in; fade into it;
5394 64 samples length plucked out of thin air.
5397 framepos_t a = i->start + 64;
5402 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5403 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5405 the_list->editor_add (p, value (the_list, p));
5406 the_list->editor_add (q, value (the_list, q));
5409 /* same thing for the end */
5412 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5416 if (j != _lines.end()) {
5417 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5419 /* j is the line that this audio range starts in; fade out of it;
5420 64 samples length plucked out of thin air.
5423 framepos_t b = i->end - 64;
5428 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
5429 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
5431 the_list->editor_add (p, value (the_list, p));
5432 the_list->editor_add (q, value (the_list, q));
5436 _nothing_to_drag = true;
5438 /* Find all the points that should be dragged and put them in the relevant
5439 points lists in the Line structs.
5442 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5444 uint32_t const N = i->line->npoints ();
5445 for (uint32_t j = 0; j < N; ++j) {
5447 /* here's a control point on this line */
5448 ControlPoint* p = i->line->nth (j);
5449 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
5451 /* see if it's inside a range */
5452 list<AudioRange>::const_iterator k = _ranges.begin ();
5453 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
5457 if (k != _ranges.end()) {
5458 /* dragging this point */
5459 _nothing_to_drag = false;
5460 i->points.push_back (p);
5466 if (_nothing_to_drag) {
5470 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5471 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
5476 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
5478 if (_nothing_to_drag) {
5482 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
5483 float const f = y_fraction (l->line, current_pointer_y());
5484 /* we are ignoring x position for this drag, so we can just pass in anything */
5486 l->line->drag_motion (0, f, true, false, ignored);
5487 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, f));
5492 AutomationRangeDrag::finished (GdkEvent* event, bool)
5494 if (_nothing_to_drag) {
5498 motion (event, false);
5499 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5500 i->line->end_drag (false, 0);
5503 _editor->commit_reversible_command ();
5507 AutomationRangeDrag::aborted (bool)
5509 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5514 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
5516 , initial_time_axis_view (itav)
5518 /* note that time_axis_view may be null if the regionview was created
5519 * as part of a copy operation.
5521 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
5522 layer = v->region()->layer ();
5523 initial_y = v->get_canvas_group()->position().y;
5524 initial_playlist = v->region()->playlist ();
5525 initial_position = v->region()->position ();
5526 initial_end = v->region()->position () + v->region()->length ();
5529 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
5530 : Drag (e, i->canvas_item ())
5533 , _cumulative_dx (0)
5535 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
5536 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
5541 PatchChangeDrag::motion (GdkEvent* ev, bool)
5543 framepos_t f = adjusted_current_frame (ev);
5544 boost::shared_ptr<Region> r = _region_view->region ();
5545 f = max (f, r->position ());
5546 f = min (f, r->last_frame ());
5548 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
5549 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
5550 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
5551 _cumulative_dx = dxu;
5555 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
5557 if (!movement_occurred) {
5561 boost::shared_ptr<Region> r (_region_view->region ());
5562 framepos_t f = adjusted_current_frame (ev);
5563 f = max (f, r->position ());
5564 f = min (f, r->last_frame ());
5566 _region_view->move_patch_change (
5568 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
5573 PatchChangeDrag::aborted (bool)
5575 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
5579 PatchChangeDrag::setup_pointer_frame_offset ()
5581 boost::shared_ptr<Region> region = _region_view->region ();
5582 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
5585 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
5586 : RubberbandSelectDrag (e, rv->get_canvas_group ())
5593 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
5595 _region_view->update_drag_selection (
5597 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
5601 MidiRubberbandSelectDrag::deselect_things ()
5606 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
5607 : RubberbandSelectDrag (e, rv->get_canvas_group ())
5610 _vertical_only = true;
5614 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
5616 double const y = _region_view->midi_view()->y_position ();
5618 y1 = max (0.0, y1 - y);
5619 y2 = max (0.0, y2 - y);
5621 _region_view->update_vertical_drag_selection (
5624 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
5629 MidiVerticalSelectDrag::deselect_things ()
5634 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5635 : RubberbandSelectDrag (e, i)
5641 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
5643 if (drag_in_progress) {
5644 /* We just want to select things at the end of the drag, not during it */
5648 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
5650 _editor->begin_reversible_selection_op (X_("rubberband selection"));
5652 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
5654 _editor->commit_reversible_selection_op ();
5658 EditorRubberbandSelectDrag::deselect_things ()
5660 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
5662 _editor->selection->clear_tracks();
5663 _editor->selection->clear_regions();
5664 _editor->selection->clear_points ();
5665 _editor->selection->clear_lines ();
5666 _editor->selection->clear_midi_notes ();
5668 _editor->commit_reversible_selection_op();
5671 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
5676 _note[0] = _note[1] = 0;
5679 NoteCreateDrag::~NoteCreateDrag ()
5685 NoteCreateDrag::grid_frames (framepos_t t) const
5688 Evoral::Beats grid_beats = _editor->get_grid_type_as_beats (success, t);
5690 grid_beats = Evoral::Beats(1);
5693 return _region_view->region_beats_to_region_frames (grid_beats);
5697 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5699 Drag::start_grab (event, cursor);
5701 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
5703 framepos_t pf = _drags->current_pointer_frame ();
5704 framecnt_t const g = grid_frames (pf);
5706 /* Hack so that we always snap to the note that we are over, instead of snapping
5707 to the next one if we're more than halfway through the one we're over.
5709 if (_editor->snap_mode() == SnapNormal && pf > g / 2) {
5713 _note[0] = adjusted_frame (pf, event) - _region_view->region()->position ();
5714 _note[1] = _note[0];
5716 MidiStreamView* sv = _region_view->midi_stream_view ();
5717 double const x = _editor->sample_to_pixel (_note[0]);
5718 double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y)));
5720 _drag_rect->set (ArdourCanvas::Rect (x, y, x, y + floor (_region_view->midi_stream_view()->note_height ())));
5721 _drag_rect->set_outline_all ();
5722 _drag_rect->set_outline_color (0xffffff99);
5723 _drag_rect->set_fill_color (0xffffff66);
5727 NoteCreateDrag::motion (GdkEvent* event, bool)
5729 _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ());
5730 double const x0 = _editor->sample_to_pixel (_note[0]);
5731 double const x1 = _editor->sample_to_pixel (_note[1]);
5732 _drag_rect->set_x0 (std::min(x0, x1));
5733 _drag_rect->set_x1 (std::max(x0, x1));
5737 NoteCreateDrag::finished (GdkEvent*, bool had_movement)
5739 if (!had_movement) {
5743 framepos_t const start = min (_note[0], _note[1]);
5744 framecnt_t length = (framecnt_t) fabs ((double)(_note[0] - _note[1]));
5746 framecnt_t const g = grid_frames (start);
5747 Evoral::Beats const one_tick = Evoral::Beats::ticks(1);
5749 if (_editor->snap_mode() == SnapNormal && length < g) {
5753 Evoral::Beats length_beats = max (
5754 one_tick, _region_view->region_frames_to_region_beats (length) - one_tick);
5756 _region_view->create_note_at (start, _drag_rect->y0(), length_beats, false);
5760 NoteCreateDrag::y_to_region (double y) const
5763 _region_view->get_canvas_group()->canvas_to_item (x, y);
5768 NoteCreateDrag::aborted (bool)
5773 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
5778 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
5782 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
5784 Drag::start_grab (event, cursor);
5788 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
5794 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
5797 distance = _drags->current_pointer_x() - grab_x();
5798 len = ar->fade_in()->back()->when;
5800 distance = grab_x() - _drags->current_pointer_x();
5801 len = ar->fade_out()->back()->when;
5804 /* how long should it be ? */
5806 new_length = len + _editor->pixel_to_sample (distance);
5808 /* now check with the region that this is legal */
5810 new_length = ar->verify_xfade_bounds (new_length, start);
5813 arv->reset_fade_in_shape_width (ar, new_length);
5815 arv->reset_fade_out_shape_width (ar, new_length);
5820 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
5826 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
5829 distance = _drags->current_pointer_x() - grab_x();
5830 len = ar->fade_in()->back()->when;
5832 distance = grab_x() - _drags->current_pointer_x();
5833 len = ar->fade_out()->back()->when;
5836 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
5838 _editor->begin_reversible_command ("xfade trim");
5839 ar->playlist()->clear_owned_changes ();
5842 ar->set_fade_in_length (new_length);
5844 ar->set_fade_out_length (new_length);
5847 /* Adjusting the xfade may affect other regions in the playlist, so we need
5848 to get undo Commands from the whole playlist rather than just the
5852 vector<Command*> cmds;
5853 ar->playlist()->rdiff (cmds);
5854 _editor->session()->add_commands (cmds);
5855 _editor->commit_reversible_command ();
5860 CrossfadeEdgeDrag::aborted (bool)
5863 // arv->redraw_start_xfade ();
5865 // arv->redraw_end_xfade ();
5869 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
5870 : Drag (e, item, true)
5871 , line (new EditorCursor (*e))
5873 line->set_position (pos);
5877 RegionCutDrag::~RegionCutDrag ()
5883 RegionCutDrag::motion (GdkEvent*, bool)
5885 framepos_t where = _drags->current_pointer_frame();
5886 _editor->snap_to (where);
5888 line->set_position (where);
5892 RegionCutDrag::finished (GdkEvent*, bool)
5894 _editor->get_track_canvas()->canvas()->re_enter();
5896 framepos_t pos = _drags->current_pointer_frame();
5900 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
5906 _editor->split_regions_at (pos, rs);
5910 RegionCutDrag::aborted (bool)