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 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4201 /* now move them all */
4203 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4205 copy_location = x->location;
4207 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4211 if (real_location->locked()) {
4215 if (copy_location->is_mark()) {
4218 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4222 framepos_t new_start = copy_location->start() + f_delta;
4223 framepos_t new_end = copy_location->end() + f_delta;
4225 if (is_start) { // start-of-range marker
4227 if (move_both || (*x).move_both) {
4228 copy_location->set_start (new_start, false, true, divisions);
4229 copy_location->set_end (new_end, false, true, divisions);
4230 } else if (new_start < copy_location->end()) {
4231 copy_location->set_start (new_start, false, true, divisions);
4232 } else if (newframe > 0) {
4233 //_editor->snap_to (next, RoundUpAlways, true);
4234 copy_location->set_end (next, false, true, divisions);
4235 copy_location->set_start (newframe, false, true, divisions);
4238 } else { // end marker
4240 if (move_both || (*x).move_both) {
4241 copy_location->set_end (new_end, divisions);
4242 copy_location->set_start (new_start, false, true, divisions);
4243 } else if (new_end > copy_location->start()) {
4244 copy_location->set_end (new_end, false, true, divisions);
4245 } else if (newframe > 0) {
4246 //_editor->snap_to (next, RoundDownAlways, true);
4247 copy_location->set_start (next, false, true, divisions);
4248 copy_location->set_end (newframe, false, true, divisions);
4253 update_item (copy_location);
4255 /* now lookup the actual GUI items used to display this
4256 * location and move them to wherever the copy of the location
4257 * is now. This means that the logic in ARDOUR::Location is
4258 * still enforced, even though we are not (yet) modifying
4259 * the real Location itself.
4262 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4265 lm->set_position (copy_location->start(), copy_location->end());
4270 assert (!_copied_locations.empty());
4272 show_verbose_cursor_time (newframe);
4276 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4278 if (!movement_occurred) {
4280 if (was_double_click()) {
4281 _editor->rename_marker (_marker);
4285 /* just a click, do nothing but finish
4286 off the selection process
4289 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4291 case Selection::Set:
4292 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4293 _editor->selection->set (_marker);
4294 _selection_changed = true;
4298 case Selection::Toggle:
4299 /* we toggle on the button release, click only */
4300 _editor->selection->toggle (_marker);
4301 _selection_changed = true;
4305 case Selection::Extend:
4306 case Selection::Add:
4310 if (_selection_changed) {
4311 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4312 _editor->commit_reversible_selection_op();
4318 _editor->_dragging_edit_point = false;
4320 XMLNode &before = _editor->session()->locations()->get_state();
4321 bool in_command = false;
4323 MarkerSelection::iterator i;
4324 CopiedLocationInfo::iterator x;
4325 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4328 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4329 x != _copied_locations.end() && i != _editor->selection->markers.end();
4332 Location * location = _editor->find_location_from_marker (*i, is_start);
4336 if (location->locked()) {
4340 _editor->begin_reversible_command ( _("move marker") );
4343 if (location->is_mark()) {
4344 location->set_start (((*x).location)->start(), false, true, divisions);
4346 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4349 if (location->is_session_range()) {
4350 _editor->session()->set_end_is_free (false);
4356 XMLNode &after = _editor->session()->locations()->get_state();
4357 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4358 _editor->commit_reversible_command ();
4363 MarkerDrag::aborted (bool movement_occurred)
4365 if (!movement_occurred) {
4369 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4371 /* move all markers to their original location */
4374 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4377 Location * location = _editor->find_location_from_marker (*m, is_start);
4380 (*m)->set_position (is_start ? location->start() : location->end());
4387 MarkerDrag::update_item (Location*)
4392 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4394 , _fixed_grab_x (0.0)
4395 , _fixed_grab_y (0.0)
4396 , _cumulative_x_drag (0.0)
4397 , _cumulative_y_drag (0.0)
4401 if (_zero_gain_fraction < 0.0) {
4402 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4405 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4407 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4413 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4415 Drag::start_grab (event, _editor->cursors()->fader);
4417 // start the grab at the center of the control point so
4418 // the point doesn't 'jump' to the mouse after the first drag
4419 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4420 _fixed_grab_y = _point->get_y();
4422 framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
4423 setup_snap_delta (pos);
4425 float const fraction = 1 - (_point->get_y() / _point->line().height());
4426 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4428 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4430 if (!_point->can_slide ()) {
4431 _x_constrained = true;
4436 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4438 double dx = _drags->current_pointer_x() - last_pointer_x();
4439 double dy = current_pointer_y() - last_pointer_y();
4440 bool need_snap = true;
4442 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4448 /* coordinate in pixels relative to the start of the region (for region-based automation)
4449 or track (for track-based automation) */
4450 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4451 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4453 // calculate zero crossing point. back off by .01 to stay on the
4454 // positive side of zero
4455 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4457 if (_x_constrained) {
4460 if (_y_constrained) {
4464 _cumulative_x_drag = cx - _fixed_grab_x;
4465 _cumulative_y_drag = cy - _fixed_grab_y;
4469 cy = min ((double) _point->line().height(), cy);
4471 // make sure we hit zero when passing through
4472 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4476 framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state);
4477 if (!_x_constrained && need_snap) {
4478 _editor->snap_to_with_modifier (cx_frames, event);
4481 cx_frames -= snap_delta (event->button.state);
4482 cx_frames = min (cx_frames, _point->line().maximum_time() + _point->line().offset());
4484 float const fraction = 1.0 - (cy / _point->line().height());
4487 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4488 _editor->begin_reversible_command (_("automation event move"));
4489 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4491 pair<double, float> result;
4492 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4494 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (result.second));
4498 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4500 if (!movement_occurred) {
4503 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4504 _editor->reset_point_selection ();
4508 _point->line().end_drag (_pushing, _final_index);
4509 _editor->commit_reversible_command ();
4514 ControlPointDrag::aborted (bool)
4516 _point->line().reset ();
4520 ControlPointDrag::active (Editing::MouseMode m)
4522 if (m == Editing::MouseDraw) {
4523 /* always active in mouse draw */
4527 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4528 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4531 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4534 , _fixed_grab_x (0.0)
4535 , _fixed_grab_y (0.0)
4536 , _cumulative_y_drag (0)
4540 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4544 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4546 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4549 _item = &_line->grab_item ();
4551 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4552 origin, and ditto for y.
4555 double mx = event->button.x;
4556 double my = event->button.y;
4558 _line->grab_item().canvas_to_item (mx, my);
4560 framecnt_t const frame_within_region = (framecnt_t) floor (mx * _editor->samples_per_pixel);
4562 if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
4563 /* no adjacent points */
4567 Drag::start_grab (event, _editor->cursors()->fader);
4569 /* store grab start in item frame */
4570 double const bx = _line->nth (_before)->get_x();
4571 double const ax = _line->nth (_after)->get_x();
4572 double const click_ratio = (ax - mx) / (ax - bx);
4574 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4579 double fraction = 1.0 - (cy / _line->height());
4581 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4585 LineDrag::motion (GdkEvent* event, bool first_move)
4587 double dy = current_pointer_y() - last_pointer_y();
4589 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4593 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4595 _cumulative_y_drag = cy - _fixed_grab_y;
4598 cy = min ((double) _line->height(), cy);
4600 double const fraction = 1.0 - (cy / _line->height());
4604 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4606 _editor->begin_reversible_command (_("automation range move"));
4607 _line->start_drag_line (_before, _after, initial_fraction);
4610 /* we are ignoring x position for this drag, so we can just pass in anything */
4611 pair<double, float> result;
4613 result = _line->drag_motion (0, fraction, true, false, ignored);
4614 show_verbose_cursor_text (_line->get_verbose_cursor_string (result.second));
4618 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4620 if (movement_occurred) {
4621 motion (event, false);
4622 _line->end_drag (false, 0);
4623 _editor->commit_reversible_command ();
4625 /* add a new control point on the line */
4627 AutomationTimeAxisView* atv;
4629 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4630 framepos_t where = grab_frame ();
4633 double cy = _fixed_grab_y;
4635 _line->grab_item().item_to_canvas (cx, cy);
4637 atv->add_automation_event (event, where, cy, false);
4638 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4639 AudioRegionView* arv;
4641 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4642 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4649 LineDrag::aborted (bool)
4654 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4658 _region_view_grab_x (0.0),
4659 _cumulative_x_drag (0),
4663 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4667 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4669 Drag::start_grab (event);
4671 _line = reinterpret_cast<Line*> (_item);
4674 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4676 double cx = event->button.x;
4677 double cy = event->button.y;
4679 _item->parent()->canvas_to_item (cx, cy);
4681 /* store grab start in parent frame */
4682 _region_view_grab_x = cx;
4684 _before = *(float*) _item->get_data ("position");
4686 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4688 _max_x = _editor->sample_to_pixel(_arv->get_duration());
4692 FeatureLineDrag::motion (GdkEvent*, bool)
4694 double dx = _drags->current_pointer_x() - last_pointer_x();
4696 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4698 _cumulative_x_drag += dx;
4700 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4709 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4711 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4713 float *pos = new float;
4716 _line->set_data ("position", pos);
4722 FeatureLineDrag::finished (GdkEvent*, bool)
4724 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4725 _arv->update_transient(_before, _before);
4729 FeatureLineDrag::aborted (bool)
4734 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4736 , _vertical_only (false)
4738 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4742 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4744 Drag::start_grab (event);
4745 show_verbose_cursor_time (adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
4749 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4756 framepos_t const pf = adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
4758 framepos_t grab = grab_frame ();
4759 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4760 _editor->snap_to_with_modifier (grab, event);
4762 grab = raw_grab_frame ();
4765 /* base start and end on initial click position */
4775 if (current_pointer_y() < grab_y()) {
4776 y1 = current_pointer_y();
4779 y2 = current_pointer_y();
4783 if (start != end || y1 != y2) {
4785 double x1 = _editor->sample_to_pixel (start);
4786 double x2 = _editor->sample_to_pixel (end);
4787 const double min_dimension = 2.0;
4789 if (_vertical_only) {
4790 /* fixed 10 pixel width */
4794 x2 = min (x1 - min_dimension, x2);
4796 x2 = max (x1 + min_dimension, x2);
4801 y2 = min (y1 - min_dimension, y2);
4803 y2 = max (y1 + min_dimension, y2);
4806 /* translate rect into item space and set */
4808 ArdourCanvas::Rect r (x1, y1, x2, y2);
4810 /* this drag is a _trackview_only == true drag, so the y1 and
4811 * y2 (computed using current_pointer_y() and grab_y()) will be
4812 * relative to the top of the trackview group). The
4813 * rubberband rect has the same parent/scroll offset as the
4814 * the trackview group, so we can use the "r" rect directly
4815 * to set the shape of the rubberband.
4818 _editor->rubberband_rect->set (r);
4819 _editor->rubberband_rect->show();
4820 _editor->rubberband_rect->raise_to_top();
4822 show_verbose_cursor_time (pf);
4824 do_select_things (event, true);
4829 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4833 framepos_t grab = grab_frame ();
4834 framepos_t lpf = last_pointer_frame ();
4836 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4837 grab = raw_grab_frame ();
4838 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4852 if (current_pointer_y() < grab_y()) {
4853 y1 = current_pointer_y();
4856 y2 = current_pointer_y();
4860 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4864 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4866 if (movement_occurred) {
4868 motion (event, false);
4869 do_select_things (event, false);
4875 bool do_deselect = true;
4876 MidiTimeAxisView* mtv;
4878 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4880 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4881 /* nothing selected */
4882 add_midi_region (mtv, true, _editor->get_grid_music_divisions(event->button.state));
4883 do_deselect = false;
4887 /* do not deselect if Primary or Tertiary (toggle-select or
4888 * extend-select are pressed.
4891 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4892 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4899 _editor->rubberband_rect->hide();
4903 RubberbandSelectDrag::aborted (bool)
4905 _editor->rubberband_rect->hide ();
4908 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4909 : RegionDrag (e, i, p, v)
4911 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4915 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4917 Drag::start_grab (event, cursor);
4919 _editor->get_selection().add (_primary);
4921 framepos_t where = _primary->region()->position();
4922 setup_snap_delta (where);
4924 show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4928 TimeFXDrag::motion (GdkEvent* event, bool)
4930 RegionView* rv = _primary;
4931 StreamView* cv = rv->get_time_axis_view().view ();
4933 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4934 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4935 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4936 framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
4937 _editor->snap_to_with_modifier (pf, event);
4938 pf -= snap_delta (event->button.state);
4940 if (pf > rv->region()->position()) {
4941 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4944 show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4948 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
4950 /* this may have been a single click, no drag. We still want the dialog
4951 to show up in that case, so that the user can manually edit the
4952 parameters for the timestretch.
4955 float fraction = 1.0;
4957 if (movement_occurred) {
4959 motion (event, false);
4961 _primary->get_time_axis_view().hide_timestretch ();
4963 framepos_t adjusted_frame_pos = adjusted_current_frame (event);
4965 if (adjusted_frame_pos < _primary->region()->position()) {
4966 /* backwards drag of the left edge - not usable */
4970 framecnt_t newlen = adjusted_frame_pos - _primary->region()->position();
4972 fraction = (double) newlen / (double) _primary->region()->length();
4974 #ifndef USE_RUBBERBAND
4975 // Soundtouch uses fraction / 100 instead of normal (/ 1)
4976 if (_primary->region()->data_type() == DataType::AUDIO) {
4977 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4982 if (!_editor->get_selection().regions.empty()) {
4983 /* primary will already be included in the selection, and edit
4984 group shared editing will propagate selection across
4985 equivalent regions, so just use the current region
4989 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
4990 error << _("An error occurred while executing time stretch operation") << endmsg;
4996 TimeFXDrag::aborted (bool)
4998 _primary->get_time_axis_view().hide_timestretch ();
5001 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5004 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5008 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5010 Drag::start_grab (event);
5014 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5016 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
5020 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5022 if (movement_occurred && _editor->session()) {
5023 /* make sure we stop */
5024 _editor->session()->request_transport_speed (0.0);
5029 ScrubDrag::aborted (bool)
5034 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5038 , _time_selection_at_start (!_editor->get_selection().time.empty())
5040 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5042 if (_time_selection_at_start) {
5043 start_at_start = _editor->get_selection().time.start();
5044 end_at_start = _editor->get_selection().time.end_frame();
5049 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5051 if (_editor->session() == 0) {
5055 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5057 switch (_operation) {
5058 case CreateSelection:
5059 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5064 cursor = _editor->cursors()->selector;
5065 Drag::start_grab (event, cursor);
5068 case SelectionStartTrim:
5069 if (_editor->clicked_axisview) {
5070 _editor->clicked_axisview->order_selection_trims (_item, true);
5072 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5075 case SelectionEndTrim:
5076 if (_editor->clicked_axisview) {
5077 _editor->clicked_axisview->order_selection_trims (_item, false);
5079 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5083 Drag::start_grab (event, cursor);
5086 case SelectionExtend:
5087 Drag::start_grab (event, cursor);
5091 if (_operation == SelectionMove) {
5092 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5094 show_verbose_cursor_time (adjusted_current_frame (event));
5099 SelectionDrag::setup_pointer_frame_offset ()
5101 switch (_operation) {
5102 case CreateSelection:
5103 _pointer_frame_offset = 0;
5106 case SelectionStartTrim:
5108 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
5111 case SelectionEndTrim:
5112 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
5115 case SelectionExtend:
5121 SelectionDrag::motion (GdkEvent* event, bool first_move)
5123 framepos_t start = 0;
5125 framecnt_t length = 0;
5126 framecnt_t distance = 0;
5128 framepos_t const pending_position = adjusted_current_frame (event);
5130 if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
5134 switch (_operation) {
5135 case CreateSelection:
5137 framepos_t grab = grab_frame ();
5140 grab = adjusted_current_frame (event, false);
5141 if (grab < pending_position) {
5142 _editor->snap_to (grab, RoundDownMaybe);
5144 _editor->snap_to (grab, RoundUpMaybe);
5148 if (pending_position < grab) {
5149 start = pending_position;
5152 end = pending_position;
5156 /* first drag: Either add to the selection
5157 or create a new selection
5164 /* adding to the selection */
5165 _editor->set_selected_track_as_side_effect (Selection::Add);
5166 _editor->clicked_selection = _editor->selection->add (start, end);
5173 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5174 _editor->set_selected_track_as_side_effect (Selection::Set);
5177 _editor->clicked_selection = _editor->selection->set (start, end);
5181 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5182 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5183 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5185 _editor->selection->add (atest);
5189 /* select all tracks within the rectangle that we've marked out so far */
5190 TrackViewList new_selection;
5191 TrackViewList& all_tracks (_editor->track_views);
5193 ArdourCanvas::Coord const top = grab_y();
5194 ArdourCanvas::Coord const bottom = current_pointer_y();
5196 if (top >= 0 && bottom >= 0) {
5198 //first, find the tracks that are covered in the y range selection
5199 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5200 if ((*i)->covered_by_y_range (top, bottom)) {
5201 new_selection.push_back (*i);
5205 //now find any tracks that are GROUPED with the tracks we selected
5206 TrackViewList grouped_add = new_selection;
5207 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5208 RouteTimeAxisView *n = dynamic_cast<RouteTimeAxisView *>(*i);
5209 if ( n && n->route()->route_group() && n->route()->route_group()->is_active() && n->route()->route_group()->enabled_property (ARDOUR::Properties::group_select.property_id) ) {
5210 for (TrackViewList::const_iterator j = all_tracks.begin(); j != all_tracks.end(); ++j) {
5211 RouteTimeAxisView *check = dynamic_cast<RouteTimeAxisView *>(*j);
5212 if ( check && (n != check) && (check->route()->route_group() == n->route()->route_group()) )
5213 grouped_add.push_back (*j);
5218 //now compare our list with the current selection, and add or remove as necessary
5219 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5220 TrackViewList tracks_to_add;
5221 TrackViewList tracks_to_remove;
5222 for (TrackViewList::const_iterator i = grouped_add.begin(); i != grouped_add.end(); ++i)
5223 if ( !_editor->selection->tracks.contains ( *i ) )
5224 tracks_to_add.push_back ( *i );
5225 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i)
5226 if ( !grouped_add.contains ( *i ) )
5227 tracks_to_remove.push_back ( *i );
5228 _editor->selection->add(tracks_to_add);
5229 _editor->selection->remove(tracks_to_remove);
5235 case SelectionStartTrim:
5237 end = _editor->selection->time[_editor->clicked_selection].end;
5239 if (pending_position > end) {
5242 start = pending_position;
5246 case SelectionEndTrim:
5248 start = _editor->selection->time[_editor->clicked_selection].start;
5250 if (pending_position < start) {
5253 end = pending_position;
5260 start = _editor->selection->time[_editor->clicked_selection].start;
5261 end = _editor->selection->time[_editor->clicked_selection].end;
5263 length = end - start;
5264 distance = pending_position - start;
5265 start = pending_position;
5266 _editor->snap_to (start);
5268 end = start + length;
5272 case SelectionExtend:
5277 switch (_operation) {
5279 if (_time_selection_at_start) {
5280 _editor->selection->move_time (distance);
5284 _editor->selection->replace (_editor->clicked_selection, start, end);
5288 if (_operation == SelectionMove) {
5289 show_verbose_cursor_time(start);
5291 show_verbose_cursor_time(pending_position);
5296 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5298 Session* s = _editor->session();
5300 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5301 if (movement_occurred) {
5302 motion (event, false);
5303 /* XXX this is not object-oriented programming at all. ick */
5304 if (_editor->selection->time.consolidate()) {
5305 _editor->selection->TimeChanged ();
5308 /* XXX what if its a music time selection? */
5310 if (s->get_play_range() && s->transport_rolling()) {
5311 s->request_play_range (&_editor->selection->time, true);
5312 } else if (!s->config.get_external_sync()) {
5313 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5314 s->request_locate (_editor->get_selection().time.start());
5318 if (_editor->get_selection().time.length() != 0) {
5319 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame());
5321 s->clear_range_selection ();
5326 /* just a click, no pointer movement.
5329 if (was_double_click()) {
5330 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5331 _editor->temporal_zoom_selection (Both);
5336 if (_operation == SelectionExtend) {
5337 if (_time_selection_at_start) {
5338 framepos_t pos = adjusted_current_frame (event, false);
5339 framepos_t start = min (pos, start_at_start);
5340 framepos_t end = max (pos, end_at_start);
5341 _editor->selection->set (start, end);
5344 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5345 if (_editor->clicked_selection) {
5346 _editor->selection->remove (_editor->clicked_selection);
5349 if (!_editor->clicked_selection) {
5350 _editor->selection->clear_time();
5355 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5356 _editor->selection->set (_editor->clicked_axisview);
5359 if (s && s->get_play_range () && s->transport_rolling()) {
5360 s->request_stop (false, false);
5365 _editor->stop_canvas_autoscroll ();
5366 _editor->clicked_selection = 0;
5367 _editor->commit_reversible_selection_op ();
5371 SelectionDrag::aborted (bool)
5376 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5377 : Drag (e, i, false),
5381 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5383 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5384 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5385 physical_screen_height (_editor->current_toplevel()->get_window())));
5386 _drag_rect->hide ();
5388 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5389 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5392 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5394 /* normal canvas items will be cleaned up when their parent group is deleted. But
5395 this item is created as the child of a long-lived parent group, and so we
5396 need to explicitly delete it.
5402 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5404 if (_editor->session() == 0) {
5408 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5410 if (!_editor->temp_location) {
5411 _editor->temp_location = new Location (*_editor->session());
5414 switch (_operation) {
5415 case CreateSkipMarker:
5416 case CreateRangeMarker:
5417 case CreateTransportMarker:
5418 case CreateCDMarker:
5420 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5425 cursor = _editor->cursors()->selector;
5429 Drag::start_grab (event, cursor);
5431 show_verbose_cursor_time (adjusted_current_frame (event));
5435 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5437 framepos_t start = 0;
5439 ArdourCanvas::Rectangle *crect;
5441 switch (_operation) {
5442 case CreateSkipMarker:
5443 crect = _editor->range_bar_drag_rect;
5445 case CreateRangeMarker:
5446 crect = _editor->range_bar_drag_rect;
5448 case CreateTransportMarker:
5449 crect = _editor->transport_bar_drag_rect;
5451 case CreateCDMarker:
5452 crect = _editor->cd_marker_bar_drag_rect;
5455 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5460 framepos_t const pf = adjusted_current_frame (event);
5462 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5463 framepos_t grab = grab_frame ();
5464 _editor->snap_to (grab);
5466 if (pf < grab_frame()) {
5474 /* first drag: Either add to the selection
5475 or create a new selection.
5480 _editor->temp_location->set (start, end);
5484 update_item (_editor->temp_location);
5486 //_drag_rect->raise_to_top();
5492 _editor->temp_location->set (start, end);
5494 double x1 = _editor->sample_to_pixel (start);
5495 double x2 = _editor->sample_to_pixel (end);
5499 update_item (_editor->temp_location);
5502 show_verbose_cursor_time (pf);
5507 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5509 Location * newloc = 0;
5513 if (movement_occurred) {
5514 motion (event, false);
5517 switch (_operation) {
5518 case CreateSkipMarker:
5519 case CreateRangeMarker:
5520 case CreateCDMarker:
5522 XMLNode &before = _editor->session()->locations()->get_state();
5523 if (_operation == CreateSkipMarker) {
5524 _editor->begin_reversible_command (_("new skip marker"));
5525 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5526 flags = Location::IsRangeMarker | Location::IsSkip;
5527 _editor->range_bar_drag_rect->hide();
5528 } else if (_operation == CreateCDMarker) {
5529 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5530 _editor->begin_reversible_command (_("new CD marker"));
5531 flags = Location::IsRangeMarker | Location::IsCDMarker;
5532 _editor->cd_marker_bar_drag_rect->hide();
5534 _editor->begin_reversible_command (_("new skip marker"));
5535 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5536 flags = Location::IsRangeMarker;
5537 _editor->range_bar_drag_rect->hide();
5539 newloc = new Location (
5540 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5541 , _editor->get_grid_music_divisions (event->button.state));
5543 _editor->session()->locations()->add (newloc, true);
5544 XMLNode &after = _editor->session()->locations()->get_state();
5545 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5546 _editor->commit_reversible_command ();
5550 case CreateTransportMarker:
5551 // popup menu to pick loop or punch
5552 _editor->new_transport_marker_context_menu (&event->button, _item);
5558 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5560 if (_operation == CreateTransportMarker) {
5562 /* didn't drag, so just locate */
5564 _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5566 } else if (_operation == CreateCDMarker) {
5568 /* didn't drag, but mark is already created so do
5571 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5576 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5578 if (end == max_framepos) {
5579 end = _editor->session()->current_end_frame ();
5582 if (start == max_framepos) {
5583 start = _editor->session()->current_start_frame ();
5586 switch (_editor->mouse_mode) {
5588 /* find the two markers on either side and then make the selection from it */
5589 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5593 /* find the two markers on either side of the click and make the range out of it */
5594 _editor->selection->set (start, end);
5603 _editor->stop_canvas_autoscroll ();
5607 RangeMarkerBarDrag::aborted (bool movement_occurred)
5609 if (movement_occurred) {
5610 _drag_rect->hide ();
5615 RangeMarkerBarDrag::update_item (Location* location)
5617 double const x1 = _editor->sample_to_pixel (location->start());
5618 double const x2 = _editor->sample_to_pixel (location->end());
5620 _drag_rect->set_x0 (x1);
5621 _drag_rect->set_x1 (x2);
5624 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5626 , _cumulative_dx (0)
5627 , _cumulative_dy (0)
5628 , _was_selected (false)
5630 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5632 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5634 _region = &_primary->region_view ();
5635 _note_height = _region->midi_stream_view()->note_height ();
5639 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5641 Drag::start_grab (event);
5642 setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5644 if (!(_was_selected = _primary->selected())) {
5646 /* tertiary-click means extend selection - we'll do that on button release,
5647 so don't add it here, because otherwise we make it hard to figure
5648 out the "extend-to" range.
5651 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5654 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5657 _region->note_selected (_primary, true);
5659 _editor->get_selection().clear_points();
5660 _region->unique_select (_primary);
5666 /** @return Current total drag x change in frames */
5668 NoteDrag::total_dx (const guint state) const
5670 if (_x_constrained) {
5673 TempoMap& map (_editor->session()->tempo_map());
5676 frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5678 /* primary note time */
5679 double const quarter_note_start = _region->region()->quarter_note() - _region->midi_region()->start_beats();
5680 frameoffset_t const n = map.frame_at_quarter_note (quarter_note_start + _primary->note()->time().to_double());
5682 /* new time of the primary note in session frames */
5683 frameoffset_t st = n + dx + snap_delta (state);
5685 framepos_t const rp = _region->region()->position ();
5687 /* prevent the note being dragged earlier than the region's position */
5690 /* possibly snap and return corresponding delta */
5694 if (ArdourKeyboard::indicates_snap (state)) {
5695 if (_editor->snap_mode () != SnapOff) {
5699 if (_editor->snap_mode () == SnapOff) {
5701 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
5702 if (ArdourKeyboard::indicates_snap_delta (state)) {
5710 bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
5711 ret = _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
5713 ret = st - n - snap_delta (state);
5718 /** @return Current total drag y change in note number */
5720 NoteDrag::total_dy () const
5722 if (_y_constrained) {
5726 double const y = _region->midi_view()->y_position ();
5727 /* new current note */
5728 uint8_t n = _region->y_to_note (current_pointer_y () - y);
5730 MidiStreamView* msv = _region->midi_stream_view ();
5731 n = max (msv->lowest_note(), n);
5732 n = min (msv->highest_note(), n);
5733 /* and work out delta */
5734 return n - _region->y_to_note (grab_y() - y);
5738 NoteDrag::motion (GdkEvent * event, bool)
5740 /* Total change in x and y since the start of the drag */
5741 frameoffset_t const dx = total_dx (event->button.state);
5742 int8_t const dy = total_dy ();
5744 /* Now work out what we have to do to the note canvas items to set this new drag delta */
5745 double const tdx = _x_constrained ? 0 : _editor->sample_to_pixel (dx) - _cumulative_dx;
5746 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
5749 _cumulative_dx += tdx;
5750 _cumulative_dy += tdy;
5752 int8_t note_delta = total_dy();
5755 _region->move_selection (tdx, tdy, note_delta);
5757 /* the new note value may be the same as the old one, but we
5758 * don't know what that means because the selection may have
5759 * involved more than one note and we might be doing something
5760 * odd with them. so show the note value anyway, always.
5763 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5765 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
5771 NoteDrag::finished (GdkEvent* ev, bool moved)
5774 /* no motion - select note */
5776 if (_editor->current_mouse_mode() == Editing::MouseContent ||
5777 _editor->current_mouse_mode() == Editing::MouseDraw) {
5779 bool changed = false;
5781 if (_was_selected) {
5782 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5784 _region->note_deselected (_primary);
5787 _editor->get_selection().clear_points();
5788 _region->unique_select (_primary);
5792 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5793 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5795 if (!extend && !add && _region->selection_size() > 1) {
5796 _editor->get_selection().clear_points();
5797 _region->unique_select (_primary);
5799 } else if (extend) {
5800 _region->note_selected (_primary, true, true);
5803 /* it was added during button press */
5810 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5811 _editor->commit_reversible_selection_op();
5815 _region->note_dropped (_primary, total_dx (ev->button.state), total_dy());
5820 NoteDrag::aborted (bool)
5825 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5826 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5827 : Drag (editor, atv->base_item ())
5829 , _y_origin (atv->y_position())
5830 , _nothing_to_drag (false)
5832 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5833 setup (atv->lines ());
5836 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5837 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5838 : Drag (editor, rv->get_canvas_group ())
5840 , _y_origin (rv->get_time_axis_view().y_position())
5841 , _nothing_to_drag (false)
5844 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5846 list<boost::shared_ptr<AutomationLine> > lines;
5848 AudioRegionView* audio_view;
5849 AutomationRegionView* automation_view;
5850 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5851 lines.push_back (audio_view->get_gain_line ());
5852 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5853 lines.push_back (automation_view->line ());
5856 error << _("Automation range drag created for invalid region type") << endmsg;
5862 /** @param lines AutomationLines to drag.
5863 * @param offset Offset from the session start to the points in the AutomationLines.
5866 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5868 /* find the lines that overlap the ranges being dragged */
5869 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5870 while (i != lines.end ()) {
5871 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5874 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5876 /* check this range against all the AudioRanges that we are using */
5877 list<AudioRange>::const_iterator k = _ranges.begin ();
5878 while (k != _ranges.end()) {
5879 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5885 /* add it to our list if it overlaps at all */
5886 if (k != _ranges.end()) {
5891 _lines.push_back (n);
5897 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5901 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5903 return 1.0 - ((global_y - _y_origin) / line->height());
5907 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5909 const double v = list->eval(x);
5910 return _integral ? rint(v) : v;
5914 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5916 Drag::start_grab (event, cursor);
5918 /* Get line states before we start changing things */
5919 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5920 i->state = &i->line->get_state ();
5921 i->original_fraction = y_fraction (i->line, current_pointer_y());
5924 if (_ranges.empty()) {
5926 /* No selected time ranges: drag all points */
5927 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5928 uint32_t const N = i->line->npoints ();
5929 for (uint32_t j = 0; j < N; ++j) {
5930 i->points.push_back (i->line->nth (j));
5936 if (_nothing_to_drag) {
5942 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
5944 if (_nothing_to_drag && !first_move) {
5949 _editor->begin_reversible_command (_("automation range move"));
5951 if (!_ranges.empty()) {
5953 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5955 framecnt_t const half = (i->start + i->end) / 2;
5957 /* find the line that this audio range starts in */
5958 list<Line>::iterator j = _lines.begin();
5959 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5963 if (j != _lines.end()) {
5964 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5966 /* j is the line that this audio range starts in; fade into it;
5967 64 samples length plucked out of thin air.
5970 framepos_t a = i->start + 64;
5975 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5976 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5978 XMLNode &before = the_list->get_state();
5979 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5980 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5982 if (add_p || add_q) {
5983 _editor->session()->add_command (
5984 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
5988 /* same thing for the end */
5991 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5995 if (j != _lines.end()) {
5996 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5998 /* j is the line that this audio range starts in; fade out of it;
5999 64 samples length plucked out of thin air.
6002 framepos_t b = i->end - 64;
6007 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6008 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6010 XMLNode &before = the_list->get_state();
6011 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6012 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6014 if (add_p || add_q) {
6015 _editor->session()->add_command (
6016 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6021 _nothing_to_drag = true;
6023 /* Find all the points that should be dragged and put them in the relevant
6024 points lists in the Line structs.
6027 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6029 uint32_t const N = i->line->npoints ();
6030 for (uint32_t j = 0; j < N; ++j) {
6032 /* here's a control point on this line */
6033 ControlPoint* p = i->line->nth (j);
6034 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6036 /* see if it's inside a range */
6037 list<AudioRange>::const_iterator k = _ranges.begin ();
6038 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6042 if (k != _ranges.end()) {
6043 /* dragging this point */
6044 _nothing_to_drag = false;
6045 i->points.push_back (p);
6051 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6052 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6056 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6057 float const f = y_fraction (l->line, current_pointer_y());
6058 /* we are ignoring x position for this drag, so we can just pass in anything */
6059 pair<double, float> result;
6061 result = l->line->drag_motion (0, f, true, false, ignored);
6062 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, result.second));
6067 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6069 if (_nothing_to_drag || !motion_occurred) {
6073 motion (event, false);
6074 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6075 i->line->end_drag (false, 0);
6078 _editor->commit_reversible_command ();
6082 AutomationRangeDrag::aborted (bool)
6084 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6089 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6091 , initial_time_axis_view (itav)
6093 /* note that time_axis_view may be null if the regionview was created
6094 * as part of a copy operation.
6096 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6097 layer = v->region()->layer ();
6098 initial_y = v->get_canvas_group()->position().y;
6099 initial_playlist = v->region()->playlist ();
6100 initial_position = v->region()->position ();
6101 initial_end = v->region()->position () + v->region()->length ();
6104 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6105 : Drag (e, i->canvas_item ())
6108 , _cumulative_dx (0)
6110 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6111 _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
6116 PatchChangeDrag::motion (GdkEvent* ev, bool)
6118 framepos_t f = adjusted_current_frame (ev);
6119 boost::shared_ptr<Region> r = _region_view->region ();
6120 f = max (f, r->position ());
6121 f = min (f, r->last_frame ());
6123 framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
6124 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6125 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6126 _cumulative_dx = dxu;
6130 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6132 if (!movement_occurred) {
6133 if (was_double_click()) {
6134 _region_view->edit_patch_change (_patch_change);
6139 boost::shared_ptr<Region> r (_region_view->region ());
6140 framepos_t f = adjusted_current_frame (ev);
6141 f = max (f, r->position ());
6142 f = min (f, r->last_frame ());
6144 _region_view->move_patch_change (
6146 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
6151 PatchChangeDrag::aborted (bool)
6153 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6157 PatchChangeDrag::setup_pointer_frame_offset ()
6159 boost::shared_ptr<Region> region = _region_view->region ();
6160 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
6163 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6164 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6171 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6173 _region_view->update_drag_selection (
6175 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6179 MidiRubberbandSelectDrag::deselect_things ()
6184 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6185 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6188 _vertical_only = true;
6192 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6194 double const y = _region_view->midi_view()->y_position ();
6196 y1 = max (0.0, y1 - y);
6197 y2 = max (0.0, y2 - y);
6199 _region_view->update_vertical_drag_selection (
6202 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6207 MidiVerticalSelectDrag::deselect_things ()
6212 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6213 : RubberbandSelectDrag (e, i)
6219 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
6221 if (drag_in_progress) {
6222 /* We just want to select things at the end of the drag, not during it */
6226 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6228 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6230 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6232 _editor->commit_reversible_selection_op ();
6236 EditorRubberbandSelectDrag::deselect_things ()
6238 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6240 _editor->selection->clear_tracks();
6241 _editor->selection->clear_regions();
6242 _editor->selection->clear_points ();
6243 _editor->selection->clear_lines ();
6244 _editor->selection->clear_midi_notes ();
6246 _editor->commit_reversible_selection_op();
6249 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6254 _note[0] = _note[1] = 0;
6257 NoteCreateDrag::~NoteCreateDrag ()
6263 NoteCreateDrag::grid_frames (framepos_t t) const
6266 const Evoral::Beats grid_beats = _region_view->get_grid_beats (t);
6267 const Evoral::Beats t_beats = _region_view->region_frames_to_region_beats (t);
6269 return _region_view->region_beats_to_region_frames (t_beats + grid_beats)
6270 - _region_view->region_beats_to_region_frames (t_beats);
6274 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6276 Drag::start_grab (event, cursor);
6278 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6279 TempoMap& map (_editor->session()->tempo_map());
6281 const framepos_t pf = _drags->current_pointer_frame ();
6282 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6284 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6286 double eqaf = map.exact_qn_at_frame (pf, divisions);
6288 if (divisions != 0) {
6290 const double qaf = map.quarter_note_at_frame (pf);
6292 /* Hack so that we always snap to the note that we are over, instead of snapping
6293 to the next one if we're more than halfway through the one we're over.
6296 const double rem = eqaf - qaf;
6298 eqaf -= grid_beats.to_double();
6302 _note[0] = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6303 /* minimum initial length is grid beats */
6304 _note[1] = map.frame_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6306 double const x0 = _editor->sample_to_pixel (_note[0]);
6307 double const x1 = _editor->sample_to_pixel (_note[1]);
6308 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6310 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6311 _drag_rect->set_outline_all ();
6312 _drag_rect->set_outline_color (0xffffff99);
6313 _drag_rect->set_fill_color (0xffffff66);
6317 NoteCreateDrag::motion (GdkEvent* event, bool)
6319 TempoMap& map (_editor->session()->tempo_map());
6320 const framepos_t pf = _drags->current_pointer_frame ();
6321 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6322 double eqaf = map.exact_qn_at_frame (pf, divisions);
6324 if (divisions != 0) {
6326 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6328 const double qaf = map.quarter_note_at_frame (pf);
6329 /* Hack so that we always snap to the note that we are over, instead of snapping
6330 to the next one if we're more than halfway through the one we're over.
6333 const double rem = eqaf - qaf;
6335 eqaf -= grid_beats.to_double();
6338 eqaf += grid_beats.to_double();
6340 _note[1] = max ((framepos_t)0, map.frame_at_quarter_note (eqaf) - _region_view->region()->position ());
6342 double const x0 = _editor->sample_to_pixel (_note[0]);
6343 double const x1 = _editor->sample_to_pixel (_note[1]);
6344 _drag_rect->set_x0 (std::min(x0, x1));
6345 _drag_rect->set_x1 (std::max(x0, x1));
6349 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6351 /* we create a note even if there was no movement */
6352 framepos_t const start = min (_note[0], _note[1]);
6353 framepos_t const start_sess_rel = start + _region_view->region()->position();
6354 framecnt_t length = max (_editor->pixel_to_sample (1.0), (framecnt_t) fabs ((double)(_note[0] - _note[1])));
6355 framecnt_t const g = grid_frames (start);
6357 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6361 TempoMap& map (_editor->session()->tempo_map());
6362 const double qn_length = map.quarter_notes_between_frames (start_sess_rel, start_sess_rel + length);
6363 Evoral::Beats qn_length_beats = max (Evoral::Beats::ticks(1), Evoral::Beats (qn_length));
6365 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6369 NoteCreateDrag::y_to_region (double y) const
6372 _region_view->get_canvas_group()->canvas_to_item (x, y);
6377 NoteCreateDrag::aborted (bool)
6382 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6390 HitCreateDrag::~HitCreateDrag ()
6395 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6397 Drag::start_grab (event, cursor);
6399 TempoMap& map (_editor->session()->tempo_map());
6401 const framepos_t pf = _drags->current_pointer_frame ();
6402 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6404 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6406 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6408 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6412 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6413 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6415 Evoral::Beats length = _region_view->get_grid_beats (pf);
6417 _region_view->create_note_at (start, y, length, event->button.state, false);
6424 HitCreateDrag::motion (GdkEvent* event, bool)
6426 TempoMap& map (_editor->session()->tempo_map());
6428 const framepos_t pf = _drags->current_pointer_frame ();
6429 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6431 if (divisions == 0) {
6435 const double eqaf = map.exact_qn_at_frame (pf, divisions);
6436 const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position ();
6437 const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6439 if (_last_pos == start && y == _last_y) {
6443 Evoral::Beats length = _region_view->get_grid_beats (pf);
6445 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6446 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6450 _region_view->create_note_at (start, y, length, event->button.state, false);
6457 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6463 HitCreateDrag::y_to_region (double y) const
6466 _region_view->get_canvas_group()->canvas_to_item (x, y);
6471 HitCreateDrag::aborted (bool)
6476 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6481 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6485 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6487 Drag::start_grab (event, cursor);
6491 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6497 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6500 distance = _drags->current_pointer_x() - grab_x();
6501 len = ar->fade_in()->back()->when;
6503 distance = grab_x() - _drags->current_pointer_x();
6504 len = ar->fade_out()->back()->when;
6507 /* how long should it be ? */
6509 new_length = len + _editor->pixel_to_sample (distance);
6511 /* now check with the region that this is legal */
6513 new_length = ar->verify_xfade_bounds (new_length, start);
6516 arv->reset_fade_in_shape_width (ar, new_length);
6518 arv->reset_fade_out_shape_width (ar, new_length);
6523 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6529 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6532 distance = _drags->current_pointer_x() - grab_x();
6533 len = ar->fade_in()->back()->when;
6535 distance = grab_x() - _drags->current_pointer_x();
6536 len = ar->fade_out()->back()->when;
6539 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6541 _editor->begin_reversible_command ("xfade trim");
6542 ar->playlist()->clear_owned_changes ();
6545 ar->set_fade_in_length (new_length);
6547 ar->set_fade_out_length (new_length);
6550 /* Adjusting the xfade may affect other regions in the playlist, so we need
6551 to get undo Commands from the whole playlist rather than just the
6555 vector<Command*> cmds;
6556 ar->playlist()->rdiff (cmds);
6557 _editor->session()->add_commands (cmds);
6558 _editor->commit_reversible_command ();
6563 CrossfadeEdgeDrag::aborted (bool)
6566 // arv->redraw_start_xfade ();
6568 // arv->redraw_end_xfade ();
6572 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
6573 : Drag (e, item, true)
6574 , line (new EditorCursor (*e))
6576 line->set_position (pos);
6578 line->track_canvas_item().reparent (_editor->_drag_motion_group);
6581 RegionCutDrag::~RegionCutDrag ()
6587 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6589 Drag::start_grab (event, c);
6590 motion (event, false);
6594 RegionCutDrag::motion (GdkEvent* event, bool)
6596 framepos_t pos = _drags->current_pointer_frame();
6597 _editor->snap_to_with_modifier (pos, event);
6599 line->set_position (pos);
6603 RegionCutDrag::finished (GdkEvent* event, bool)
6605 _editor->get_track_canvas()->canvas()->re_enter();
6607 framepos_t pos = _drags->current_pointer_frame();
6608 _editor->snap_to_with_modifier (pos, event);
6612 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
6618 _editor->split_regions_at (pos, rs, _editor->get_grid_music_divisions (event->button.state),
6623 RegionCutDrag::aborted (bool)
6627 RulerZoomDrag::RulerZoomDrag (Editor* e, ArdourCanvas::Item* item)
6628 : Drag (e, item, true)
6633 RulerZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6635 Drag::start_grab (event, c);
6637 framepos_t where = _editor->canvas_event_sample(event);
6639 _editor->_dragging_playhead = true;
6641 _editor->playhead_cursor->set_position (where);
6645 RulerZoomDrag::motion (GdkEvent* event, bool)
6647 framepos_t where = _editor->canvas_event_sample(event);
6649 _editor->playhead_cursor->set_position (where);
6651 const double movement_limit = 20.0;
6652 const double scale = 1.08;
6653 const double y_delta = last_pointer_y() - current_pointer_y();
6655 if (y_delta > 0 && y_delta < movement_limit) {
6656 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
6657 } else if (y_delta < 0 && y_delta > -movement_limit) {
6658 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
6663 RulerZoomDrag::finished (GdkEvent*, bool)
6665 _editor->_dragging_playhead = false;
6667 Session* s = _editor->session ();
6669 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
6670 _editor->_pending_locate_request = true;
6676 RulerZoomDrag::aborted (bool)