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/profile.h"
41 #include "ardour/region_factory.h"
42 #include "ardour/session.h"
43 #include "ardour/session_playlists.h"
45 #include "canvas/canvas.h"
46 #include "canvas/scroll_group.h"
51 #include "audio_region_view.h"
52 #include "automation_region_view.h"
53 #include "midi_region_view.h"
54 #include "ardour_ui.h"
55 #include "gui_thread.h"
56 #include "control_point.h"
57 #include "region_gain_line.h"
58 #include "editor_drag.h"
59 #include "audio_time_axis.h"
60 #include "midi_time_axis.h"
61 #include "selection.h"
62 #include "midi_selection.h"
63 #include "automation_time_axis.h"
65 #include "editor_cursors.h"
66 #include "mouse_cursors.h"
67 #include "note_base.h"
68 #include "patch_change.h"
69 #include "ui_config.h"
70 #include "verbose_cursor.h"
73 using namespace ARDOUR;
76 using namespace Gtkmm2ext;
77 using namespace Editing;
78 using namespace ArdourCanvas;
80 using Gtkmm2ext::Keyboard;
82 double ControlPointDrag::_zero_gain_fraction = -1.0;
84 DragManager::DragManager (Editor* e)
87 , _current_pointer_x (0.0)
88 , _current_pointer_y (0.0)
89 , _current_pointer_sample (0)
90 , _old_follow_playhead (false)
94 DragManager::~DragManager ()
99 /** Call abort for each active drag */
101 DragManager::abort ()
105 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
110 if (!_drags.empty ()) {
111 _editor->set_follow_playhead (_old_follow_playhead, false);
115 _editor->abort_reversible_command();
121 DragManager::add (Drag* d)
123 d->set_manager (this);
124 _drags.push_back (d);
128 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
130 d->set_manager (this);
131 _drags.push_back (d);
136 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
138 /* Prevent follow playhead during the drag to be nice to the user */
139 _old_follow_playhead = _editor->follow_playhead ();
140 _editor->set_follow_playhead (false);
142 _current_pointer_sample = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
144 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
145 (*i)->start_grab (e, c);
149 /** Call end_grab for each active drag.
150 * @return true if any drag reported movement having occurred.
153 DragManager::end_grab (GdkEvent* e)
158 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
159 bool const t = (*i)->end_grab (e);
170 _editor->set_follow_playhead (_old_follow_playhead, false);
176 DragManager::mark_double_click ()
178 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
179 (*i)->set_double_click (true);
184 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
188 /* calling this implies that we expect the event to have canvas
191 * Can we guarantee that this is true?
194 _current_pointer_sample = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
196 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
197 bool const t = (*i)->motion_handler (e, from_autoscroll);
198 /* run all handlers; return true if at least one of them
199 returns true (indicating that the event has been handled).
211 DragManager::have_item (ArdourCanvas::Item* i) const
213 list<Drag*>::const_iterator j = _drags.begin ();
214 while (j != _drags.end() && (*j)->item () != i) {
218 return j != _drags.end ();
221 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
225 , _pointer_sample_offset (0)
226 , _x_constrained (false)
227 , _y_constrained (false)
228 , _was_rolling (false)
229 , _trackview_only (trackview_only)
230 , _move_threshold_passed (false)
231 , _starting_point_passed (false)
232 , _initially_vertical (false)
233 , _was_double_click (false)
236 , _last_pointer_x (0.0)
237 , _last_pointer_y (0.0)
238 , _raw_grab_sample (0)
240 , _last_pointer_sample (0)
242 , _snap_delta_music (0.0)
243 , _constraint_pressed (false)
249 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
255 _cursor_ctx = CursorContext::create (*_editor, cursor);
257 _cursor_ctx->change (cursor);
264 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
267 /* we set up x/y dragging constraints on first move */
268 _constraint_pressed = ArdourKeyboard::indicates_constraint (event->button.state);
270 _raw_grab_sample = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
272 setup_pointer_sample_offset ();
273 _grab_sample = adjusted_sample (_raw_grab_sample, event).sample;
274 _last_pointer_sample = _grab_sample;
275 _last_pointer_x = _grab_x;
277 if (_trackview_only) {
278 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
281 _last_pointer_y = _grab_y;
285 if (!_editor->cursors()->is_invalid (cursor)) {
286 /* CAIROCANVAS need a variant here that passes *cursor */
287 _cursor_ctx = CursorContext::create (*_editor, cursor);
290 if (_editor->session() && _editor->session()->transport_rolling()) {
293 _was_rolling = false;
296 switch (_editor->snap_type()) {
297 case SnapToRegionStart:
298 case SnapToRegionEnd:
299 case SnapToRegionSync:
300 case SnapToRegionBoundary:
301 _editor->build_region_boundary_cache ();
308 /** Call to end a drag `successfully'. Ungrabs item and calls
309 * subclass' finished() method.
311 * @param event GDK event, or 0.
312 * @return true if some movement occurred, otherwise false.
315 Drag::end_grab (GdkEvent* event)
317 _editor->stop_canvas_autoscroll ();
321 finished (event, _move_threshold_passed);
323 _editor->verbose_cursor()->hide ();
326 return _move_threshold_passed;
330 Drag::adjusted_sample (samplepos_t f, GdkEvent const * event, bool snap) const
332 MusicSample pos (0, 0);
334 if (f > _pointer_sample_offset) {
335 pos.sample = f - _pointer_sample_offset;
339 _editor->snap_to_with_modifier (pos, event);
346 Drag::adjusted_current_sample (GdkEvent const * event, bool snap) const
348 return adjusted_sample (_drags->current_pointer_sample (), event, snap).sample;
352 Drag::snap_delta (guint state) const
354 if (ArdourKeyboard::indicates_snap_delta (state)) {
361 Drag::snap_delta_music (guint state) const
363 if (ArdourKeyboard::indicates_snap_delta (state)) {
364 return _snap_delta_music;
371 Drag::current_pointer_x() const
373 return _drags->current_pointer_x ();
377 Drag::current_pointer_y () const
379 if (!_trackview_only) {
380 return _drags->current_pointer_y ();
383 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
387 Drag::setup_snap_delta (MusicSample pos)
389 TempoMap& map (_editor->session()->tempo_map());
390 MusicSample snap (pos);
391 _editor->snap_to (snap, ARDOUR::RoundNearest, false, true);
392 _snap_delta = snap.sample - pos.sample;
394 _snap_delta_music = 0.0;
396 if (_snap_delta != 0) {
397 _snap_delta_music = map.exact_qn_at_sample (snap.sample, snap.division) - map.exact_qn_at_sample (pos.sample, pos.division);
402 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
404 /* check to see if we have moved in any way that matters since the last motion event */
405 if (_move_threshold_passed &&
406 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
407 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
411 pair<samplecnt_t, int> const threshold = move_threshold ();
413 bool const old_move_threshold_passed = _move_threshold_passed;
415 if (!_move_threshold_passed) {
417 bool const xp = (::llabs (_drags->current_pointer_sample () - _raw_grab_sample) >= threshold.first);
418 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
420 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
423 if (active (_editor->mouse_mode) && _move_threshold_passed) {
425 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
427 if (old_move_threshold_passed != _move_threshold_passed) {
431 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
432 _initially_vertical = true;
434 _initially_vertical = false;
436 /** check constraints for this drag.
437 * Note that the current convention is to use "contains" for
438 * key modifiers during motion and "equals" when initiating a drag.
439 * In this case we haven't moved yet, so "equals" applies here.
441 if (Config->get_edit_mode() != Lock) {
442 if (event->motion.state & Gdk::BUTTON2_MASK) {
443 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
444 if (_constraint_pressed) {
445 _x_constrained = false;
446 _y_constrained = true;
448 _x_constrained = true;
449 _y_constrained = false;
451 } else if (_constraint_pressed) {
452 // if dragging normally, the motion is constrained to the first direction of movement.
453 if (_initially_vertical) {
454 _x_constrained = true;
455 _y_constrained = false;
457 _x_constrained = false;
458 _y_constrained = true;
462 if (event->button.state & Gdk::BUTTON2_MASK) {
463 _x_constrained = false;
465 _x_constrained = true;
467 _y_constrained = false;
471 if (!from_autoscroll) {
472 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
475 if (!_editor->autoscroll_active() || from_autoscroll) {
478 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
480 motion (event, first_move && !_starting_point_passed);
482 if (first_move && !_starting_point_passed) {
483 _starting_point_passed = true;
486 _last_pointer_x = _drags->current_pointer_x ();
487 _last_pointer_y = current_pointer_y ();
488 _last_pointer_sample = adjusted_current_sample (event, false);
498 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
506 aborted (_move_threshold_passed);
508 _editor->stop_canvas_autoscroll ();
509 _editor->verbose_cursor()->hide ();
513 Drag::show_verbose_cursor_time (samplepos_t sample)
515 _editor->verbose_cursor()->set_time (sample);
516 _editor->verbose_cursor()->show ();
520 Drag::show_verbose_cursor_duration (samplepos_t start, samplepos_t end, double /*xoffset*/)
522 _editor->verbose_cursor()->set_duration (start, end);
523 _editor->verbose_cursor()->show ();
527 Drag::show_verbose_cursor_text (string const & text)
529 _editor->verbose_cursor()->set (text);
530 _editor->verbose_cursor()->show ();
533 boost::shared_ptr<Region>
534 Drag::add_midi_region (MidiTimeAxisView* view, bool commit)
536 if (_editor->session()) {
537 const TempoMap& map (_editor->session()->tempo_map());
538 samplecnt_t pos = grab_sample();
539 /* not that the frame rate used here can be affected by pull up/down which
542 samplecnt_t len = map.sample_at_beat (max (0.0, map.beat_at_sample (pos)) + 1.0) - pos;
543 return view->add_region (grab_sample(), len, commit);
546 return boost::shared_ptr<Region>();
549 struct TimeAxisViewStripableSorter {
550 bool operator() (TimeAxisView* tav_a, TimeAxisView* tav_b) {
551 boost::shared_ptr<ARDOUR::Stripable> const& a = tav_a->stripable ();
552 boost::shared_ptr<ARDOUR::Stripable> const& b = tav_b->stripable ();
553 return ARDOUR::Stripable::Sorter () (a, b);
557 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
562 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
564 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
565 as some of the regions we are dragging may be on such tracks.
568 TrackViewList track_views = _editor->track_views;
569 track_views.sort (TimeAxisViewStripableSorter ());
571 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
572 _time_axis_views.push_back (*i);
574 TimeAxisView::Children children_list = (*i)->get_child_list ();
575 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
576 _time_axis_views.push_back (j->get());
580 /* the list of views can be empty at this point if this is a region list-insert drag
583 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
584 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
587 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
591 RegionDrag::region_going_away (RegionView* v)
593 list<DraggingView>::iterator i = _views.begin ();
594 while (i != _views.end() && i->view != v) {
598 if (i != _views.end()) {
603 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
604 * or -1 if it is not found.
607 RegionDrag::find_time_axis_view (TimeAxisView* t) const
610 int const N = _time_axis_views.size ();
611 while (i < N && _time_axis_views[i] != t) {
615 if (_time_axis_views[i] != t) {
622 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
623 : RegionDrag (e, i, p, v)
625 , _ignore_video_lock (false)
626 , _last_position (0, 0)
628 , _last_pointer_time_axis_view (0)
629 , _last_pointer_layer (0)
634 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
638 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
640 Drag::start_grab (event, cursor);
641 setup_snap_delta (_last_position);
643 show_verbose_cursor_time (_last_position.sample);
645 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
647 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
648 assert(_last_pointer_time_axis_view >= 0);
649 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
652 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
653 _ignore_video_lock = true;
657 /* cross track dragging seems broken here. disabled for now. */
658 _y_constrained = true;
663 RegionMotionDrag::compute_x_delta (GdkEvent const * event, MusicSample* pending_region_position)
665 /* compute the amount of pointer motion in samples, and where
666 the region would be if we moved it by that much.
668 if (_x_constrained) {
669 *pending_region_position = _last_position;
673 *pending_region_position = adjusted_sample (_drags->current_pointer_sample (), event, false);
675 samplecnt_t sync_offset;
678 sync_offset = _primary->region()->sync_offset (sync_dir);
680 /* we don't handle a sync point that lies before zero.
682 if (sync_dir >= 0 || (sync_dir < 0 && pending_region_position->sample >= sync_offset)) {
684 samplecnt_t const sd = snap_delta (event->button.state);
685 MusicSample sync_snap (pending_region_position->sample + (sync_dir * sync_offset) + sd, 0);
686 _editor->snap_to_with_modifier (sync_snap, event);
687 if (sync_offset == 0 && sd == 0) {
688 *pending_region_position = sync_snap;
690 pending_region_position->set (_primary->region()->adjust_to_sync (sync_snap.sample) - sd, 0);
693 *pending_region_position = _last_position;
696 if (pending_region_position->sample > max_samplepos - _primary->region()->length()) {
697 *pending_region_position = _last_position;
702 bool const x_move_allowed = !_x_constrained;
704 if ((pending_region_position->sample != _last_position.sample) && x_move_allowed) {
706 /* x movement since last time (in pixels) */
707 dx = _editor->sample_to_pixel_unrounded (pending_region_position->sample - _last_position.sample);
709 /* total x movement */
710 samplecnt_t total_dx = _editor->pixel_to_sample (_total_x_delta + dx);
712 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
713 sampleoffset_t const off = i->view->region()->position() + total_dx;
715 dx = dx - _editor->sample_to_pixel_unrounded (off);
716 *pending_region_position = MusicSample (pending_region_position->sample - off, 0);
722 _editor->set_snapped_cursor_position(pending_region_position->sample);
728 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
734 const int tavsize = _time_axis_views.size();
735 const int dt = delta > 0 ? +1 : -1;
737 int target = start + delta - skip;
739 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
740 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
742 while (current >= 0 && current != target) {
744 if (current < 0 && dt < 0) {
747 if (current >= tavsize && dt > 0) {
750 if (current < 0 || current >= tavsize) {
754 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
755 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
759 if (distance_only && current == start + delta) {
767 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
769 if (_y_constrained) {
773 const int tavsize = _time_axis_views.size();
774 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
775 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
776 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
778 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
779 /* already in the drop zone */
780 if (delta_track >= 0) {
781 /* downward motion - OK if others are still not in the dropzone */
790 } else if (n >= tavsize) {
791 /* downward motion into drop zone. That's fine. */
795 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
796 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
797 /* not a track, or the wrong type */
801 double const l = i->layer + delta_layer;
803 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
804 mode to allow the user to place a region below another on layer 0.
806 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
807 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
808 If it has, the layers will be munged later anyway, so it's ok.
814 /* all regions being dragged are ok with this change */
818 struct DraggingViewSorter {
819 bool operator() (const DraggingView& a, const DraggingView& b) {
820 return a.time_axis_view < b.time_axis_view;
825 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
827 double delta_layer = 0;
828 int delta_time_axis_view = 0;
829 int current_pointer_time_axis_view = -1;
831 assert (!_views.empty ());
833 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
835 /* Find the TimeAxisView that the pointer is now over */
836 const double cur_y = current_pointer_y ();
837 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
838 TimeAxisView* tv = r.first;
840 if (!tv && cur_y < 0) {
841 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
845 /* find drop-zone y-position */
846 Coord last_track_bottom_edge;
847 last_track_bottom_edge = 0;
848 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
849 if (!(*t)->hidden()) {
850 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
855 if (tv && tv->view()) {
856 /* the mouse is over a track */
857 double layer = r.second;
859 if (first_move && tv->view()->layer_display() == Stacked) {
860 tv->view()->set_layer_display (Expanded);
863 /* Here's the current pointer position in terms of time axis view and layer */
864 current_pointer_time_axis_view = find_time_axis_view (tv);
865 assert(current_pointer_time_axis_view >= 0);
867 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
869 /* Work out the change in y */
871 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
872 if (!rtv || !rtv->is_track()) {
873 /* ignore non-tracks early on. we can't move any regions on them */
874 } else if (_last_pointer_time_axis_view < 0) {
875 /* Was in the drop-zone, now over a track.
876 * Hence it must be an upward move (from the bottom)
878 * track_index is still -1, so delta must be set to
879 * move up the correct number of tracks from the bottom.
881 * This is necessary because steps may be skipped if
882 * the bottom-most track is not a valid target and/or
883 * if there are hidden tracks at the bottom.
884 * Hence the initial offset (_ddropzone) as well as the
885 * last valid pointer position (_pdropzone) need to be
886 * taken into account.
888 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
890 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
893 /* TODO needs adjustment per DraggingView,
895 * e.g. select one region on the top-layer of a track
896 * and one region which is at the bottom-layer of another track
899 * Indicated drop-zones and layering is wrong.
900 * and may infer additional layers on the target-track
901 * (depending how many layers the original track had).
903 * Or select two regions (different layers) on a same track,
904 * move across a non-layer track.. -> layering info is lost.
905 * on drop either of the regions may be on top.
907 * Proposed solution: screw it :) well,
908 * don't use delta_layer, use an absolute value
909 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
910 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
911 * 3) iterate over all DraggingView, find the one that is over the track with most layers
912 * 4) proportionally scale layer to layers available on target
914 delta_layer = current_pointer_layer - _last_pointer_layer;
917 /* for automation lanes, there is a TimeAxisView but no ->view()
918 * if (!tv) -> dropzone
920 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
921 /* Moving into the drop-zone.. */
922 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
923 /* delta_time_axis_view may not be sufficient to move into the DZ
924 * the mouse may enter it, but it may not be a valid move due to
927 * -> remember the delta needed to move into the dropzone
929 _ddropzone = delta_time_axis_view;
930 /* ..but subtract hidden tracks (or routes) at the bottom.
931 * we silently move mover them
933 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
934 - _time_axis_views.size();
936 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
937 /* move around inside the zone.
938 * This allows to move further down until all regions are in the zone.
940 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
941 assert(ptr_y >= last_track_bottom_edge);
942 assert(_ddropzone > 0);
944 /* calculate mouse position in 'tracks' below last track. */
945 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
946 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
948 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
950 delta_time_axis_view = dzpos - _pdropzone;
951 } else if (dzpos < _pdropzone && _ndropzone > 0) {
952 // move up inside the DZ
953 delta_time_axis_view = dzpos - _pdropzone;
957 /* Work out the change in x */
958 TempoMap& tmap = _editor->session()->tempo_map();
959 MusicSample pending_region_position (0, 0);
960 double const x_delta = compute_x_delta (event, &pending_region_position);
962 double const last_pos_qn = tmap.exact_qn_at_sample (_last_position.sample, _last_position.division);
963 double const qn_delta = tmap.exact_qn_at_sample (pending_region_position.sample, pending_region_position.division) - last_pos_qn;
965 _last_position = pending_region_position;
967 /* calculate hidden tracks in current y-axis delta */
969 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
970 /* The mouse is more than one track below the dropzone.
971 * distance calculation is not needed (and would not work, either
972 * because the dropzone is "packed").
974 * Except when [partially] moving regions out of dropzone in a large step.
975 * (the mouse may or may not remain in the DZ)
976 * Hidden tracks at the bottom of the TAV need to be skipped.
978 * This also handles the case if the mouse entered the DZ
979 * in a large step (exessive delta), either due to fast-movement,
980 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
982 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
983 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
985 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
986 -_time_axis_views.size() - dt;
989 else if (_last_pointer_time_axis_view < 0) {
990 /* Moving out of the zone. Check for hidden tracks at the bottom. */
991 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
992 -_time_axis_views.size() - delta_time_axis_view;
994 /* calculate hidden tracks that are skipped by the pointer movement */
995 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
996 - _last_pointer_time_axis_view
997 - delta_time_axis_view;
1000 /* Verify change in y */
1001 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
1002 /* this y movement is not allowed, so do no y movement this time */
1003 delta_time_axis_view = 0;
1008 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1009 /* haven't reached next snap point, and we're not switching
1010 trackviews nor layers. nothing to do.
1015 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1016 PlaylistDropzoneMap playlist_dropzone_map;
1017 _ndropzone = 0; // number of elements currently in the dropzone
1020 /* sort views by time_axis.
1021 * This retains track order in the dropzone, regardless
1022 * of actual selection order
1024 _views.sort (DraggingViewSorter());
1026 /* count number of distinct tracks of all regions
1027 * being dragged, used for dropzone.
1029 int prev_track = -1;
1030 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1031 if (i->time_axis_view != prev_track) {
1032 prev_track = i->time_axis_view;
1038 _views.back().time_axis_view -
1039 _views.front().time_axis_view;
1041 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1042 - _views.back().time_axis_view;
1044 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1048 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1050 RegionView* rv = i->view;
1055 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1062 /* reparent the regionview into a group above all
1066 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1067 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1068 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1069 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1070 /* move the item so that it continues to appear at the
1071 same location now that its parent has changed.
1073 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1076 /* If we have moved tracks, we'll fudge the layer delta so that the
1077 region gets moved back onto layer 0 on its new track; this avoids
1078 confusion when dragging regions from non-zero layers onto different
1081 double this_delta_layer = delta_layer;
1082 if (delta_time_axis_view != 0) {
1083 this_delta_layer = - i->layer;
1086 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1088 int track_index = i->time_axis_view + this_delta_time_axis_view;
1089 assert(track_index >= 0);
1091 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1092 /* Track is in the Dropzone */
1094 i->time_axis_view = track_index;
1095 assert(i->time_axis_view >= (int) _time_axis_views.size());
1098 double yposition = 0;
1099 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1100 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1103 /* store index of each new playlist as a negative count, starting at -1 */
1105 if (pdz == playlist_dropzone_map.end()) {
1106 /* compute where this new track (which doesn't exist yet) will live
1109 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1111 /* How high is this region view ? */
1113 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1114 ArdourCanvas::Rect bbox;
1117 bbox = obbox.get ();
1120 last_track_bottom_edge += bbox.height();
1122 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1125 yposition = pdz->second;
1128 /* values are zero or negative, hence the use of min() */
1129 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1132 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1134 mrv->apply_note_range (60, 71, true);
1138 /* The TimeAxisView that this region is now over */
1139 TimeAxisView* current_tv = _time_axis_views[track_index];
1141 /* Ensure it is moved from stacked -> expanded if appropriate */
1142 if (current_tv->view()->layer_display() == Stacked) {
1143 current_tv->view()->set_layer_display (Expanded);
1146 /* We're only allowed to go -ve in layer on Expanded views */
1147 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1148 this_delta_layer = - i->layer;
1152 rv->set_height (current_tv->view()->child_height ());
1154 /* Update show/hidden status as the region view may have come from a hidden track,
1155 or have moved to one.
1157 if (current_tv->hidden ()) {
1158 rv->get_canvas_group()->hide ();
1160 rv->get_canvas_group()->show ();
1163 /* Update the DraggingView */
1164 i->time_axis_view = track_index;
1165 i->layer += this_delta_layer;
1168 _editor->mouse_brush_insert_region (rv, pending_region_position.sample);
1172 /* Get the y coordinate of the top of the track that this region is now over */
1173 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1175 /* And adjust for the layer that it should be on */
1176 StreamView* cv = current_tv->view ();
1177 switch (cv->layer_display ()) {
1181 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1184 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1188 /* need to get the parent of the regionview
1189 * canvas group and get its position in
1190 * equivalent coordinate space as the trackview
1191 * we are now dragging over.
1194 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1198 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1200 MidiStreamView* msv;
1201 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1202 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1207 /* Now move the region view */
1208 if (rv->region()->position_lock_style() == MusicTime) {
1209 double const last_qn = tmap.quarter_note_at_sample (rv->get_position());
1210 samplepos_t const x_pos_music = tmap.sample_at_quarter_note (last_qn + qn_delta);
1212 rv->set_position (x_pos_music, 0);
1213 rv->move (0, y_delta);
1215 rv->move (x_delta, y_delta);
1218 } /* foreach region */
1220 _total_x_delta += x_delta;
1222 if (x_delta != 0 && !_brushing) {
1223 show_verbose_cursor_time (_last_position.sample);
1226 /* keep track of pointer movement */
1228 /* the pointer is currently over a time axis view */
1230 if (_last_pointer_time_axis_view < 0) {
1231 /* last motion event was not over a time axis view
1232 * or last y-movement out of the dropzone was not valid
1235 if (delta_time_axis_view < 0) {
1236 /* in the drop zone, moving up */
1238 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1239 * We do not use negative _last_pointer_time_axis_view because
1240 * the dropzone is "packed" (the actual track offset is ignored)
1242 * As opposed to the actual number
1243 * of elements in the dropzone (_ndropzone)
1244 * _pdropzone is not constrained. This is necessary
1245 * to allow moving multiple regions with y-distance
1248 * There can be 0 elements in the dropzone,
1249 * even though the drag-pointer is inside the DZ.
1252 * [ Audio-track, Midi-track, Audio-track, DZ ]
1253 * move regions from both audio tracks at the same time into the
1254 * DZ by grabbing the region in the bottom track.
1256 assert(current_pointer_time_axis_view >= 0);
1257 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1261 /* only move out of the zone if the movement is OK */
1262 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1263 assert(delta_time_axis_view < 0);
1264 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1265 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1266 * the current position can be calculated as follows:
1268 // a well placed oofus attack can still throw this off.
1269 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1270 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1273 /* last motion event was also over a time axis view */
1274 _last_pointer_time_axis_view += delta_time_axis_view;
1275 assert(_last_pointer_time_axis_view >= 0);
1280 /* the pointer is not over a time axis view */
1281 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1282 _pdropzone += delta_time_axis_view - delta_skip;
1283 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1286 _last_pointer_layer += delta_layer;
1290 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1292 if (_copy && first_move) {
1293 if (_x_constrained && !_brushing) {
1294 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1295 } else if (!_brushing) {
1296 _editor->begin_reversible_command (Operations::region_copy);
1297 } else if (_brushing) {
1298 _editor->begin_reversible_command (Operations::drag_region_brush);
1300 /* duplicate the regionview(s) and region(s) */
1302 list<DraggingView> new_regionviews;
1304 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1306 RegionView* rv = i->view;
1307 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1308 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1310 const boost::shared_ptr<const Region> original = rv->region();
1311 boost::shared_ptr<Region> region_copy;
1313 region_copy = RegionFactory::create (original, true);
1315 /* need to set this so that the drop zone code can work. This doesn't
1316 actually put the region into the playlist, but just sets a weak pointer
1319 region_copy->set_playlist (original->playlist());
1323 boost::shared_ptr<AudioRegion> audioregion_copy
1324 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1326 nrv = new AudioRegionView (*arv, audioregion_copy);
1328 boost::shared_ptr<MidiRegion> midiregion_copy
1329 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1330 nrv = new MidiRegionView (*mrv, midiregion_copy);
1335 nrv->get_canvas_group()->show ();
1336 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1338 /* swap _primary to the copy */
1340 if (rv == _primary) {
1344 /* ..and deselect the one we copied */
1346 rv->set_selected (false);
1349 if (!new_regionviews.empty()) {
1351 /* reflect the fact that we are dragging the copies */
1353 _views = new_regionviews;
1355 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1358 } else if (!_copy && first_move) {
1359 if (_x_constrained && !_brushing) {
1360 _editor->begin_reversible_command (_("fixed time region drag"));
1361 } else if (!_brushing) {
1362 _editor->begin_reversible_command (Operations::region_drag);
1363 } else if (_brushing) {
1364 _editor->begin_reversible_command (Operations::drag_region_brush);
1367 RegionMotionDrag::motion (event, first_move);
1371 RegionMotionDrag::finished (GdkEvent *, bool)
1373 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1374 if (!(*i)->view()) {
1378 if ((*i)->view()->layer_display() == Expanded) {
1379 (*i)->view()->set_layer_display (Stacked);
1385 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1387 RegionMotionDrag::finished (ev, movement_occurred);
1389 if (!movement_occurred) {
1393 if (was_double_click() && !_views.empty()) {
1394 DraggingView dv = _views.front();
1395 _editor->edit_region (dv.view);
1401 assert (!_views.empty ());
1403 /* We might have hidden region views so that they weren't visible during the drag
1404 (when they have been reparented). Now everything can be shown again, as region
1405 views are back in their track parent groups.
1407 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1408 i->view->get_canvas_group()->show ();
1411 bool const changed_position = (_last_position.sample != _primary->region()->position());
1412 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1436 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1438 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1441 TimeAxisView* tav = 0;
1443 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1444 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1445 uint32_t output_chan = region->n_channels();
1446 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1447 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1449 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1450 tav =_editor->time_axis_view_from_stripable (audio_tracks.front());
1452 ChanCount one_midi_port (DataType::MIDI, 1);
1453 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1454 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1455 Config->get_strict_io () || Profile->get_mixbus (),
1456 boost::shared_ptr<ARDOUR::PluginInfo>(),
1457 (ARDOUR::Plugin::PresetRecord*) 0,
1458 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1459 tav = _editor->time_axis_view_from_stripable (midi_tracks.front());
1463 tav->set_height (original->current_height());
1466 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1469 return dynamic_cast<RouteTimeAxisView*> (tav);
1473 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, MusicSample last_position, int32_t const ev_state)
1475 RegionSelection new_views;
1476 PlaylistSet modified_playlists;
1477 RouteTimeAxisView* new_time_axis_view = 0;
1478 samplecnt_t const drag_delta = _primary->region()->position() - _last_position.sample;
1480 TempoMap& tmap (_editor->session()->tempo_map());
1481 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1482 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1485 /* all changes were made during motion event handlers */
1487 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1491 _editor->commit_reversible_command ();
1495 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1496 PlaylistMapping playlist_mapping;
1498 /* insert the regions into their new playlists */
1499 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1501 RouteTimeAxisView* dest_rtv = 0;
1503 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1507 MusicSample where (0, 0);
1508 double quarter_note;
1510 if (changed_position && !_x_constrained) {
1511 where.set (i->view->region()->position() - drag_delta, 0);
1512 quarter_note = i->view->region()->quarter_note() - qn_delta;
1514 /* region has not moved - divisor will not affect musical pos */
1515 where.set (i->view->region()->position(), 0);
1516 quarter_note = i->view->region()->quarter_note();
1519 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1520 /* dragged to drop zone */
1522 PlaylistMapping::iterator pm;
1524 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1525 /* first region from this original playlist: create a new track */
1526 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1527 if(!new_time_axis_view) {
1531 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1532 dest_rtv = new_time_axis_view;
1534 /* we already created a new track for regions from this playlist, use it */
1535 dest_rtv = pm->second;
1538 /* destination time axis view is the one we dragged to */
1539 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1542 if (dest_rtv != 0) {
1543 RegionView* new_view;
1544 if (i->view == _primary && !_x_constrained) {
1545 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, last_position, last_pos_qn,
1546 modified_playlists, true);
1548 if (i->view->region()->position_lock_style() == AudioTime) {
1549 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1550 modified_playlists);
1552 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1553 modified_playlists, true);
1557 if (new_view != 0) {
1558 new_views.push_back (new_view);
1562 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1563 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1566 list<DraggingView>::const_iterator next = i;
1572 /* If we've created new regions either by copying or moving
1573 to a new track, we want to replace the old selection with the new ones
1576 if (new_views.size() > 0) {
1577 _editor->selection->set (new_views);
1580 /* write commands for the accumulated diffs for all our modified playlists */
1581 add_stateful_diff_commands_for_playlists (modified_playlists);
1583 _editor->commit_reversible_command ();
1587 RegionMoveDrag::finished_no_copy (
1588 bool const changed_position,
1589 bool const changed_tracks,
1590 MusicSample last_position,
1591 int32_t const ev_state
1594 RegionSelection new_views;
1595 PlaylistSet modified_playlists;
1596 PlaylistSet frozen_playlists;
1597 set<RouteTimeAxisView*> views_to_update;
1598 RouteTimeAxisView* new_time_axis_view = 0;
1599 samplecnt_t const drag_delta = _primary->region()->position() - last_position.sample;
1601 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1602 PlaylistMapping playlist_mapping;
1604 TempoMap& tmap (_editor->session()->tempo_map());
1605 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1606 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1608 std::set<boost::shared_ptr<const Region> > uniq;
1609 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1611 RegionView* rv = i->view;
1612 RouteTimeAxisView* dest_rtv = 0;
1614 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1619 if (uniq.find (rv->region()) != uniq.end()) {
1620 /* prevent duplicate moves when selecting regions from shared playlists */
1624 uniq.insert(rv->region());
1626 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1627 /* dragged to drop zone */
1629 PlaylistMapping::iterator pm;
1631 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1632 /* first region from this original playlist: create a new track */
1633 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1634 if(!new_time_axis_view) { // New track creation failed
1638 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1639 dest_rtv = new_time_axis_view;
1641 /* we already created a new track for regions from this playlist, use it */
1642 dest_rtv = pm->second;
1646 /* destination time axis view is the one we dragged to */
1647 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1652 double const dest_layer = i->layer;
1654 views_to_update.insert (dest_rtv);
1656 MusicSample where (0, 0);
1657 double quarter_note;
1659 if (changed_position && !_x_constrained) {
1660 where.set (rv->region()->position() - drag_delta, 0);
1661 quarter_note = i->view->region()->quarter_note() - qn_delta;
1663 where.set (rv->region()->position(), 0);
1664 quarter_note = i->view->region()->quarter_note();
1667 if (changed_tracks) {
1669 /* insert into new playlist */
1670 RegionView* new_view;
1671 if (rv == _primary && !_x_constrained) {
1672 new_view = insert_region_into_playlist (
1673 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, last_position, last_pos_qn,
1674 modified_playlists, true
1677 if (rv->region()->position_lock_style() == AudioTime) {
1679 new_view = insert_region_into_playlist (
1680 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1684 new_view = insert_region_into_playlist (
1685 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1686 modified_playlists, true
1691 if (new_view == 0) {
1696 new_views.push_back (new_view);
1698 /* remove from old playlist */
1700 /* the region that used to be in the old playlist is not
1701 moved to the new one - we use a copy of it. as a result,
1702 any existing editor for the region should no longer be
1705 rv->hide_region_editor();
1708 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1712 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1714 /* this movement may result in a crossfade being modified, or a layering change,
1715 so we need to get undo data from the playlist as well as the region.
1718 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1720 playlist->clear_changes ();
1723 rv->region()->clear_changes ();
1726 motion on the same track. plonk the previously reparented region
1727 back to its original canvas group (its streamview).
1728 No need to do anything for copies as they are fake regions which will be deleted.
1731 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1732 rv->get_canvas_group()->set_y_position (i->initial_y);
1735 /* just change the model */
1736 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1737 playlist->set_layer (rv->region(), dest_layer);
1740 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1742 r = frozen_playlists.insert (playlist);
1745 playlist->freeze ();
1747 if (rv == _primary) {
1748 rv->region()->set_position (where.sample, last_position.division);
1750 if (rv->region()->position_lock_style() == AudioTime) {
1751 /* move by sample offset */
1752 rv->region()->set_position (where.sample, 0);
1754 /* move by music offset */
1755 rv->region()->set_position_music (rv->region()->quarter_note() - qn_delta);
1758 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1761 if (changed_tracks) {
1763 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1764 was selected in all of them, then removing it from a playlist will have removed all
1765 trace of it from _views (i.e. there were N regions selected, we removed 1,
1766 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1767 corresponding regionview, and _views is now empty).
1769 This could have invalidated any and all iterators into _views.
1771 The heuristic we use here is: if the region selection is empty, break out of the loop
1772 here. if the region selection is not empty, then restart the loop because we know that
1773 we must have removed at least the region(view) we've just been working on as well as any
1774 that we processed on previous iterations.
1776 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1777 we can just iterate.
1781 if (_views.empty()) {
1792 /* If we've created new regions either by copying or moving
1793 to a new track, we want to replace the old selection with the new ones
1796 if (new_views.size() > 0) {
1797 _editor->selection->set (new_views);
1800 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1804 /* write commands for the accumulated diffs for all our modified playlists */
1805 add_stateful_diff_commands_for_playlists (modified_playlists);
1806 /* applies to _brushing */
1807 _editor->commit_reversible_command ();
1809 /* We have futzed with the layering of canvas items on our streamviews.
1810 If any region changed layer, this will have resulted in the stream
1811 views being asked to set up their region views, and all will be well.
1812 If not, we might now have badly-ordered region views. Ask the StreamViews
1813 involved to sort themselves out, just in case.
1816 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1817 (*i)->view()->playlist_layered ((*i)->track ());
1821 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1822 * @param region Region to remove.
1823 * @param playlist playlist To remove from.
1824 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1825 * that clear_changes () is only called once per playlist.
1828 RegionMoveDrag::remove_region_from_playlist (
1829 boost::shared_ptr<Region> region,
1830 boost::shared_ptr<Playlist> playlist,
1831 PlaylistSet& modified_playlists
1834 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1837 playlist->clear_changes ();
1840 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1844 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1845 * clearing the playlist's diff history first if necessary.
1846 * @param region Region to insert.
1847 * @param dest_rtv Destination RouteTimeAxisView.
1848 * @param dest_layer Destination layer.
1849 * @param where Destination position.
1850 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1851 * that clear_changes () is only called once per playlist.
1852 * @return New RegionView, or 0 if no insert was performed.
1855 RegionMoveDrag::insert_region_into_playlist (
1856 boost::shared_ptr<Region> region,
1857 RouteTimeAxisView* dest_rtv,
1860 double quarter_note,
1861 PlaylistSet& modified_playlists,
1865 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1866 if (!dest_playlist) {
1870 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1871 _new_region_view = 0;
1872 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1874 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1875 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1877 dest_playlist->clear_changes ();
1880 dest_playlist->add_region (region, where.sample, 1.0, false, where.division, quarter_note, true);
1882 dest_playlist->add_region (region, where.sample, 1.0, false, where.division);
1885 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1886 dest_playlist->set_layer (region, dest_layer);
1891 assert (_new_region_view);
1893 return _new_region_view;
1897 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1899 _new_region_view = rv;
1903 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1905 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1906 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1908 _editor->session()->add_command (c);
1917 RegionMoveDrag::aborted (bool movement_occurred)
1921 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1922 list<DraggingView>::const_iterator next = i;
1931 RegionMotionDrag::aborted (movement_occurred);
1936 RegionMotionDrag::aborted (bool)
1938 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1940 StreamView* sview = (*i)->view();
1943 if (sview->layer_display() == Expanded) {
1944 sview->set_layer_display (Stacked);
1949 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1950 RegionView* rv = i->view;
1951 TimeAxisView* tv = &(rv->get_time_axis_view ());
1952 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1954 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1955 rv->get_canvas_group()->set_y_position (0);
1957 rv->move (-_total_x_delta, 0);
1958 rv->set_height (rtv->view()->child_height ());
1962 /** @param b true to brush, otherwise false.
1963 * @param c true to make copies of the regions being moved, otherwise false.
1965 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1966 : RegionMotionDrag (e, i, p, v, b)
1968 , _new_region_view (0)
1970 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1972 _last_position = MusicSample (_primary->region()->position(), 0);
1976 RegionMoveDrag::setup_pointer_sample_offset ()
1978 _pointer_sample_offset = raw_grab_sample() - _last_position.sample;
1981 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, samplepos_t pos)
1982 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1984 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1986 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1987 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1989 _primary = v->view()->create_region_view (r, false, false);
1991 _primary->get_canvas_group()->show ();
1992 _primary->set_position (pos, 0);
1993 _views.push_back (DraggingView (_primary, this, v));
1995 _last_position = MusicSample (pos, 0);
1997 _item = _primary->get_canvas_group ();
2001 RegionInsertDrag::finished (GdkEvent * event, bool)
2003 int pos = _views.front().time_axis_view;
2004 assert(pos >= 0 && pos < (int)_time_axis_views.size());
2006 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
2008 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
2009 _primary->get_canvas_group()->set_y_position (0);
2011 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
2013 _editor->begin_reversible_command (Operations::insert_region);
2014 playlist->clear_changes ();
2015 _editor->snap_to_with_modifier (_last_position, event);
2017 playlist->add_region (_primary->region (), _last_position.sample, 1.0, false, _last_position.division);
2019 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2020 if (Config->get_edit_mode() == Ripple) {
2021 playlist->ripple (_last_position.sample, _primary->region()->length(), _primary->region());
2024 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2025 _editor->commit_reversible_command ();
2033 RegionInsertDrag::aborted (bool)
2040 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2041 : RegionMoveDrag (e, i, p, v, false, false)
2043 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2046 struct RegionSelectionByPosition {
2047 bool operator() (RegionView*a, RegionView* b) {
2048 return a->region()->position () < b->region()->position();
2053 RegionSpliceDrag::motion (GdkEvent* event, bool)
2055 /* Which trackview is this ? */
2057 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2058 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2060 /* The region motion is only processed if the pointer is over
2064 if (!tv || !tv->is_track()) {
2065 /* To make sure we hide the verbose canvas cursor when the mouse is
2066 not held over an audio track.
2068 _editor->verbose_cursor()->hide ();
2071 _editor->verbose_cursor()->show ();
2076 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2082 RegionSelection copy;
2083 _editor->selection->regions.by_position(copy);
2085 samplepos_t const pf = adjusted_current_sample (event);
2087 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2089 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2095 boost::shared_ptr<Playlist> playlist;
2097 if ((playlist = atv->playlist()) == 0) {
2101 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2106 if (pf < (*i)->region()->last_sample() + 1) {
2110 if (pf > (*i)->region()->first_sample()) {
2116 playlist->shuffle ((*i)->region(), dir);
2121 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2123 RegionMoveDrag::finished (event, movement_occurred);
2127 RegionSpliceDrag::aborted (bool)
2137 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, samplepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2140 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<samplepos_t>(where, max_samplepos));
2142 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2143 RegionSelection to_ripple;
2144 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2145 if ((*i)->position() >= where) {
2146 to_ripple.push_back (rtv->view()->find_view(*i));
2150 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2151 if (!exclude.contains (*i)) {
2152 // the selection has already been added to _views
2154 if (drag_in_progress) {
2155 // do the same things that RegionMotionDrag::motion does when
2156 // first_move is true, for the region views that we're adding
2157 // to _views this time
2160 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2161 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2162 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2163 rvg->reparent (_editor->_drag_motion_group);
2165 // we only need to move in the y direction
2166 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2171 _views.push_back (DraggingView (*i, this, tav));
2177 RegionRippleDrag::remove_unselected_from_views(samplecnt_t amount, bool move_regions)
2180 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2181 // we added all the regions after the selection
2183 std::list<DraggingView>::iterator to_erase = i++;
2184 if (!_editor->selection->regions.contains (to_erase->view)) {
2185 // restore the non-selected regions to their original playlist & positions,
2186 // and then ripple them back by the length of the regions that were dragged away
2187 // do the same things as RegionMotionDrag::aborted
2189 RegionView *rv = to_erase->view;
2190 TimeAxisView* tv = &(rv->get_time_axis_view ());
2191 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2194 // plonk them back onto their own track
2195 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2196 rv->get_canvas_group()->set_y_position (0);
2200 // move the underlying region to match the view
2201 rv->region()->set_position (rv->region()->position() + amount);
2203 // restore the view to match the underlying region's original position
2204 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2207 rv->set_height (rtv->view()->child_height ());
2208 _views.erase (to_erase);
2214 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2216 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2218 return allow_moves_across_tracks;
2226 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2227 : RegionMoveDrag (e, i, p, v, false, false)
2229 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2230 // compute length of selection
2231 RegionSelection selected_regions = _editor->selection->regions;
2232 selection_length = selected_regions.end_sample() - selected_regions.start();
2234 // we'll only allow dragging to another track in ripple mode if all the regions
2235 // being dragged start off on the same track
2236 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2239 exclude = new RegionList;
2240 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2241 exclude->push_back((*i)->region());
2244 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2245 RegionSelection copy;
2246 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2248 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2249 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2251 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2252 // find ripple start point on each applicable playlist
2253 RegionView *first_selected_on_this_track = NULL;
2254 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2255 if ((*i)->region()->playlist() == (*pi)) {
2256 // region is on this playlist - it's the first, because they're sorted
2257 first_selected_on_this_track = *i;
2261 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2262 add_all_after_to_views (
2263 &first_selected_on_this_track->get_time_axis_view(),
2264 first_selected_on_this_track->region()->position(),
2265 selected_regions, false);
2268 if (allow_moves_across_tracks) {
2269 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2277 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2279 /* Which trackview is this ? */
2281 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2282 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2284 /* The region motion is only processed if the pointer is over
2288 if (!tv || !tv->is_track()) {
2289 /* To make sure we hide the verbose canvas cursor when the mouse is
2290 not held over an audiotrack.
2292 _editor->verbose_cursor()->hide ();
2296 samplepos_t where = adjusted_current_sample (event);
2297 assert (where >= 0);
2298 MusicSample after (0, 0);
2299 double delta = compute_x_delta (event, &after);
2301 samplecnt_t amount = _editor->pixel_to_sample (delta);
2303 if (allow_moves_across_tracks) {
2304 // all the originally selected regions were on the same track
2306 samplecnt_t adjust = 0;
2307 if (prev_tav && tv != prev_tav) {
2308 // dragged onto a different track
2309 // remove the unselected regions from _views, restore them to their original positions
2310 // and add the regions after the drop point on the new playlist to _views instead.
2311 // undo the effect of rippling the previous playlist, and include the effect of removing
2312 // the dragged region(s) from this track
2314 remove_unselected_from_views (prev_amount, false);
2315 // ripple previous playlist according to the regions that have been removed onto the new playlist
2316 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2319 // move just the selected regions
2320 RegionMoveDrag::motion(event, first_move);
2322 // ensure that the ripple operation on the new playlist inserts selection_length time
2323 adjust = selection_length;
2324 // ripple the new current playlist
2325 tv->playlist()->ripple (where, amount+adjust, exclude);
2327 // add regions after point where drag entered this track to subsequent ripples
2328 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2331 // motion on same track
2332 RegionMoveDrag::motion(event, first_move);
2336 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2337 prev_position = where;
2339 // selection encompasses multiple tracks - just drag
2340 // cross-track drags are forbidden
2341 RegionMoveDrag::motion(event, first_move);
2344 if (!_x_constrained) {
2345 prev_amount += amount;
2348 _last_position = after;
2352 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2354 if (!movement_occurred) {
2358 if (was_double_click() && !_views.empty()) {
2359 DraggingView dv = _views.front();
2360 _editor->edit_region (dv.view);
2366 _editor->begin_reversible_command(_("Ripple drag"));
2368 // remove the regions being rippled from the dragging view, updating them to
2369 // their new positions
2370 remove_unselected_from_views (prev_amount, true);
2372 if (allow_moves_across_tracks) {
2374 // if regions were dragged across tracks, we've rippled any later
2375 // regions on the track the regions were dragged off, so we need
2376 // to add the original track to the undo record
2377 orig_tav->playlist()->clear_changes();
2378 vector<Command*> cmds;
2379 orig_tav->playlist()->rdiff (cmds);
2380 _editor->session()->add_commands (cmds);
2382 if (prev_tav && prev_tav != orig_tav) {
2383 prev_tav->playlist()->clear_changes();
2384 vector<Command*> cmds;
2385 prev_tav->playlist()->rdiff (cmds);
2386 _editor->session()->add_commands (cmds);
2389 // selection spanned multiple tracks - all will need adding to undo record
2391 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2392 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2394 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2395 (*pi)->clear_changes();
2396 vector<Command*> cmds;
2397 (*pi)->rdiff (cmds);
2398 _editor->session()->add_commands (cmds);
2402 // other modified playlists are added to undo by RegionMoveDrag::finished()
2403 RegionMoveDrag::finished (event, movement_occurred);
2404 _editor->commit_reversible_command();
2408 RegionRippleDrag::aborted (bool movement_occurred)
2410 RegionMoveDrag::aborted (movement_occurred);
2415 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2417 _view (dynamic_cast<MidiTimeAxisView*> (v))
2419 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2425 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2429 _editor->begin_reversible_command (_("create region"));
2430 _region = add_midi_region (_view, false);
2431 _view->playlist()->freeze ();
2435 samplepos_t const f = adjusted_current_sample (event);
2436 if (f <= grab_sample()) {
2437 _region->set_initial_position (f);
2440 /* Don't use a zero-length region, and subtract 1 sample from the snapped length
2441 so that if this region is duplicated, its duplicate starts on
2442 a snap point rather than 1 sample after a snap point. Otherwise things get
2443 a bit confusing as if a region starts 1 sample after a snap point, one cannot
2444 place snapped notes at the start of the region.
2446 if (f != grab_sample()) {
2447 samplecnt_t const len = (samplecnt_t) fabs ((double)(f - grab_sample () - 1));
2448 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2455 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2457 if (!movement_occurred) {
2458 add_midi_region (_view, true);
2460 _view->playlist()->thaw ();
2461 _editor->commit_reversible_command();
2466 RegionCreateDrag::aborted (bool)
2469 _view->playlist()->thaw ();
2475 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2480 , _was_selected (false)
2483 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2487 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2489 Gdk::Cursor* cursor;
2490 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2492 float x_fraction = cnote->mouse_x_fraction ();
2494 if (x_fraction > 0.0 && x_fraction < 0.25) {
2495 cursor = _editor->cursors()->left_side_trim;
2498 cursor = _editor->cursors()->right_side_trim;
2502 Drag::start_grab (event, cursor);
2504 region = &cnote->region_view();
2507 temp = region->snap_to_pixel (cnote->x0 (), true);
2508 _snap_delta = temp - cnote->x0 ();
2512 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2517 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2518 if (ms.size() > 1) {
2519 /* has to be relative, may make no sense otherwise */
2523 if (!(_was_selected = cnote->selected())) {
2525 /* tertiary-click means extend selection - we'll do that on button release,
2526 so don't add it here, because otherwise we make it hard to figure
2527 out the "extend-to" range.
2530 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2533 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2536 region->note_selected (cnote, true);
2538 _editor->get_selection().clear_points();
2539 region->unique_select (cnote);
2546 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2548 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2550 _editor->begin_reversible_command (_("resize notes"));
2552 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2553 MidiRegionSelection::iterator next;
2556 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2558 mrv->begin_resizing (at_front);
2564 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2565 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2567 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2571 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2573 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2574 if (_editor->snap_mode () != SnapOff) {
2578 if (_editor->snap_mode () == SnapOff) {
2580 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2581 if (apply_snap_delta) {
2587 if (apply_snap_delta) {
2591 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2597 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2599 if (!movement_occurred) {
2600 /* no motion - select note */
2601 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2602 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2603 _editor->current_mouse_mode() == Editing::MouseDraw) {
2605 bool changed = false;
2607 if (_was_selected) {
2608 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2610 region->note_deselected (cnote);
2613 _editor->get_selection().clear_points();
2614 region->unique_select (cnote);
2618 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2619 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2621 if (!extend && !add && region->selection_size() > 1) {
2622 _editor->get_selection().clear_points();
2623 region->unique_select (cnote);
2625 } else if (extend) {
2626 region->note_selected (cnote, true, true);
2629 /* it was added during button press */
2635 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2636 _editor->commit_reversible_selection_op();
2643 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2644 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2645 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2647 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2650 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2652 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2653 if (_editor->snap_mode () != SnapOff) {
2657 if (_editor->snap_mode () == SnapOff) {
2659 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2660 if (apply_snap_delta) {
2666 if (apply_snap_delta) {
2670 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2674 _editor->commit_reversible_command ();
2678 NoteResizeDrag::aborted (bool)
2680 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2681 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2682 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2684 mrv->abort_resizing ();
2689 AVDraggingView::AVDraggingView (RegionView* v)
2692 initial_position = v->region()->position ();
2695 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2698 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2701 TrackViewList empty;
2703 _editor->get_regions_after(rs, (samplepos_t) 0, empty);
2704 std::list<RegionView*> views = rs.by_layer();
2707 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2708 RegionView* rv = (*i);
2709 if (!rv->region()->video_locked()) {
2712 if (rv->region()->locked()) {
2715 _views.push_back (AVDraggingView (rv));
2720 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2722 Drag::start_grab (event);
2723 if (_editor->session() == 0) {
2727 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2733 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2737 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2738 _max_backwards_drag = (
2739 ARDOUR_UI::instance()->video_timeline->get_duration()
2740 + ARDOUR_UI::instance()->video_timeline->get_offset()
2741 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2744 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2745 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2746 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv (i->initial_position);
2749 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2752 Timecode::Time timecode;
2753 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2754 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);
2755 show_verbose_cursor_text (buf);
2759 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2761 if (_editor->session() == 0) {
2764 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2768 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2772 samplecnt_t dt = adjusted_current_sample (event) - raw_grab_sample() + _pointer_sample_offset;
2773 dt = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2775 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2776 dt = - _max_backwards_drag;
2779 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2780 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2782 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2783 RegionView* rv = i->view;
2784 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2787 rv->region()->clear_changes ();
2788 rv->region()->suspend_property_changes();
2790 rv->region()->set_position(i->initial_position + dt);
2791 rv->region_changed(ARDOUR::Properties::position);
2794 const samplepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2795 Timecode::Time timecode;
2796 Timecode::Time timediff;
2798 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2799 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2800 snprintf (buf, sizeof (buf),
2801 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2802 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2803 , _("Video Start:"),
2804 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2806 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2808 show_verbose_cursor_text (buf);
2812 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2814 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2821 if (!movement_occurred || ! _editor->session()) {
2825 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2827 _editor->begin_reversible_command (_("Move Video"));
2829 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2830 ARDOUR_UI::instance()->video_timeline->save_undo();
2831 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2832 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2834 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2835 i->view->drag_end();
2836 i->view->region()->resume_property_changes ();
2838 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2841 _editor->session()->maybe_update_session_range(
2842 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::sampleoffset_t) 0),
2843 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::sampleoffset_t) 0)
2847 _editor->commit_reversible_command ();
2851 VideoTimeLineDrag::aborted (bool)
2853 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2856 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2857 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2859 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2860 i->view->region()->resume_property_changes ();
2861 i->view->region()->set_position(i->initial_position);
2865 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2866 : RegionDrag (e, i, p, v)
2867 , _operation (StartTrim)
2868 , _preserve_fade_anchor (preserve_fade_anchor)
2869 , _jump_position_when_done (false)
2871 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2875 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2877 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2879 samplepos_t const region_start = _primary->region()->position();
2880 samplepos_t const region_end = _primary->region()->last_sample();
2881 samplecnt_t const region_length = _primary->region()->length();
2883 samplepos_t const pf = adjusted_current_sample (event);
2884 setup_snap_delta (MusicSample(region_start, 0));
2886 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2887 /* Move the contents of the region around without changing the region bounds */
2888 _operation = ContentsTrim;
2889 Drag::start_grab (event, _editor->cursors()->trimmer);
2891 /* These will get overridden for a point trim.*/
2892 if (pf < (region_start + region_length/2)) {
2893 /* closer to front */
2894 _operation = StartTrim;
2895 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2896 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2898 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2902 _operation = EndTrim;
2903 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2904 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2906 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2910 /* jump trim disabled for now
2911 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2912 _jump_position_when_done = true;
2916 switch (_operation) {
2918 show_verbose_cursor_time (region_start);
2921 show_verbose_cursor_duration (region_start, region_end);
2924 show_verbose_cursor_time (pf);
2928 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2929 i->view->region()->suspend_property_changes ();
2934 TrimDrag::motion (GdkEvent* event, bool first_move)
2936 RegionView* rv = _primary;
2938 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2939 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2940 sampleoffset_t sample_delta = 0;
2942 MusicSample adj_sample = adjusted_sample (_drags->current_pointer_sample () + snap_delta (event->button.state), event, true);
2943 samplecnt_t dt = adj_sample.sample - raw_grab_sample () + _pointer_sample_offset - snap_delta (event->button.state);
2949 switch (_operation) {
2951 trim_type = "Region start trim";
2954 trim_type = "Region end trim";
2957 trim_type = "Region content trim";
2964 _editor->begin_reversible_command (trim_type);
2966 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2967 RegionView* rv = i->view;
2968 rv->region()->playlist()->clear_owned_changes ();
2970 if (_operation == StartTrim) {
2971 rv->trim_front_starting ();
2974 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2977 arv->temporarily_hide_envelope ();
2981 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2982 insert_result = _editor->motion_frozen_playlists.insert (pl);
2984 if (insert_result.second) {
2988 MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (rv);
2989 /* a MRV start trim may change the source length. ensure we cover all playlists here */
2990 if (mrv && _operation == StartTrim) {
2991 vector<boost::shared_ptr<Playlist> > all_playlists;
2992 _editor->session()->playlists->get (all_playlists);
2993 for (vector<boost::shared_ptr<Playlist> >::iterator x = all_playlists.begin(); x != all_playlists.end(); ++x) {
2995 if ((*x)->uses_source (rv->region()->source(0))) {
2996 insert_result = _editor->motion_frozen_playlists.insert (*x);
2997 if (insert_result.second) {
2998 (*x)->clear_owned_changes ();
3008 bool non_overlap_trim = false;
3010 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
3011 non_overlap_trim = true;
3014 /* contstrain trim to fade length */
3015 if (_preserve_fade_anchor) {
3016 switch (_operation) {
3018 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3019 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3021 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3022 if (ar->locked()) continue;
3023 samplecnt_t len = ar->fade_in()->back()->when;
3024 if (len < dt) dt = min(dt, len);
3028 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3029 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3031 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3032 if (ar->locked()) continue;
3033 samplecnt_t len = ar->fade_out()->back()->when;
3034 if (len < -dt) dt = max(dt, -len);
3042 switch (_operation) {
3044 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3045 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3046 , adj_sample.division);
3048 if (changed && _preserve_fade_anchor) {
3049 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3051 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3052 samplecnt_t len = ar->fade_in()->back()->when;
3053 samplecnt_t diff = ar->first_sample() - i->initial_position;
3054 samplepos_t new_length = len - diff;
3055 i->anchored_fade_length = min (ar->length(), new_length);
3056 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3057 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3064 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3065 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, adj_sample.division);
3066 if (changed && _preserve_fade_anchor) {
3067 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3069 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3070 samplecnt_t len = ar->fade_out()->back()->when;
3071 samplecnt_t diff = 1 + ar->last_sample() - i->initial_end;
3072 samplepos_t new_length = len + diff;
3073 i->anchored_fade_length = min (ar->length(), new_length);
3074 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3075 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3083 sample_delta = (last_pointer_sample() - adjusted_current_sample(event));
3085 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3086 i->view->move_contents (sample_delta);
3092 switch (_operation) {
3094 show_verbose_cursor_time (rv->region()->position());
3097 show_verbose_cursor_duration (rv->region()->position(), rv->region()->last_sample());
3100 // show_verbose_cursor_time (sample_delta);
3106 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3108 if (movement_occurred) {
3109 motion (event, false);
3111 if (_operation == StartTrim) {
3112 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3114 /* This must happen before the region's StatefulDiffCommand is created, as it may
3115 `correct' (ahem) the region's _start from being negative to being zero. It
3116 needs to be zero in the undo record.
3118 i->view->trim_front_ending ();
3120 if (_preserve_fade_anchor) {
3121 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3123 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3124 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3125 ar->set_fade_in_length(i->anchored_fade_length);
3126 ar->set_fade_in_active(true);
3129 if (_jump_position_when_done) {
3130 i->view->region()->set_position (i->initial_position);
3133 } else if (_operation == EndTrim) {
3134 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3135 if (_preserve_fade_anchor) {
3136 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3138 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3139 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3140 ar->set_fade_out_length(i->anchored_fade_length);
3141 ar->set_fade_out_active(true);
3144 if (_jump_position_when_done) {
3145 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3150 if (!_editor->selection->selected (_primary)) {
3151 _primary->thaw_after_trim ();
3153 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3154 i->view->thaw_after_trim ();
3155 i->view->enable_display (true);
3159 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3160 /* Trimming one region may affect others on the playlist, so we need
3161 to get undo Commands from the whole playlist rather than just the
3162 region. Use motion_frozen_playlists (a set) to make sure we don't
3163 diff a given playlist more than once.
3166 vector<Command*> cmds;
3168 _editor->session()->add_commands (cmds);
3172 _editor->motion_frozen_playlists.clear ();
3173 _editor->commit_reversible_command();
3176 /* no mouse movement */
3177 if (adjusted_current_sample (event) != adjusted_sample (_drags->current_pointer_sample(), event, false).sample) {
3178 _editor->point_trim (event, adjusted_current_sample (event));
3182 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3183 i->view->region()->resume_property_changes ();
3188 TrimDrag::aborted (bool movement_occurred)
3190 /* Our motion method is changing model state, so use the Undo system
3191 to cancel. Perhaps not ideal, as this will leave an Undo point
3192 behind which may be slightly odd from the user's point of view.
3196 finished (&ev, true);
3198 if (movement_occurred) {
3199 _editor->session()->undo (1);
3202 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3203 i->view->region()->resume_property_changes ();
3208 TrimDrag::setup_pointer_sample_offset ()
3210 list<DraggingView>::iterator i = _views.begin ();
3211 while (i != _views.end() && i->view != _primary) {
3215 if (i == _views.end()) {
3219 switch (_operation) {
3221 _pointer_sample_offset = raw_grab_sample() - i->initial_position;
3224 _pointer_sample_offset = raw_grab_sample() - i->initial_end;
3231 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3234 , _old_snap_type (e->snap_type())
3235 , _old_snap_mode (e->snap_mode())
3238 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3239 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3241 _real_section = &_marker->meter();
3246 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3248 Drag::start_grab (event, cursor);
3249 show_verbose_cursor_time (adjusted_current_sample(event));
3253 MeterMarkerDrag::setup_pointer_sample_offset ()
3255 _pointer_sample_offset = raw_grab_sample() - _marker->meter().sample();
3259 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3262 // create a dummy marker to catch events, then hide it.
3265 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3267 _marker = new MeterMarker (
3269 *_editor->meter_group,
3270 UIConfiguration::instance().color ("meter marker"),
3272 *new MeterSection (_marker->meter())
3275 /* use the new marker for the grab */
3276 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3279 TempoMap& map (_editor->session()->tempo_map());
3280 /* get current state */
3281 before_state = &map.get_state();
3284 _editor->begin_reversible_command (_("move meter mark"));
3286 _editor->begin_reversible_command (_("copy meter mark"));
3288 Timecode::BBT_Time bbt = _real_section->bbt();
3290 /* we can't add a meter where one currently exists */
3291 if (_real_section->sample() < adjusted_current_sample (event, false)) {
3296 const samplepos_t sample = map.sample_at_bbt (bbt);
3297 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3298 , bbt, sample, _real_section->position_lock_style());
3299 if (!_real_section) {
3305 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3306 if (_real_section->position_lock_style() != AudioTime) {
3307 _editor->set_snap_to (SnapToBar);
3308 _editor->set_snap_mode (SnapNormal);
3312 samplepos_t pf = adjusted_current_sample (event);
3314 if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3315 /* never snap to music for audio locked */
3316 pf = adjusted_current_sample (event, false);
3319 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3321 /* fake marker meeds to stay under the mouse, unlike the real one. */
3322 _marker->set_position (adjusted_current_sample (event, false));
3324 show_verbose_cursor_time (_real_section->sample());
3325 _editor->set_snapped_cursor_position(_real_section->sample());
3329 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3331 if (!movement_occurred) {
3332 if (was_double_click()) {
3333 _editor->edit_meter_marker (*_marker);
3338 /* reinstate old snap setting */
3339 _editor->set_snap_to (_old_snap_type);
3340 _editor->set_snap_mode (_old_snap_mode);
3342 TempoMap& map (_editor->session()->tempo_map());
3344 XMLNode &after = map.get_state();
3345 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3346 _editor->commit_reversible_command ();
3348 // delete the dummy marker we used for visual representation while moving.
3349 // a new visual marker will show up automatically.
3354 MeterMarkerDrag::aborted (bool moved)
3356 _marker->set_position (_marker->meter().sample ());
3358 /* reinstate old snap setting */
3359 _editor->set_snap_to (_old_snap_type);
3360 _editor->set_snap_mode (_old_snap_mode);
3362 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3363 // delete the dummy marker we used for visual representation while moving.
3364 // a new visual marker will show up automatically.
3369 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3372 , _grab_bpm (120.0, 4.0)
3376 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3378 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3379 _real_section = &_marker->tempo();
3380 _movable = !_real_section->initial();
3381 _grab_bpm = Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), _real_section->end_note_types_per_minute());
3382 _grab_qn = _real_section->pulse() * 4.0;
3387 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3389 Drag::start_grab (event, cursor);
3390 if (!_real_section->active()) {
3391 show_verbose_cursor_text (_("inactive"));
3393 show_verbose_cursor_time (adjusted_current_sample (event));
3398 TempoMarkerDrag::setup_pointer_sample_offset ()
3400 _pointer_sample_offset = raw_grab_sample() - _real_section->sample();
3404 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3406 if (!_real_section->active()) {
3409 TempoMap& map (_editor->session()->tempo_map());
3413 // mvc drag - create a dummy marker to catch events, hide it.
3416 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3418 TempoSection section (_marker->tempo());
3420 _marker = new TempoMarker (
3422 *_editor->tempo_group,
3423 UIConfiguration::instance().color ("tempo marker"),
3425 *new TempoSection (_marker->tempo())
3428 /* use the new marker for the grab */
3429 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3432 /* get current state */
3433 _before_state = &map.get_state();
3436 _editor->begin_reversible_command (_("move tempo mark"));
3439 const Tempo tempo (_marker->tempo());
3440 const samplepos_t sample = adjusted_current_sample (event) + 1;
3442 _editor->begin_reversible_command (_("copy tempo mark"));
3444 if (_real_section->position_lock_style() == MusicTime) {
3445 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3446 _real_section = map.add_tempo (tempo, map.exact_qn_at_sample (sample, divisions), 0, MusicTime);
3448 _real_section = map.add_tempo (tempo, 0.0, sample, AudioTime);
3451 if (!_real_section) {
3458 if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) {
3459 double new_bpm = max (1.5, _grab_bpm.end_note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3461 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), new_bpm));
3462 strs << "end:" << fixed << setprecision(3) << new_bpm;
3463 show_verbose_cursor_text (strs.str());
3465 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3466 /* use vertical movement to alter tempo .. should be log */
3467 double new_bpm = max (1.5, _grab_bpm.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3469 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type(), _real_section->end_note_types_per_minute()));
3470 strs << "start:" << fixed << setprecision(3) << new_bpm;
3471 show_verbose_cursor_text (strs.str());
3473 } else if (_movable && !_real_section->locked_to_meter()) {
3476 if (_editor->snap_musical()) {
3477 /* we can't snap to a grid that we are about to move.
3478 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3480 pf = adjusted_current_sample (event, false);
3482 pf = adjusted_current_sample (event);
3485 /* snap to beat is 1, snap to bar is -1 (sorry) */
3486 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3488 map.gui_set_tempo_position (_real_section, pf, sub_num);
3490 show_verbose_cursor_time (_real_section->sample());
3491 _editor->set_snapped_cursor_position(_real_section->sample());
3493 _marker->set_position (adjusted_current_sample (event, false));
3497 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3499 if (!_real_section->active()) {
3502 if (!movement_occurred) {
3503 if (was_double_click()) {
3504 _editor->edit_tempo_marker (*_marker);
3509 TempoMap& map (_editor->session()->tempo_map());
3511 XMLNode &after = map.get_state();
3512 _editor->session()->add_command (new MementoCommand<TempoMap>(map, _before_state, &after));
3513 _editor->commit_reversible_command ();
3515 // delete the dummy marker we used for visual representation while moving.
3516 // a new visual marker will show up automatically.
3521 TempoMarkerDrag::aborted (bool moved)
3523 _marker->set_position (_marker->tempo().sample());
3525 TempoMap& map (_editor->session()->tempo_map());
3526 map.set_state (*_before_state, Stateful::current_state_version);
3527 // delete the dummy (hidden) marker we used for events while moving.
3532 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3537 , _drag_valid (true)
3539 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3544 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3546 Drag::start_grab (event, cursor);
3547 TempoMap& map (_editor->session()->tempo_map());
3548 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3550 if (adjusted_current_sample (event, false) <= _tempo->sample()) {
3551 _drag_valid = false;
3555 _editor->tempo_curve_selected (_tempo, true);
3558 if (_tempo->clamped()) {
3559 TempoSection* prev = map.previous_tempo_section (_tempo);
3561 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3565 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3566 show_verbose_cursor_text (sstr.str());
3570 BBTRulerDrag::setup_pointer_sample_offset ()
3572 TempoMap& map (_editor->session()->tempo_map());
3573 /* get current state */
3574 _before_state = &map.get_state();
3576 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3577 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3580 if (divisions > 0) {
3581 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3583 /* while it makes some sense for the user to determine the division to 'grab',
3584 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3585 and the result over steep tempo curves. Use sixteenths.
3587 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3590 _grab_qn = map.quarter_note_at_beat (beat);
3592 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3597 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3604 _editor->begin_reversible_command (_("stretch tempo"));
3607 TempoMap& map (_editor->session()->tempo_map());
3610 if (_editor->snap_musical()) {
3611 pf = adjusted_current_sample (event, false);
3613 pf = adjusted_current_sample (event);
3616 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3617 /* adjust previous tempo to match pointer sample */
3618 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.sample_at_quarter_note (_grab_qn), pf, _grab_qn, map.quarter_note_at_sample (pf));
3622 if (_tempo->clamped()) {
3623 TempoSection* prev = map.previous_tempo_section (_tempo);
3625 _editor->tempo_curve_selected (prev, true);
3626 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3629 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3630 show_verbose_cursor_text (sstr.str());
3634 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3636 if (!movement_occurred) {
3640 TempoMap& map (_editor->session()->tempo_map());
3642 _editor->tempo_curve_selected (_tempo, false);
3643 if (_tempo->clamped()) {
3644 TempoSection* prev_tempo = map.previous_tempo_section (_tempo);
3646 _editor->tempo_curve_selected (prev_tempo, false);
3650 if (!movement_occurred || !_drag_valid) {
3654 XMLNode &after = map.get_state();
3655 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3656 _editor->commit_reversible_command ();
3661 BBTRulerDrag::aborted (bool moved)
3664 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3668 TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i)
3674 , _drag_valid (true)
3677 DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n");
3682 TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3684 Drag::start_grab (event, cursor);
3685 TempoMap& map (_editor->session()->tempo_map());
3686 /* get current state */
3687 _before_state = &map.get_state();
3688 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3690 if (_tempo->locked_to_meter()) {
3691 _drag_valid = false;
3695 _next_tempo = map.next_tempo_section (_tempo);
3697 if (!map.next_tempo_section (_next_tempo)) {
3698 _drag_valid = false;
3699 finished (event, false);
3703 _editor->tempo_curve_selected (_tempo, true);
3704 _editor->tempo_curve_selected (_next_tempo, true);
3707 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3708 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3709 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3710 show_verbose_cursor_text (sstr.str());
3712 _drag_valid = false;
3715 _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type());
3719 TempoTwistDrag::setup_pointer_sample_offset ()
3721 TempoMap& map (_editor->session()->tempo_map());
3722 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3723 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3726 if (divisions > 0) {
3727 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3729 /* while it makes some sense for the user to determine the division to 'grab',
3730 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3731 and the result over steep tempo curves. Use sixteenths.
3733 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3736 _grab_qn = map.quarter_note_at_beat (beat);
3738 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3743 TempoTwistDrag::motion (GdkEvent* event, bool first_move)
3746 if (!_next_tempo || !_drag_valid) {
3750 TempoMap& map (_editor->session()->tempo_map());
3753 _editor->begin_reversible_command (_("twist tempo"));
3758 if (_editor->snap_musical()) {
3759 pf = adjusted_current_sample (event, false);
3761 pf = adjusted_current_sample (event);
3764 /* adjust this and the next tempi to match pointer sample */
3765 double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3766 _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.sample_at_quarter_note (_grab_qn), pf);
3769 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3770 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3771 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3772 show_verbose_cursor_text (sstr.str());
3776 TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred)
3778 if (!movement_occurred || !_drag_valid) {
3782 _editor->tempo_curve_selected (_tempo, false);
3783 _editor->tempo_curve_selected (_next_tempo, false);
3785 TempoMap& map (_editor->session()->tempo_map());
3786 XMLNode &after = map.get_state();
3787 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3788 _editor->commit_reversible_command ();
3792 TempoTwistDrag::aborted (bool moved)
3795 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3799 TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i)
3804 , _drag_valid (true)
3806 DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n");
3807 TempoMarker* marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3808 _tempo = &marker->tempo();
3809 _grab_qn = _tempo->pulse() * 4.0;
3813 TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3815 Drag::start_grab (event, cursor);
3816 TempoMap& tmap (_editor->session()->tempo_map());
3818 /* get current state */
3819 _before_state = &tmap.get_state();
3821 if (_tempo->locked_to_meter()) {
3822 _drag_valid = false;
3828 TempoSection* prev = 0;
3829 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3830 _editor->tempo_curve_selected (tmap.previous_tempo_section (_tempo), true);
3831 sstr << "end: " << fixed << setprecision(3) << tmap.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3834 if (_tempo->clamped()) {
3835 _editor->tempo_curve_selected (_tempo, true);
3836 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3839 show_verbose_cursor_text (sstr.str());
3843 TempoEndDrag::setup_pointer_sample_offset ()
3845 TempoMap& map (_editor->session()->tempo_map());
3847 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3852 TempoEndDrag::motion (GdkEvent* event, bool first_move)
3858 TempoMap& map (_editor->session()->tempo_map());
3861 _editor->begin_reversible_command (_("stretch end tempo"));
3864 samplepos_t const pf = adjusted_current_sample (event, false);
3865 map.gui_stretch_tempo_end (&map.tempo_section_at_sample (_tempo->sample() - 1), map.sample_at_quarter_note (_grab_qn), pf);
3868 sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3870 if (_tempo->clamped()) {
3871 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3874 show_verbose_cursor_text (sstr.str());
3878 TempoEndDrag::finished (GdkEvent* event, bool movement_occurred)
3880 if (!movement_occurred || !_drag_valid) {
3884 TempoMap& tmap (_editor->session()->tempo_map());
3886 XMLNode &after = tmap.get_state();
3887 _editor->session()->add_command(new MementoCommand<TempoMap>(tmap, _before_state, &after));
3888 _editor->commit_reversible_command ();
3890 TempoSection* prev = 0;
3891 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3892 _editor->tempo_curve_selected (prev, false);
3895 if (_tempo->clamped()) {
3896 _editor->tempo_curve_selected (_tempo, false);
3902 TempoEndDrag::aborted (bool moved)
3905 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3909 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3910 : Drag (e, &c.track_canvas_item(), false)
3915 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3918 /** Do all the things we do when dragging the playhead to make it look as though
3919 * we have located, without actually doing the locate (because that would cause
3920 * the diskstream buffers to be refilled, which is too slow).
3923 CursorDrag::fake_locate (samplepos_t t)
3925 if (_editor->session () == 0) {
3929 _editor->playhead_cursor->set_position (t);
3931 Session* s = _editor->session ();
3932 if (s->timecode_transmission_suspended ()) {
3933 samplepos_t const f = _editor->playhead_cursor->current_sample ();
3934 /* This is asynchronous so it will be sent "now"
3936 s->send_mmc_locate (f);
3937 /* These are synchronous and will be sent during the next
3940 s->queue_full_time_code ();
3941 s->queue_song_position_pointer ();
3944 show_verbose_cursor_time (t);
3945 _editor->UpdateAllTransportClocks (t);
3949 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3951 Drag::start_grab (event, c);
3952 setup_snap_delta (MusicSample (_editor->playhead_cursor->current_sample(), 0));
3954 _grab_zoom = _editor->samples_per_pixel;
3956 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3958 _editor->snap_to_with_modifier (where, event);
3959 _editor->_dragging_playhead = true;
3960 _editor->_control_scroll_target = where.sample;
3962 Session* s = _editor->session ();
3964 /* grab the track canvas item as well */
3966 _cursor.track_canvas_item().grab();
3969 if (_was_rolling && _stop) {
3973 if (s->is_auditioning()) {
3974 s->cancel_audition ();
3978 if (AudioEngine::instance()->running()) {
3980 /* do this only if we're the engine is connected
3981 * because otherwise this request will never be
3982 * serviced and we'll busy wait forever. likewise,
3983 * notice if we are disconnected while waiting for the
3984 * request to be serviced.
3987 s->request_suspend_timecode_transmission ();
3988 while (AudioEngine::instance()->running() && !s->timecode_transmission_suspended ()) {
3989 /* twiddle our thumbs */
3994 fake_locate (where.sample - snap_delta (event->button.state));
4000 CursorDrag::motion (GdkEvent* event, bool)
4002 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4004 _editor->snap_to_with_modifier (where, event);
4006 if (where.sample != last_pointer_sample()) {
4007 fake_locate (where.sample - snap_delta (event->button.state));
4010 //maybe do zooming, too, if the option is enabled
4011 if (UIConfiguration::instance ().get_use_time_rulers_to_zoom_with_vertical_drag () ) {
4013 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
4014 //we use screen coordinates for this, not canvas-based grab_x
4015 double mx = event->button.x;
4016 double dx = fabs(mx - _last_mx);
4017 double my = event->button.y;
4018 double dy = fabs(my - _last_my);
4021 //do zooming in windowed "steps" so it feels more reversible ?
4022 const int stepsize = 2; //stepsize ==1 means "trigger on every pixel of movement"
4023 int y_delta = grab_y() - current_pointer_y();
4024 y_delta = y_delta / stepsize;
4026 //if all requirements are met, do the actual zoom
4027 const double scale = 1.2;
4028 if ( (dy>dx) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
4029 if ( _last_y_delta > y_delta ) {
4030 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
4032 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
4034 _last_y_delta = y_delta;
4045 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
4047 _editor->_dragging_playhead = false;
4049 _cursor.track_canvas_item().ungrab();
4051 if (!movement_occurred && _stop) {
4055 motion (event, false);
4057 Session* s = _editor->session ();
4059 s->request_locate (_editor->playhead_cursor->current_sample (), _was_rolling);
4060 _editor->_pending_locate_request = true;
4061 s->request_resume_timecode_transmission ();
4066 CursorDrag::aborted (bool)
4068 _cursor.track_canvas_item().ungrab();
4070 if (_editor->_dragging_playhead) {
4071 _editor->session()->request_resume_timecode_transmission ();
4072 _editor->_dragging_playhead = false;
4075 _editor->playhead_cursor->set_position (adjusted_sample (grab_sample (), 0, false).sample);
4078 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4079 : RegionDrag (e, i, p, v)
4081 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
4085 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4087 Drag::start_grab (event, cursor);
4089 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4090 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4091 setup_snap_delta (MusicSample (r->position(), 0));
4093 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
4097 FadeInDrag::setup_pointer_sample_offset ()
4099 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4100 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4101 _pointer_sample_offset = raw_grab_sample() - ((samplecnt_t) r->fade_in()->back()->when + r->position());
4105 FadeInDrag::motion (GdkEvent* event, bool)
4107 samplecnt_t fade_length;
4109 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4110 _editor->snap_to_with_modifier (pos, event);
4112 pos.sample -= snap_delta (event->button.state);
4114 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4116 if (pos.sample < (region->position() + 64)) {
4117 fade_length = 64; // this should be a minimum defined somewhere
4118 } else if (pos.sample > region->position() + region->length() - region->fade_out()->back()->when) {
4119 fade_length = region->length() - region->fade_out()->back()->when - 1;
4121 fade_length = pos.sample - region->position();
4124 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4126 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4132 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
4135 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
4139 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
4141 if (!movement_occurred) {
4145 samplecnt_t fade_length;
4146 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4148 _editor->snap_to_with_modifier (pos, event);
4149 pos.sample -= snap_delta (event->button.state);
4151 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4153 if (pos.sample < (region->position() + 64)) {
4154 fade_length = 64; // this should be a minimum defined somewhere
4155 } else if (pos.sample >= region->position() + region->length() - region->fade_out()->back()->when) {
4156 fade_length = region->length() - region->fade_out()->back()->when - 1;
4158 fade_length = pos.sample - region->position();
4161 bool in_command = false;
4163 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4165 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4171 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
4172 XMLNode &before = alist->get_state();
4174 tmp->audio_region()->set_fade_in_length (fade_length);
4175 tmp->audio_region()->set_fade_in_active (true);
4178 _editor->begin_reversible_command (_("change fade in length"));
4181 XMLNode &after = alist->get_state();
4182 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4186 _editor->commit_reversible_command ();
4191 FadeInDrag::aborted (bool)
4193 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4194 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4200 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
4204 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4205 : RegionDrag (e, i, p, v)
4207 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
4211 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4213 Drag::start_grab (event, cursor);
4215 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4216 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4217 setup_snap_delta (MusicSample (r->last_sample(), 0));
4219 show_verbose_cursor_duration (r->last_sample() - r->fade_out()->back()->when, r->last_sample());
4223 FadeOutDrag::setup_pointer_sample_offset ()
4225 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4226 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4227 _pointer_sample_offset = raw_grab_sample() - (r->length() - (samplecnt_t) r->fade_out()->back()->when + r->position());
4231 FadeOutDrag::motion (GdkEvent* event, bool)
4233 samplecnt_t fade_length;
4234 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4236 _editor->snap_to_with_modifier (pos, event);
4237 pos.sample -= snap_delta (event->button.state);
4239 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4241 if (pos.sample > (region->last_sample() - 64)) {
4242 fade_length = 64; // this should really be a minimum fade defined somewhere
4243 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4244 fade_length = region->length() - region->fade_in()->back()->when - 1;
4246 fade_length = region->last_sample() - pos.sample;
4249 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4251 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4257 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
4260 show_verbose_cursor_duration (region->last_sample() - fade_length, region->last_sample());
4264 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
4266 if (!movement_occurred) {
4270 samplecnt_t fade_length;
4271 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4273 _editor->snap_to_with_modifier (pos, event);
4274 pos.sample -= snap_delta (event->button.state);
4276 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4278 if (pos.sample > (region->last_sample() - 64)) {
4279 fade_length = 64; // this should really be a minimum fade defined somewhere
4280 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4281 fade_length = region->length() - region->fade_in()->back()->when - 1;
4283 fade_length = region->last_sample() - pos.sample;
4286 bool in_command = false;
4288 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4290 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4296 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
4297 XMLNode &before = alist->get_state();
4299 tmp->audio_region()->set_fade_out_length (fade_length);
4300 tmp->audio_region()->set_fade_out_active (true);
4303 _editor->begin_reversible_command (_("change fade out length"));
4306 XMLNode &after = alist->get_state();
4307 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4311 _editor->commit_reversible_command ();
4316 FadeOutDrag::aborted (bool)
4318 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4319 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4325 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
4329 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
4331 , _selection_changed (false)
4333 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
4334 Gtk::Window* toplevel = _editor->current_toplevel();
4335 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
4339 _points.push_back (ArdourCanvas::Duple (0, 0));
4341 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4344 MarkerDrag::~MarkerDrag ()
4346 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4351 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4353 location = new Location (*l);
4354 markers.push_back (m);
4359 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4361 Drag::start_grab (event, cursor);
4365 Location *location = _editor->find_location_from_marker (_marker, is_start);
4366 _editor->_dragging_edit_point = true;
4368 update_item (location);
4370 // _drag_line->show();
4371 // _line->raise_to_top();
4374 show_verbose_cursor_time (location->start());
4376 show_verbose_cursor_time (location->end());
4378 setup_snap_delta (MusicSample (is_start ? location->start() : location->end(), 0));
4380 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4383 case Selection::Toggle:
4384 /* we toggle on the button release */
4386 case Selection::Set:
4387 if (!_editor->selection->selected (_marker)) {
4388 _editor->selection->set (_marker);
4389 _selection_changed = true;
4392 case Selection::Extend:
4394 Locations::LocationList ll;
4395 list<ArdourMarker*> to_add;
4397 _editor->selection->markers.range (s, e);
4398 s = min (_marker->position(), s);
4399 e = max (_marker->position(), e);
4402 if (e < max_samplepos) {
4405 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4406 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4407 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4410 to_add.push_back (lm->start);
4413 to_add.push_back (lm->end);
4417 if (!to_add.empty()) {
4418 _editor->selection->add (to_add);
4419 _selection_changed = true;
4423 case Selection::Add:
4424 _editor->selection->add (_marker);
4425 _selection_changed = true;
4430 /* Set up copies for us to manipulate during the drag
4433 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4435 Location* l = _editor->find_location_from_marker (*i, is_start);
4442 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4444 /* range: check that the other end of the range isn't
4447 CopiedLocationInfo::iterator x;
4448 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4449 if (*(*x).location == *l) {
4453 if (x == _copied_locations.end()) {
4454 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4456 (*x).markers.push_back (*i);
4457 (*x).move_both = true;
4465 MarkerDrag::setup_pointer_sample_offset ()
4468 Location *location = _editor->find_location_from_marker (_marker, is_start);
4469 _pointer_sample_offset = raw_grab_sample() - (is_start ? location->start() : location->end());
4473 MarkerDrag::motion (GdkEvent* event, bool)
4475 samplecnt_t f_delta = 0;
4477 bool move_both = false;
4478 Location *real_location;
4479 Location *copy_location = 0;
4480 samplecnt_t const sd = snap_delta (event->button.state);
4482 samplecnt_t const newframe = adjusted_sample (_drags->current_pointer_sample () + sd, event, true).sample - sd;
4483 samplepos_t next = newframe;
4485 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4489 CopiedLocationInfo::iterator x;
4491 /* find the marker we're dragging, and compute the delta */
4493 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4495 copy_location = (*x).location;
4497 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4499 /* this marker is represented by this
4500 * CopiedLocationMarkerInfo
4503 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4508 if (real_location->is_mark()) {
4509 f_delta = newframe - copy_location->start();
4513 switch (_marker->type()) {
4514 case ArdourMarker::SessionStart:
4515 case ArdourMarker::RangeStart:
4516 case ArdourMarker::LoopStart:
4517 case ArdourMarker::PunchIn:
4518 f_delta = newframe - copy_location->start();
4521 case ArdourMarker::SessionEnd:
4522 case ArdourMarker::RangeEnd:
4523 case ArdourMarker::LoopEnd:
4524 case ArdourMarker::PunchOut:
4525 f_delta = newframe - copy_location->end();
4528 /* what kind of marker is this ? */
4537 if (x == _copied_locations.end()) {
4538 /* hmm, impossible - we didn't find the dragged marker */
4542 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4544 /* now move them all */
4546 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4548 copy_location = x->location;
4550 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4554 if (real_location->locked()) {
4558 if (copy_location->is_mark()) {
4561 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4565 samplepos_t new_start = copy_location->start() + f_delta;
4566 samplepos_t new_end = copy_location->end() + f_delta;
4568 if (is_start) { // start-of-range marker
4570 if (move_both || (*x).move_both) {
4571 copy_location->set_start (new_start, false, true, divisions);
4572 copy_location->set_end (new_end, false, true, divisions);
4573 } else if (new_start < copy_location->end()) {
4574 copy_location->set_start (new_start, false, true, divisions);
4575 } else if (newframe > 0) {
4576 //_editor->snap_to (next, RoundUpAlways, true);
4577 copy_location->set_end (next, false, true, divisions);
4578 copy_location->set_start (newframe, false, true, divisions);
4581 } else { // end marker
4583 if (move_both || (*x).move_both) {
4584 copy_location->set_end (new_end, divisions);
4585 copy_location->set_start (new_start, false, true, divisions);
4586 } else if (new_end > copy_location->start()) {
4587 copy_location->set_end (new_end, false, true, divisions);
4588 } else if (newframe > 0) {
4589 //_editor->snap_to (next, RoundDownAlways, true);
4590 copy_location->set_start (next, false, true, divisions);
4591 copy_location->set_end (newframe, false, true, divisions);
4596 update_item (copy_location);
4598 /* now lookup the actual GUI items used to display this
4599 * location and move them to wherever the copy of the location
4600 * is now. This means that the logic in ARDOUR::Location is
4601 * still enforced, even though we are not (yet) modifying
4602 * the real Location itself.
4605 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4608 lm->set_position (copy_location->start(), copy_location->end());
4613 assert (!_copied_locations.empty());
4615 show_verbose_cursor_time (newframe);
4616 _editor->set_snapped_cursor_position(newframe);
4620 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4622 if (!movement_occurred) {
4624 if (was_double_click()) {
4625 _editor->rename_marker (_marker);
4629 /* just a click, do nothing but finish
4630 off the selection process
4633 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4635 case Selection::Set:
4636 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4637 _editor->selection->set (_marker);
4638 _selection_changed = true;
4642 case Selection::Toggle:
4643 /* we toggle on the button release, click only */
4644 _editor->selection->toggle (_marker);
4645 _selection_changed = true;
4649 case Selection::Extend:
4650 case Selection::Add:
4654 if (_selection_changed) {
4655 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4656 _editor->commit_reversible_selection_op();
4662 _editor->_dragging_edit_point = false;
4664 XMLNode &before = _editor->session()->locations()->get_state();
4665 bool in_command = false;
4667 MarkerSelection::iterator i;
4668 CopiedLocationInfo::iterator x;
4669 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4672 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4673 x != _copied_locations.end() && i != _editor->selection->markers.end();
4676 Location * location = _editor->find_location_from_marker (*i, is_start);
4680 if (location->locked()) {
4684 _editor->begin_reversible_command ( _("move marker") );
4687 if (location->is_mark()) {
4688 location->set_start (((*x).location)->start(), false, true, divisions);
4690 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4693 if (location->is_session_range()) {
4694 _editor->session()->set_end_is_free (false);
4700 XMLNode &after = _editor->session()->locations()->get_state();
4701 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4702 _editor->commit_reversible_command ();
4707 MarkerDrag::aborted (bool movement_occurred)
4709 if (!movement_occurred) {
4713 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4715 /* move all markers to their original location */
4718 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4721 Location * location = _editor->find_location_from_marker (*m, is_start);
4724 (*m)->set_position (is_start ? location->start() : location->end());
4731 MarkerDrag::update_item (Location*)
4736 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4738 , _fixed_grab_x (0.0)
4739 , _fixed_grab_y (0.0)
4740 , _cumulative_x_drag (0.0)
4741 , _cumulative_y_drag (0.0)
4745 if (_zero_gain_fraction < 0.0) {
4746 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4749 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4751 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4757 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4759 Drag::start_grab (event, _editor->cursors()->fader);
4761 // start the grab at the center of the control point so
4762 // the point doesn't 'jump' to the mouse after the first drag
4763 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4764 _fixed_grab_y = _point->get_y();
4766 setup_snap_delta (MusicSample (_editor->pixel_to_sample (_fixed_grab_x), 0));
4768 float const fraction = 1 - (_point->get_y() / _point->line().height());
4769 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4771 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4773 if (!_point->can_slide ()) {
4774 _x_constrained = true;
4779 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4781 double dx = _drags->current_pointer_x() - last_pointer_x();
4782 double dy = current_pointer_y() - last_pointer_y();
4783 bool need_snap = true;
4785 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4791 /* coordinate in pixels relative to the start of the region (for region-based automation)
4792 or track (for track-based automation) */
4793 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4794 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4796 // calculate zero crossing point. back off by .01 to stay on the
4797 // positive side of zero
4798 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4800 if (_x_constrained) {
4803 if (_y_constrained) {
4807 _cumulative_x_drag = cx - _fixed_grab_x;
4808 _cumulative_y_drag = cy - _fixed_grab_y;
4812 cy = min ((double) _point->line().height(), cy);
4814 // make sure we hit zero when passing through
4815 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4819 MusicSample cx_mf (_editor->pixel_to_sample (cx) + snap_delta (event->button.state), 0);
4821 if (!_x_constrained && need_snap) {
4822 _editor->snap_to_with_modifier (cx_mf, event);
4825 cx_mf.sample -= snap_delta (event->button.state);
4826 cx_mf.sample = min (cx_mf.sample, _point->line().maximum_time() + _point->line().offset());
4828 float const fraction = 1.0 - (cy / _point->line().height());
4831 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4832 _editor->begin_reversible_command (_("automation event move"));
4833 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4835 pair<float, float> result;
4836 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_mf.sample), fraction, false, _pushing, _final_index);
4837 show_verbose_cursor_text (_point->line().get_verbose_cursor_relative_string (result.first, result.second));
4841 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4843 if (!movement_occurred) {
4846 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4847 _editor->reset_point_selection ();
4851 _point->line().end_drag (_pushing, _final_index);
4852 _editor->commit_reversible_command ();
4857 ControlPointDrag::aborted (bool)
4859 _point->line().reset ();
4863 ControlPointDrag::active (Editing::MouseMode m)
4865 if (m == Editing::MouseDraw) {
4866 /* always active in mouse draw */
4870 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4871 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4874 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4877 , _fixed_grab_x (0.0)
4878 , _fixed_grab_y (0.0)
4879 , _cumulative_y_drag (0)
4883 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4887 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4889 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4892 _item = &_line->grab_item ();
4894 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4895 origin, and ditto for y.
4898 double mx = event->button.x;
4899 double my = event->button.y;
4901 _line->grab_item().canvas_to_item (mx, my);
4903 samplecnt_t const sample_within_region = (samplecnt_t) floor (mx * _editor->samples_per_pixel);
4905 if (!_line->control_points_adjacent (sample_within_region, _before, _after)) {
4906 /* no adjacent points */
4910 Drag::start_grab (event, _editor->cursors()->fader);
4912 /* store grab start in item sample */
4913 double const bx = _line->nth (_before)->get_x();
4914 double const ax = _line->nth (_after)->get_x();
4915 double const click_ratio = (ax - mx) / (ax - bx);
4917 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4922 double fraction = 1.0 - (cy / _line->height());
4924 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4928 LineDrag::motion (GdkEvent* event, bool first_move)
4930 double dy = current_pointer_y() - last_pointer_y();
4932 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4936 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4938 _cumulative_y_drag = cy - _fixed_grab_y;
4941 cy = min ((double) _line->height(), cy);
4943 double const fraction = 1.0 - (cy / _line->height());
4947 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4949 _editor->begin_reversible_command (_("automation range move"));
4950 _line->start_drag_line (_before, _after, initial_fraction);
4953 /* we are ignoring x position for this drag, so we can just pass in anything */
4954 pair<float, float> result;
4956 result = _line->drag_motion (0, fraction, true, false, ignored);
4957 show_verbose_cursor_text (_line->get_verbose_cursor_relative_string (result.first, result.second));
4961 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4963 if (movement_occurred) {
4964 motion (event, false);
4965 _line->end_drag (false, 0);
4966 _editor->commit_reversible_command ();
4968 /* add a new control point on the line */
4970 AutomationTimeAxisView* atv;
4972 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4973 samplepos_t where = grab_sample ();
4976 double cy = _fixed_grab_y;
4978 _line->grab_item().item_to_canvas (cx, cy);
4980 atv->add_automation_event (event, where, cy, false);
4981 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4982 AudioRegionView* arv;
4984 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4985 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4992 LineDrag::aborted (bool)
4997 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
5001 _region_view_grab_x (0.0),
5002 _cumulative_x_drag (0),
5006 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
5010 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
5012 Drag::start_grab (event);
5014 _line = reinterpret_cast<Line*> (_item);
5017 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
5019 double cx = event->button.x;
5020 double cy = event->button.y;
5022 _item->parent()->canvas_to_item (cx, cy);
5024 /* store grab start in parent sample */
5025 _region_view_grab_x = cx;
5027 _before = *(float*) _item->get_data ("position");
5029 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5031 _max_x = _editor->sample_to_pixel(_arv->get_duration());
5035 FeatureLineDrag::motion (GdkEvent*, bool)
5037 double dx = _drags->current_pointer_x() - last_pointer_x();
5039 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
5041 _cumulative_x_drag += dx;
5043 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
5052 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
5054 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
5056 float *pos = new float;
5059 _line->set_data ("position", pos);
5065 FeatureLineDrag::finished (GdkEvent*, bool)
5067 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5068 _arv->update_transient(_before, _before);
5072 FeatureLineDrag::aborted (bool)
5077 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5079 , _vertical_only (false)
5081 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
5085 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5087 Drag::start_grab (event);
5088 show_verbose_cursor_time (adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
5092 RubberbandSelectDrag::motion (GdkEvent* event, bool)
5098 samplepos_t const pf = adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
5099 MusicSample grab (grab_sample (), 0);
5101 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5102 _editor->snap_to_with_modifier (grab, event);
5104 grab.sample = raw_grab_sample ();
5107 /* base start and end on initial click position */
5109 if (pf < grab.sample) {
5114 start = grab.sample;
5117 if (current_pointer_y() < grab_y()) {
5118 y1 = current_pointer_y();
5121 y2 = current_pointer_y();
5125 if (start != end || y1 != y2) {
5127 double x1 = _editor->sample_to_pixel (start);
5128 double x2 = _editor->sample_to_pixel (end);
5129 const double min_dimension = 2.0;
5131 if (_vertical_only) {
5132 /* fixed 10 pixel width */
5136 x2 = min (x1 - min_dimension, x2);
5138 x2 = max (x1 + min_dimension, x2);
5143 y2 = min (y1 - min_dimension, y2);
5145 y2 = max (y1 + min_dimension, y2);
5148 /* translate rect into item space and set */
5150 ArdourCanvas::Rect r (x1, y1, x2, y2);
5152 /* this drag is a _trackview_only == true drag, so the y1 and
5153 * y2 (computed using current_pointer_y() and grab_y()) will be
5154 * relative to the top of the trackview group). The
5155 * rubberband rect has the same parent/scroll offset as the
5156 * the trackview group, so we can use the "r" rect directly
5157 * to set the shape of the rubberband.
5160 _editor->rubberband_rect->set (r);
5161 _editor->rubberband_rect->show();
5162 _editor->rubberband_rect->raise_to_top();
5164 show_verbose_cursor_time (pf);
5166 do_select_things (event, true);
5171 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
5175 samplepos_t grab = grab_sample ();
5176 samplepos_t lpf = last_pointer_sample ();
5178 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5179 grab = raw_grab_sample ();
5180 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
5194 if (current_pointer_y() < grab_y()) {
5195 y1 = current_pointer_y();
5198 y2 = current_pointer_y();
5202 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
5206 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
5208 if (movement_occurred) {
5210 motion (event, false);
5211 do_select_things (event, false);
5217 bool do_deselect = true;
5218 MidiTimeAxisView* mtv;
5220 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
5222 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
5223 /* nothing selected */
5224 add_midi_region (mtv, true);
5225 do_deselect = false;
5229 /* do not deselect if Primary or Tertiary (toggle-select or
5230 * extend-select are pressed.
5233 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
5234 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
5241 _editor->rubberband_rect->hide();
5245 RubberbandSelectDrag::aborted (bool)
5247 _editor->rubberband_rect->hide ();
5250 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
5251 : RegionDrag (e, i, p, v)
5253 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
5257 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5259 Drag::start_grab (event, cursor);
5261 _editor->get_selection().add (_primary);
5263 MusicSample where (_primary->region()->position(), 0);
5264 setup_snap_delta (where);
5266 show_verbose_cursor_duration (where.sample, adjusted_current_sample (event), 0);
5270 TimeFXDrag::motion (GdkEvent* event, bool)
5272 RegionView* rv = _primary;
5273 StreamView* cv = rv->get_time_axis_view().view ();
5274 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
5275 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
5276 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
5277 MusicSample pf (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
5279 _editor->snap_to_with_modifier (pf, event);
5280 pf.sample -= snap_delta (event->button.state);
5282 if (pf.sample > rv->region()->position()) {
5283 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf.sample, layers, layer);
5286 show_verbose_cursor_duration (_primary->region()->position(), pf.sample, 0);
5290 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
5292 /* this may have been a single click, no drag. We still want the dialog
5293 to show up in that case, so that the user can manually edit the
5294 parameters for the timestretch.
5297 float fraction = 1.0;
5299 if (movement_occurred) {
5301 motion (event, false);
5303 _primary->get_time_axis_view().hide_timestretch ();
5305 samplepos_t adjusted_sample_pos = adjusted_current_sample (event);
5307 if (adjusted_sample_pos < _primary->region()->position()) {
5308 /* backwards drag of the left edge - not usable */
5312 samplecnt_t newlen = adjusted_sample_pos - _primary->region()->position();
5314 fraction = (double) newlen / (double) _primary->region()->length();
5316 #ifndef USE_RUBBERBAND
5317 // Soundtouch uses fraction / 100 instead of normal (/ 1)
5318 if (_primary->region()->data_type() == DataType::AUDIO) {
5319 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
5324 if (!_editor->get_selection().regions.empty()) {
5325 /* primary will already be included in the selection, and edit
5326 group shared editing will propagate selection across
5327 equivalent regions, so just use the current region
5331 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
5332 error << _("An error occurred while executing time stretch operation") << endmsg;
5338 TimeFXDrag::aborted (bool)
5340 _primary->get_time_axis_view().hide_timestretch ();
5343 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5346 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5350 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5352 Drag::start_grab (event);
5356 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5358 _editor->scrub (adjusted_current_sample (0, false), _drags->current_pointer_x ());
5362 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5364 if (movement_occurred && _editor->session()) {
5365 /* make sure we stop */
5366 _editor->session()->request_transport_speed (0.0);
5371 ScrubDrag::aborted (bool)
5376 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5380 , _track_selection_at_start (e)
5381 , _time_selection_at_start (!_editor->get_selection().time.empty())
5383 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5385 if (_time_selection_at_start) {
5386 start_at_start = _editor->get_selection().time.start();
5387 end_at_start = _editor->get_selection().time.end_sample();
5392 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5394 if (_editor->session() == 0) {
5398 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5400 switch (_operation) {
5401 case CreateSelection:
5402 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5407 cursor = _editor->cursors()->selector;
5408 Drag::start_grab (event, cursor);
5411 case SelectionStartTrim:
5412 if (_editor->clicked_axisview) {
5413 _editor->clicked_axisview->order_selection_trims (_item, true);
5415 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5418 case SelectionEndTrim:
5419 if (_editor->clicked_axisview) {
5420 _editor->clicked_axisview->order_selection_trims (_item, false);
5422 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5426 Drag::start_grab (event, cursor);
5429 case SelectionExtend:
5430 Drag::start_grab (event, cursor);
5434 if (_operation == SelectionMove) {
5435 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5437 show_verbose_cursor_time (adjusted_current_sample (event));
5442 SelectionDrag::setup_pointer_sample_offset ()
5444 switch (_operation) {
5445 case CreateSelection:
5446 _pointer_sample_offset = 0;
5449 case SelectionStartTrim:
5451 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].start;
5454 case SelectionEndTrim:
5455 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].end;
5458 case SelectionExtend:
5464 SelectionDrag::motion (GdkEvent* event, bool first_move)
5466 samplepos_t start = 0;
5467 samplepos_t end = 0;
5468 samplecnt_t length = 0;
5469 samplecnt_t distance = 0;
5470 MusicSample start_mf (0, 0);
5471 samplepos_t const pending_position = adjusted_current_sample (event);
5473 if (_operation != CreateSelection && pending_position == last_pointer_sample()) {
5478 _track_selection_at_start = _editor->selection->tracks;
5481 switch (_operation) {
5482 case CreateSelection:
5484 MusicSample grab (grab_sample (), 0);
5486 grab.sample = adjusted_current_sample (event, false);
5487 if (grab.sample < pending_position) {
5488 _editor->snap_to (grab, RoundDownMaybe);
5490 _editor->snap_to (grab, RoundUpMaybe);
5494 if (pending_position < grab.sample) {
5495 start = pending_position;
5498 end = pending_position;
5499 start = grab.sample;
5502 /* first drag: Either add to the selection
5503 or create a new selection
5510 /* adding to the selection */
5511 _editor->set_selected_track_as_side_effect (Selection::Add);
5512 _editor->clicked_selection = _editor->selection->add (start, end);
5519 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5520 _editor->set_selected_track_as_side_effect (Selection::Set);
5523 _editor->clicked_selection = _editor->selection->set (start, end);
5527 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5528 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5529 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5531 _editor->selection->add (atest);
5535 /* select all tracks within the rectangle that we've marked out so far */
5536 TrackViewList new_selection;
5537 TrackViewList& all_tracks (_editor->track_views);
5539 ArdourCanvas::Coord const top = grab_y();
5540 ArdourCanvas::Coord const bottom = current_pointer_y();
5542 if (top >= 0 && bottom >= 0) {
5544 //first, find the tracks that are covered in the y range selection
5545 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5546 if ((*i)->covered_by_y_range (top, bottom)) {
5547 new_selection.push_back (*i);
5551 //now compare our list with the current selection, and add as necessary
5552 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5553 TrackViewList tracks_to_add;
5554 TrackViewList tracks_to_remove;
5555 vector<RouteGroup*> selected_route_groups;
5558 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5559 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5560 tracks_to_remove.push_back (*i);
5562 RouteGroup* rg = (*i)->route_group();
5563 if (rg && rg->is_active() && rg->is_select()) {
5564 selected_route_groups.push_back (rg);
5570 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5571 if (!_editor->selection->tracks.contains (*i)) {
5572 tracks_to_add.push_back (*i);
5573 RouteGroup* rg = (*i)->route_group();
5575 if (rg && rg->is_active() && rg->is_select()) {
5576 selected_route_groups.push_back (rg);
5581 _editor->selection->add (tracks_to_add);
5583 if (!tracks_to_remove.empty()) {
5585 /* check all these to-be-removed tracks against the
5586 * possibility that they are selected by being
5587 * in the same group as an approved track.
5590 for (TrackViewList::iterator i = tracks_to_remove.begin(); i != tracks_to_remove.end(); ) {
5591 RouteGroup* rg = (*i)->route_group();
5593 if (rg && find (selected_route_groups.begin(), selected_route_groups.end(), rg) != selected_route_groups.end()) {
5594 i = tracks_to_remove.erase (i);
5600 /* remove whatever is left */
5602 _editor->selection->remove (tracks_to_remove);
5608 case SelectionStartTrim:
5610 end = _editor->selection->time[_editor->clicked_selection].end;
5612 if (pending_position > end) {
5615 start = pending_position;
5619 case SelectionEndTrim:
5621 start = _editor->selection->time[_editor->clicked_selection].start;
5623 if (pending_position < start) {
5626 end = pending_position;
5633 start = _editor->selection->time[_editor->clicked_selection].start;
5634 end = _editor->selection->time[_editor->clicked_selection].end;
5636 length = end - start;
5637 distance = pending_position - start;
5638 start = pending_position;
5640 start_mf.sample = start;
5641 _editor->snap_to (start_mf);
5643 end = start_mf.sample + length;
5647 case SelectionExtend:
5652 switch (_operation) {
5654 if (_time_selection_at_start) {
5655 _editor->selection->move_time (distance);
5659 _editor->selection->replace (_editor->clicked_selection, start, end);
5663 if (_operation == SelectionMove) {
5664 show_verbose_cursor_time(start);
5666 show_verbose_cursor_time(pending_position);
5671 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5673 Session* s = _editor->session();
5675 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5676 if (movement_occurred) {
5677 motion (event, false);
5678 /* XXX this is not object-oriented programming at all. ick */
5679 if (_editor->selection->time.consolidate()) {
5680 _editor->selection->TimeChanged ();
5683 /* XXX what if its a music time selection? */
5685 if (s->get_play_range() && s->transport_rolling()) {
5686 s->request_play_range (&_editor->selection->time, true);
5687 } else if (!s->config.get_external_sync()) {
5688 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5689 s->request_locate (_editor->get_selection().time.start());
5693 if (_editor->get_selection().time.length() != 0) {
5694 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_sample());
5696 s->clear_range_selection ();
5701 /* just a click, no pointer movement.
5704 if (was_double_click()) {
5705 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5706 _editor->temporal_zoom_selection (Both);
5711 if (_operation == SelectionExtend) {
5712 if (_time_selection_at_start) {
5713 samplepos_t pos = adjusted_current_sample (event, false);
5714 samplepos_t start = min (pos, start_at_start);
5715 samplepos_t end = max (pos, end_at_start);
5716 _editor->selection->set (start, end);
5719 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5720 if (_editor->clicked_selection) {
5721 _editor->selection->remove (_editor->clicked_selection);
5724 if (!_editor->clicked_selection) {
5725 _editor->selection->clear_time();
5730 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5731 _editor->selection->set (_editor->clicked_axisview);
5734 if (s && s->get_play_range () && s->transport_rolling()) {
5735 s->request_stop (false, false);
5740 _editor->stop_canvas_autoscroll ();
5741 _editor->clicked_selection = 0;
5742 _editor->commit_reversible_selection_op ();
5746 SelectionDrag::aborted (bool)
5751 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5752 : Drag (e, i, false),
5756 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5758 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5759 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5760 physical_screen_height (_editor->current_toplevel()->get_window())));
5761 _drag_rect->hide ();
5763 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5764 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5767 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5769 /* normal canvas items will be cleaned up when their parent group is deleted. But
5770 this item is created as the child of a long-lived parent group, and so we
5771 need to explicitly delete it.
5777 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5779 if (_editor->session() == 0) {
5783 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5785 if (!_editor->temp_location) {
5786 _editor->temp_location = new Location (*_editor->session());
5789 switch (_operation) {
5790 case CreateSkipMarker:
5791 case CreateRangeMarker:
5792 case CreateTransportMarker:
5793 case CreateCDMarker:
5795 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5800 cursor = _editor->cursors()->selector;
5804 Drag::start_grab (event, cursor);
5806 show_verbose_cursor_time (adjusted_current_sample (event));
5810 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5812 samplepos_t start = 0;
5813 samplepos_t end = 0;
5814 ArdourCanvas::Rectangle *crect;
5816 switch (_operation) {
5817 case CreateSkipMarker:
5818 crect = _editor->range_bar_drag_rect;
5820 case CreateRangeMarker:
5821 crect = _editor->range_bar_drag_rect;
5823 case CreateTransportMarker:
5824 crect = _editor->transport_bar_drag_rect;
5826 case CreateCDMarker:
5827 crect = _editor->cd_marker_bar_drag_rect;
5830 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5835 samplepos_t const pf = adjusted_current_sample (event);
5837 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5838 MusicSample grab (grab_sample (), 0);
5839 _editor->snap_to (grab);
5841 if (pf < grab_sample()) {
5846 start = grab.sample;
5849 /* first drag: Either add to the selection
5850 or create a new selection.
5855 _editor->temp_location->set (start, end);
5859 update_item (_editor->temp_location);
5861 //_drag_rect->raise_to_top();
5867 _editor->temp_location->set (start, end);
5869 double x1 = _editor->sample_to_pixel (start);
5870 double x2 = _editor->sample_to_pixel (end);
5874 update_item (_editor->temp_location);
5877 show_verbose_cursor_time (pf);
5882 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5884 Location * newloc = 0;
5888 if (movement_occurred) {
5889 motion (event, false);
5892 switch (_operation) {
5893 case CreateSkipMarker:
5894 case CreateRangeMarker:
5895 case CreateCDMarker:
5897 XMLNode &before = _editor->session()->locations()->get_state();
5898 if (_operation == CreateSkipMarker) {
5899 _editor->begin_reversible_command (_("new skip marker"));
5900 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5901 flags = Location::IsRangeMarker | Location::IsSkip;
5902 _editor->range_bar_drag_rect->hide();
5903 } else if (_operation == CreateCDMarker) {
5904 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5905 _editor->begin_reversible_command (_("new CD marker"));
5906 flags = Location::IsRangeMarker | Location::IsCDMarker;
5907 _editor->cd_marker_bar_drag_rect->hide();
5909 _editor->begin_reversible_command (_("new skip marker"));
5910 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5911 flags = Location::IsRangeMarker;
5912 _editor->range_bar_drag_rect->hide();
5914 newloc = new Location (
5915 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5916 , _editor->get_grid_music_divisions (event->button.state));
5918 _editor->session()->locations()->add (newloc, true);
5919 XMLNode &after = _editor->session()->locations()->get_state();
5920 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5921 _editor->commit_reversible_command ();
5925 case CreateTransportMarker:
5926 // popup menu to pick loop or punch
5927 _editor->new_transport_marker_context_menu (&event->button, _item);
5933 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5935 if (_operation == CreateTransportMarker) {
5937 /* didn't drag, so just locate */
5939 _editor->session()->request_locate (grab_sample(), _editor->session()->transport_rolling());
5941 } else if (_operation == CreateCDMarker) {
5943 /* didn't drag, but mark is already created so do
5946 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5951 _editor->session()->locations()->marks_either_side (grab_sample(), start, end);
5953 if (end == max_samplepos) {
5954 end = _editor->session()->current_end_sample ();
5957 if (start == max_samplepos) {
5958 start = _editor->session()->current_start_sample ();
5961 switch (_editor->mouse_mode) {
5963 /* find the two markers on either side and then make the selection from it */
5964 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5968 /* find the two markers on either side of the click and make the range out of it */
5969 _editor->selection->set (start, end);
5978 _editor->stop_canvas_autoscroll ();
5982 RangeMarkerBarDrag::aborted (bool movement_occurred)
5984 if (movement_occurred) {
5985 _drag_rect->hide ();
5990 RangeMarkerBarDrag::update_item (Location* location)
5992 double const x1 = _editor->sample_to_pixel (location->start());
5993 double const x2 = _editor->sample_to_pixel (location->end());
5995 _drag_rect->set_x0 (x1);
5996 _drag_rect->set_x1 (x2);
5999 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
6001 , _cumulative_dx (0)
6002 , _cumulative_dy (0)
6004 , _was_selected (false)
6007 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
6009 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
6011 _region = &_primary->region_view ();
6012 _note_height = _region->midi_stream_view()->note_height ();
6016 NoteDrag::setup_pointer_sample_offset ()
6018 _pointer_sample_offset = raw_grab_sample()
6019 - _editor->session()->tempo_map().sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6023 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
6025 Drag::start_grab (event);
6027 if (ArdourKeyboard::indicates_copy (event->button.state)) {
6033 setup_snap_delta (MusicSample (_region->source_beats_to_absolute_samples (_primary->note()->time ()), 0));
6035 if (!(_was_selected = _primary->selected())) {
6037 /* tertiary-click means extend selection - we'll do that on button release,
6038 so don't add it here, because otherwise we make it hard to figure
6039 out the "extend-to" range.
6042 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
6045 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
6048 _region->note_selected (_primary, true);
6050 _editor->get_selection().clear_points();
6051 _region->unique_select (_primary);
6057 /** @return Current total drag x change in quarter notes */
6059 NoteDrag::total_dx (GdkEvent * event) const
6061 if (_x_constrained) {
6065 TempoMap& map (_editor->session()->tempo_map());
6068 sampleoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
6070 /* primary note time */
6071 sampleoffset_t const n = map.sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6073 /* primary note time in quarter notes */
6074 double const n_qn = _region->session_relative_qn (_primary->note()->time().to_double());
6076 /* new time of the primary note in session samples */
6077 sampleoffset_t st = n + dx + snap_delta (event->button.state);
6079 /* possibly snap and return corresponding delta in quarter notes */
6080 MusicSample snap (st, 0);
6081 _editor->snap_to_with_modifier (snap, event);
6082 double ret = map.exact_qn_at_sample (snap.sample, snap.division) - n_qn - snap_delta_music (event->button.state);
6084 /* prevent the earliest note being dragged earlier than the region's start position */
6085 if (_earliest + ret < _region->midi_region()->start_beats()) {
6086 ret -= (_earliest + ret) - _region->midi_region()->start_beats();
6092 /** @return Current total drag y change in note number */
6094 NoteDrag::total_dy () const
6096 if (_y_constrained) {
6100 double const y = _region->midi_view()->y_position ();
6101 /* new current note */
6102 uint8_t n = _region->y_to_note (current_pointer_y () - y);
6104 MidiStreamView* msv = _region->midi_stream_view ();
6105 n = max (msv->lowest_note(), n);
6106 n = min (msv->highest_note(), n);
6107 /* and work out delta */
6108 return n - _region->y_to_note (grab_y() - y);
6112 NoteDrag::motion (GdkEvent * event, bool first_move)
6115 _earliest = _region->earliest_in_selection().to_double();
6117 /* make copies of all the selected notes */
6118 _primary = _region->copy_selection (_primary);
6122 /* Total change in x and y since the start of the drag */
6123 double const dx_qn = total_dx (event);
6124 int8_t const dy = total_dy ();
6126 /* Now work out what we have to do to the note canvas items to set this new drag delta */
6127 double const tdx = _x_constrained ? 0 : dx_qn - _cumulative_dx;
6128 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
6131 _cumulative_dx = dx_qn;
6132 _cumulative_dy += tdy;
6134 int8_t note_delta = total_dy();
6138 _region->move_copies (dx_qn, tdy, note_delta);
6140 _region->move_selection (dx_qn, tdy, note_delta);
6143 /* the new note value may be the same as the old one, but we
6144 * don't know what that means because the selection may have
6145 * involved more than one note and we might be doing something
6146 * odd with them. so show the note value anyway, always.
6149 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
6151 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
6153 _editor->set_snapped_cursor_position( _region->source_beats_to_absolute_samples(_primary->note()->time()) );
6159 NoteDrag::finished (GdkEvent* ev, bool moved)
6162 /* no motion - select note */
6164 if (_editor->current_mouse_mode() == Editing::MouseContent ||
6165 _editor->current_mouse_mode() == Editing::MouseDraw) {
6167 bool changed = false;
6169 if (_was_selected) {
6170 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6172 _region->note_deselected (_primary);
6175 _editor->get_selection().clear_points();
6176 _region->unique_select (_primary);
6180 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
6181 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6183 if (!extend && !add && _region->selection_size() > 1) {
6184 _editor->get_selection().clear_points();
6185 _region->unique_select (_primary);
6187 } else if (extend) {
6188 _region->note_selected (_primary, true, true);
6191 /* it was added during button press */
6198 _editor->begin_reversible_selection_op(X_("Select Note Release"));
6199 _editor->commit_reversible_selection_op();
6203 _region->note_dropped (_primary, total_dx (ev), total_dy(), _copy);
6208 NoteDrag::aborted (bool)
6213 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
6214 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
6215 : Drag (editor, atv->base_item ())
6217 , _y_origin (atv->y_position())
6218 , _nothing_to_drag (false)
6220 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6221 setup (atv->lines ());
6224 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
6225 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
6226 : Drag (editor, rv->get_canvas_group ())
6228 , _y_origin (rv->get_time_axis_view().y_position())
6229 , _nothing_to_drag (false)
6232 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6234 list<boost::shared_ptr<AutomationLine> > lines;
6236 AudioRegionView* audio_view;
6237 AutomationRegionView* automation_view;
6238 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
6239 lines.push_back (audio_view->get_gain_line ());
6240 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
6241 lines.push_back (automation_view->line ());
6244 error << _("Automation range drag created for invalid region type") << endmsg;
6250 /** @param lines AutomationLines to drag.
6251 * @param offset Offset from the session start to the points in the AutomationLines.
6254 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
6256 /* find the lines that overlap the ranges being dragged */
6257 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
6258 while (i != lines.end ()) {
6259 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
6262 pair<samplepos_t, samplepos_t> r = (*i)->get_point_x_range ();
6264 /* check this range against all the AudioRanges that we are using */
6265 list<AudioRange>::const_iterator k = _ranges.begin ();
6266 while (k != _ranges.end()) {
6267 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
6273 /* add it to our list if it overlaps at all */
6274 if (k != _ranges.end()) {
6279 _lines.push_back (n);
6285 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
6289 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
6291 return 1.0 - ((global_y - _y_origin) / line->height());
6295 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
6297 const double v = list->eval(x);
6298 return _integral ? rint(v) : v;
6302 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6304 Drag::start_grab (event, cursor);
6306 /* Get line states before we start changing things */
6307 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6308 i->state = &i->line->get_state ();
6309 i->original_fraction = y_fraction (i->line, current_pointer_y());
6312 if (_ranges.empty()) {
6314 /* No selected time ranges: drag all points */
6315 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6316 uint32_t const N = i->line->npoints ();
6317 for (uint32_t j = 0; j < N; ++j) {
6318 i->points.push_back (i->line->nth (j));
6324 if (_nothing_to_drag) {
6330 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
6332 if (_nothing_to_drag && !first_move) {
6337 _editor->begin_reversible_command (_("automation range move"));
6339 if (!_ranges.empty()) {
6341 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
6343 samplecnt_t const half = (i->start + i->end) / 2;
6345 /* find the line that this audio range starts in */
6346 list<Line>::iterator j = _lines.begin();
6347 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
6351 if (j != _lines.end()) {
6352 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6354 /* j is the line that this audio range starts in; fade into it;
6355 64 samples length plucked out of thin air.
6358 samplepos_t a = i->start + 64;
6363 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
6364 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
6366 XMLNode &before = the_list->get_state();
6367 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6368 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6370 if (add_p || add_q) {
6371 _editor->session()->add_command (
6372 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6376 /* same thing for the end */
6379 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
6383 if (j != _lines.end()) {
6384 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6386 /* j is the line that this audio range starts in; fade out of it;
6387 64 samples length plucked out of thin air.
6390 samplepos_t b = i->end - 64;
6395 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6396 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6398 XMLNode &before = the_list->get_state();
6399 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6400 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6402 if (add_p || add_q) {
6403 _editor->session()->add_command (
6404 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6409 _nothing_to_drag = true;
6411 /* Find all the points that should be dragged and put them in the relevant
6412 points lists in the Line structs.
6415 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6417 uint32_t const N = i->line->npoints ();
6418 for (uint32_t j = 0; j < N; ++j) {
6420 /* here's a control point on this line */
6421 ControlPoint* p = i->line->nth (j);
6422 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6424 /* see if it's inside a range */
6425 list<AudioRange>::const_iterator k = _ranges.begin ();
6426 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6430 if (k != _ranges.end()) {
6431 /* dragging this point */
6432 _nothing_to_drag = false;
6433 i->points.push_back (p);
6439 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6440 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6444 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6445 float const f = y_fraction (l->line, current_pointer_y());
6446 /* we are ignoring x position for this drag, so we can just pass in anything */
6447 pair<float, float> result;
6449 result = l->line->drag_motion (0, f, true, false, ignored);
6450 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (result.first, result.second));
6455 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6457 if (_nothing_to_drag || !motion_occurred) {
6461 motion (event, false);
6462 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6463 i->line->end_drag (false, 0);
6466 _editor->commit_reversible_command ();
6470 AutomationRangeDrag::aborted (bool)
6472 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6477 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6479 , initial_time_axis_view (itav)
6481 /* note that time_axis_view may be null if the regionview was created
6482 * as part of a copy operation.
6484 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6485 layer = v->region()->layer ();
6486 initial_y = v->get_canvas_group()->position().y;
6487 initial_playlist = v->region()->playlist ();
6488 initial_position = v->region()->position ();
6489 initial_end = v->region()->position () + v->region()->length ();
6492 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6493 : Drag (e, i->canvas_item ())
6496 , _cumulative_dx (0)
6498 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6499 _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time()),
6504 PatchChangeDrag::motion (GdkEvent* ev, bool)
6506 samplepos_t f = adjusted_current_sample (ev);
6507 boost::shared_ptr<Region> r = _region_view->region ();
6508 f = max (f, r->position ());
6509 f = min (f, r->last_sample ());
6511 samplecnt_t const dxf = f - grab_sample(); // permitted dx in samples
6512 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6513 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6514 _cumulative_dx = dxu;
6518 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6520 if (!movement_occurred) {
6521 if (was_double_click()) {
6522 _region_view->edit_patch_change (_patch_change);
6527 boost::shared_ptr<Region> r (_region_view->region ());
6528 samplepos_t f = adjusted_current_sample (ev);
6529 f = max (f, r->position ());
6530 f = min (f, r->last_sample ());
6532 _region_view->move_patch_change (
6534 _region_view->region_samples_to_region_beats (f - (r->position() - r->start()))
6539 PatchChangeDrag::aborted (bool)
6541 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6545 PatchChangeDrag::setup_pointer_sample_offset ()
6547 boost::shared_ptr<Region> region = _region_view->region ();
6548 _pointer_sample_offset = raw_grab_sample() - _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time());
6551 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6552 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6559 MidiRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6561 _region_view->update_drag_selection (
6563 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6567 MidiRubberbandSelectDrag::deselect_things ()
6572 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6573 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6576 _vertical_only = true;
6580 MidiVerticalSelectDrag::select_things (int button_state, samplepos_t /*x1*/, samplepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6582 double const y = _region_view->midi_view()->y_position ();
6584 y1 = max (0.0, y1 - y);
6585 y2 = max (0.0, y2 - y);
6587 _region_view->update_vertical_drag_selection (
6590 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6595 MidiVerticalSelectDrag::deselect_things ()
6600 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6601 : RubberbandSelectDrag (e, i)
6607 EditorRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool drag_in_progress)
6609 if (drag_in_progress) {
6610 /* We just want to select things at the end of the drag, not during it */
6614 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6616 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6618 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6620 _editor->commit_reversible_selection_op ();
6624 EditorRubberbandSelectDrag::deselect_things ()
6626 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6628 _editor->selection->clear_tracks();
6629 _editor->selection->clear_regions();
6630 _editor->selection->clear_points ();
6631 _editor->selection->clear_lines ();
6632 _editor->selection->clear_midi_notes ();
6634 _editor->commit_reversible_selection_op();
6637 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6642 _note[0] = _note[1] = 0;
6645 NoteCreateDrag::~NoteCreateDrag ()
6651 NoteCreateDrag::grid_samples (samplepos_t t) const
6654 const Temporal::Beats grid_beats = _region_view->get_grid_beats (t);
6655 const Temporal::Beats t_beats = _region_view->region_samples_to_region_beats (t);
6657 return _region_view->region_beats_to_region_samples (t_beats + grid_beats)
6658 - _region_view->region_beats_to_region_samples (t_beats);
6662 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6664 Drag::start_grab (event, cursor);
6666 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6667 TempoMap& map (_editor->session()->tempo_map());
6669 const samplepos_t pf = _drags->current_pointer_sample ();
6670 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6672 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6674 double eqaf = map.exact_qn_at_sample (pf, divisions);
6676 if (divisions != 0) {
6678 const double qaf = map.quarter_note_at_sample (pf);
6680 /* Hack so that we always snap to the note that we are over, instead of snapping
6681 to the next one if we're more than halfway through the one we're over.
6684 const double rem = eqaf - qaf;
6686 eqaf -= grid_beats.to_double();
6690 _note[0] = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6691 /* minimum initial length is grid beats */
6692 _note[1] = map.sample_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6694 double const x0 = _editor->sample_to_pixel (_note[0]);
6695 double const x1 = _editor->sample_to_pixel (_note[1]);
6696 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6698 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6699 _drag_rect->set_outline_all ();
6700 _drag_rect->set_outline_color (0xffffff99);
6701 _drag_rect->set_fill_color (0xffffff66);
6705 NoteCreateDrag::motion (GdkEvent* event, bool)
6707 TempoMap& map (_editor->session()->tempo_map());
6708 const samplepos_t pf = _drags->current_pointer_sample ();
6709 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6710 double eqaf = map.exact_qn_at_sample (pf, divisions);
6712 if (divisions != 0) {
6714 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6716 const double qaf = map.quarter_note_at_sample (pf);
6717 /* Hack so that we always snap to the note that we are over, instead of snapping
6718 to the next one if we're more than halfway through the one we're over.
6721 const double rem = eqaf - qaf;
6723 eqaf -= grid_beats.to_double();
6726 eqaf += grid_beats.to_double();
6728 _note[1] = max ((samplepos_t)0, map.sample_at_quarter_note (eqaf) - _region_view->region()->position ());
6730 double const x0 = _editor->sample_to_pixel (_note[0]);
6731 double const x1 = _editor->sample_to_pixel (_note[1]);
6732 _drag_rect->set_x0 (std::min(x0, x1));
6733 _drag_rect->set_x1 (std::max(x0, x1));
6737 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6739 /* we create a note even if there was no movement */
6740 samplepos_t const start = min (_note[0], _note[1]);
6741 samplepos_t const start_sess_rel = start + _region_view->region()->position();
6742 samplecnt_t length = max (_editor->pixel_to_sample (1.0), (samplecnt_t) fabs ((double)(_note[0] - _note[1])));
6743 samplecnt_t const g = grid_samples (start_sess_rel);
6745 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6749 TempoMap& map (_editor->session()->tempo_map());
6750 const double qn_length = map.quarter_notes_between_samples (start_sess_rel, start_sess_rel + length);
6751 Temporal::Beats qn_length_beats = max (Temporal::Beats::ticks(1), Temporal::Beats (qn_length));
6753 _editor->begin_reversible_command (_("Create Note"));
6754 _region_view->clear_editor_note_selection();
6755 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6756 _editor->commit_reversible_command ();
6760 NoteCreateDrag::y_to_region (double y) const
6763 _region_view->get_canvas_group()->canvas_to_item (x, y);
6768 NoteCreateDrag::aborted (bool)
6773 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6781 HitCreateDrag::~HitCreateDrag ()
6786 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6788 Drag::start_grab (event, cursor);
6790 TempoMap& map (_editor->session()->tempo_map());
6792 _y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6794 const samplepos_t pf = _drags->current_pointer_sample ();
6795 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6797 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6799 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6801 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6805 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6806 Temporal::Beats length = _region_view->get_grid_beats (pf);
6808 _editor->begin_reversible_command (_("Create Hit"));
6809 _region_view->clear_editor_note_selection();
6810 _region_view->create_note_at (start, _y, length, event->button.state, false);
6816 HitCreateDrag::motion (GdkEvent* event, bool)
6818 TempoMap& map (_editor->session()->tempo_map());
6820 const samplepos_t pf = _drags->current_pointer_sample ();
6821 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6823 if (divisions == 0) {
6827 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6828 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position ();
6830 if (_last_pos == start) {
6834 Temporal::Beats length = _region_view->get_grid_beats (pf);
6836 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6838 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6842 _region_view->create_note_at (start, _y, length, event->button.state, false);
6848 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6850 _editor->commit_reversible_command ();
6855 HitCreateDrag::y_to_region (double y) const
6858 _region_view->get_canvas_group()->canvas_to_item (x, y);
6863 HitCreateDrag::aborted (bool)
6868 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6873 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6877 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6879 Drag::start_grab (event, cursor);
6883 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6889 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6892 distance = _drags->current_pointer_x() - grab_x();
6893 len = ar->fade_in()->back()->when;
6895 distance = grab_x() - _drags->current_pointer_x();
6896 len = ar->fade_out()->back()->when;
6899 /* how long should it be ? */
6901 new_length = len + _editor->pixel_to_sample (distance);
6903 /* now check with the region that this is legal */
6905 new_length = ar->verify_xfade_bounds (new_length, start);
6908 arv->reset_fade_in_shape_width (ar, new_length);
6910 arv->reset_fade_out_shape_width (ar, new_length);
6915 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6921 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6924 distance = _drags->current_pointer_x() - grab_x();
6925 len = ar->fade_in()->back()->when;
6927 distance = grab_x() - _drags->current_pointer_x();
6928 len = ar->fade_out()->back()->when;
6931 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6933 _editor->begin_reversible_command ("xfade trim");
6934 ar->playlist()->clear_owned_changes ();
6937 ar->set_fade_in_length (new_length);
6939 ar->set_fade_out_length (new_length);
6942 /* Adjusting the xfade may affect other regions in the playlist, so we need
6943 to get undo Commands from the whole playlist rather than just the
6947 vector<Command*> cmds;
6948 ar->playlist()->rdiff (cmds);
6949 _editor->session()->add_commands (cmds);
6950 _editor->commit_reversible_command ();
6955 CrossfadeEdgeDrag::aborted (bool)
6958 // arv->redraw_start_xfade ();
6960 // arv->redraw_end_xfade ();
6964 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, samplepos_t pos)
6965 : Drag (e, item, true)
6969 RegionCutDrag::~RegionCutDrag ()
6974 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6976 Drag::start_grab (event, c);
6977 motion (event, false);
6981 RegionCutDrag::motion (GdkEvent* event, bool)
6986 RegionCutDrag::finished (GdkEvent* event, bool)
6988 _editor->get_track_canvas()->canvas()->re_enter();
6991 MusicSample pos (_drags->current_pointer_sample(), 0);
6992 _editor->snap_to_with_modifier (pos, event);
6994 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos.sample);
7000 _editor->split_regions_at (pos, rs, false);
7004 RegionCutDrag::aborted (bool)