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 "ui_config.h"
68 #include "verbose_cursor.h"
71 using namespace ARDOUR;
74 using namespace Gtkmm2ext;
75 using namespace Editing;
76 using namespace ArdourCanvas;
78 using Gtkmm2ext::Keyboard;
80 double ControlPointDrag::_zero_gain_fraction = -1.0;
82 DragManager::DragManager (Editor* e)
85 , _current_pointer_x (0.0)
86 , _current_pointer_y (0.0)
87 , _current_pointer_frame (0)
88 , _old_follow_playhead (false)
92 DragManager::~DragManager ()
97 /** Call abort for each active drag */
103 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
108 if (!_drags.empty ()) {
109 _editor->set_follow_playhead (_old_follow_playhead, false);
113 _editor->abort_reversible_command();
119 DragManager::add (Drag* d)
121 d->set_manager (this);
122 _drags.push_back (d);
126 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
128 d->set_manager (this);
129 _drags.push_back (d);
134 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
136 /* Prevent follow playhead during the drag to be nice to the user */
137 _old_follow_playhead = _editor->follow_playhead ();
138 _editor->set_follow_playhead (false);
140 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
142 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
143 (*i)->start_grab (e, c);
147 /** Call end_grab for each active drag.
148 * @return true if any drag reported movement having occurred.
151 DragManager::end_grab (GdkEvent* e)
156 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
157 bool const t = (*i)->end_grab (e);
168 _editor->set_follow_playhead (_old_follow_playhead, false);
174 DragManager::mark_double_click ()
176 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
177 (*i)->set_double_click (true);
182 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
186 /* calling this implies that we expect the event to have canvas
189 * Can we guarantee that this is true?
192 _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
194 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
195 bool const t = (*i)->motion_handler (e, from_autoscroll);
196 /* run all handlers; return true if at least one of them
197 returns true (indicating that the event has been handled).
209 DragManager::have_item (ArdourCanvas::Item* i) const
211 list<Drag*>::const_iterator j = _drags.begin ();
212 while (j != _drags.end() && (*j)->item () != i) {
216 return j != _drags.end ();
219 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
223 , _pointer_frame_offset (0)
224 , _x_constrained (false)
225 , _y_constrained (false)
226 , _was_rolling (false)
227 , _trackview_only (trackview_only)
228 , _move_threshold_passed (false)
229 , _starting_point_passed (false)
230 , _initially_vertical (false)
231 , _was_double_click (false)
234 , _last_pointer_x (0.0)
235 , _last_pointer_y (0.0)
236 , _raw_grab_frame (0)
238 , _last_pointer_frame (0)
240 , _constraint_pressed (false)
246 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
252 _cursor_ctx = CursorContext::create (*_editor, cursor);
254 _cursor_ctx->change (cursor);
261 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
264 /* we set up x/y dragging constraints on first move */
265 _constraint_pressed = ArdourKeyboard::indicates_constraint (event->button.state);
267 _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
269 setup_pointer_frame_offset ();
270 _grab_frame = adjusted_frame (_raw_grab_frame, event);
271 _last_pointer_frame = _grab_frame;
272 _last_pointer_x = _grab_x;
274 if (_trackview_only) {
275 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
278 _last_pointer_y = _grab_y;
282 if (!_editor->cursors()->is_invalid (cursor)) {
283 /* CAIROCANVAS need a variant here that passes *cursor */
284 _cursor_ctx = CursorContext::create (*_editor, cursor);
287 if (_editor->session() && _editor->session()->transport_rolling()) {
290 _was_rolling = false;
293 switch (_editor->snap_type()) {
294 case SnapToRegionStart:
295 case SnapToRegionEnd:
296 case SnapToRegionSync:
297 case SnapToRegionBoundary:
298 _editor->build_region_boundary_cache ();
305 /** Call to end a drag `successfully'. Ungrabs item and calls
306 * subclass' finished() method.
308 * @param event GDK event, or 0.
309 * @return true if some movement occurred, otherwise false.
312 Drag::end_grab (GdkEvent* event)
314 _editor->stop_canvas_autoscroll ();
318 finished (event, _move_threshold_passed);
320 _editor->verbose_cursor()->hide ();
323 return _move_threshold_passed;
327 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
331 if (f > _pointer_frame_offset) {
332 pos = f - _pointer_frame_offset;
336 _editor->snap_to_with_modifier (pos, event);
343 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
345 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
349 Drag::snap_delta (guint state) const
351 if (ArdourKeyboard::indicates_snap_delta (state)) {
359 Drag::current_pointer_x() const
361 return _drags->current_pointer_x ();
365 Drag::current_pointer_y () const
367 if (!_trackview_only) {
368 return _drags->current_pointer_y ();
371 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
375 Drag::setup_snap_delta (framepos_t pos)
377 framepos_t temp = pos;
378 _editor->snap_to (temp, ARDOUR::RoundNearest, false, true);
379 _snap_delta = temp - pos;
383 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
385 /* check to see if we have moved in any way that matters since the last motion event */
386 if (_move_threshold_passed &&
387 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
388 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
392 pair<framecnt_t, int> const threshold = move_threshold ();
394 bool const old_move_threshold_passed = _move_threshold_passed;
396 if (!_move_threshold_passed) {
398 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
399 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
401 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
404 if (active (_editor->mouse_mode) && _move_threshold_passed) {
406 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
408 if (old_move_threshold_passed != _move_threshold_passed) {
412 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
413 _initially_vertical = true;
415 _initially_vertical = false;
417 /** check constraints for this drag.
418 * Note that the current convention is to use "contains" for
419 * key modifiers during motion and "equals" when initiating a drag.
420 * In this case we haven't moved yet, so "equals" applies here.
422 if (Config->get_edit_mode() != Lock) {
423 if (event->motion.state & Gdk::BUTTON2_MASK) {
424 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
425 if (_constraint_pressed) {
426 _x_constrained = false;
427 _y_constrained = true;
429 _x_constrained = true;
430 _y_constrained = false;
432 } else if (_constraint_pressed) {
433 // if dragging normally, the motion is constrained to the first direction of movement.
434 if (_initially_vertical) {
435 _x_constrained = true;
436 _y_constrained = false;
438 _x_constrained = false;
439 _y_constrained = true;
443 if (event->button.state & Gdk::BUTTON2_MASK) {
444 _x_constrained = false;
446 _x_constrained = true;
448 _y_constrained = false;
452 if (!from_autoscroll) {
453 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
456 if (!_editor->autoscroll_active() || from_autoscroll) {
459 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
461 motion (event, first_move && !_starting_point_passed);
463 if (first_move && !_starting_point_passed) {
464 _starting_point_passed = true;
467 _last_pointer_x = _drags->current_pointer_x ();
468 _last_pointer_y = current_pointer_y ();
469 _last_pointer_frame = adjusted_current_frame (event, false);
479 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
487 aborted (_move_threshold_passed);
489 _editor->stop_canvas_autoscroll ();
490 _editor->verbose_cursor()->hide ();
494 Drag::show_verbose_cursor_time (framepos_t frame)
496 _editor->verbose_cursor()->set_time (frame);
497 _editor->verbose_cursor()->show ();
501 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
503 _editor->verbose_cursor()->set_duration (start, end);
504 _editor->verbose_cursor()->show ();
508 Drag::show_verbose_cursor_text (string const & text)
510 _editor->verbose_cursor()->set (text);
511 _editor->verbose_cursor()->show ();
514 boost::shared_ptr<Region>
515 Drag::add_midi_region (MidiTimeAxisView* view, bool commit, const int32_t sub_num)
517 if (_editor->session()) {
518 const TempoMap& map (_editor->session()->tempo_map());
519 framecnt_t pos = grab_frame();
520 /* not that the frame rate used here can be affected by pull up/down which
523 framecnt_t len = map.frame_at_beat (max (0.0, map.beat_at_frame (pos)) + 1.0) - pos;
524 return view->add_region (grab_frame(), len, commit, sub_num);
527 return boost::shared_ptr<Region>();
530 struct PresentationInfoTimeAxisViewSorter {
531 bool operator() (TimeAxisView* a, TimeAxisView* b) {
532 return a->stripable()->presentation_info().order() < b->stripable()->presentation_info().order();
536 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
541 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
543 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
544 as some of the regions we are dragging may be on such tracks.
547 TrackViewList track_views = _editor->track_views;
548 track_views.sort (PresentationInfoTimeAxisViewSorter ());
550 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
551 _time_axis_views.push_back (*i);
553 TimeAxisView::Children children_list = (*i)->get_child_list ();
554 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
555 _time_axis_views.push_back (j->get());
559 /* the list of views can be empty at this point if this is a region list-insert drag
562 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
563 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
566 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
570 RegionDrag::region_going_away (RegionView* v)
572 list<DraggingView>::iterator i = _views.begin ();
573 while (i != _views.end() && i->view != v) {
577 if (i != _views.end()) {
582 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
583 * or -1 if it is not found.
586 RegionDrag::find_time_axis_view (TimeAxisView* t) const
589 int const N = _time_axis_views.size ();
590 while (i < N && _time_axis_views[i] != t) {
594 if (_time_axis_views[i] != t) {
601 /** determines if @pos is the closest frame to an exact musical division when using SnapMagnetic.
602 * (SnapMagnetic may snap to an exact division or no division at all).
603 * this is a hotfix for musical region position/trim. we should really
604 * deliver musical divisors when snapping magnetically to avoid this.
607 RegionDrag::current_music_divisor (framepos_t pos, int32_t button_state)
609 int32_t divisions = _editor->get_grid_music_divisions (button_state);
611 if (_editor->snap_mode() == Editing::SnapMagnetic && !ArdourKeyboard::indicates_snap (button_state)) {
612 TempoMap& tmap (_editor->session()->tempo_map());
613 const framepos_t exact_qn_pos = tmap.frame_at_quarter_note (tmap.exact_qn_at_frame (pos, divisions));
615 if (pos != exact_qn_pos) {
616 /* magnetic has not snapped */
624 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
625 : RegionDrag (e, i, p, v)
627 , _ignore_video_lock (false)
629 , _last_pointer_time_axis_view (0)
630 , _last_pointer_layer (0)
635 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
639 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
641 Drag::start_grab (event, cursor);
642 setup_snap_delta (_last_frame_position);
644 show_verbose_cursor_time (_last_frame_position);
646 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
648 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
649 assert(_last_pointer_time_axis_view >= 0);
650 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
653 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
654 _ignore_video_lock = true;
658 /* cross track dragging seems broken here. disabled for now. */
659 _y_constrained = true;
664 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
666 /* compute the amount of pointer motion in frames, and where
667 the region would be if we moved it by that much.
669 *pending_region_position = adjusted_frame (_drags->current_pointer_frame (), event, false);
671 framepos_t sync_frame;
672 framecnt_t sync_offset;
675 sync_offset = _primary->region()->sync_offset (sync_dir);
677 /* we don't handle a sync point that lies before zero.
679 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
681 framecnt_t const sd = snap_delta (event->button.state);
682 sync_frame = *pending_region_position + (sync_dir * sync_offset) + sd;
684 _editor->snap_to_with_modifier (sync_frame, event);
686 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - sd;
689 *pending_region_position = _last_frame_position;
692 if (*pending_region_position > max_framepos - _primary->region()->length()) {
693 *pending_region_position = _last_frame_position;
698 bool const x_move_allowed = !_x_constrained;
700 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
702 /* x movement since last time (in pixels) */
703 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
705 /* total x movement */
706 framecnt_t total_dx = *pending_region_position;
707 if (regions_came_from_canvas()) {
708 total_dx = total_dx - grab_frame ();
711 /* check that no regions have gone off the start of the session */
712 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
713 if ((i->view->region()->position() + total_dx) < 0) {
715 *pending_region_position = _last_frame_position;
726 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
732 const int tavsize = _time_axis_views.size();
733 const int dt = delta > 0 ? +1 : -1;
735 int target = start + delta - skip;
737 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
738 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
740 while (current >= 0 && current != target) {
742 if (current < 0 && dt < 0) {
745 if (current >= tavsize && dt > 0) {
748 if (current < 0 || current >= tavsize) {
752 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
753 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
757 if (distance_only && current == start + delta) {
765 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
767 if (_y_constrained) {
771 const int tavsize = _time_axis_views.size();
772 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
773 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
774 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
776 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
777 /* already in the drop zone */
778 if (delta_track >= 0) {
779 /* downward motion - OK if others are still not in the dropzone */
788 } else if (n >= tavsize) {
789 /* downward motion into drop zone. That's fine. */
793 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
794 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
795 /* not a track, or the wrong type */
799 double const l = i->layer + delta_layer;
801 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
802 mode to allow the user to place a region below another on layer 0.
804 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
805 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
806 If it has, the layers will be munged later anyway, so it's ok.
812 /* all regions being dragged are ok with this change */
816 struct DraggingViewSorter {
817 bool operator() (const DraggingView& a, const DraggingView& b) {
818 return a.time_axis_view < b.time_axis_view;
823 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
825 double delta_layer = 0;
826 int delta_time_axis_view = 0;
827 int current_pointer_time_axis_view = -1;
829 assert (!_views.empty ());
831 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
833 /* Find the TimeAxisView that the pointer is now over */
834 const double cur_y = current_pointer_y ();
835 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
836 TimeAxisView* tv = r.first;
838 if (!tv && cur_y < 0) {
839 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
843 /* find drop-zone y-position */
844 Coord last_track_bottom_edge;
845 last_track_bottom_edge = 0;
846 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
847 if (!(*t)->hidden()) {
848 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
853 if (tv && tv->view()) {
854 /* the mouse is over a track */
855 double layer = r.second;
857 if (first_move && tv->view()->layer_display() == Stacked) {
858 tv->view()->set_layer_display (Expanded);
861 /* Here's the current pointer position in terms of time axis view and layer */
862 current_pointer_time_axis_view = find_time_axis_view (tv);
863 assert(current_pointer_time_axis_view >= 0);
865 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
867 /* Work out the change in y */
869 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
870 if (!rtv || !rtv->is_track()) {
871 /* ignore non-tracks early on. we can't move any regions on them */
872 } else if (_last_pointer_time_axis_view < 0) {
873 /* Was in the drop-zone, now over a track.
874 * Hence it must be an upward move (from the bottom)
876 * track_index is still -1, so delta must be set to
877 * move up the correct number of tracks from the bottom.
879 * This is necessary because steps may be skipped if
880 * the bottom-most track is not a valid target and/or
881 * if there are hidden tracks at the bottom.
882 * Hence the initial offset (_ddropzone) as well as the
883 * last valid pointer position (_pdropzone) need to be
884 * taken into account.
886 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
888 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
891 /* TODO needs adjustment per DraggingView,
893 * e.g. select one region on the top-layer of a track
894 * and one region which is at the bottom-layer of another track
897 * Indicated drop-zones and layering is wrong.
898 * and may infer additional layers on the target-track
899 * (depending how many layers the original track had).
901 * Or select two regions (different layers) on a same track,
902 * move across a non-layer track.. -> layering info is lost.
903 * on drop either of the regions may be on top.
905 * Proposed solution: screw it :) well,
906 * don't use delta_layer, use an absolute value
907 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
908 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
909 * 3) iterate over all DraggingView, find the one that is over the track with most layers
910 * 4) proportionally scale layer to layers available on target
912 delta_layer = current_pointer_layer - _last_pointer_layer;
915 /* for automation lanes, there is a TimeAxisView but no ->view()
916 * if (!tv) -> dropzone
918 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
919 /* Moving into the drop-zone.. */
920 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
921 /* delta_time_axis_view may not be sufficient to move into the DZ
922 * the mouse may enter it, but it may not be a valid move due to
925 * -> remember the delta needed to move into the dropzone
927 _ddropzone = delta_time_axis_view;
928 /* ..but subtract hidden tracks (or routes) at the bottom.
929 * we silently move mover them
931 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
932 - _time_axis_views.size();
934 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
935 /* move around inside the zone.
936 * This allows to move further down until all regions are in the zone.
938 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
939 assert(ptr_y >= last_track_bottom_edge);
940 assert(_ddropzone > 0);
942 /* calculate mouse position in 'tracks' below last track. */
943 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
944 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
946 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
948 delta_time_axis_view = dzpos - _pdropzone;
949 } else if (dzpos < _pdropzone && _ndropzone > 0) {
950 // move up inside the DZ
951 delta_time_axis_view = dzpos - _pdropzone;
955 /* Work out the change in x */
956 framepos_t pending_region_position;
957 double const x_delta = compute_x_delta (event, &pending_region_position);
958 _last_frame_position = pending_region_position;
960 /* calculate hidden tracks in current y-axis delta */
962 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
963 /* The mouse is more than one track below the dropzone.
964 * distance calculation is not needed (and would not work, either
965 * because the dropzone is "packed").
967 * Except when [partially] moving regions out of dropzone in a large step.
968 * (the mouse may or may not remain in the DZ)
969 * Hidden tracks at the bottom of the TAV need to be skipped.
971 * This also handles the case if the mouse entered the DZ
972 * in a large step (exessive delta), either due to fast-movement,
973 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
975 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
976 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
978 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
979 -_time_axis_views.size() - dt;
982 else if (_last_pointer_time_axis_view < 0) {
983 /* Moving out of the zone. Check for hidden tracks at the bottom. */
984 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
985 -_time_axis_views.size() - delta_time_axis_view;
987 /* calculate hidden tracks that are skipped by the pointer movement */
988 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
989 - _last_pointer_time_axis_view
990 - delta_time_axis_view;
993 /* Verify change in y */
994 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
995 /* this y movement is not allowed, so do no y movement this time */
996 delta_time_axis_view = 0;
1001 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1002 /* haven't reached next snap point, and we're not switching
1003 trackviews nor layers. nothing to do.
1008 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1009 PlaylistDropzoneMap playlist_dropzone_map;
1010 _ndropzone = 0; // number of elements currently in the dropzone
1013 /* sort views by time_axis.
1014 * This retains track order in the dropzone, regardless
1015 * of actual selection order
1017 _views.sort (DraggingViewSorter());
1019 /* count number of distinct tracks of all regions
1020 * being dragged, used for dropzone.
1022 int prev_track = -1;
1023 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1024 if (i->time_axis_view != prev_track) {
1025 prev_track = i->time_axis_view;
1031 _views.back().time_axis_view -
1032 _views.front().time_axis_view;
1034 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1035 - _views.back().time_axis_view;
1037 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1041 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1043 RegionView* rv = i->view;
1048 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1055 /* reparent the regionview into a group above all
1059 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1060 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1061 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1062 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1063 /* move the item so that it continues to appear at the
1064 same location now that its parent has changed.
1066 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1069 /* If we have moved tracks, we'll fudge the layer delta so that the
1070 region gets moved back onto layer 0 on its new track; this avoids
1071 confusion when dragging regions from non-zero layers onto different
1074 double this_delta_layer = delta_layer;
1075 if (delta_time_axis_view != 0) {
1076 this_delta_layer = - i->layer;
1079 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1081 int track_index = i->time_axis_view + this_delta_time_axis_view;
1082 assert(track_index >= 0);
1084 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1085 /* Track is in the Dropzone */
1087 i->time_axis_view = track_index;
1088 assert(i->time_axis_view >= (int) _time_axis_views.size());
1091 double yposition = 0;
1092 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1093 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1096 /* store index of each new playlist as a negative count, starting at -1 */
1098 if (pdz == playlist_dropzone_map.end()) {
1099 /* compute where this new track (which doesn't exist yet) will live
1102 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1104 /* How high is this region view ? */
1106 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1107 ArdourCanvas::Rect bbox;
1110 bbox = obbox.get ();
1113 last_track_bottom_edge += bbox.height();
1115 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1118 yposition = pdz->second;
1121 /* values are zero or negative, hence the use of min() */
1122 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1125 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1127 mrv->apply_note_range (60, 71, true);
1131 /* The TimeAxisView that this region is now over */
1132 TimeAxisView* current_tv = _time_axis_views[track_index];
1134 /* Ensure it is moved from stacked -> expanded if appropriate */
1135 if (current_tv->view()->layer_display() == Stacked) {
1136 current_tv->view()->set_layer_display (Expanded);
1139 /* We're only allowed to go -ve in layer on Expanded views */
1140 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1141 this_delta_layer = - i->layer;
1145 rv->set_height (current_tv->view()->child_height ());
1147 /* Update show/hidden status as the region view may have come from a hidden track,
1148 or have moved to one.
1150 if (current_tv->hidden ()) {
1151 rv->get_canvas_group()->hide ();
1153 rv->get_canvas_group()->show ();
1156 /* Update the DraggingView */
1157 i->time_axis_view = track_index;
1158 i->layer += this_delta_layer;
1161 _editor->mouse_brush_insert_region (rv, pending_region_position);
1165 /* Get the y coordinate of the top of the track that this region is now over */
1166 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1168 /* And adjust for the layer that it should be on */
1169 StreamView* cv = current_tv->view ();
1170 switch (cv->layer_display ()) {
1174 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1177 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1181 /* need to get the parent of the regionview
1182 * canvas group and get its position in
1183 * equivalent coordinate space as the trackview
1184 * we are now dragging over.
1187 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1191 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1193 MidiStreamView* msv;
1194 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1195 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1200 /* Now move the region view */
1201 rv->move (x_delta, y_delta);
1203 } /* foreach region */
1205 _total_x_delta += x_delta;
1207 if (x_delta != 0 && !_brushing) {
1208 show_verbose_cursor_time (_last_frame_position);
1211 /* keep track of pointer movement */
1213 /* the pointer is currently over a time axis view */
1215 if (_last_pointer_time_axis_view < 0) {
1216 /* last motion event was not over a time axis view
1217 * or last y-movement out of the dropzone was not valid
1220 if (delta_time_axis_view < 0) {
1221 /* in the drop zone, moving up */
1223 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1224 * We do not use negative _last_pointer_time_axis_view because
1225 * the dropzone is "packed" (the actual track offset is ignored)
1227 * As opposed to the actual number
1228 * of elements in the dropzone (_ndropzone)
1229 * _pdropzone is not constrained. This is necessary
1230 * to allow moving multiple regions with y-distance
1233 * There can be 0 elements in the dropzone,
1234 * even though the drag-pointer is inside the DZ.
1237 * [ Audio-track, Midi-track, Audio-track, DZ ]
1238 * move regions from both audio tracks at the same time into the
1239 * DZ by grabbing the region in the bottom track.
1241 assert(current_pointer_time_axis_view >= 0);
1242 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1246 /* only move out of the zone if the movement is OK */
1247 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1248 assert(delta_time_axis_view < 0);
1249 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1250 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1251 * the current position can be calculated as follows:
1253 // a well placed oofus attack can still throw this off.
1254 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1255 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1258 /* last motion event was also over a time axis view */
1259 _last_pointer_time_axis_view += delta_time_axis_view;
1260 assert(_last_pointer_time_axis_view >= 0);
1265 /* the pointer is not over a time axis view */
1266 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1267 _pdropzone += delta_time_axis_view - delta_skip;
1268 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1271 _last_pointer_layer += delta_layer;
1275 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1277 if (_copy && first_move) {
1278 if (_x_constrained && !_brushing) {
1279 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1280 } else if (!_brushing) {
1281 _editor->begin_reversible_command (Operations::region_copy);
1282 } else if (_brushing) {
1283 _editor->begin_reversible_command (Operations::drag_region_brush);
1285 /* duplicate the regionview(s) and region(s) */
1287 list<DraggingView> new_regionviews;
1289 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1291 RegionView* rv = i->view;
1292 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1293 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1295 const boost::shared_ptr<const Region> original = rv->region();
1296 boost::shared_ptr<Region> region_copy;
1298 if (rv == _primary) {
1299 region_copy = RegionFactory::create (original, true
1300 , current_music_divisor (original->position(), event->button.state));
1302 region_copy = RegionFactory::create (original, true, 0);
1305 /* need to set this so that the drop zone code can work. This doesn't
1306 actually put the region into the playlist, but just sets a weak pointer
1309 region_copy->set_playlist (original->playlist());
1313 boost::shared_ptr<AudioRegion> audioregion_copy
1314 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1316 nrv = new AudioRegionView (*arv, audioregion_copy);
1318 boost::shared_ptr<MidiRegion> midiregion_copy
1319 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1320 nrv = new MidiRegionView (*mrv, midiregion_copy);
1325 nrv->get_canvas_group()->show ();
1326 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1328 /* swap _primary to the copy */
1330 if (rv == _primary) {
1334 /* ..and deselect the one we copied */
1336 rv->set_selected (false);
1339 if (!new_regionviews.empty()) {
1341 /* reflect the fact that we are dragging the copies */
1343 _views = new_regionviews;
1345 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1348 } else if (!_copy && first_move) {
1349 if (_x_constrained && !_brushing) {
1350 _editor->begin_reversible_command (_("fixed time region drag"));
1351 } else if (!_brushing) {
1352 _editor->begin_reversible_command (Operations::region_drag);
1353 } else if (_brushing) {
1354 _editor->begin_reversible_command (Operations::drag_region_brush);
1357 RegionMotionDrag::motion (event, first_move);
1361 RegionMotionDrag::finished (GdkEvent *, bool)
1363 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1364 if (!(*i)->view()) {
1368 if ((*i)->view()->layer_display() == Expanded) {
1369 (*i)->view()->set_layer_display (Stacked);
1375 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1377 RegionMotionDrag::finished (ev, movement_occurred);
1379 if (!movement_occurred) {
1383 if (was_double_click() && !_views.empty()) {
1384 DraggingView dv = _views.front();
1385 _editor->edit_region (dv.view);
1391 assert (!_views.empty ());
1393 /* We might have hidden region views so that they weren't visible during the drag
1394 (when they have been reparented). Now everything can be shown again, as region
1395 views are back in their track parent groups.
1397 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1398 i->view->get_canvas_group()->show ();
1401 bool const changed_position = (_last_frame_position != _primary->region()->position());
1402 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1403 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1427 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1429 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1432 TimeAxisView* tav = 0;
1434 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1435 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1436 uint32_t output_chan = region->n_channels();
1437 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1438 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1440 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1441 tav =_editor->axis_view_from_stripable (audio_tracks.front());
1443 ChanCount one_midi_port (DataType::MIDI, 1);
1444 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1445 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port, boost::shared_ptr<ARDOUR::PluginInfo>(),
1446 (ARDOUR::Plugin::PresetRecord*) 0,
1447 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1448 tav = _editor->axis_view_from_stripable (midi_tracks.front());
1452 tav->set_height (original->current_height());
1455 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1458 return dynamic_cast<RouteTimeAxisView*> (tav);
1462 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta, int32_t const ev_state)
1464 RegionSelection new_views;
1465 PlaylistSet modified_playlists;
1466 RouteTimeAxisView* new_time_axis_view = 0;
1468 int32_t divisor = current_music_divisor (_primary->region()->position() - drag_delta, ev_state);
1469 TempoMap& tmap (_editor->session()->tempo_map());
1470 double qn_delta = _primary->region()->quarter_note() - tmap.exact_qn_at_frame (_primary->region()->position() - drag_delta, divisor);
1473 /* all changes were made during motion event handlers */
1475 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1479 _editor->commit_reversible_command ();
1483 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1484 PlaylistMapping playlist_mapping;
1486 /* insert the regions into their new playlists */
1487 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1489 RouteTimeAxisView* dest_rtv = 0;
1491 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1497 if (changed_position && !_x_constrained) {
1498 where = i->view->region()->position() - drag_delta;
1500 where = i->view->region()->position();
1503 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1504 /* dragged to drop zone */
1506 PlaylistMapping::iterator pm;
1508 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1509 /* first region from this original playlist: create a new track */
1510 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1511 if(!new_time_axis_view) {
1515 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1516 dest_rtv = new_time_axis_view;
1518 /* we already created a new track for regions from this playlist, use it */
1519 dest_rtv = pm->second;
1522 /* destination time axis view is the one we dragged to */
1523 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1526 if (dest_rtv != 0) {
1527 RegionView* new_view;
1528 if (i->view == _primary) {
1529 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1530 modified_playlists, current_music_divisor (where, ev_state));
1532 if (i->view->region()->position_lock_style() == AudioTime) {
1533 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1534 modified_playlists, 0);
1536 where = tmap.frame_at_quarter_note (i->view->region()->quarter_note() - qn_delta);
1537 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1538 modified_playlists, 0);
1542 if (new_view != 0) {
1543 new_views.push_back (new_view);
1547 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1548 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1551 list<DraggingView>::const_iterator next = i;
1557 /* If we've created new regions either by copying or moving
1558 to a new track, we want to replace the old selection with the new ones
1561 if (new_views.size() > 0) {
1562 _editor->selection->set (new_views);
1565 /* write commands for the accumulated diffs for all our modified playlists */
1566 add_stateful_diff_commands_for_playlists (modified_playlists);
1568 _editor->commit_reversible_command ();
1572 RegionMoveDrag::finished_no_copy (
1573 bool const changed_position,
1574 bool const changed_tracks,
1575 framecnt_t const drag_delta,
1576 int32_t const ev_state
1579 RegionSelection new_views;
1580 PlaylistSet modified_playlists;
1581 PlaylistSet frozen_playlists;
1582 set<RouteTimeAxisView*> views_to_update;
1583 RouteTimeAxisView* new_time_axis_view = 0;
1585 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1586 PlaylistMapping playlist_mapping;
1588 int32_t divisor = current_music_divisor (_primary->region()->position() - drag_delta, ev_state);
1589 TempoMap& tmap (_editor->session()->tempo_map());
1590 double qn_delta = _primary->region()->quarter_note() - tmap.exact_qn_at_frame (_primary->region()->position() - drag_delta, divisor);
1592 std::set<boost::shared_ptr<const Region> > uniq;
1593 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1595 RegionView* rv = i->view;
1596 RouteTimeAxisView* dest_rtv = 0;
1598 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1603 if (uniq.find (rv->region()) != uniq.end()) {
1604 /* prevent duplicate moves when selecting regions from shared playlists */
1608 uniq.insert(rv->region());
1610 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1611 /* dragged to drop zone */
1613 PlaylistMapping::iterator pm;
1615 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1616 /* first region from this original playlist: create a new track */
1617 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1618 if(!new_time_axis_view) { // New track creation failed
1622 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1623 dest_rtv = new_time_axis_view;
1625 /* we already created a new track for regions from this playlist, use it */
1626 dest_rtv = pm->second;
1630 /* destination time axis view is the one we dragged to */
1631 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1636 double const dest_layer = i->layer;
1638 views_to_update.insert (dest_rtv);
1642 if (changed_position && !_x_constrained) {
1643 where = rv->region()->position() - drag_delta;
1645 where = rv->region()->position();
1648 if (changed_tracks) {
1650 /* insert into new playlist */
1651 RegionView* new_view;
1652 if (rv == _primary) {
1653 new_view = insert_region_into_playlist (
1654 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1655 modified_playlists, current_music_divisor (where, ev_state)
1658 if (rv->region()->position_lock_style() == AudioTime) {
1660 new_view = insert_region_into_playlist (
1661 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1662 modified_playlists, 0
1665 where = tmap.frame_at_quarter_note (rv->region()->quarter_note() - qn_delta);
1666 new_view = insert_region_into_playlist (
1667 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1668 modified_playlists, 0
1673 if (new_view == 0) {
1678 new_views.push_back (new_view);
1680 /* remove from old playlist */
1682 /* the region that used to be in the old playlist is not
1683 moved to the new one - we use a copy of it. as a result,
1684 any existing editor for the region should no longer be
1687 rv->hide_region_editor();
1690 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1694 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1696 /* this movement may result in a crossfade being modified, or a layering change,
1697 so we need to get undo data from the playlist as well as the region.
1700 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1702 playlist->clear_changes ();
1705 rv->region()->clear_changes ();
1708 motion on the same track. plonk the previously reparented region
1709 back to its original canvas group (its streamview).
1710 No need to do anything for copies as they are fake regions which will be deleted.
1713 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1714 rv->get_canvas_group()->set_y_position (i->initial_y);
1717 /* just change the model */
1718 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1719 playlist->set_layer (rv->region(), dest_layer);
1722 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1724 r = frozen_playlists.insert (playlist);
1727 playlist->freeze ();
1729 if (rv == _primary) {
1730 rv->region()->set_position (where, current_music_divisor (where, ev_state));
1732 if (rv->region()->position_lock_style() == AudioTime) {
1733 rv->region()->set_position (where, 0);
1735 rv->region()->set_position (tmap.frame_at_quarter_note (rv->region()->quarter_note() - qn_delta), 0);
1739 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1742 if (changed_tracks) {
1744 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1745 was selected in all of them, then removing it from a playlist will have removed all
1746 trace of it from _views (i.e. there were N regions selected, we removed 1,
1747 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1748 corresponding regionview, and _views is now empty).
1750 This could have invalidated any and all iterators into _views.
1752 The heuristic we use here is: if the region selection is empty, break out of the loop
1753 here. if the region selection is not empty, then restart the loop because we know that
1754 we must have removed at least the region(view) we've just been working on as well as any
1755 that we processed on previous iterations.
1757 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1758 we can just iterate.
1762 if (_views.empty()) {
1773 /* If we've created new regions either by copying or moving
1774 to a new track, we want to replace the old selection with the new ones
1777 if (new_views.size() > 0) {
1778 _editor->selection->set (new_views);
1781 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1785 /* write commands for the accumulated diffs for all our modified playlists */
1786 add_stateful_diff_commands_for_playlists (modified_playlists);
1787 /* applies to _brushing */
1788 _editor->commit_reversible_command ();
1790 /* We have futzed with the layering of canvas items on our streamviews.
1791 If any region changed layer, this will have resulted in the stream
1792 views being asked to set up their region views, and all will be well.
1793 If not, we might now have badly-ordered region views. Ask the StreamViews
1794 involved to sort themselves out, just in case.
1797 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1798 (*i)->view()->playlist_layered ((*i)->track ());
1802 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1803 * @param region Region to remove.
1804 * @param playlist playlist To remove from.
1805 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1806 * that clear_changes () is only called once per playlist.
1809 RegionMoveDrag::remove_region_from_playlist (
1810 boost::shared_ptr<Region> region,
1811 boost::shared_ptr<Playlist> playlist,
1812 PlaylistSet& modified_playlists
1815 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1818 playlist->clear_changes ();
1821 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1825 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1826 * clearing the playlist's diff history first if necessary.
1827 * @param region Region to insert.
1828 * @param dest_rtv Destination RouteTimeAxisView.
1829 * @param dest_layer Destination layer.
1830 * @param where Destination position.
1831 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1832 * that clear_changes () is only called once per playlist.
1833 * @return New RegionView, or 0 if no insert was performed.
1836 RegionMoveDrag::insert_region_into_playlist (
1837 boost::shared_ptr<Region> region,
1838 RouteTimeAxisView* dest_rtv,
1841 PlaylistSet& modified_playlists,
1842 const int32_t sub_num
1845 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1846 if (!dest_playlist) {
1850 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1851 _new_region_view = 0;
1852 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1854 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1855 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1857 dest_playlist->clear_changes ();
1859 dest_playlist->add_region (region, where, 1.0, false, sub_num);
1861 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1862 dest_playlist->set_layer (region, dest_layer);
1867 assert (_new_region_view);
1869 return _new_region_view;
1873 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1875 _new_region_view = rv;
1879 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1881 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1882 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1884 _editor->session()->add_command (c);
1893 RegionMoveDrag::aborted (bool movement_occurred)
1897 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1898 list<DraggingView>::const_iterator next = i;
1907 RegionMotionDrag::aborted (movement_occurred);
1912 RegionMotionDrag::aborted (bool)
1914 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1916 StreamView* sview = (*i)->view();
1919 if (sview->layer_display() == Expanded) {
1920 sview->set_layer_display (Stacked);
1925 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1926 RegionView* rv = i->view;
1927 TimeAxisView* tv = &(rv->get_time_axis_view ());
1928 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1930 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1931 rv->get_canvas_group()->set_y_position (0);
1933 rv->move (-_total_x_delta, 0);
1934 rv->set_height (rtv->view()->child_height ());
1938 /** @param b true to brush, otherwise false.
1939 * @param c true to make copies of the regions being moved, otherwise false.
1941 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1942 : RegionMotionDrag (e, i, p, v, b)
1944 , _new_region_view (0)
1946 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1949 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1950 if (rtv && rtv->is_track()) {
1951 speed = rtv->track()->speed ();
1954 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1958 RegionMoveDrag::setup_pointer_frame_offset ()
1960 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1963 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1964 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1966 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1968 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1969 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1971 _primary = v->view()->create_region_view (r, false, false);
1973 _primary->get_canvas_group()->show ();
1974 _primary->set_position (pos, 0);
1975 _views.push_back (DraggingView (_primary, this, v));
1977 _last_frame_position = pos;
1979 _item = _primary->get_canvas_group ();
1983 RegionInsertDrag::finished (GdkEvent * event, bool)
1985 int pos = _views.front().time_axis_view;
1986 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1988 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1990 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1991 _primary->get_canvas_group()->set_y_position (0);
1993 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1995 _editor->begin_reversible_command (Operations::insert_region);
1996 playlist->clear_changes ();
1997 playlist->add_region (_primary->region (), _last_frame_position);
1999 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2000 if (Config->get_edit_mode() == Ripple) {
2001 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
2004 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2005 _editor->commit_reversible_command ();
2013 RegionInsertDrag::aborted (bool)
2020 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2021 : RegionMoveDrag (e, i, p, v, false, false)
2023 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2026 struct RegionSelectionByPosition {
2027 bool operator() (RegionView*a, RegionView* b) {
2028 return a->region()->position () < b->region()->position();
2033 RegionSpliceDrag::motion (GdkEvent* event, bool)
2035 /* Which trackview is this ? */
2037 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2038 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2040 /* The region motion is only processed if the pointer is over
2044 if (!tv || !tv->is_track()) {
2045 /* To make sure we hide the verbose canvas cursor when the mouse is
2046 not held over an audio track.
2048 _editor->verbose_cursor()->hide ();
2051 _editor->verbose_cursor()->show ();
2056 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2062 RegionSelection copy;
2063 _editor->selection->regions.by_position(copy);
2065 framepos_t const pf = adjusted_current_frame (event);
2067 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2069 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2075 boost::shared_ptr<Playlist> playlist;
2077 if ((playlist = atv->playlist()) == 0) {
2081 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2086 if (pf < (*i)->region()->last_frame() + 1) {
2090 if (pf > (*i)->region()->first_frame()) {
2096 playlist->shuffle ((*i)->region(), dir);
2101 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2103 RegionMoveDrag::finished (event, movement_occurred);
2107 RegionSpliceDrag::aborted (bool)
2117 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2120 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
2122 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2123 RegionSelection to_ripple;
2124 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2125 if ((*i)->position() >= where) {
2126 to_ripple.push_back (rtv->view()->find_view(*i));
2130 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2131 if (!exclude.contains (*i)) {
2132 // the selection has already been added to _views
2134 if (drag_in_progress) {
2135 // do the same things that RegionMotionDrag::motion does when
2136 // first_move is true, for the region views that we're adding
2137 // to _views this time
2140 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2141 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2142 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2143 rvg->reparent (_editor->_drag_motion_group);
2145 // we only need to move in the y direction
2146 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2151 _views.push_back (DraggingView (*i, this, tav));
2157 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2160 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2161 // we added all the regions after the selection
2163 std::list<DraggingView>::iterator to_erase = i++;
2164 if (!_editor->selection->regions.contains (to_erase->view)) {
2165 // restore the non-selected regions to their original playlist & positions,
2166 // and then ripple them back by the length of the regions that were dragged away
2167 // do the same things as RegionMotionDrag::aborted
2169 RegionView *rv = to_erase->view;
2170 TimeAxisView* tv = &(rv->get_time_axis_view ());
2171 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2174 // plonk them back onto their own track
2175 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2176 rv->get_canvas_group()->set_y_position (0);
2180 // move the underlying region to match the view
2181 rv->region()->set_position (rv->region()->position() + amount);
2183 // restore the view to match the underlying region's original position
2184 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2187 rv->set_height (rtv->view()->child_height ());
2188 _views.erase (to_erase);
2194 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2196 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2198 return allow_moves_across_tracks;
2206 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2207 : RegionMoveDrag (e, i, p, v, false, false)
2209 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2210 // compute length of selection
2211 RegionSelection selected_regions = _editor->selection->regions;
2212 selection_length = selected_regions.end_frame() - selected_regions.start();
2214 // we'll only allow dragging to another track in ripple mode if all the regions
2215 // being dragged start off on the same track
2216 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2219 exclude = new RegionList;
2220 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2221 exclude->push_back((*i)->region());
2224 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2225 RegionSelection copy;
2226 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2228 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2229 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2231 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2232 // find ripple start point on each applicable playlist
2233 RegionView *first_selected_on_this_track = NULL;
2234 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2235 if ((*i)->region()->playlist() == (*pi)) {
2236 // region is on this playlist - it's the first, because they're sorted
2237 first_selected_on_this_track = *i;
2241 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2242 add_all_after_to_views (
2243 &first_selected_on_this_track->get_time_axis_view(),
2244 first_selected_on_this_track->region()->position(),
2245 selected_regions, false);
2248 if (allow_moves_across_tracks) {
2249 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2257 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2259 /* Which trackview is this ? */
2261 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2262 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2264 /* The region motion is only processed if the pointer is over
2268 if (!tv || !tv->is_track()) {
2269 /* To make sure we hide the verbose canvas cursor when the mouse is
2270 not held over an audiotrack.
2272 _editor->verbose_cursor()->hide ();
2276 framepos_t where = adjusted_current_frame (event);
2277 assert (where >= 0);
2279 double delta = compute_x_delta (event, &after);
2281 framecnt_t amount = _editor->pixel_to_sample (delta);
2283 if (allow_moves_across_tracks) {
2284 // all the originally selected regions were on the same track
2286 framecnt_t adjust = 0;
2287 if (prev_tav && tv != prev_tav) {
2288 // dragged onto a different track
2289 // remove the unselected regions from _views, restore them to their original positions
2290 // and add the regions after the drop point on the new playlist to _views instead.
2291 // undo the effect of rippling the previous playlist, and include the effect of removing
2292 // the dragged region(s) from this track
2294 remove_unselected_from_views (prev_amount, false);
2295 // ripple previous playlist according to the regions that have been removed onto the new playlist
2296 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2299 // move just the selected regions
2300 RegionMoveDrag::motion(event, first_move);
2302 // ensure that the ripple operation on the new playlist inserts selection_length time
2303 adjust = selection_length;
2304 // ripple the new current playlist
2305 tv->playlist()->ripple (where, amount+adjust, exclude);
2307 // add regions after point where drag entered this track to subsequent ripples
2308 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2311 // motion on same track
2312 RegionMoveDrag::motion(event, first_move);
2316 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2317 prev_position = where;
2319 // selection encompasses multiple tracks - just drag
2320 // cross-track drags are forbidden
2321 RegionMoveDrag::motion(event, first_move);
2324 if (!_x_constrained) {
2325 prev_amount += amount;
2328 _last_frame_position = after;
2332 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2334 if (!movement_occurred) {
2338 if (was_double_click() && !_views.empty()) {
2339 DraggingView dv = _views.front();
2340 _editor->edit_region (dv.view);
2346 _editor->begin_reversible_command(_("Ripple drag"));
2348 // remove the regions being rippled from the dragging view, updating them to
2349 // their new positions
2350 remove_unselected_from_views (prev_amount, true);
2352 if (allow_moves_across_tracks) {
2354 // if regions were dragged across tracks, we've rippled any later
2355 // regions on the track the regions were dragged off, so we need
2356 // to add the original track to the undo record
2357 orig_tav->playlist()->clear_changes();
2358 vector<Command*> cmds;
2359 orig_tav->playlist()->rdiff (cmds);
2360 _editor->session()->add_commands (cmds);
2362 if (prev_tav && prev_tav != orig_tav) {
2363 prev_tav->playlist()->clear_changes();
2364 vector<Command*> cmds;
2365 prev_tav->playlist()->rdiff (cmds);
2366 _editor->session()->add_commands (cmds);
2369 // selection spanned multiple tracks - all will need adding to undo record
2371 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2372 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2374 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2375 (*pi)->clear_changes();
2376 vector<Command*> cmds;
2377 (*pi)->rdiff (cmds);
2378 _editor->session()->add_commands (cmds);
2382 // other modified playlists are added to undo by RegionMoveDrag::finished()
2383 RegionMoveDrag::finished (event, movement_occurred);
2384 _editor->commit_reversible_command();
2388 RegionRippleDrag::aborted (bool movement_occurred)
2390 RegionMoveDrag::aborted (movement_occurred);
2395 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2397 _view (dynamic_cast<MidiTimeAxisView*> (v))
2399 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2405 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2408 _editor->begin_reversible_command (_("create region"));
2409 _region = add_midi_region (_view, false, _editor->get_grid_music_divisions (event->button.state));
2410 _view->playlist()->freeze ();
2413 framepos_t const f = adjusted_current_frame (event);
2414 if (f < grab_frame()) {
2415 _region->set_initial_position (f);
2418 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2419 so that if this region is duplicated, its duplicate starts on
2420 a snap point rather than 1 frame after a snap point. Otherwise things get
2421 a bit confusing as if a region starts 1 frame after a snap point, one cannot
2422 place snapped notes at the start of the region.
2425 framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2426 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2432 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2434 if (!movement_occurred) {
2435 add_midi_region (_view, true, _editor->get_grid_music_divisions (event->button.state));
2437 _view->playlist()->thaw ();
2438 _editor->commit_reversible_command();
2443 RegionCreateDrag::aborted (bool)
2446 _view->playlist()->thaw ();
2452 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2457 , _was_selected (false)
2460 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2464 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2466 Gdk::Cursor* cursor;
2467 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2469 float x_fraction = cnote->mouse_x_fraction ();
2471 if (x_fraction > 0.0 && x_fraction < 0.25) {
2472 cursor = _editor->cursors()->left_side_trim;
2475 cursor = _editor->cursors()->right_side_trim;
2479 Drag::start_grab (event, cursor);
2481 region = &cnote->region_view();
2484 temp = region->snap_to_pixel (cnote->x0 (), true);
2485 _snap_delta = temp - cnote->x0 ();
2489 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2494 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2495 if (ms.size() > 1) {
2496 /* has to be relative, may make no sense otherwise */
2500 if (!(_was_selected = cnote->selected())) {
2502 /* tertiary-click means extend selection - we'll do that on button release,
2503 so don't add it here, because otherwise we make it hard to figure
2504 out the "extend-to" range.
2507 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2510 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2513 region->note_selected (cnote, true);
2515 _editor->get_selection().clear_points();
2516 region->unique_select (cnote);
2523 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2525 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2527 _editor->begin_reversible_command (_("resize notes"));
2529 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2530 MidiRegionSelection::iterator next;
2533 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2535 mrv->begin_resizing (at_front);
2541 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2542 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2544 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2548 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2550 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2551 if (_editor->snap_mode () != SnapOff) {
2555 if (_editor->snap_mode () == SnapOff) {
2557 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2558 if (apply_snap_delta) {
2564 if (apply_snap_delta) {
2568 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2574 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2576 if (!movement_occurred) {
2577 /* no motion - select note */
2578 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2579 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2580 _editor->current_mouse_mode() == Editing::MouseDraw) {
2582 bool changed = false;
2584 if (_was_selected) {
2585 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2587 region->note_deselected (cnote);
2590 _editor->get_selection().clear_points();
2591 region->unique_select (cnote);
2595 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2596 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2598 if (!extend && !add && region->selection_size() > 1) {
2599 _editor->get_selection().clear_points();
2600 region->unique_select (cnote);
2602 } else if (extend) {
2603 region->note_selected (cnote, true, true);
2606 /* it was added during button press */
2612 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2613 _editor->commit_reversible_selection_op();
2620 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2621 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2622 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2624 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2627 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2629 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2630 if (_editor->snap_mode () != SnapOff) {
2634 if (_editor->snap_mode () == SnapOff) {
2636 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2637 if (apply_snap_delta) {
2643 if (apply_snap_delta) {
2647 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2651 _editor->commit_reversible_command ();
2655 NoteResizeDrag::aborted (bool)
2657 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2658 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2659 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2661 mrv->abort_resizing ();
2666 AVDraggingView::AVDraggingView (RegionView* v)
2669 initial_position = v->region()->position ();
2672 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2675 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2678 TrackViewList empty;
2680 _editor->get_regions_after(rs, (framepos_t) 0, empty);
2681 std::list<RegionView*> views = rs.by_layer();
2684 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2685 RegionView* rv = (*i);
2686 if (!rv->region()->video_locked()) {
2689 if (rv->region()->locked()) {
2692 _views.push_back (AVDraggingView (rv));
2697 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2699 Drag::start_grab (event);
2700 if (_editor->session() == 0) {
2704 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2710 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2714 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2715 _max_backwards_drag = (
2716 ARDOUR_UI::instance()->video_timeline->get_duration()
2717 + ARDOUR_UI::instance()->video_timeline->get_offset()
2718 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2721 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2722 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2723 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2726 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2729 Timecode::Time timecode;
2730 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2731 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);
2732 show_verbose_cursor_text (buf);
2736 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2738 if (_editor->session() == 0) {
2741 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2745 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2749 framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2750 dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2752 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2753 dt = - _max_backwards_drag;
2756 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2757 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2759 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2760 RegionView* rv = i->view;
2761 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2764 rv->region()->clear_changes ();
2765 rv->region()->suspend_property_changes();
2767 rv->region()->set_position(i->initial_position + dt);
2768 rv->region_changed(ARDOUR::Properties::position);
2771 const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2772 Timecode::Time timecode;
2773 Timecode::Time timediff;
2775 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2776 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2777 snprintf (buf, sizeof (buf),
2778 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2779 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2780 , _("Video Start:"),
2781 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2783 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2785 show_verbose_cursor_text (buf);
2789 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2791 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2798 if (!movement_occurred || ! _editor->session()) {
2802 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2804 _editor->begin_reversible_command (_("Move Video"));
2806 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2807 ARDOUR_UI::instance()->video_timeline->save_undo();
2808 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2809 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2811 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2812 i->view->drag_end();
2813 i->view->region()->resume_property_changes ();
2815 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2818 _editor->session()->maybe_update_session_range(
2819 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2820 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2824 _editor->commit_reversible_command ();
2828 VideoTimeLineDrag::aborted (bool)
2830 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2833 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2834 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2836 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2837 i->view->region()->resume_property_changes ();
2838 i->view->region()->set_position(i->initial_position);
2842 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2843 : RegionDrag (e, i, p, v)
2844 , _operation (StartTrim)
2845 , _preserve_fade_anchor (preserve_fade_anchor)
2846 , _jump_position_when_done (false)
2848 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2852 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2855 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2856 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2858 if (tv && tv->is_track()) {
2859 speed = tv->track()->speed();
2862 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2863 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2864 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2866 framepos_t const pf = adjusted_current_frame (event);
2867 setup_snap_delta (region_start);
2869 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2870 /* Move the contents of the region around without changing the region bounds */
2871 _operation = ContentsTrim;
2872 Drag::start_grab (event, _editor->cursors()->trimmer);
2874 /* These will get overridden for a point trim.*/
2875 if (pf < (region_start + region_length/2)) {
2876 /* closer to front */
2877 _operation = StartTrim;
2878 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2879 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2881 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2885 _operation = EndTrim;
2886 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2887 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2889 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2893 /* jump trim disabled for now
2894 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2895 _jump_position_when_done = true;
2899 switch (_operation) {
2901 show_verbose_cursor_time (region_start);
2904 show_verbose_cursor_duration (region_start, region_end);
2907 show_verbose_cursor_time (pf);
2911 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2912 i->view->region()->suspend_property_changes ();
2917 TrimDrag::motion (GdkEvent* event, bool first_move)
2919 RegionView* rv = _primary;
2922 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2923 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2924 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2925 frameoffset_t frame_delta = 0;
2927 if (tv && tv->is_track()) {
2928 speed = tv->track()->speed();
2930 framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
2931 framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state);
2937 switch (_operation) {
2939 trim_type = "Region start trim";
2942 trim_type = "Region end trim";
2945 trim_type = "Region content trim";
2952 _editor->begin_reversible_command (trim_type);
2954 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2955 RegionView* rv = i->view;
2956 rv->region()->playlist()->clear_owned_changes ();
2958 if (_operation == StartTrim) {
2959 rv->trim_front_starting ();
2962 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2965 arv->temporarily_hide_envelope ();
2969 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2970 insert_result = _editor->motion_frozen_playlists.insert (pl);
2972 if (insert_result.second) {
2978 bool non_overlap_trim = false;
2980 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
2981 non_overlap_trim = true;
2984 /* contstrain trim to fade length */
2985 if (_preserve_fade_anchor) {
2986 switch (_operation) {
2988 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2989 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2991 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2992 if (ar->locked()) continue;
2993 framecnt_t len = ar->fade_in()->back()->when;
2994 if (len < dt) dt = min(dt, len);
2998 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2999 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3001 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3002 if (ar->locked()) continue;
3003 framecnt_t len = ar->fade_out()->back()->when;
3004 if (len < -dt) dt = max(dt, -len);
3012 int32_t divisions = current_music_divisor (adj_frame, event->button.state);
3014 switch (_operation) {
3016 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3017 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3020 if (changed && _preserve_fade_anchor) {
3021 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3023 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3024 framecnt_t len = ar->fade_in()->back()->when;
3025 framecnt_t diff = ar->first_frame() - i->initial_position;
3026 framepos_t new_length = len - diff;
3027 i->anchored_fade_length = min (ar->length(), new_length);
3028 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3029 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3036 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3037 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, divisions);
3038 if (changed && _preserve_fade_anchor) {
3039 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3041 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3042 framecnt_t len = ar->fade_out()->back()->when;
3043 framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
3044 framepos_t new_length = len + diff;
3045 i->anchored_fade_length = min (ar->length(), new_length);
3046 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3047 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3055 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
3057 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3058 i->view->move_contents (frame_delta);
3064 switch (_operation) {
3066 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
3069 show_verbose_cursor_duration ((framepos_t) rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
3072 // show_verbose_cursor_time (frame_delta);
3078 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3080 if (movement_occurred) {
3081 motion (event, false);
3083 if (_operation == StartTrim) {
3084 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3086 /* This must happen before the region's StatefulDiffCommand is created, as it may
3087 `correct' (ahem) the region's _start from being negative to being zero. It
3088 needs to be zero in the undo record.
3090 i->view->trim_front_ending ();
3092 if (_preserve_fade_anchor) {
3093 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3095 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3096 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3097 ar->set_fade_in_length(i->anchored_fade_length);
3098 ar->set_fade_in_active(true);
3101 if (_jump_position_when_done) {
3102 i->view->region()->set_position (i->initial_position);
3105 } else if (_operation == EndTrim) {
3106 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3107 if (_preserve_fade_anchor) {
3108 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3110 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3111 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3112 ar->set_fade_out_length(i->anchored_fade_length);
3113 ar->set_fade_out_active(true);
3116 if (_jump_position_when_done) {
3117 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3122 if (!_editor->selection->selected (_primary)) {
3123 _primary->thaw_after_trim ();
3126 set<boost::shared_ptr<Playlist> > diffed_playlists;
3128 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3129 i->view->thaw_after_trim ();
3130 i->view->enable_display (true);
3132 /* Trimming one region may affect others on the playlist, so we need
3133 to get undo Commands from the whole playlist rather than just the
3134 region. Use diffed_playlists to make sure we don't diff a given
3135 playlist more than once.
3137 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
3138 if (diffed_playlists.find (p) == diffed_playlists.end()) {
3139 vector<Command*> cmds;
3141 _editor->session()->add_commands (cmds);
3142 diffed_playlists.insert (p);
3147 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3151 _editor->motion_frozen_playlists.clear ();
3152 _editor->commit_reversible_command();
3155 /* no mouse movement */
3156 if (adjusted_current_frame (event) != adjusted_frame (_drags->current_pointer_frame(), event, false)) {
3157 _editor->point_trim (event, adjusted_current_frame (event));
3161 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3162 i->view->region()->resume_property_changes ();
3167 TrimDrag::aborted (bool movement_occurred)
3169 /* Our motion method is changing model state, so use the Undo system
3170 to cancel. Perhaps not ideal, as this will leave an Undo point
3171 behind which may be slightly odd from the user's point of view.
3175 finished (&ev, true);
3177 if (movement_occurred) {
3178 _editor->session()->undo (1);
3181 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3182 i->view->region()->resume_property_changes ();
3187 TrimDrag::setup_pointer_frame_offset ()
3189 list<DraggingView>::iterator i = _views.begin ();
3190 while (i != _views.end() && i->view != _primary) {
3194 if (i == _views.end()) {
3198 switch (_operation) {
3200 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
3203 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
3210 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3213 , _old_snap_type (e->snap_type())
3214 , _old_snap_mode (e->snap_mode())
3217 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3218 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3220 _real_section = &_marker->meter();
3225 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3227 Drag::start_grab (event, cursor);
3228 show_verbose_cursor_time (adjusted_current_frame(event));
3232 MeterMarkerDrag::setup_pointer_frame_offset ()
3234 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
3238 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3241 // create a dummy marker to catch events, then hide it.
3244 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3246 _marker = new MeterMarker (
3248 *_editor->meter_group,
3249 UIConfiguration::instance().color ("meter marker"),
3251 *new MeterSection (_marker->meter())
3254 /* use the new marker for the grab */
3255 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3258 TempoMap& map (_editor->session()->tempo_map());
3259 /* get current state */
3260 before_state = &map.get_state();
3263 _editor->begin_reversible_command (_("move meter mark"));
3265 _editor->begin_reversible_command (_("copy meter mark"));
3267 Timecode::BBT_Time bbt = _real_section->bbt();
3269 /* we can't add a meter where one currently exists */
3270 if (_real_section->frame() < adjusted_current_frame (event, false)) {
3275 const double beat = map.beat_at_bbt (bbt);
3276 const framepos_t frame = map.frame_at_beat (beat);
3277 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3278 , beat, bbt, frame, _real_section->position_lock_style());
3279 if (!_real_section) {
3285 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3286 if (_real_section->position_lock_style() != AudioTime) {
3287 _editor->set_snap_to (SnapToBar);
3288 _editor->set_snap_mode (SnapNormal);
3292 framepos_t pf = adjusted_current_frame (event);
3294 if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3295 /* never snap to music for audio locked */
3296 pf = adjusted_current_frame (event, false);
3299 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3301 /* fake marker meeds to stay under the mouse, unlike the real one. */
3302 _marker->set_position (adjusted_current_frame (event, false));
3304 show_verbose_cursor_time (_real_section->frame());
3308 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3310 if (!movement_occurred) {
3311 if (was_double_click()) {
3312 _editor->edit_meter_marker (*_marker);
3317 /* reinstate old snap setting */
3318 _editor->set_snap_to (_old_snap_type);
3319 _editor->set_snap_mode (_old_snap_mode);
3321 TempoMap& map (_editor->session()->tempo_map());
3323 XMLNode &after = map.get_state();
3324 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3325 _editor->commit_reversible_command ();
3327 // delete the dummy marker we used for visual representation while moving.
3328 // a new visual marker will show up automatically.
3333 MeterMarkerDrag::aborted (bool moved)
3335 _marker->set_position (_marker->meter().frame ());
3337 /* reinstate old snap setting */
3338 _editor->set_snap_to (_old_snap_type);
3339 _editor->set_snap_mode (_old_snap_mode);
3341 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3342 // delete the dummy marker we used for visual representation while moving.
3343 // a new visual marker will show up automatically.
3348 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3354 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3356 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3357 _real_section = &_marker->tempo();
3358 _movable = !_real_section->initial();
3359 _grab_bpm = _real_section->note_types_per_minute();
3364 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3366 Drag::start_grab (event, cursor);
3367 if (!_real_section->active()) {
3368 show_verbose_cursor_text (_("inactive"));
3370 show_verbose_cursor_time (adjusted_current_frame (event));
3375 TempoMarkerDrag::setup_pointer_frame_offset ()
3377 _pointer_frame_offset = raw_grab_frame() - _real_section->frame();
3381 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3383 if (!_real_section->active()) {
3389 // mvc drag - create a dummy marker to catch events, hide it.
3392 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3394 TempoSection section (_marker->tempo());
3396 _marker = new TempoMarker (
3398 *_editor->tempo_group,
3399 UIConfiguration::instance().color ("tempo marker"),
3401 *new TempoSection (_marker->tempo())
3404 /* use the new marker for the grab */
3405 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3408 TempoMap& map (_editor->session()->tempo_map());
3409 /* get current state */
3410 before_state = &map.get_state();
3413 _editor->begin_reversible_command (_("move tempo mark"));
3416 const Tempo tempo (_marker->tempo());
3417 const framepos_t frame = adjusted_current_frame (event) + 1;
3418 const TempoSection::Type type = _real_section->type();
3420 _editor->begin_reversible_command (_("copy tempo mark"));
3422 if (_real_section->position_lock_style() == MusicTime) {
3423 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3424 _real_section = map.add_tempo (tempo, map.exact_qn_at_frame (frame, divisions), 0, type, MusicTime);
3426 _real_section = map.add_tempo (tempo, 0.0, frame, type, AudioTime);
3429 if (!_real_section) {
3437 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3438 /* use vertical movement to alter tempo .. should be log */
3439 double new_bpm = max (1.5, _grab_bpm + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3441 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type()));
3443 show_verbose_cursor_text (strs.str());
3445 } else if (_movable && !_real_section->locked_to_meter()) {
3448 if (_editor->snap_musical()) {
3449 /* we can't snap to a grid that we are about to move.
3450 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3452 pf = adjusted_current_frame (event, false);
3454 pf = adjusted_current_frame (event);
3457 TempoMap& map (_editor->session()->tempo_map());
3459 /* snap to beat is 1, snap to bar is -1 (sorry) */
3460 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3462 map.gui_set_tempo_position (_real_section, pf, sub_num);
3464 show_verbose_cursor_time (_real_section->frame());
3466 _marker->set_position (adjusted_current_frame (event, false));
3470 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3472 if (!_real_section->active()) {
3475 if (!movement_occurred) {
3476 if (was_double_click()) {
3477 _editor->edit_tempo_marker (*_marker);
3482 TempoMap& map (_editor->session()->tempo_map());
3484 XMLNode &after = map.get_state();
3485 _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3486 _editor->commit_reversible_command ();
3488 // delete the dummy marker we used for visual representation while moving.
3489 // a new visual marker will show up automatically.
3494 TempoMarkerDrag::aborted (bool moved)
3496 _marker->set_position (_marker->tempo().frame());
3498 TempoMap& map (_editor->session()->tempo_map());
3499 map.set_state (*before_state, Stateful::current_state_version);
3500 // delete the dummy (hidden) marker we used for events while moving.
3505 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3511 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3516 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3518 Drag::start_grab (event, cursor);
3519 TempoMap& map (_editor->session()->tempo_map());
3520 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
3523 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).note_types_per_minute() << "\n";
3524 sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3525 show_verbose_cursor_text (sstr.str());
3526 finished (event, false);
3530 BBTRulerDrag::setup_pointer_frame_offset ()
3532 TempoMap& map (_editor->session()->tempo_map());
3533 const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame()));
3534 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3537 if (divisions > 0) {
3538 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions);
3540 /* while it makes some sense for the user to determine the division to 'grab',
3541 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3542 and the result over steep tempo curves. Use sixteenths.
3544 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4);
3547 _grab_qn = map.quarter_note_at_beat (beat);
3549 _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn);
3554 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3556 TempoMap& map (_editor->session()->tempo_map());
3559 /* get current state */
3560 before_state = &map.get_state();
3561 _editor->begin_reversible_command (_("stretch tempo"));
3566 if (_editor->snap_musical()) {
3567 pf = adjusted_current_frame (event, false);
3569 pf = adjusted_current_frame (event);
3572 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3573 /* adjust previous tempo to match pointer frame */
3574 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.frame_at_quarter_note (_grab_qn), pf);
3577 sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute() << "\n";
3578 sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3579 show_verbose_cursor_text (sstr.str());
3583 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3585 if (!movement_occurred) {
3589 TempoMap& map (_editor->session()->tempo_map());
3591 XMLNode &after = map.get_state();
3592 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3593 _editor->commit_reversible_command ();
3597 BBTRulerDrag::aborted (bool moved)
3600 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3605 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3606 : Drag (e, &c.track_canvas_item(), false)
3611 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3614 /** Do all the things we do when dragging the playhead to make it look as though
3615 * we have located, without actually doing the locate (because that would cause
3616 * the diskstream buffers to be refilled, which is too slow).
3619 CursorDrag::fake_locate (framepos_t t)
3621 if (_editor->session () == 0) {
3625 _editor->playhead_cursor->set_position (t);
3627 Session* s = _editor->session ();
3628 if (s->timecode_transmission_suspended ()) {
3629 framepos_t const f = _editor->playhead_cursor->current_frame ();
3630 /* This is asynchronous so it will be sent "now"
3632 s->send_mmc_locate (f);
3633 /* These are synchronous and will be sent during the next
3636 s->queue_full_time_code ();
3637 s->queue_song_position_pointer ();
3640 show_verbose_cursor_time (t);
3641 _editor->UpdateAllTransportClocks (t);
3645 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3647 Drag::start_grab (event, c);
3648 setup_snap_delta (_editor->playhead_cursor->current_frame ());
3650 _grab_zoom = _editor->samples_per_pixel;
3652 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3654 _editor->snap_to_with_modifier (where, event);
3656 _editor->_dragging_playhead = true;
3658 Session* s = _editor->session ();
3660 /* grab the track canvas item as well */
3662 _cursor.track_canvas_item().grab();
3665 if (_was_rolling && _stop) {
3669 if (s->is_auditioning()) {
3670 s->cancel_audition ();
3674 if (AudioEngine::instance()->connected()) {
3676 /* do this only if we're the engine is connected
3677 * because otherwise this request will never be
3678 * serviced and we'll busy wait forever. likewise,
3679 * notice if we are disconnected while waiting for the
3680 * request to be serviced.
3683 s->request_suspend_timecode_transmission ();
3684 while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3685 /* twiddle our thumbs */
3690 fake_locate (where - snap_delta (event->button.state));
3694 CursorDrag::motion (GdkEvent* event, bool)
3696 framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3697 _editor->snap_to_with_modifier (where, event);
3698 if (where != last_pointer_frame()) {
3699 fake_locate (where - snap_delta (event->button.state));
3704 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3706 _editor->_dragging_playhead = false;
3708 _cursor.track_canvas_item().ungrab();
3710 if (!movement_occurred && _stop) {
3714 motion (event, false);
3716 Session* s = _editor->session ();
3718 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3719 _editor->_pending_locate_request = true;
3720 s->request_resume_timecode_transmission ();
3725 CursorDrag::aborted (bool)
3727 _cursor.track_canvas_item().ungrab();
3729 if (_editor->_dragging_playhead) {
3730 _editor->session()->request_resume_timecode_transmission ();
3731 _editor->_dragging_playhead = false;
3734 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3737 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3738 : RegionDrag (e, i, p, v)
3740 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3744 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3746 Drag::start_grab (event, cursor);
3748 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3749 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3750 setup_snap_delta (r->position ());
3752 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3756 FadeInDrag::setup_pointer_frame_offset ()
3758 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3759 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3760 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3764 FadeInDrag::motion (GdkEvent* event, bool)
3766 framecnt_t fade_length;
3768 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3769 _editor->snap_to_with_modifier (pos, event);
3770 pos -= snap_delta (event->button.state);
3772 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3774 if (pos < (region->position() + 64)) {
3775 fade_length = 64; // this should be a minimum defined somewhere
3776 } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3777 fade_length = region->length() - region->fade_out()->back()->when - 1;
3779 fade_length = pos - region->position();
3782 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3784 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3790 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3793 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3797 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3799 if (!movement_occurred) {
3803 framecnt_t fade_length;
3804 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3805 _editor->snap_to_with_modifier (pos, event);
3806 pos -= snap_delta (event->button.state);
3808 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3810 if (pos < (region->position() + 64)) {
3811 fade_length = 64; // this should be a minimum defined somewhere
3812 } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3813 fade_length = region->length() - region->fade_out()->back()->when - 1;
3815 fade_length = pos - region->position();
3818 bool in_command = false;
3820 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3822 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3828 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3829 XMLNode &before = alist->get_state();
3831 tmp->audio_region()->set_fade_in_length (fade_length);
3832 tmp->audio_region()->set_fade_in_active (true);
3835 _editor->begin_reversible_command (_("change fade in length"));
3838 XMLNode &after = alist->get_state();
3839 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3843 _editor->commit_reversible_command ();
3848 FadeInDrag::aborted (bool)
3850 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3851 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3857 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3861 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3862 : RegionDrag (e, i, p, v)
3864 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3868 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3870 Drag::start_grab (event, cursor);
3872 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3873 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3874 setup_snap_delta (r->last_frame ());
3876 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3880 FadeOutDrag::setup_pointer_frame_offset ()
3882 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3883 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3884 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3888 FadeOutDrag::motion (GdkEvent* event, bool)
3890 framecnt_t fade_length;
3892 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3893 _editor->snap_to_with_modifier (pos, event);
3894 pos -= snap_delta (event->button.state);
3896 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3898 if (pos > (region->last_frame() - 64)) {
3899 fade_length = 64; // this should really be a minimum fade defined somewhere
3900 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3901 fade_length = region->length() - region->fade_in()->back()->when - 1;
3903 fade_length = region->last_frame() - pos;
3906 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3908 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3914 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3917 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3921 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3923 if (!movement_occurred) {
3927 framecnt_t fade_length;
3929 framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3930 _editor->snap_to_with_modifier (pos, event);
3931 pos -= snap_delta (event->button.state);
3933 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3935 if (pos > (region->last_frame() - 64)) {
3936 fade_length = 64; // this should really be a minimum fade defined somewhere
3937 } else if (pos <= region->position() + region->fade_in()->back()->when) {
3938 fade_length = region->length() - region->fade_in()->back()->when - 1;
3940 fade_length = region->last_frame() - pos;
3943 bool in_command = false;
3945 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3947 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3953 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3954 XMLNode &before = alist->get_state();
3956 tmp->audio_region()->set_fade_out_length (fade_length);
3957 tmp->audio_region()->set_fade_out_active (true);
3960 _editor->begin_reversible_command (_("change fade out length"));
3963 XMLNode &after = alist->get_state();
3964 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3968 _editor->commit_reversible_command ();
3973 FadeOutDrag::aborted (bool)
3975 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3976 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3982 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3986 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3988 , _selection_changed (false)
3990 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3991 Gtk::Window* toplevel = _editor->current_toplevel();
3992 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
3996 _points.push_back (ArdourCanvas::Duple (0, 0));
3998 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4001 MarkerDrag::~MarkerDrag ()
4003 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4008 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4010 location = new Location (*l);
4011 markers.push_back (m);
4016 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4018 Drag::start_grab (event, cursor);
4022 Location *location = _editor->find_location_from_marker (_marker, is_start);
4023 _editor->_dragging_edit_point = true;
4025 update_item (location);
4027 // _drag_line->show();
4028 // _line->raise_to_top();
4031 show_verbose_cursor_time (location->start());
4033 show_verbose_cursor_time (location->end());
4035 setup_snap_delta (is_start ? location->start() : location->end());
4037 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4040 case Selection::Toggle:
4041 /* we toggle on the button release */
4043 case Selection::Set:
4044 if (!_editor->selection->selected (_marker)) {
4045 _editor->selection->set (_marker);
4046 _selection_changed = true;
4049 case Selection::Extend:
4051 Locations::LocationList ll;
4052 list<ArdourMarker*> to_add;
4054 _editor->selection->markers.range (s, e);
4055 s = min (_marker->position(), s);
4056 e = max (_marker->position(), e);
4059 if (e < max_framepos) {
4062 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4063 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4064 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4067 to_add.push_back (lm->start);
4070 to_add.push_back (lm->end);
4074 if (!to_add.empty()) {
4075 _editor->selection->add (to_add);
4076 _selection_changed = true;
4080 case Selection::Add:
4081 _editor->selection->add (_marker);
4082 _selection_changed = true;
4087 /* Set up copies for us to manipulate during the drag
4090 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4092 Location* l = _editor->find_location_from_marker (*i, is_start);
4099 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4101 /* range: check that the other end of the range isn't
4104 CopiedLocationInfo::iterator x;
4105 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4106 if (*(*x).location == *l) {
4110 if (x == _copied_locations.end()) {
4111 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4113 (*x).markers.push_back (*i);
4114 (*x).move_both = true;
4122 MarkerDrag::setup_pointer_frame_offset ()
4125 Location *location = _editor->find_location_from_marker (_marker, is_start);
4126 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
4130 MarkerDrag::motion (GdkEvent* event, bool)
4132 framecnt_t f_delta = 0;
4134 bool move_both = false;
4135 Location *real_location;
4136 Location *copy_location = 0;
4137 framecnt_t const sd = snap_delta (event->button.state);
4139 framecnt_t const newframe = adjusted_frame (_drags->current_pointer_frame () + sd, event, true) - sd;
4140 framepos_t next = newframe;
4142 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4146 CopiedLocationInfo::iterator x;
4148 /* find the marker we're dragging, and compute the delta */
4150 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4152 copy_location = (*x).location;
4154 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4156 /* this marker is represented by this
4157 * CopiedLocationMarkerInfo
4160 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4165 if (real_location->is_mark()) {
4166 f_delta = newframe - copy_location->start();
4170 switch (_marker->type()) {
4171 case ArdourMarker::SessionStart:
4172 case ArdourMarker::RangeStart:
4173 case ArdourMarker::LoopStart:
4174 case ArdourMarker::PunchIn:
4175 f_delta = newframe - copy_location->start();
4178 case ArdourMarker::SessionEnd:
4179 case ArdourMarker::RangeEnd:
4180 case ArdourMarker::LoopEnd:
4181 case ArdourMarker::PunchOut:
4182 f_delta = newframe - copy_location->end();
4185 /* what kind of marker is this ? */
4194 if (x == _copied_locations.end()) {
4195 /* hmm, impossible - we didn't find the dragged marker */
4199 /* now move them all */
4201 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4203 copy_location = x->location;
4205 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4209 if (real_location->locked()) {
4213 if (copy_location->is_mark()) {
4217 copy_location->set_start (copy_location->start() + f_delta);
4221 framepos_t new_start = copy_location->start() + f_delta;
4222 framepos_t new_end = copy_location->end() + f_delta;
4224 if (is_start) { // start-of-range marker
4226 if (move_both || (*x).move_both) {
4227 copy_location->set_start (new_start);
4228 copy_location->set_end (new_end);
4229 } else if (new_start < copy_location->end()) {
4230 copy_location->set_start (new_start);
4231 } else if (newframe > 0) {
4232 //_editor->snap_to (next, RoundUpAlways, true);
4233 copy_location->set_end (next);
4234 copy_location->set_start (newframe);
4237 } else { // end marker
4239 if (move_both || (*x).move_both) {
4240 copy_location->set_end (new_end);
4241 copy_location->set_start (new_start);
4242 } else if (new_end > copy_location->start()) {
4243 copy_location->set_end (new_end);
4244 } else if (newframe > 0) {
4245 //_editor->snap_to (next, RoundDownAlways, true);
4246 copy_location->set_start (next);
4247 copy_location->set_end (newframe);
4252 update_item (copy_location);
4254 /* now lookup the actual GUI items used to display this
4255 * location and move them to wherever the copy of the location
4256 * is now. This means that the logic in ARDOUR::Location is
4257 * still enforced, even though we are not (yet) modifying
4258 * the real Location itself.
4261 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4264 lm->set_position (copy_location->start(), copy_location->end());
4269 assert (!_copied_locations.empty());
4271 show_verbose_cursor_time (newframe);
4275 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4277 if (!movement_occurred) {
4279 if (was_double_click()) {
4280 _editor->rename_marker (_marker);
4284 /* just a click, do nothing but finish
4285 off the selection process
4288 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4290 case Selection::Set:
4291 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4292 _editor->selection->set (_marker);
4293 _selection_changed = true;
4297 case Selection::Toggle:
4298 /* we toggle on the button release, click only */
4299 _editor->selection->toggle (_marker);
4300 _selection_changed = true;
4304 case Selection::Extend:
4305 case Selection::Add:
4309 if (_selection_changed) {
4310 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4311 _editor->commit_reversible_selection_op();
4317 _editor->_dragging_edit_point = false;
4319 XMLNode &before = _editor->session()->locations()->get_state();
4320 bool in_command = false;
4322 MarkerSelection::iterator i;
4323 CopiedLocationInfo::iterator x;
4326 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4327 x != _copied_locations.end() && i != _editor->selection->markers.end();
4330 Location * location = _editor->find_location_from_marker (*i, is_start);
4334 if (location->locked()) {
4338 _editor->begin_reversible_command ( _("move marker") );
4341 if (location->is_mark()) {
4342 location->set_start (((*x).location)->start());
4344 location->set (((*x).location)->start(), ((*x).location)->end());
4347 if (location->is_session_range()) {
4348 _editor->session()->set_end_is_free (false);
4354 XMLNode &after = _editor->session()->locations()->get_state();
4355 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4356 _editor->commit_reversible_command ();
4361 MarkerDrag::aborted (bool movement_occurred)
4363 if (!movement_occurred) {
4367 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4369 /* move all markers to their original location */
4372 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4375 Location * location = _editor->find_location_from_marker (*m, is_start);
4378 (*m)->set_position (is_start ? location->start() : location->end());
4385 MarkerDrag::update_item (Location*)
4390 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4392 , _fixed_grab_x (0.0)
4393 , _fixed_grab_y (0.0)
4394 , _cumulative_x_drag (0.0)
4395 , _cumulative_y_drag (0.0)
4399 if (_zero_gain_fraction < 0.0) {
4400 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4403 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4405 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4411 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4413 Drag::start_grab (event, _editor->cursors()->fader);
4415 // start the grab at the center of the control point so
4416 // the point doesn't 'jump' to the mouse after the first drag
4417 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4418 _fixed_grab_y = _point->get_y();
4420 framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
4421 setup_snap_delta (pos);
4423 float const fraction = 1 - (_point->get_y() / _point->line().height());
4424 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4426 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4428 if (!_point->can_slide ()) {
4429 _x_constrained = true;
4434 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4436 double dx = _drags->current_pointer_x() - last_pointer_x();
4437 double dy = current_pointer_y() - last_pointer_y();
4438 bool need_snap = true;
4440 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4446 /* coordinate in pixels relative to the start of the region (for region-based automation)
4447 or track (for track-based automation) */
4448 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4449 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4451 // calculate zero crossing point. back off by .01 to stay on the
4452 // positive side of zero
4453 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4455 if (_x_constrained) {
4458 if (_y_constrained) {
4462 _cumulative_x_drag = cx - _fixed_grab_x;
4463 _cumulative_y_drag = cy - _fixed_grab_y;
4467 cy = min ((double) _point->line().height(), cy);
4469 // make sure we hit zero when passing through
4470 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4474 framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state);
4475 if (!_x_constrained && need_snap) {
4476 _editor->snap_to_with_modifier (cx_frames, event);
4479 cx_frames -= snap_delta (event->button.state);
4480 cx_frames = min (cx_frames, _point->line().maximum_time() + _point->line().offset());
4482 float const fraction = 1.0 - (cy / _point->line().height());
4485 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4486 _editor->begin_reversible_command (_("automation event move"));
4487 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4489 pair<double, float> result;
4490 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4492 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (result.second));
4496 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4498 if (!movement_occurred) {
4501 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4502 _editor->reset_point_selection ();
4506 _point->line().end_drag (_pushing, _final_index);
4507 _editor->commit_reversible_command ();
4512 ControlPointDrag::aborted (bool)
4514 _point->line().reset ();
4518 ControlPointDrag::active (Editing::MouseMode m)
4520 if (m == Editing::MouseDraw) {
4521 /* always active in mouse draw */
4525 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4526 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4529 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4532 , _fixed_grab_x (0.0)
4533 , _fixed_grab_y (0.0)
4534 , _cumulative_y_drag (0)
4538 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4542 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4544 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4547 _item = &_line->grab_item ();
4549 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4550 origin, and ditto for y.
4553 double mx = event->button.x;
4554 double my = event->button.y;
4556 _line->grab_item().canvas_to_item (mx, my);
4558 framecnt_t const frame_within_region = (framecnt_t) floor (mx * _editor->samples_per_pixel);
4560 if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
4561 /* no adjacent points */
4565 Drag::start_grab (event, _editor->cursors()->fader);
4567 /* store grab start in item frame */
4568 double const bx = _line->nth (_before)->get_x();
4569 double const ax = _line->nth (_after)->get_x();
4570 double const click_ratio = (ax - mx) / (ax - bx);
4572 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4577 double fraction = 1.0 - (cy / _line->height());
4579 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4583 LineDrag::motion (GdkEvent* event, bool first_move)
4585 double dy = current_pointer_y() - last_pointer_y();
4587 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4591 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4593 _cumulative_y_drag = cy - _fixed_grab_y;
4596 cy = min ((double) _line->height(), cy);
4598 double const fraction = 1.0 - (cy / _line->height());
4602 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4604 _editor->begin_reversible_command (_("automation range move"));
4605 _line->start_drag_line (_before, _after, initial_fraction);
4608 /* we are ignoring x position for this drag, so we can just pass in anything */
4609 pair<double, float> result;
4611 result = _line->drag_motion (0, fraction, true, false, ignored);
4612 show_verbose_cursor_text (_line->get_verbose_cursor_string (result.second));
4616 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4618 if (movement_occurred) {
4619 motion (event, false);
4620 _line->end_drag (false, 0);
4621 _editor->commit_reversible_command ();
4623 /* add a new control point on the line */
4625 AutomationTimeAxisView* atv;
4627 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4628 framepos_t where = grab_frame ();
4631 double cy = _fixed_grab_y;
4633 _line->grab_item().item_to_canvas (cx, cy);
4635 atv->add_automation_event (event, where, cy, false);
4636 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4637 AudioRegionView* arv;
4639 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4640 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4647 LineDrag::aborted (bool)
4652 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4656 _region_view_grab_x (0.0),
4657 _cumulative_x_drag (0),
4661 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4665 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4667 Drag::start_grab (event);
4669 _line = reinterpret_cast<Line*> (_item);
4672 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4674 double cx = event->button.x;
4675 double cy = event->button.y;
4677 _item->parent()->canvas_to_item (cx, cy);
4679 /* store grab start in parent frame */
4680 _region_view_grab_x = cx;
4682 _before = *(float*) _item->get_data ("position");
4684 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4686 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4690 FeatureLineDrag::motion (GdkEvent*, bool)
4692 double dx = _drags->current_pointer_x() - last_pointer_x();
4694 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4696 _cumulative_x_drag += dx;
4698 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4707 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4709 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4711 float *pos = new float;
4714 _line->set_data ("position", pos);
4720 FeatureLineDrag::finished (GdkEvent*, bool)
4722 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4723 _arv->update_transient(_before, _before);
4727 FeatureLineDrag::aborted (bool)
4732 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4734 , _vertical_only (false)
4736 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4740 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4742 Drag::start_grab (event);
4743 show_verbose_cursor_time (adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
4747 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4754 framepos_t const pf = adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
4756 framepos_t grab = grab_frame ();
4757 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4758 _editor->snap_to_with_modifier (grab, event);
4760 grab = raw_grab_frame ();
4763 /* base start and end on initial click position */
4773 if (current_pointer_y() < grab_y()) {
4774 y1 = current_pointer_y();
4777 y2 = current_pointer_y();
4781 if (start != end || y1 != y2) {
4783 double x1 = _editor->sample_to_pixel (start);
4784 double x2 = _editor->sample_to_pixel (end);
4785 const double min_dimension = 2.0;
4787 if (_vertical_only) {
4788 /* fixed 10 pixel width */
4792 x2 = min (x1 - min_dimension, x2);
4794 x2 = max (x1 + min_dimension, x2);
4799 y2 = min (y1 - min_dimension, y2);
4801 y2 = max (y1 + min_dimension, y2);
4804 /* translate rect into item space and set */
4806 ArdourCanvas::Rect r (x1, y1, x2, y2);
4808 /* this drag is a _trackview_only == true drag, so the y1 and
4809 * y2 (computed using current_pointer_y() and grab_y()) will be
4810 * relative to the top of the trackview group). The
4811 * rubberband rect has the same parent/scroll offset as the
4812 * the trackview group, so we can use the "r" rect directly
4813 * to set the shape of the rubberband.
4816 _editor->rubberband_rect->set (r);
4817 _editor->rubberband_rect->show();
4818 _editor->rubberband_rect->raise_to_top();
4820 show_verbose_cursor_time (pf);
4822 do_select_things (event, true);
4827 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4831 framepos_t grab = grab_frame ();
4832 framepos_t lpf = last_pointer_frame ();
4834 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4835 grab = raw_grab_frame ();
4836 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4850 if (current_pointer_y() < grab_y()) {
4851 y1 = current_pointer_y();
4854 y2 = current_pointer_y();
4858 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4862 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4864 if (movement_occurred) {
4866 motion (event, false);
4867 do_select_things (event, false);
4873 bool do_deselect = true;
4874 MidiTimeAxisView* mtv;
4876 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4878 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4879 /* nothing selected */
4880 add_midi_region (mtv, true, _editor->get_grid_music_divisions(event->button.state));
4881 do_deselect = false;
4885 /* do not deselect if Primary or Tertiary (toggle-select or
4886 * extend-select are pressed.
4889 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4890 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4897 _editor->rubberband_rect->hide();
4901 RubberbandSelectDrag::aborted (bool)
4903 _editor->rubberband_rect->hide ();
4906 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4907 : RegionDrag (e, i, p, v)
4909 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4913 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4915 Drag::start_grab (event, cursor);
4917 _editor->get_selection().add (_primary);
4919 framepos_t where = _primary->region()->position();
4920 setup_snap_delta (where);
4922 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4926 TimeFXDrag::motion (GdkEvent* event, bool)
4928 RegionView* rv = _primary;
4929 StreamView* cv = rv->get_time_axis_view().view ();
4931 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4932 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4933 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4934 framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
4935 _editor->snap_to_with_modifier (pf, event);
4936 pf -= snap_delta (event->button.state);
4938 if (pf > rv->region()->position()) {
4939 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4942 show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4946 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
4948 /* this may have been a single click, no drag. We still want the dialog
4949 to show up in that case, so that the user can manually edit the
4950 parameters for the timestretch.
4953 float fraction = 1.0;
4955 if (movement_occurred) {
4957 motion (event, false);
4959 _primary->get_time_axis_view().hide_timestretch ();
4961 framepos_t adjusted_frame_pos = adjusted_current_frame (event);
4963 if (adjusted_frame_pos < _primary->region()->position()) {
4964 /* backwards drag of the left edge - not usable */
4968 framecnt_t newlen = adjusted_frame_pos - _primary->region()->position();
4970 fraction = (double) newlen / (double) _primary->region()->length();
4972 #ifndef USE_RUBBERBAND
4973 // Soundtouch uses fraction / 100 instead of normal (/ 1)
4974 if (_primary->region()->data_type() == DataType::AUDIO) {
4975 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4980 if (!_editor->get_selection().regions.empty()) {
4981 /* primary will already be included in the selection, and edit
4982 group shared editing will propagate selection across
4983 equivalent regions, so just use the current region
4987 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
4988 error << _("An error occurred while executing time stretch operation") << endmsg;
4994 TimeFXDrag::aborted (bool)
4996 _primary->get_time_axis_view().hide_timestretch ();
4999 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5002 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5006 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5008 Drag::start_grab (event);
5012 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5014 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
5018 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5020 if (movement_occurred && _editor->session()) {
5021 /* make sure we stop */
5022 _editor->session()->request_transport_speed (0.0);
5027 ScrubDrag::aborted (bool)
5032 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5036 , _time_selection_at_start (!_editor->get_selection().time.empty())
5038 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5040 if (_time_selection_at_start) {
5041 start_at_start = _editor->get_selection().time.start();
5042 end_at_start = _editor->get_selection().time.end_frame();
5047 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5049 if (_editor->session() == 0) {
5053 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5055 switch (_operation) {
5056 case CreateSelection:
5057 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5062 cursor = _editor->cursors()->selector;
5063 Drag::start_grab (event, cursor);
5066 case SelectionStartTrim:
5067 if (_editor->clicked_axisview) {
5068 _editor->clicked_axisview->order_selection_trims (_item, true);
5070 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5073 case SelectionEndTrim:
5074 if (_editor->clicked_axisview) {
5075 _editor->clicked_axisview->order_selection_trims (_item, false);
5077 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5081 Drag::start_grab (event, cursor);
5084 case SelectionExtend:
5085 Drag::start_grab (event, cursor);
5089 if (_operation == SelectionMove) {
5090 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5092 show_verbose_cursor_time (adjusted_current_frame (event));
5097 SelectionDrag::setup_pointer_frame_offset ()
5099 switch (_operation) {
5100 case CreateSelection:
5101 _pointer_frame_offset = 0;
5104 case SelectionStartTrim:
5106 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
5109 case SelectionEndTrim:
5110 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
5113 case SelectionExtend:
5119 SelectionDrag::motion (GdkEvent* event, bool first_move)
5121 framepos_t start = 0;
5123 framecnt_t length = 0;
5124 framecnt_t distance = 0;
5126 framepos_t const pending_position = adjusted_current_frame (event);
5128 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
5132 switch (_operation) {
5133 case CreateSelection:
5135 framepos_t grab = grab_frame ();
5138 grab = adjusted_current_frame (event, false);
5139 if (grab < pending_position) {
5140 _editor->snap_to (grab, RoundDownMaybe);
5142 _editor->snap_to (grab, RoundUpMaybe);
5146 if (pending_position < grab) {
5147 start = pending_position;
5150 end = pending_position;
5154 /* first drag: Either add to the selection
5155 or create a new selection
5162 /* adding to the selection */
5163 _editor->set_selected_track_as_side_effect (Selection::Add);
5164 _editor->clicked_selection = _editor->selection->add (start, end);
5171 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5172 _editor->set_selected_track_as_side_effect (Selection::Set);
5175 _editor->clicked_selection = _editor->selection->set (start, end);
5179 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5180 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5181 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5183 _editor->selection->add (atest);
5187 /* select all tracks within the rectangle that we've marked out so far */
5188 TrackViewList new_selection;
5189 TrackViewList& all_tracks (_editor->track_views);
5191 ArdourCanvas::Coord const top = grab_y();
5192 ArdourCanvas::Coord const bottom = current_pointer_y();
5194 if (top >= 0 && bottom >= 0) {
5196 //first, find the tracks that are covered in the y range selection
5197 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5198 if ((*i)->covered_by_y_range (top, bottom)) {
5199 new_selection.push_back (*i);
5203 //now find any tracks that are GROUPED with the tracks we selected
5204 TrackViewList grouped_add = new_selection;
5205 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5206 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
5207 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::group_select.property_id) ) {
5208 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
5209 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
5210 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
5211 grouped_add.push_back (*j);
5216 //now compare our list with the current selection, and add or remove as necessary
5217 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5218 TrackViewList tracks_to_add;
5219 TrackViewList tracks_to_remove;
5220 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
5221 if ( !_editor->selection->tracks.contains ( *i ) )
5222 tracks_to_add.push_back ( *i );
5223 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
5224 if ( !grouped_add.contains ( *i ) )
5225 tracks_to_remove.push_back ( *i );
5226 _editor->selection->add(tracks_to_add);
5227 _editor->selection->remove(tracks_to_remove);
5233 case SelectionStartTrim:
5235 end = _editor->selection->time[_editor->clicked_selection].end;
5237 if (pending_position > end) {
5240 start = pending_position;
5244 case SelectionEndTrim:
5246 start = _editor->selection->time[_editor->clicked_selection].start;
5248 if (pending_position < start) {
5251 end = pending_position;
5258 start = _editor->selection->time[_editor->clicked_selection].start;
5259 end = _editor->selection->time[_editor->clicked_selection].end;
5261 length = end - start;
5262 distance = pending_position - start;
5263 start = pending_position;
5264 _editor->snap_to (start);
5266 end = start + length;
5270 case SelectionExtend:
5275 switch (_operation) {
5277 if (_time_selection_at_start) {
5278 _editor->selection->move_time (distance);
5282 _editor->selection->replace (_editor->clicked_selection, start, end);
5286 if (_operation == SelectionMove) {
5287 show_verbose_cursor_time(start);
5289 show_verbose_cursor_time(pending_position);
5294 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5296 Session* s = _editor->session();
5298 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5299 if (movement_occurred) {
5300 motion (event, false);
5301 /* XXX this is not object-oriented programming at all. ick */
5302 if (_editor->selection->time.consolidate()) {
5303 _editor->selection->TimeChanged ();
5306 /* XXX what if its a music time selection? */
5308 if (s->get_play_range() && s->transport_rolling()) {
5309 s->request_play_range (&_editor->selection->time, true);
5310 } else if (!s->config.get_external_sync()) {
5311 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5312 s->request_locate (_editor->get_selection().time.start());
5316 if (_editor->get_selection().time.length() != 0) {
5317 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame());
5319 s->clear_range_selection ();
5324 /* just a click, no pointer movement.
5327 if (was_double_click()) {
5328 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5329 _editor->temporal_zoom_selection (Both);
5334 if (_operation == SelectionExtend) {
5335 if (_time_selection_at_start) {
5336 framepos_t pos = adjusted_current_frame (event, false);
5337 framepos_t start = min (pos, start_at_start);
5338 framepos_t end = max (pos, end_at_start);
5339 _editor->selection->set (start, end);
5342 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5343 if (_editor->clicked_selection) {
5344 _editor->selection->remove (_editor->clicked_selection);
5347 if (!_editor->clicked_selection) {
5348 _editor->selection->clear_time();
5353 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5354 _editor->selection->set (_editor->clicked_axisview);
5357 if (s && s->get_play_range () && s->transport_rolling()) {
5358 s->request_stop (false, false);
5363 _editor->stop_canvas_autoscroll ();
5364 _editor->clicked_selection = 0;
5365 _editor->commit_reversible_selection_op ();
5369 SelectionDrag::aborted (bool)
5374 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5375 : Drag (e, i, false),
5379 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5381 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5382 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5383 physical_screen_height (_editor->current_toplevel()->get_window())));
5384 _drag_rect->hide ();
5386 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5387 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5390 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5392 /* normal canvas items will be cleaned up when their parent group is deleted. But
5393 this item is created as the child of a long-lived parent group, and so we
5394 need to explicitly delete it.
5400 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5402 if (_editor->session() == 0) {
5406 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5408 if (!_editor->temp_location) {
5409 _editor->temp_location = new Location (*_editor->session());
5412 switch (_operation) {
5413 case CreateSkipMarker:
5414 case CreateRangeMarker:
5415 case CreateTransportMarker:
5416 case CreateCDMarker:
5418 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5423 cursor = _editor->cursors()->selector;
5427 Drag::start_grab (event, cursor);
5429 show_verbose_cursor_time (adjusted_current_frame (event));
5433 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5435 framepos_t start = 0;
5437 ArdourCanvas::Rectangle *crect;
5439 switch (_operation) {
5440 case CreateSkipMarker:
5441 crect = _editor->range_bar_drag_rect;
5443 case CreateRangeMarker:
5444 crect = _editor->range_bar_drag_rect;
5446 case CreateTransportMarker:
5447 crect = _editor->transport_bar_drag_rect;
5449 case CreateCDMarker:
5450 crect = _editor->cd_marker_bar_drag_rect;
5453 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5458 framepos_t const pf = adjusted_current_frame (event);
5460 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5461 framepos_t grab = grab_frame ();
5462 _editor->snap_to (grab);
5464 if (pf < grab_frame()) {
5472 /* first drag: Either add to the selection
5473 or create a new selection.
5478 _editor->temp_location->set (start, end);
5482 update_item (_editor->temp_location);
5484 //_drag_rect->raise_to_top();
5490 _editor->temp_location->set (start, end);
5492 double x1 = _editor->sample_to_pixel (start);
5493 double x2 = _editor->sample_to_pixel (end);
5497 update_item (_editor->temp_location);
5500 show_verbose_cursor_time (pf);
5505 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5507 Location * newloc = 0;
5511 if (movement_occurred) {
5512 motion (event, false);
5515 switch (_operation) {
5516 case CreateSkipMarker:
5517 case CreateRangeMarker:
5518 case CreateCDMarker:
5520 XMLNode &before = _editor->session()->locations()->get_state();
5521 if (_operation == CreateSkipMarker) {
5522 _editor->begin_reversible_command (_("new skip marker"));
5523 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5524 flags = Location::IsRangeMarker | Location::IsSkip;
5525 _editor->range_bar_drag_rect->hide();
5526 } else if (_operation == CreateCDMarker) {
5527 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5528 _editor->begin_reversible_command (_("new CD marker"));
5529 flags = Location::IsRangeMarker | Location::IsCDMarker;
5530 _editor->cd_marker_bar_drag_rect->hide();
5532 _editor->begin_reversible_command (_("new skip marker"));
5533 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5534 flags = Location::IsRangeMarker;
5535 _editor->range_bar_drag_rect->hide();
5537 newloc = new Location (
5538 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5541 _editor->session()->locations()->add (newloc, true);
5542 XMLNode &after = _editor->session()->locations()->get_state();
5543 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5544 _editor->commit_reversible_command ();
5548 case CreateTransportMarker:
5549 // popup menu to pick loop or punch
5550 _editor->new_transport_marker_context_menu (&event->button, _item);
5556 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5558 if (_operation == CreateTransportMarker) {
5560 /* didn't drag, so just locate */
5562 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5564 } else if (_operation == CreateCDMarker) {
5566 /* didn't drag, but mark is already created so do
5569 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5574 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5576 if (end == max_framepos) {
5577 end = _editor->session()->current_end_frame ();
5580 if (start == max_framepos) {
5581 start = _editor->session()->current_start_frame ();
5584 switch (_editor->mouse_mode) {
5586 /* find the two markers on either side and then make the selection from it */
5587 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5591 /* find the two markers on either side of the click and make the range out of it */
5592 _editor->selection->set (start, end);
5601 _editor->stop_canvas_autoscroll ();
5605 RangeMarkerBarDrag::aborted (bool movement_occurred)
5607 if (movement_occurred) {
5608 _drag_rect->hide ();
5613 RangeMarkerBarDrag::update_item (Location* location)
5615 double const x1 = _editor->sample_to_pixel (location->start());
5616 double const x2 = _editor->sample_to_pixel (location->end());
5618 _drag_rect->set_x0 (x1);
5619 _drag_rect->set_x1 (x2);
5622 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5624 , _cumulative_dx (0)
5625 , _cumulative_dy (0)
5626 , _was_selected (false)
5628 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5630 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5632 _region = &_primary->region_view ();
5633 _note_height = _region->midi_stream_view()->note_height ();
5637 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5639 Drag::start_grab (event);
5640 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5642 if (!(_was_selected = _primary->selected())) {
5644 /* tertiary-click means extend selection - we'll do that on button release,
5645 so don't add it here, because otherwise we make it hard to figure
5646 out the "extend-to" range.
5649 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5652 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5655 _region->note_selected (_primary, true);
5657 _editor->get_selection().clear_points();
5658 _region->unique_select (_primary);
5664 /** @return Current total drag x change in frames */
5666 NoteDrag::total_dx (const guint state) const
5668 if (_x_constrained) {
5671 TempoMap& map (_editor->session()->tempo_map());
5674 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5676 /* primary note time */
5677 double const quarter_note_start = _region->region()->quarter_note() - _region->midi_region()->start_beats();
5678 frameoffset_t const n = map.frame_at_quarter_note (quarter_note_start + _primary->note()->time().to_double());
5680 /* new time of the primary note in session frames */
5681 frameoffset_t st = n + dx + snap_delta (state);
5683 framepos_t const rp = _region->region()->position ();
5685 /* prevent the note being dragged earlier than the region's position */
5688 /* possibly snap and return corresponding delta */
5692 if (ArdourKeyboard::indicates_snap (state)) {
5693 if (_editor->snap_mode () != SnapOff) {
5697 if (_editor->snap_mode () == SnapOff) {
5699 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
5700 if (ArdourKeyboard::indicates_snap_delta (state)) {
5708 bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
5709 ret = _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
5711 ret = st - n - snap_delta (state);
5716 /** @return Current total drag y change in note number */
5718 NoteDrag::total_dy () const
5720 if (_y_constrained) {
5724 double const y = _region->midi_view()->y_position ();
5725 /* new current note */
5726 uint8_t n = _region->y_to_note (current_pointer_y () - y);
5728 MidiStreamView* msv = _region->midi_stream_view ();
5729 n = max (msv->lowest_note(), n);
5730 n = min (msv->highest_note(), n);
5731 /* and work out delta */
5732 return n - _region->y_to_note (grab_y() - y);
5736 NoteDrag::motion (GdkEvent * event, bool)
5738 /* Total change in x and y since the start of the drag */
5739 frameoffset_t const dx = total_dx (event->button.state);
5740 int8_t const dy = total_dy ();
5742 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5743 double const tdx = _x_constrained ? 0 : _editor->sample_to_pixel (dx) - _cumulative_dx;
5744 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
5747 _cumulative_dx += tdx;
5748 _cumulative_dy += tdy;
5750 int8_t note_delta = total_dy();
5753 _region->move_selection (tdx, tdy, note_delta);
5755 /* the new note value may be the same as the old one, but we
5756 * don't know what that means because the selection may have
5757 * involved more than one note and we might be doing something
5758 * odd with them. so show the note value anyway, always.
5761 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5763 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
5769 NoteDrag::finished (GdkEvent* ev, bool moved)
5772 /* no motion - select note */
5774 if (_editor->current_mouse_mode() == Editing::MouseContent ||
5775 _editor->current_mouse_mode() == Editing::MouseDraw) {
5777 bool changed = false;
5779 if (_was_selected) {
5780 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5782 _region->note_deselected (_primary);
5785 _editor->get_selection().clear_points();
5786 _region->unique_select (_primary);
5790 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5791 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5793 if (!extend && !add && _region->selection_size() > 1) {
5794 _editor->get_selection().clear_points();
5795 _region->unique_select (_primary);
5797 } else if (extend) {
5798 _region->note_selected (_primary, true, true);
5801 /* it was added during button press */
5808 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5809 _editor->commit_reversible_selection_op();
5813 _region->note_dropped (_primary, total_dx (ev->button.state), total_dy());
5818 NoteDrag::aborted (bool)
5823 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5824 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5825 : Drag (editor, atv->base_item ())
5827 , _y_origin (atv->y_position())
5828 , _nothing_to_drag (false)
5830 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5831 setup (atv->lines ());
5834 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5835 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5836 : Drag (editor, rv->get_canvas_group ())
5838 , _y_origin (rv->get_time_axis_view().y_position())
5839 , _nothing_to_drag (false)
5842 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5844 list<boost::shared_ptr<AutomationLine> > lines;
5846 AudioRegionView* audio_view;
5847 AutomationRegionView* automation_view;
5848 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5849 lines.push_back (audio_view->get_gain_line ());
5850 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5851 lines.push_back (automation_view->line ());
5854 error << _("Automation range drag created for invalid region type") << endmsg;
5860 /** @param lines AutomationLines to drag.
5861 * @param offset Offset from the session start to the points in the AutomationLines.
5864 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5866 /* find the lines that overlap the ranges being dragged */
5867 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5868 while (i != lines.end ()) {
5869 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5872 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5874 /* check this range against all the AudioRanges that we are using */
5875 list<AudioRange>::const_iterator k = _ranges.begin ();
5876 while (k != _ranges.end()) {
5877 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5883 /* add it to our list if it overlaps at all */
5884 if (k != _ranges.end()) {
5889 _lines.push_back (n);
5895 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5899 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5901 return 1.0 - ((global_y - _y_origin) / line->height());
5905 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5907 const double v = list->eval(x);
5908 return _integral ? rint(v) : v;
5912 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5914 Drag::start_grab (event, cursor);
5916 /* Get line states before we start changing things */
5917 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5918 i->state = &i->line->get_state ();
5919 i->original_fraction = y_fraction (i->line, current_pointer_y());
5922 if (_ranges.empty()) {
5924 /* No selected time ranges: drag all points */
5925 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5926 uint32_t const N = i->line->npoints ();
5927 for (uint32_t j = 0; j < N; ++j) {
5928 i->points.push_back (i->line->nth (j));
5934 if (_nothing_to_drag) {
5940 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
5942 if (_nothing_to_drag && !first_move) {
5947 _editor->begin_reversible_command (_("automation range move"));
5949 if (!_ranges.empty()) {
5951 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5953 framecnt_t const half = (i->start + i->end) / 2;
5955 /* find the line that this audio range starts in */
5956 list<Line>::iterator j = _lines.begin();
5957 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5961 if (j != _lines.end()) {
5962 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5964 /* j is the line that this audio range starts in; fade into it;
5965 64 samples length plucked out of thin air.
5968 framepos_t a = i->start + 64;
5973 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5974 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5976 XMLNode &before = the_list->get_state();
5977 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5978 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5980 if (add_p || add_q) {
5981 _editor->session()->add_command (
5982 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
5986 /* same thing for the end */
5989 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5993 if (j != _lines.end()) {
5994 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5996 /* j is the line that this audio range starts in; fade out of it;
5997 64 samples length plucked out of thin air.
6000 framepos_t b = i->end - 64;
6005 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6006 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6008 XMLNode &before = the_list->get_state();
6009 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6010 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6012 if (add_p || add_q) {
6013 _editor->session()->add_command (
6014 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6019 _nothing_to_drag = true;
6021 /* Find all the points that should be dragged and put them in the relevant
6022 points lists in the Line structs.
6025 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6027 uint32_t const N = i->line->npoints ();
6028 for (uint32_t j = 0; j < N; ++j) {
6030 /* here's a control point on this line */
6031 ControlPoint* p = i->line->nth (j);
6032 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6034 /* see if it's inside a range */
6035 list<AudioRange>::const_iterator k = _ranges.begin ();
6036 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6040 if (k != _ranges.end()) {
6041 /* dragging this point */
6042 _nothing_to_drag = false;
6043 i->points.push_back (p);
6049 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6050 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6054 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6055 float const f = y_fraction (l->line, current_pointer_y());
6056 /* we are ignoring x position for this drag, so we can just pass in anything */
6057 pair<double, float> result;
6059 result = l->line->drag_motion (0, f, true, false, ignored);
6060 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, result.second));
6065 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6067 if (_nothing_to_drag || !motion_occurred) {
6071 motion (event, false);
6072 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6073 i->line->end_drag (false, 0);
6076 _editor->commit_reversible_command ();
6080 AutomationRangeDrag::aborted (bool)
6082 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6087 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6089 , initial_time_axis_view (itav)
6091 /* note that time_axis_view may be null if the regionview was created
6092 * as part of a copy operation.
6094 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6095 layer = v->region()->layer ();
6096 initial_y = v->get_canvas_group()->position().y;
6097 initial_playlist = v->region()->playlist ();
6098 initial_position = v->region()->position ();
6099 initial_end = v->region()->position () + v->region()->length ();
6102 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6103 : Drag (e, i->canvas_item ())
6106 , _cumulative_dx (0)
6108 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6109 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
6114 PatchChangeDrag::motion (GdkEvent* ev, bool)
6116 framepos_t f = adjusted_current_frame (ev);
6117 boost::shared_ptr<Region> r = _region_view->region ();
6118 f = max (f, r->position ());
6119 f = min (f, r->last_frame ());
6121 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
6122 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6123 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6124 _cumulative_dx = dxu;
6128 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6130 if (!movement_occurred) {
6131 if (was_double_click()) {
6132 _region_view->edit_patch_change (_patch_change);
6137 boost::shared_ptr<Region> r (_region_view->region ());
6138 framepos_t f = adjusted_current_frame (ev);
6139 f = max (f, r->position ());
6140 f = min (f, r->last_frame ());
6142 _region_view->move_patch_change (
6144 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
6149 PatchChangeDrag::aborted (bool)
6151 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6155 PatchChangeDrag::setup_pointer_frame_offset ()
6157 boost::shared_ptr<Region> region = _region_view->region ();
6158 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
6161 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6162 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6169 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6171 _region_view->update_drag_selection (
6173 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6177 MidiRubberbandSelectDrag::deselect_things ()
6182 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6183 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6186 _vertical_only = true;
6190 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6192 double const y = _region_view->midi_view()->y_position ();
6194 y1 = max (0.0, y1 - y);
6195 y2 = max (0.0, y2 - y);
6197 _region_view->update_vertical_drag_selection (
6200 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6205 MidiVerticalSelectDrag::deselect_things ()
6210 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6211 : RubberbandSelectDrag (e, i)
6217 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
6219 if (drag_in_progress) {
6220 /* We just want to select things at the end of the drag, not during it */
6224 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6226 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6228 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6230 _editor->commit_reversible_selection_op ();
6234 EditorRubberbandSelectDrag::deselect_things ()
6236 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6238 _editor->selection->clear_tracks();
6239 _editor->selection->clear_regions();
6240 _editor->selection->clear_points ();
6241 _editor->selection->clear_lines ();
6242 _editor->selection->clear_midi_notes ();
6244 _editor->commit_reversible_selection_op();
6247 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6252 _note[0] = _note[1] = 0;
6255 NoteCreateDrag::~NoteCreateDrag ()
6261 NoteCreateDrag::grid_frames (framepos_t t) const
6264 const Evoral::Beats grid_beats = _region_view->get_grid_beats (t);
6265 const Evoral::Beats t_beats = _region_view->region_frames_to_region_beats (t);
6267 return _region_view->region_beats_to_region_frames (t_beats + grid_beats)
6268 - _region_view->region_beats_to_region_frames (t_beats);
6272 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6274 Drag::start_grab (event, cursor);
6276 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6277 TempoMap& map (_editor->session()->tempo_map());
6279 const framepos_t pf = _drags->current_pointer_frame ();
6280 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6282 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6284 double eqaf = map.exact_qn_at_frame (pf, divisions);
6286 if (divisions != 0) {
6288 const double qaf = map.quarter_note_at_frame (pf);
6290 /* Hack so that we always snap to the note that we are over, instead of snapping
6291 to the next one if we're more than halfway through the one we're over.
6294 const double rem = eqaf - qaf;
6296 eqaf -= grid_beats.to_double();
6300 _note[0] = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6301 /* minimum initial length is grid beats */
6302 _note[1] = map.frame_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6304 double const x0 = _editor->sample_to_pixel (_note[0]);
6305 double const x1 = _editor->sample_to_pixel (_note[1]);
6306 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6308 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6309 _drag_rect->set_outline_all ();
6310 _drag_rect->set_outline_color (0xffffff99);
6311 _drag_rect->set_fill_color (0xffffff66);
6315 NoteCreateDrag::motion (GdkEvent* event, bool)
6317 TempoMap& map (_editor->session()->tempo_map());
6318 const framepos_t pf = _drags->current_pointer_frame ();
6319 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6320 double eqaf = map.exact_qn_at_frame (pf, divisions);
6322 if (divisions != 0) {
6324 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6326 const double qaf = map.quarter_note_at_frame (pf);
6327 /* Hack so that we always snap to the note that we are over, instead of snapping
6328 to the next one if we're more than halfway through the one we're over.
6331 const double rem = eqaf - qaf;
6333 eqaf -= grid_beats.to_double();
6336 eqaf += grid_beats.to_double();
6338 _note[1] = max ((framepos_t)0, map.frame_at_quarter_note (eqaf) - _region_view->region()->position ());
6340 double const x0 = _editor->sample_to_pixel (_note[0]);
6341 double const x1 = _editor->sample_to_pixel (_note[1]);
6342 _drag_rect->set_x0 (std::min(x0, x1));
6343 _drag_rect->set_x1 (std::max(x0, x1));
6347 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6349 /* we create a note even if there was no movement */
6350 framepos_t const start = min (_note[0], _note[1]);
6351 framepos_t const start_sess_rel = start + _region_view->region()->position();
6352 framecnt_t length = max (_editor->pixel_to_sample (1.0), (framecnt_t) fabs ((double)(_note[0] - _note[1])));
6353 framecnt_t const g = grid_frames (start);
6355 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6359 TempoMap& map (_editor->session()->tempo_map());
6360 const double qn_length = map.quarter_notes_between_frames (start_sess_rel, start_sess_rel + length);
6361 Evoral::Beats qn_length_beats = max (Evoral::Beats::ticks(1), Evoral::Beats (qn_length));
6363 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6367 NoteCreateDrag::y_to_region (double y) const
6370 _region_view->get_canvas_group()->canvas_to_item (x, y);
6375 NoteCreateDrag::aborted (bool)
6380 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6388 HitCreateDrag::~HitCreateDrag ()
6393 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6395 Drag::start_grab (event, cursor);
6397 TempoMap& map (_editor->session()->tempo_map());
6399 const framepos_t pf = _drags->current_pointer_frame ();
6400 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6402 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6404 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6406 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6410 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6411 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6413 Evoral::Beats length = _region_view->get_grid_beats (pf);
6415 _region_view->create_note_at (start, y, length, event->button.state, false);
6422 HitCreateDrag::motion (GdkEvent* event, bool)
6424 TempoMap& map (_editor->session()->tempo_map());
6426 const framepos_t pf = _drags->current_pointer_frame ();
6427 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6429 if (divisions == 0) {
6433 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6434 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position ();
6435 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6437 if (_last_pos == start && y == _last_y) {
6441 Evoral::Beats length = _region_view->get_grid_beats (pf);
6443 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6444 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6448 _region_view->create_note_at (start, y, length, event->button.state, false);
6455 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6461 HitCreateDrag::y_to_region (double y) const
6464 _region_view->get_canvas_group()->canvas_to_item (x, y);
6469 HitCreateDrag::aborted (bool)
6474 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6479 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6483 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6485 Drag::start_grab (event, cursor);
6489 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6495 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6498 distance = _drags->current_pointer_x() - grab_x();
6499 len = ar->fade_in()->back()->when;
6501 distance = grab_x() - _drags->current_pointer_x();
6502 len = ar->fade_out()->back()->when;
6505 /* how long should it be ? */
6507 new_length = len + _editor->pixel_to_sample (distance);
6509 /* now check with the region that this is legal */
6511 new_length = ar->verify_xfade_bounds (new_length, start);
6514 arv->reset_fade_in_shape_width (ar, new_length);
6516 arv->reset_fade_out_shape_width (ar, new_length);
6521 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6527 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6530 distance = _drags->current_pointer_x() - grab_x();
6531 len = ar->fade_in()->back()->when;
6533 distance = grab_x() - _drags->current_pointer_x();
6534 len = ar->fade_out()->back()->when;
6537 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6539 _editor->begin_reversible_command ("xfade trim");
6540 ar->playlist()->clear_owned_changes ();
6543 ar->set_fade_in_length (new_length);
6545 ar->set_fade_out_length (new_length);
6548 /* Adjusting the xfade may affect other regions in the playlist, so we need
6549 to get undo Commands from the whole playlist rather than just the
6553 vector<Command*> cmds;
6554 ar->playlist()->rdiff (cmds);
6555 _editor->session()->add_commands (cmds);
6556 _editor->commit_reversible_command ();
6561 CrossfadeEdgeDrag::aborted (bool)
6564 // arv->redraw_start_xfade ();
6566 // arv->redraw_end_xfade ();
6570 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
6571 : Drag (e, item, true)
6572 , line (new EditorCursor (*e))
6574 line->set_position (pos);
6576 line->track_canvas_item().reparent (_editor->_drag_motion_group);
6579 RegionCutDrag::~RegionCutDrag ()
6585 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6587 Drag::start_grab (event, c);
6588 motion (event, false);
6592 RegionCutDrag::motion (GdkEvent* event, bool)
6594 framepos_t pos = _drags->current_pointer_frame();
6595 _editor->snap_to_with_modifier (pos, event);
6597 line->set_position (pos);
6601 RegionCutDrag::finished (GdkEvent* event, bool)
6603 _editor->get_track_canvas()->canvas()->re_enter();
6605 framepos_t pos = _drags->current_pointer_frame();
6606 _editor->snap_to_with_modifier (pos, event);
6610 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
6616 _editor->split_regions_at (pos, rs, _editor->get_grid_music_divisions (event->button.state),
6621 RegionCutDrag::aborted (bool)
6625 RulerZoomDrag::RulerZoomDrag (Editor* e, ArdourCanvas::Item* item)
6626 : Drag (e, item, true)
6631 RulerZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6633 Drag::start_grab (event, c);
6635 framepos_t where = _editor->canvas_event_sample(event);
6637 _editor->_dragging_playhead = true;
6639 _editor->playhead_cursor->set_position (where);
6643 RulerZoomDrag::motion (GdkEvent* event, bool)
6645 framepos_t where = _editor->canvas_event_sample(event);
6647 _editor->playhead_cursor->set_position (where);
6649 const double movement_limit = 20.0;
6650 const double scale = 1.08;
6651 const double y_delta = last_pointer_y() - current_pointer_y();
6653 if (y_delta > 0 && y_delta < movement_limit) {
6654 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
6655 } else if (y_delta < 0 && y_delta > -movement_limit) {
6656 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
6661 RulerZoomDrag::finished (GdkEvent*, bool)
6663 _editor->_dragging_playhead = false;
6665 Session* s = _editor->session ();
6667 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
6668 _editor->_pending_locate_request = true;
6674 RulerZoomDrag::aborted (bool)