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);
726 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
732 const int tavsize = _time_axis_views.size();
733 const int dt = delta > 0 ? +1 : -1;
735 int target = start + delta - skip;
737 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
738 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
740 while (current >= 0 && current != target) {
742 if (current < 0 && dt < 0) {
745 if (current >= tavsize && dt > 0) {
748 if (current < 0 || current >= tavsize) {
752 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
753 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
757 if (distance_only && current == start + delta) {
765 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
767 if (_y_constrained) {
771 const int tavsize = _time_axis_views.size();
772 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
773 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
774 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
776 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
777 /* already in the drop zone */
778 if (delta_track >= 0) {
779 /* downward motion - OK if others are still not in the dropzone */
788 } else if (n >= tavsize) {
789 /* downward motion into drop zone. That's fine. */
793 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
794 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
795 /* not a track, or the wrong type */
799 double const l = i->layer + delta_layer;
801 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
802 mode to allow the user to place a region below another on layer 0.
804 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
805 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
806 If it has, the layers will be munged later anyway, so it's ok.
812 /* all regions being dragged are ok with this change */
816 struct DraggingViewSorter {
817 bool operator() (const DraggingView& a, const DraggingView& b) {
818 return a.time_axis_view < b.time_axis_view;
823 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
825 double delta_layer = 0;
826 int delta_time_axis_view = 0;
827 int current_pointer_time_axis_view = -1;
829 assert (!_views.empty ());
831 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
833 /* Find the TimeAxisView that the pointer is now over */
834 const double cur_y = current_pointer_y ();
835 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
836 TimeAxisView* tv = r.first;
838 if (!tv && cur_y < 0) {
839 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
843 /* find drop-zone y-position */
844 Coord last_track_bottom_edge;
845 last_track_bottom_edge = 0;
846 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
847 if (!(*t)->hidden()) {
848 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
853 if (tv && tv->view()) {
854 /* the mouse is over a track */
855 double layer = r.second;
857 if (first_move && tv->view()->layer_display() == Stacked) {
858 tv->view()->set_layer_display (Expanded);
861 /* Here's the current pointer position in terms of time axis view and layer */
862 current_pointer_time_axis_view = find_time_axis_view (tv);
863 assert(current_pointer_time_axis_view >= 0);
865 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
867 /* Work out the change in y */
869 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
870 if (!rtv || !rtv->is_track()) {
871 /* ignore non-tracks early on. we can't move any regions on them */
872 } else if (_last_pointer_time_axis_view < 0) {
873 /* Was in the drop-zone, now over a track.
874 * Hence it must be an upward move (from the bottom)
876 * track_index is still -1, so delta must be set to
877 * move up the correct number of tracks from the bottom.
879 * This is necessary because steps may be skipped if
880 * the bottom-most track is not a valid target and/or
881 * if there are hidden tracks at the bottom.
882 * Hence the initial offset (_ddropzone) as well as the
883 * last valid pointer position (_pdropzone) need to be
884 * taken into account.
886 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
888 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
891 /* TODO needs adjustment per DraggingView,
893 * e.g. select one region on the top-layer of a track
894 * and one region which is at the bottom-layer of another track
897 * Indicated drop-zones and layering is wrong.
898 * and may infer additional layers on the target-track
899 * (depending how many layers the original track had).
901 * Or select two regions (different layers) on a same track,
902 * move across a non-layer track.. -> layering info is lost.
903 * on drop either of the regions may be on top.
905 * Proposed solution: screw it :) well,
906 * don't use delta_layer, use an absolute value
907 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
908 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
909 * 3) iterate over all DraggingView, find the one that is over the track with most layers
910 * 4) proportionally scale layer to layers available on target
912 delta_layer = current_pointer_layer - _last_pointer_layer;
915 /* for automation lanes, there is a TimeAxisView but no ->view()
916 * if (!tv) -> dropzone
918 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
919 /* Moving into the drop-zone.. */
920 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
921 /* delta_time_axis_view may not be sufficient to move into the DZ
922 * the mouse may enter it, but it may not be a valid move due to
925 * -> remember the delta needed to move into the dropzone
927 _ddropzone = delta_time_axis_view;
928 /* ..but subtract hidden tracks (or routes) at the bottom.
929 * we silently move mover them
931 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
932 - _time_axis_views.size();
934 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
935 /* move around inside the zone.
936 * This allows to move further down until all regions are in the zone.
938 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
939 assert(ptr_y >= last_track_bottom_edge);
940 assert(_ddropzone > 0);
942 /* calculate mouse position in 'tracks' below last track. */
943 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
944 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
946 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
948 delta_time_axis_view = dzpos - _pdropzone;
949 } else if (dzpos < _pdropzone && _ndropzone > 0) {
950 // move up inside the DZ
951 delta_time_axis_view = dzpos - _pdropzone;
955 /* Work out the change in x */
956 TempoMap& tmap = _editor->session()->tempo_map();
957 MusicSample pending_region_position (0, 0);
958 double const x_delta = compute_x_delta (event, &pending_region_position);
960 double const last_pos_qn = tmap.exact_qn_at_sample (_last_position.sample, _last_position.division);
961 double const qn_delta = tmap.exact_qn_at_sample (pending_region_position.sample, pending_region_position.division) - last_pos_qn;
963 _last_position = pending_region_position;
965 /* calculate hidden tracks in current y-axis delta */
967 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
968 /* The mouse is more than one track below the dropzone.
969 * distance calculation is not needed (and would not work, either
970 * because the dropzone is "packed").
972 * Except when [partially] moving regions out of dropzone in a large step.
973 * (the mouse may or may not remain in the DZ)
974 * Hidden tracks at the bottom of the TAV need to be skipped.
976 * This also handles the case if the mouse entered the DZ
977 * in a large step (exessive delta), either due to fast-movement,
978 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
980 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
981 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
983 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
984 -_time_axis_views.size() - dt;
987 else if (_last_pointer_time_axis_view < 0) {
988 /* Moving out of the zone. Check for hidden tracks at the bottom. */
989 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
990 -_time_axis_views.size() - delta_time_axis_view;
992 /* calculate hidden tracks that are skipped by the pointer movement */
993 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
994 - _last_pointer_time_axis_view
995 - delta_time_axis_view;
998 /* Verify change in y */
999 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
1000 /* this y movement is not allowed, so do no y movement this time */
1001 delta_time_axis_view = 0;
1006 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1007 /* haven't reached next snap point, and we're not switching
1008 trackviews nor layers. nothing to do.
1013 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1014 PlaylistDropzoneMap playlist_dropzone_map;
1015 _ndropzone = 0; // number of elements currently in the dropzone
1018 /* sort views by time_axis.
1019 * This retains track order in the dropzone, regardless
1020 * of actual selection order
1022 _views.sort (DraggingViewSorter());
1024 /* count number of distinct tracks of all regions
1025 * being dragged, used for dropzone.
1027 int prev_track = -1;
1028 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1029 if (i->time_axis_view != prev_track) {
1030 prev_track = i->time_axis_view;
1036 _views.back().time_axis_view -
1037 _views.front().time_axis_view;
1039 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1040 - _views.back().time_axis_view;
1042 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1046 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1048 RegionView* rv = i->view;
1053 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1060 /* reparent the regionview into a group above all
1064 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1065 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1066 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1067 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1068 /* move the item so that it continues to appear at the
1069 same location now that its parent has changed.
1071 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1074 /* If we have moved tracks, we'll fudge the layer delta so that the
1075 region gets moved back onto layer 0 on its new track; this avoids
1076 confusion when dragging regions from non-zero layers onto different
1079 double this_delta_layer = delta_layer;
1080 if (delta_time_axis_view != 0) {
1081 this_delta_layer = - i->layer;
1084 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1086 int track_index = i->time_axis_view + this_delta_time_axis_view;
1087 assert(track_index >= 0);
1089 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1090 /* Track is in the Dropzone */
1092 i->time_axis_view = track_index;
1093 assert(i->time_axis_view >= (int) _time_axis_views.size());
1096 double yposition = 0;
1097 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1098 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1101 /* store index of each new playlist as a negative count, starting at -1 */
1103 if (pdz == playlist_dropzone_map.end()) {
1104 /* compute where this new track (which doesn't exist yet) will live
1107 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1109 /* How high is this region view ? */
1111 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1112 ArdourCanvas::Rect bbox;
1115 bbox = obbox.get ();
1118 last_track_bottom_edge += bbox.height();
1120 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1123 yposition = pdz->second;
1126 /* values are zero or negative, hence the use of min() */
1127 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1130 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1132 mrv->apply_note_range (60, 71, true);
1136 /* The TimeAxisView that this region is now over */
1137 TimeAxisView* current_tv = _time_axis_views[track_index];
1139 /* Ensure it is moved from stacked -> expanded if appropriate */
1140 if (current_tv->view()->layer_display() == Stacked) {
1141 current_tv->view()->set_layer_display (Expanded);
1144 /* We're only allowed to go -ve in layer on Expanded views */
1145 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1146 this_delta_layer = - i->layer;
1150 rv->set_height (current_tv->view()->child_height ());
1152 /* Update show/hidden status as the region view may have come from a hidden track,
1153 or have moved to one.
1155 if (current_tv->hidden ()) {
1156 rv->get_canvas_group()->hide ();
1158 rv->get_canvas_group()->show ();
1161 /* Update the DraggingView */
1162 i->time_axis_view = track_index;
1163 i->layer += this_delta_layer;
1166 _editor->mouse_brush_insert_region (rv, pending_region_position.sample);
1170 /* Get the y coordinate of the top of the track that this region is now over */
1171 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1173 /* And adjust for the layer that it should be on */
1174 StreamView* cv = current_tv->view ();
1175 switch (cv->layer_display ()) {
1179 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1182 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1186 /* need to get the parent of the regionview
1187 * canvas group and get its position in
1188 * equivalent coordinate space as the trackview
1189 * we are now dragging over.
1192 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1196 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1198 MidiStreamView* msv;
1199 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1200 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1205 /* Now move the region view */
1206 if (rv->region()->position_lock_style() == MusicTime) {
1207 double const last_qn = tmap.quarter_note_at_sample (rv->get_position());
1208 samplepos_t const x_pos_music = tmap.sample_at_quarter_note (last_qn + qn_delta);
1210 rv->set_position (x_pos_music, 0);
1211 rv->move (0, y_delta);
1213 rv->move (x_delta, y_delta);
1216 } /* foreach region */
1218 _total_x_delta += x_delta;
1220 if (x_delta != 0 && !_brushing) {
1221 show_verbose_cursor_time (_last_position.sample);
1224 /* keep track of pointer movement */
1226 /* the pointer is currently over a time axis view */
1228 if (_last_pointer_time_axis_view < 0) {
1229 /* last motion event was not over a time axis view
1230 * or last y-movement out of the dropzone was not valid
1233 if (delta_time_axis_view < 0) {
1234 /* in the drop zone, moving up */
1236 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1237 * We do not use negative _last_pointer_time_axis_view because
1238 * the dropzone is "packed" (the actual track offset is ignored)
1240 * As opposed to the actual number
1241 * of elements in the dropzone (_ndropzone)
1242 * _pdropzone is not constrained. This is necessary
1243 * to allow moving multiple regions with y-distance
1246 * There can be 0 elements in the dropzone,
1247 * even though the drag-pointer is inside the DZ.
1250 * [ Audio-track, Midi-track, Audio-track, DZ ]
1251 * move regions from both audio tracks at the same time into the
1252 * DZ by grabbing the region in the bottom track.
1254 assert(current_pointer_time_axis_view >= 0);
1255 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1259 /* only move out of the zone if the movement is OK */
1260 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1261 assert(delta_time_axis_view < 0);
1262 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1263 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1264 * the current position can be calculated as follows:
1266 // a well placed oofus attack can still throw this off.
1267 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1268 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1271 /* last motion event was also over a time axis view */
1272 _last_pointer_time_axis_view += delta_time_axis_view;
1273 assert(_last_pointer_time_axis_view >= 0);
1278 /* the pointer is not over a time axis view */
1279 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1280 _pdropzone += delta_time_axis_view - delta_skip;
1281 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1284 _last_pointer_layer += delta_layer;
1288 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1290 if (_copy && first_move) {
1291 if (_x_constrained && !_brushing) {
1292 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1293 } else if (!_brushing) {
1294 _editor->begin_reversible_command (Operations::region_copy);
1295 } else if (_brushing) {
1296 _editor->begin_reversible_command (Operations::drag_region_brush);
1298 /* duplicate the regionview(s) and region(s) */
1300 list<DraggingView> new_regionviews;
1302 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1304 RegionView* rv = i->view;
1305 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1306 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1308 const boost::shared_ptr<const Region> original = rv->region();
1309 boost::shared_ptr<Region> region_copy;
1311 region_copy = RegionFactory::create (original, true);
1313 /* need to set this so that the drop zone code can work. This doesn't
1314 actually put the region into the playlist, but just sets a weak pointer
1317 region_copy->set_playlist (original->playlist());
1321 boost::shared_ptr<AudioRegion> audioregion_copy
1322 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1324 nrv = new AudioRegionView (*arv, audioregion_copy);
1326 boost::shared_ptr<MidiRegion> midiregion_copy
1327 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1328 nrv = new MidiRegionView (*mrv, midiregion_copy);
1333 nrv->get_canvas_group()->show ();
1334 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1336 /* swap _primary to the copy */
1338 if (rv == _primary) {
1342 /* ..and deselect the one we copied */
1344 rv->set_selected (false);
1347 if (!new_regionviews.empty()) {
1349 /* reflect the fact that we are dragging the copies */
1351 _views = new_regionviews;
1353 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1356 } else if (!_copy && first_move) {
1357 if (_x_constrained && !_brushing) {
1358 _editor->begin_reversible_command (_("fixed time region drag"));
1359 } else if (!_brushing) {
1360 _editor->begin_reversible_command (Operations::region_drag);
1361 } else if (_brushing) {
1362 _editor->begin_reversible_command (Operations::drag_region_brush);
1365 RegionMotionDrag::motion (event, first_move);
1369 RegionMotionDrag::finished (GdkEvent *, bool)
1371 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1372 if (!(*i)->view()) {
1376 if ((*i)->view()->layer_display() == Expanded) {
1377 (*i)->view()->set_layer_display (Stacked);
1383 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1385 RegionMotionDrag::finished (ev, movement_occurred);
1387 if (!movement_occurred) {
1391 if (was_double_click() && !_views.empty()) {
1392 DraggingView dv = _views.front();
1393 _editor->edit_region (dv.view);
1399 assert (!_views.empty ());
1401 /* We might have hidden region views so that they weren't visible during the drag
1402 (when they have been reparented). Now everything can be shown again, as region
1403 views are back in their track parent groups.
1405 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1406 i->view->get_canvas_group()->show ();
1409 bool const changed_position = (_last_position.sample != _primary->region()->position());
1410 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1434 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1436 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1439 TimeAxisView* tav = 0;
1441 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1442 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1443 uint32_t output_chan = region->n_channels();
1444 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1445 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1447 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1448 tav =_editor->time_axis_view_from_stripable (audio_tracks.front());
1450 ChanCount one_midi_port (DataType::MIDI, 1);
1451 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1452 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1453 Config->get_strict_io () || Profile->get_mixbus (),
1454 boost::shared_ptr<ARDOUR::PluginInfo>(),
1455 (ARDOUR::Plugin::PresetRecord*) 0,
1456 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1457 tav = _editor->time_axis_view_from_stripable (midi_tracks.front());
1461 tav->set_height (original->current_height());
1464 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1467 return dynamic_cast<RouteTimeAxisView*> (tav);
1471 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, MusicSample last_position, int32_t const ev_state)
1473 RegionSelection new_views;
1474 PlaylistSet modified_playlists;
1475 RouteTimeAxisView* new_time_axis_view = 0;
1476 samplecnt_t const drag_delta = _primary->region()->position() - _last_position.sample;
1478 TempoMap& tmap (_editor->session()->tempo_map());
1479 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1480 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1483 /* all changes were made during motion event handlers */
1485 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1489 _editor->commit_reversible_command ();
1493 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1494 PlaylistMapping playlist_mapping;
1496 /* insert the regions into their new playlists */
1497 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1499 RouteTimeAxisView* dest_rtv = 0;
1501 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1505 MusicSample where (0, 0);
1506 double quarter_note;
1508 if (changed_position && !_x_constrained) {
1509 where.set (i->view->region()->position() - drag_delta, 0);
1510 quarter_note = i->view->region()->quarter_note() - qn_delta;
1512 /* region has not moved - divisor will not affect musical pos */
1513 where.set (i->view->region()->position(), 0);
1514 quarter_note = i->view->region()->quarter_note();
1517 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1518 /* dragged to drop zone */
1520 PlaylistMapping::iterator pm;
1522 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1523 /* first region from this original playlist: create a new track */
1524 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1525 if(!new_time_axis_view) {
1529 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1530 dest_rtv = new_time_axis_view;
1532 /* we already created a new track for regions from this playlist, use it */
1533 dest_rtv = pm->second;
1536 /* destination time axis view is the one we dragged to */
1537 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1540 if (dest_rtv != 0) {
1541 RegionView* new_view;
1542 if (i->view == _primary && !_x_constrained) {
1543 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, last_position, last_pos_qn,
1544 modified_playlists, true);
1546 if (i->view->region()->position_lock_style() == AudioTime) {
1547 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1548 modified_playlists);
1550 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1551 modified_playlists, true);
1555 if (new_view != 0) {
1556 new_views.push_back (new_view);
1560 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1561 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1564 list<DraggingView>::const_iterator next = i;
1570 /* If we've created new regions either by copying or moving
1571 to a new track, we want to replace the old selection with the new ones
1574 if (new_views.size() > 0) {
1575 _editor->selection->set (new_views);
1578 /* write commands for the accumulated diffs for all our modified playlists */
1579 add_stateful_diff_commands_for_playlists (modified_playlists);
1581 _editor->commit_reversible_command ();
1585 RegionMoveDrag::finished_no_copy (
1586 bool const changed_position,
1587 bool const changed_tracks,
1588 MusicSample last_position,
1589 int32_t const ev_state
1592 RegionSelection new_views;
1593 PlaylistSet modified_playlists;
1594 PlaylistSet frozen_playlists;
1595 set<RouteTimeAxisView*> views_to_update;
1596 RouteTimeAxisView* new_time_axis_view = 0;
1597 samplecnt_t const drag_delta = _primary->region()->position() - last_position.sample;
1599 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1600 PlaylistMapping playlist_mapping;
1602 TempoMap& tmap (_editor->session()->tempo_map());
1603 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1604 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1606 std::set<boost::shared_ptr<const Region> > uniq;
1607 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1609 RegionView* rv = i->view;
1610 RouteTimeAxisView* dest_rtv = 0;
1612 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1617 if (uniq.find (rv->region()) != uniq.end()) {
1618 /* prevent duplicate moves when selecting regions from shared playlists */
1622 uniq.insert(rv->region());
1624 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1625 /* dragged to drop zone */
1627 PlaylistMapping::iterator pm;
1629 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1630 /* first region from this original playlist: create a new track */
1631 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1632 if(!new_time_axis_view) { // New track creation failed
1636 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1637 dest_rtv = new_time_axis_view;
1639 /* we already created a new track for regions from this playlist, use it */
1640 dest_rtv = pm->second;
1644 /* destination time axis view is the one we dragged to */
1645 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1650 double const dest_layer = i->layer;
1652 views_to_update.insert (dest_rtv);
1654 MusicSample where (0, 0);
1655 double quarter_note;
1657 if (changed_position && !_x_constrained) {
1658 where.set (rv->region()->position() - drag_delta, 0);
1659 quarter_note = i->view->region()->quarter_note() - qn_delta;
1661 where.set (rv->region()->position(), 0);
1662 quarter_note = i->view->region()->quarter_note();
1665 if (changed_tracks) {
1667 /* insert into new playlist */
1668 RegionView* new_view;
1669 if (rv == _primary && !_x_constrained) {
1670 new_view = insert_region_into_playlist (
1671 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, last_position, last_pos_qn,
1672 modified_playlists, true
1675 if (rv->region()->position_lock_style() == AudioTime) {
1677 new_view = insert_region_into_playlist (
1678 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1682 new_view = insert_region_into_playlist (
1683 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1684 modified_playlists, true
1689 if (new_view == 0) {
1694 new_views.push_back (new_view);
1696 /* remove from old playlist */
1698 /* the region that used to be in the old playlist is not
1699 moved to the new one - we use a copy of it. as a result,
1700 any existing editor for the region should no longer be
1703 rv->hide_region_editor();
1706 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1710 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1712 /* this movement may result in a crossfade being modified, or a layering change,
1713 so we need to get undo data from the playlist as well as the region.
1716 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1718 playlist->clear_changes ();
1721 rv->region()->clear_changes ();
1724 motion on the same track. plonk the previously reparented region
1725 back to its original canvas group (its streamview).
1726 No need to do anything for copies as they are fake regions which will be deleted.
1729 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1730 rv->get_canvas_group()->set_y_position (i->initial_y);
1733 /* just change the model */
1734 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1735 playlist->set_layer (rv->region(), dest_layer);
1738 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1740 r = frozen_playlists.insert (playlist);
1743 playlist->freeze ();
1745 if (rv == _primary) {
1746 rv->region()->set_position (where.sample, last_position.division);
1748 if (rv->region()->position_lock_style() == AudioTime) {
1749 /* move by sample offset */
1750 rv->region()->set_position (where.sample, 0);
1752 /* move by music offset */
1753 rv->region()->set_position_music (rv->region()->quarter_note() - qn_delta);
1756 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1759 if (changed_tracks) {
1761 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1762 was selected in all of them, then removing it from a playlist will have removed all
1763 trace of it from _views (i.e. there were N regions selected, we removed 1,
1764 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1765 corresponding regionview, and _views is now empty).
1767 This could have invalidated any and all iterators into _views.
1769 The heuristic we use here is: if the region selection is empty, break out of the loop
1770 here. if the region selection is not empty, then restart the loop because we know that
1771 we must have removed at least the region(view) we've just been working on as well as any
1772 that we processed on previous iterations.
1774 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1775 we can just iterate.
1779 if (_views.empty()) {
1790 /* If we've created new regions either by copying or moving
1791 to a new track, we want to replace the old selection with the new ones
1794 if (new_views.size() > 0) {
1795 _editor->selection->set (new_views);
1798 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1802 /* write commands for the accumulated diffs for all our modified playlists */
1803 add_stateful_diff_commands_for_playlists (modified_playlists);
1804 /* applies to _brushing */
1805 _editor->commit_reversible_command ();
1807 /* We have futzed with the layering of canvas items on our streamviews.
1808 If any region changed layer, this will have resulted in the stream
1809 views being asked to set up their region views, and all will be well.
1810 If not, we might now have badly-ordered region views. Ask the StreamViews
1811 involved to sort themselves out, just in case.
1814 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1815 (*i)->view()->playlist_layered ((*i)->track ());
1819 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1820 * @param region Region to remove.
1821 * @param playlist playlist To remove from.
1822 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1823 * that clear_changes () is only called once per playlist.
1826 RegionMoveDrag::remove_region_from_playlist (
1827 boost::shared_ptr<Region> region,
1828 boost::shared_ptr<Playlist> playlist,
1829 PlaylistSet& modified_playlists
1832 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1835 playlist->clear_changes ();
1838 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1842 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1843 * clearing the playlist's diff history first if necessary.
1844 * @param region Region to insert.
1845 * @param dest_rtv Destination RouteTimeAxisView.
1846 * @param dest_layer Destination layer.
1847 * @param where Destination position.
1848 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1849 * that clear_changes () is only called once per playlist.
1850 * @return New RegionView, or 0 if no insert was performed.
1853 RegionMoveDrag::insert_region_into_playlist (
1854 boost::shared_ptr<Region> region,
1855 RouteTimeAxisView* dest_rtv,
1858 double quarter_note,
1859 PlaylistSet& modified_playlists,
1863 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1864 if (!dest_playlist) {
1868 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1869 _new_region_view = 0;
1870 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1872 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1873 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1875 dest_playlist->clear_changes ();
1878 dest_playlist->add_region (region, where.sample, 1.0, false, where.division, quarter_note, true);
1880 dest_playlist->add_region (region, where.sample, 1.0, false, where.division);
1883 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1884 dest_playlist->set_layer (region, dest_layer);
1889 assert (_new_region_view);
1891 return _new_region_view;
1895 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1897 _new_region_view = rv;
1901 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1903 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1904 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1906 _editor->session()->add_command (c);
1915 RegionMoveDrag::aborted (bool movement_occurred)
1919 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1920 list<DraggingView>::const_iterator next = i;
1929 RegionMotionDrag::aborted (movement_occurred);
1934 RegionMotionDrag::aborted (bool)
1936 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1938 StreamView* sview = (*i)->view();
1941 if (sview->layer_display() == Expanded) {
1942 sview->set_layer_display (Stacked);
1947 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1948 RegionView* rv = i->view;
1949 TimeAxisView* tv = &(rv->get_time_axis_view ());
1950 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1952 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1953 rv->get_canvas_group()->set_y_position (0);
1955 rv->move (-_total_x_delta, 0);
1956 rv->set_height (rtv->view()->child_height ());
1960 /** @param b true to brush, otherwise false.
1961 * @param c true to make copies of the regions being moved, otherwise false.
1963 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1964 : RegionMotionDrag (e, i, p, v, b)
1966 , _new_region_view (0)
1968 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1970 _last_position = MusicSample (_primary->region()->position(), 0);
1974 RegionMoveDrag::setup_pointer_sample_offset ()
1976 _pointer_sample_offset = raw_grab_sample() - _last_position.sample;
1979 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, samplepos_t pos)
1980 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1982 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1984 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1985 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1987 _primary = v->view()->create_region_view (r, false, false);
1989 _primary->get_canvas_group()->show ();
1990 _primary->set_position (pos, 0);
1991 _views.push_back (DraggingView (_primary, this, v));
1993 _last_position = MusicSample (pos, 0);
1995 _item = _primary->get_canvas_group ();
1999 RegionInsertDrag::finished (GdkEvent * event, bool)
2001 int pos = _views.front().time_axis_view;
2002 assert(pos >= 0 && pos < (int)_time_axis_views.size());
2004 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
2006 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
2007 _primary->get_canvas_group()->set_y_position (0);
2009 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
2011 _editor->begin_reversible_command (Operations::insert_region);
2012 playlist->clear_changes ();
2013 _editor->snap_to_with_modifier (_last_position, event);
2015 playlist->add_region (_primary->region (), _last_position.sample, 1.0, false, _last_position.division);
2017 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2018 if (Config->get_edit_mode() == Ripple) {
2019 playlist->ripple (_last_position.sample, _primary->region()->length(), _primary->region());
2022 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2023 _editor->commit_reversible_command ();
2031 RegionInsertDrag::aborted (bool)
2038 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2039 : RegionMoveDrag (e, i, p, v, false, false)
2041 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2044 struct RegionSelectionByPosition {
2045 bool operator() (RegionView*a, RegionView* b) {
2046 return a->region()->position () < b->region()->position();
2051 RegionSpliceDrag::motion (GdkEvent* event, bool)
2053 /* Which trackview is this ? */
2055 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2056 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2058 /* The region motion is only processed if the pointer is over
2062 if (!tv || !tv->is_track()) {
2063 /* To make sure we hide the verbose canvas cursor when the mouse is
2064 not held over an audio track.
2066 _editor->verbose_cursor()->hide ();
2069 _editor->verbose_cursor()->show ();
2074 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2080 RegionSelection copy;
2081 _editor->selection->regions.by_position(copy);
2083 samplepos_t const pf = adjusted_current_sample (event);
2085 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2087 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2093 boost::shared_ptr<Playlist> playlist;
2095 if ((playlist = atv->playlist()) == 0) {
2099 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2104 if (pf < (*i)->region()->last_sample() + 1) {
2108 if (pf > (*i)->region()->first_sample()) {
2114 playlist->shuffle ((*i)->region(), dir);
2119 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2121 RegionMoveDrag::finished (event, movement_occurred);
2125 RegionSpliceDrag::aborted (bool)
2135 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, samplepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2138 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<samplepos_t>(where, max_samplepos));
2140 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2141 RegionSelection to_ripple;
2142 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2143 if ((*i)->position() >= where) {
2144 to_ripple.push_back (rtv->view()->find_view(*i));
2148 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2149 if (!exclude.contains (*i)) {
2150 // the selection has already been added to _views
2152 if (drag_in_progress) {
2153 // do the same things that RegionMotionDrag::motion does when
2154 // first_move is true, for the region views that we're adding
2155 // to _views this time
2158 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2159 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2160 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2161 rvg->reparent (_editor->_drag_motion_group);
2163 // we only need to move in the y direction
2164 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2169 _views.push_back (DraggingView (*i, this, tav));
2175 RegionRippleDrag::remove_unselected_from_views(samplecnt_t amount, bool move_regions)
2178 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2179 // we added all the regions after the selection
2181 std::list<DraggingView>::iterator to_erase = i++;
2182 if (!_editor->selection->regions.contains (to_erase->view)) {
2183 // restore the non-selected regions to their original playlist & positions,
2184 // and then ripple them back by the length of the regions that were dragged away
2185 // do the same things as RegionMotionDrag::aborted
2187 RegionView *rv = to_erase->view;
2188 TimeAxisView* tv = &(rv->get_time_axis_view ());
2189 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2192 // plonk them back onto their own track
2193 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2194 rv->get_canvas_group()->set_y_position (0);
2198 // move the underlying region to match the view
2199 rv->region()->set_position (rv->region()->position() + amount);
2201 // restore the view to match the underlying region's original position
2202 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2205 rv->set_height (rtv->view()->child_height ());
2206 _views.erase (to_erase);
2212 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2214 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2216 return allow_moves_across_tracks;
2224 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2225 : RegionMoveDrag (e, i, p, v, false, false)
2227 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2228 // compute length of selection
2229 RegionSelection selected_regions = _editor->selection->regions;
2230 selection_length = selected_regions.end_sample() - selected_regions.start();
2232 // we'll only allow dragging to another track in ripple mode if all the regions
2233 // being dragged start off on the same track
2234 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2237 exclude = new RegionList;
2238 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2239 exclude->push_back((*i)->region());
2242 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2243 RegionSelection copy;
2244 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2246 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2247 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2249 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2250 // find ripple start point on each applicable playlist
2251 RegionView *first_selected_on_this_track = NULL;
2252 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2253 if ((*i)->region()->playlist() == (*pi)) {
2254 // region is on this playlist - it's the first, because they're sorted
2255 first_selected_on_this_track = *i;
2259 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2260 add_all_after_to_views (
2261 &first_selected_on_this_track->get_time_axis_view(),
2262 first_selected_on_this_track->region()->position(),
2263 selected_regions, false);
2266 if (allow_moves_across_tracks) {
2267 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2275 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2277 /* Which trackview is this ? */
2279 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2280 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2282 /* The region motion is only processed if the pointer is over
2286 if (!tv || !tv->is_track()) {
2287 /* To make sure we hide the verbose canvas cursor when the mouse is
2288 not held over an audiotrack.
2290 _editor->verbose_cursor()->hide ();
2294 samplepos_t where = adjusted_current_sample (event);
2295 assert (where >= 0);
2296 MusicSample after (0, 0);
2297 double delta = compute_x_delta (event, &after);
2299 samplecnt_t amount = _editor->pixel_to_sample (delta);
2301 if (allow_moves_across_tracks) {
2302 // all the originally selected regions were on the same track
2304 samplecnt_t adjust = 0;
2305 if (prev_tav && tv != prev_tav) {
2306 // dragged onto a different track
2307 // remove the unselected regions from _views, restore them to their original positions
2308 // and add the regions after the drop point on the new playlist to _views instead.
2309 // undo the effect of rippling the previous playlist, and include the effect of removing
2310 // the dragged region(s) from this track
2312 remove_unselected_from_views (prev_amount, false);
2313 // ripple previous playlist according to the regions that have been removed onto the new playlist
2314 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2317 // move just the selected regions
2318 RegionMoveDrag::motion(event, first_move);
2320 // ensure that the ripple operation on the new playlist inserts selection_length time
2321 adjust = selection_length;
2322 // ripple the new current playlist
2323 tv->playlist()->ripple (where, amount+adjust, exclude);
2325 // add regions after point where drag entered this track to subsequent ripples
2326 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2329 // motion on same track
2330 RegionMoveDrag::motion(event, first_move);
2334 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2335 prev_position = where;
2337 // selection encompasses multiple tracks - just drag
2338 // cross-track drags are forbidden
2339 RegionMoveDrag::motion(event, first_move);
2342 if (!_x_constrained) {
2343 prev_amount += amount;
2346 _last_position = after;
2350 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2352 if (!movement_occurred) {
2356 if (was_double_click() && !_views.empty()) {
2357 DraggingView dv = _views.front();
2358 _editor->edit_region (dv.view);
2364 _editor->begin_reversible_command(_("Ripple drag"));
2366 // remove the regions being rippled from the dragging view, updating them to
2367 // their new positions
2368 remove_unselected_from_views (prev_amount, true);
2370 if (allow_moves_across_tracks) {
2372 // if regions were dragged across tracks, we've rippled any later
2373 // regions on the track the regions were dragged off, so we need
2374 // to add the original track to the undo record
2375 orig_tav->playlist()->clear_changes();
2376 vector<Command*> cmds;
2377 orig_tav->playlist()->rdiff (cmds);
2378 _editor->session()->add_commands (cmds);
2380 if (prev_tav && prev_tav != orig_tav) {
2381 prev_tav->playlist()->clear_changes();
2382 vector<Command*> cmds;
2383 prev_tav->playlist()->rdiff (cmds);
2384 _editor->session()->add_commands (cmds);
2387 // selection spanned multiple tracks - all will need adding to undo record
2389 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2390 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2392 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2393 (*pi)->clear_changes();
2394 vector<Command*> cmds;
2395 (*pi)->rdiff (cmds);
2396 _editor->session()->add_commands (cmds);
2400 // other modified playlists are added to undo by RegionMoveDrag::finished()
2401 RegionMoveDrag::finished (event, movement_occurred);
2402 _editor->commit_reversible_command();
2406 RegionRippleDrag::aborted (bool movement_occurred)
2408 RegionMoveDrag::aborted (movement_occurred);
2413 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2415 _view (dynamic_cast<MidiTimeAxisView*> (v))
2417 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2423 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2427 _editor->begin_reversible_command (_("create region"));
2428 _region = add_midi_region (_view, false);
2429 _view->playlist()->freeze ();
2433 samplepos_t const f = adjusted_current_sample (event);
2434 if (f <= grab_sample()) {
2435 _region->set_initial_position (f);
2438 /* Don't use a zero-length region, and subtract 1 sample from the snapped length
2439 so that if this region is duplicated, its duplicate starts on
2440 a snap point rather than 1 sample after a snap point. Otherwise things get
2441 a bit confusing as if a region starts 1 sample after a snap point, one cannot
2442 place snapped notes at the start of the region.
2444 if (f != grab_sample()) {
2445 samplecnt_t const len = (samplecnt_t) fabs ((double)(f - grab_sample () - 1));
2446 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2453 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2455 if (!movement_occurred) {
2456 add_midi_region (_view, true);
2458 _view->playlist()->thaw ();
2459 _editor->commit_reversible_command();
2464 RegionCreateDrag::aborted (bool)
2467 _view->playlist()->thaw ();
2473 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2478 , _was_selected (false)
2481 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2485 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2487 Gdk::Cursor* cursor;
2488 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2490 float x_fraction = cnote->mouse_x_fraction ();
2492 if (x_fraction > 0.0 && x_fraction < 0.25) {
2493 cursor = _editor->cursors()->left_side_trim;
2496 cursor = _editor->cursors()->right_side_trim;
2500 Drag::start_grab (event, cursor);
2502 region = &cnote->region_view();
2505 temp = region->snap_to_pixel (cnote->x0 (), true);
2506 _snap_delta = temp - cnote->x0 ();
2510 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2515 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2516 if (ms.size() > 1) {
2517 /* has to be relative, may make no sense otherwise */
2521 if (!(_was_selected = cnote->selected())) {
2523 /* tertiary-click means extend selection - we'll do that on button release,
2524 so don't add it here, because otherwise we make it hard to figure
2525 out the "extend-to" range.
2528 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2531 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2534 region->note_selected (cnote, true);
2536 _editor->get_selection().clear_points();
2537 region->unique_select (cnote);
2544 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2546 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2548 _editor->begin_reversible_command (_("resize notes"));
2550 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2551 MidiRegionSelection::iterator next;
2554 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2556 mrv->begin_resizing (at_front);
2562 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2563 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2565 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2569 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2571 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2572 if (_editor->snap_mode () != SnapOff) {
2576 if (_editor->snap_mode () == SnapOff) {
2578 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2579 if (apply_snap_delta) {
2585 if (apply_snap_delta) {
2589 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2595 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2597 if (!movement_occurred) {
2598 /* no motion - select note */
2599 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2600 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2601 _editor->current_mouse_mode() == Editing::MouseDraw) {
2603 bool changed = false;
2605 if (_was_selected) {
2606 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2608 region->note_deselected (cnote);
2611 _editor->get_selection().clear_points();
2612 region->unique_select (cnote);
2616 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2617 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2619 if (!extend && !add && region->selection_size() > 1) {
2620 _editor->get_selection().clear_points();
2621 region->unique_select (cnote);
2623 } else if (extend) {
2624 region->note_selected (cnote, true, true);
2627 /* it was added during button press */
2633 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2634 _editor->commit_reversible_selection_op();
2641 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2642 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2643 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2645 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2648 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2650 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2651 if (_editor->snap_mode () != SnapOff) {
2655 if (_editor->snap_mode () == SnapOff) {
2657 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2658 if (apply_snap_delta) {
2664 if (apply_snap_delta) {
2668 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2672 _editor->commit_reversible_command ();
2676 NoteResizeDrag::aborted (bool)
2678 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2679 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2680 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2682 mrv->abort_resizing ();
2687 AVDraggingView::AVDraggingView (RegionView* v)
2690 initial_position = v->region()->position ();
2693 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2696 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2699 TrackViewList empty;
2701 _editor->get_regions_after(rs, (samplepos_t) 0, empty);
2702 std::list<RegionView*> views = rs.by_layer();
2705 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2706 RegionView* rv = (*i);
2707 if (!rv->region()->video_locked()) {
2710 if (rv->region()->locked()) {
2713 _views.push_back (AVDraggingView (rv));
2718 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2720 Drag::start_grab (event);
2721 if (_editor->session() == 0) {
2725 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2731 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2735 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2736 _max_backwards_drag = (
2737 ARDOUR_UI::instance()->video_timeline->get_duration()
2738 + ARDOUR_UI::instance()->video_timeline->get_offset()
2739 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2742 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2743 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2744 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv (i->initial_position);
2747 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2750 Timecode::Time timecode;
2751 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2752 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);
2753 show_verbose_cursor_text (buf);
2757 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2759 if (_editor->session() == 0) {
2762 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2766 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2770 samplecnt_t dt = adjusted_current_sample (event) - raw_grab_sample() + _pointer_sample_offset;
2771 dt = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2773 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2774 dt = - _max_backwards_drag;
2777 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2778 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2780 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2781 RegionView* rv = i->view;
2782 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2785 rv->region()->clear_changes ();
2786 rv->region()->suspend_property_changes();
2788 rv->region()->set_position(i->initial_position + dt);
2789 rv->region_changed(ARDOUR::Properties::position);
2792 const samplepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2793 Timecode::Time timecode;
2794 Timecode::Time timediff;
2796 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2797 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2798 snprintf (buf, sizeof (buf),
2799 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2800 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2801 , _("Video Start:"),
2802 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2804 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2806 show_verbose_cursor_text (buf);
2810 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2812 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2819 if (!movement_occurred || ! _editor->session()) {
2823 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2825 _editor->begin_reversible_command (_("Move Video"));
2827 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2828 ARDOUR_UI::instance()->video_timeline->save_undo();
2829 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2830 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2832 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2833 i->view->drag_end();
2834 i->view->region()->resume_property_changes ();
2836 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2839 _editor->session()->maybe_update_session_range(
2840 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::sampleoffset_t) 0),
2841 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::sampleoffset_t) 0)
2845 _editor->commit_reversible_command ();
2849 VideoTimeLineDrag::aborted (bool)
2851 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2854 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2855 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2857 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2858 i->view->region()->resume_property_changes ();
2859 i->view->region()->set_position(i->initial_position);
2863 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2864 : RegionDrag (e, i, p, v)
2865 , _operation (StartTrim)
2866 , _preserve_fade_anchor (preserve_fade_anchor)
2867 , _jump_position_when_done (false)
2869 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2873 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2875 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2876 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2878 samplepos_t const region_start = _primary->region()->position();
2879 samplepos_t const region_end = _primary->region()->last_sample();
2880 samplecnt_t const region_length = _primary->region()->length();
2882 samplepos_t const pf = adjusted_current_sample (event);
2883 setup_snap_delta (MusicSample(region_start, 0));
2885 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2886 /* Move the contents of the region around without changing the region bounds */
2887 _operation = ContentsTrim;
2888 Drag::start_grab (event, _editor->cursors()->trimmer);
2890 /* These will get overridden for a point trim.*/
2891 if (pf < (region_start + region_length/2)) {
2892 /* closer to front */
2893 _operation = StartTrim;
2894 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2895 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2897 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2901 _operation = EndTrim;
2902 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2903 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2905 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2909 /* jump trim disabled for now
2910 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2911 _jump_position_when_done = true;
2915 switch (_operation) {
2917 show_verbose_cursor_time (region_start);
2920 show_verbose_cursor_duration (region_start, region_end);
2923 show_verbose_cursor_time (pf);
2927 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2928 i->view->region()->suspend_property_changes ();
2933 TrimDrag::motion (GdkEvent* event, bool first_move)
2935 RegionView* rv = _primary;
2937 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2938 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
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());
3328 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3330 if (!movement_occurred) {
3331 if (was_double_click()) {
3332 _editor->edit_meter_marker (*_marker);
3337 /* reinstate old snap setting */
3338 _editor->set_snap_to (_old_snap_type);
3339 _editor->set_snap_mode (_old_snap_mode);
3341 TempoMap& map (_editor->session()->tempo_map());
3343 XMLNode &after = map.get_state();
3344 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3345 _editor->commit_reversible_command ();
3347 // delete the dummy marker we used for visual representation while moving.
3348 // a new visual marker will show up automatically.
3353 MeterMarkerDrag::aborted (bool moved)
3355 _marker->set_position (_marker->meter().sample ());
3357 /* reinstate old snap setting */
3358 _editor->set_snap_to (_old_snap_type);
3359 _editor->set_snap_mode (_old_snap_mode);
3361 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3362 // delete the dummy marker we used for visual representation while moving.
3363 // a new visual marker will show up automatically.
3368 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3371 , _grab_bpm (120.0, 4.0)
3375 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3377 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3378 _real_section = &_marker->tempo();
3379 _movable = !_real_section->initial();
3380 _grab_bpm = Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), _real_section->end_note_types_per_minute());
3381 _grab_qn = _real_section->pulse() * 4.0;
3386 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3388 Drag::start_grab (event, cursor);
3389 if (!_real_section->active()) {
3390 show_verbose_cursor_text (_("inactive"));
3392 show_verbose_cursor_time (adjusted_current_sample (event));
3397 TempoMarkerDrag::setup_pointer_sample_offset ()
3399 _pointer_sample_offset = raw_grab_sample() - _real_section->sample();
3403 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3405 if (!_real_section->active()) {
3408 TempoMap& map (_editor->session()->tempo_map());
3412 // mvc drag - create a dummy marker to catch events, hide it.
3415 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3417 TempoSection section (_marker->tempo());
3419 _marker = new TempoMarker (
3421 *_editor->tempo_group,
3422 UIConfiguration::instance().color ("tempo marker"),
3424 *new TempoSection (_marker->tempo())
3427 /* use the new marker for the grab */
3428 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3431 /* get current state */
3432 _before_state = &map.get_state();
3435 _editor->begin_reversible_command (_("move tempo mark"));
3438 const Tempo tempo (_marker->tempo());
3439 const samplepos_t sample = adjusted_current_sample (event) + 1;
3441 _editor->begin_reversible_command (_("copy tempo mark"));
3443 if (_real_section->position_lock_style() == MusicTime) {
3444 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3445 _real_section = map.add_tempo (tempo, map.exact_qn_at_sample (sample, divisions), 0, MusicTime);
3447 _real_section = map.add_tempo (tempo, 0.0, sample, AudioTime);
3450 if (!_real_section) {
3457 if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) {
3458 double new_bpm = max (1.5, _grab_bpm.end_note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3460 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), new_bpm));
3461 strs << "end:" << fixed << setprecision(3) << new_bpm;
3462 show_verbose_cursor_text (strs.str());
3464 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3465 /* use vertical movement to alter tempo .. should be log */
3466 double new_bpm = max (1.5, _grab_bpm.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3468 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type(), _real_section->end_note_types_per_minute()));
3469 strs << "start:" << fixed << setprecision(3) << new_bpm;
3470 show_verbose_cursor_text (strs.str());
3472 } else if (_movable && !_real_section->locked_to_meter()) {
3475 if (_editor->snap_musical()) {
3476 /* we can't snap to a grid that we are about to move.
3477 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3479 pf = adjusted_current_sample (event, false);
3481 pf = adjusted_current_sample (event);
3484 /* snap to beat is 1, snap to bar is -1 (sorry) */
3485 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3487 map.gui_set_tempo_position (_real_section, pf, sub_num);
3489 show_verbose_cursor_time (_real_section->sample());
3491 _marker->set_position (adjusted_current_sample (event, false));
3495 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3497 if (!_real_section->active()) {
3500 if (!movement_occurred) {
3501 if (was_double_click()) {
3502 _editor->edit_tempo_marker (*_marker);
3507 TempoMap& map (_editor->session()->tempo_map());
3509 XMLNode &after = map.get_state();
3510 _editor->session()->add_command (new MementoCommand<TempoMap>(map, _before_state, &after));
3511 _editor->commit_reversible_command ();
3513 // delete the dummy marker we used for visual representation while moving.
3514 // a new visual marker will show up automatically.
3519 TempoMarkerDrag::aborted (bool moved)
3521 _marker->set_position (_marker->tempo().sample());
3523 TempoMap& map (_editor->session()->tempo_map());
3524 map.set_state (*_before_state, Stateful::current_state_version);
3525 // delete the dummy (hidden) marker we used for events while moving.
3530 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3535 , _drag_valid (true)
3537 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3542 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3544 Drag::start_grab (event, cursor);
3545 TempoMap& map (_editor->session()->tempo_map());
3546 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3548 if (adjusted_current_sample (event, false) <= _tempo->sample()) {
3549 _drag_valid = false;
3553 _editor->tempo_curve_selected (_tempo, true);
3556 if (_tempo->clamped()) {
3557 TempoSection* prev = map.previous_tempo_section (_tempo);
3559 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3563 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3564 show_verbose_cursor_text (sstr.str());
3568 BBTRulerDrag::setup_pointer_sample_offset ()
3570 TempoMap& map (_editor->session()->tempo_map());
3571 /* get current state */
3572 _before_state = &map.get_state();
3574 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3575 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3578 if (divisions > 0) {
3579 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3581 /* while it makes some sense for the user to determine the division to 'grab',
3582 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3583 and the result over steep tempo curves. Use sixteenths.
3585 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3588 _grab_qn = map.quarter_note_at_beat (beat);
3590 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3595 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3602 _editor->begin_reversible_command (_("stretch tempo"));
3605 TempoMap& map (_editor->session()->tempo_map());
3608 if (_editor->snap_musical()) {
3609 pf = adjusted_current_sample (event, false);
3611 pf = adjusted_current_sample (event);
3614 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3615 /* adjust previous tempo to match pointer sample */
3616 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.sample_at_quarter_note (_grab_qn), pf, _grab_qn, map.quarter_note_at_sample (pf));
3620 if (_tempo->clamped()) {
3621 TempoSection* prev = map.previous_tempo_section (_tempo);
3623 _editor->tempo_curve_selected (prev, true);
3624 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3627 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3628 show_verbose_cursor_text (sstr.str());
3632 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3634 if (!movement_occurred) {
3638 TempoMap& map (_editor->session()->tempo_map());
3640 _editor->tempo_curve_selected (_tempo, false);
3641 if (_tempo->clamped()) {
3642 TempoSection* prev_tempo = map.previous_tempo_section (_tempo);
3644 _editor->tempo_curve_selected (prev_tempo, false);
3648 if (!movement_occurred || !_drag_valid) {
3652 XMLNode &after = map.get_state();
3653 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3654 _editor->commit_reversible_command ();
3659 BBTRulerDrag::aborted (bool moved)
3662 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3666 TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i)
3672 , _drag_valid (true)
3675 DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n");
3680 TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3682 Drag::start_grab (event, cursor);
3683 TempoMap& map (_editor->session()->tempo_map());
3684 /* get current state */
3685 _before_state = &map.get_state();
3686 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3688 if (_tempo->locked_to_meter()) {
3689 _drag_valid = false;
3693 _next_tempo = map.next_tempo_section (_tempo);
3695 if (!map.next_tempo_section (_next_tempo)) {
3696 _drag_valid = false;
3697 finished (event, false);
3701 _editor->tempo_curve_selected (_tempo, true);
3702 _editor->tempo_curve_selected (_next_tempo, true);
3705 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3706 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3707 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3708 show_verbose_cursor_text (sstr.str());
3710 _drag_valid = false;
3713 _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type());
3717 TempoTwistDrag::setup_pointer_sample_offset ()
3719 TempoMap& map (_editor->session()->tempo_map());
3720 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3721 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3724 if (divisions > 0) {
3725 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3727 /* while it makes some sense for the user to determine the division to 'grab',
3728 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3729 and the result over steep tempo curves. Use sixteenths.
3731 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3734 _grab_qn = map.quarter_note_at_beat (beat);
3736 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3741 TempoTwistDrag::motion (GdkEvent* event, bool first_move)
3744 if (!_next_tempo || !_drag_valid) {
3748 TempoMap& map (_editor->session()->tempo_map());
3751 _editor->begin_reversible_command (_("twist tempo"));
3756 if (_editor->snap_musical()) {
3757 pf = adjusted_current_sample (event, false);
3759 pf = adjusted_current_sample (event);
3762 /* adjust this and the next tempi to match pointer sample */
3763 double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3764 _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.sample_at_quarter_note (_grab_qn), pf);
3767 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3768 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3769 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3770 show_verbose_cursor_text (sstr.str());
3774 TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred)
3776 if (!movement_occurred || !_drag_valid) {
3780 _editor->tempo_curve_selected (_tempo, false);
3781 _editor->tempo_curve_selected (_next_tempo, false);
3783 TempoMap& map (_editor->session()->tempo_map());
3784 XMLNode &after = map.get_state();
3785 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3786 _editor->commit_reversible_command ();
3790 TempoTwistDrag::aborted (bool moved)
3793 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3797 TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i)
3802 , _drag_valid (true)
3804 DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n");
3805 TempoMarker* marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3806 _tempo = &marker->tempo();
3807 _grab_qn = _tempo->pulse() * 4.0;
3811 TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3813 Drag::start_grab (event, cursor);
3814 TempoMap& tmap (_editor->session()->tempo_map());
3816 /* get current state */
3817 _before_state = &tmap.get_state();
3819 if (_tempo->locked_to_meter()) {
3820 _drag_valid = false;
3826 TempoSection* prev = 0;
3827 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3828 _editor->tempo_curve_selected (tmap.previous_tempo_section (_tempo), true);
3829 sstr << "end: " << fixed << setprecision(3) << tmap.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3832 if (_tempo->clamped()) {
3833 _editor->tempo_curve_selected (_tempo, true);
3834 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3837 show_verbose_cursor_text (sstr.str());
3841 TempoEndDrag::setup_pointer_sample_offset ()
3843 TempoMap& map (_editor->session()->tempo_map());
3845 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3850 TempoEndDrag::motion (GdkEvent* event, bool first_move)
3856 TempoMap& map (_editor->session()->tempo_map());
3859 _editor->begin_reversible_command (_("stretch end tempo"));
3862 samplepos_t const pf = adjusted_current_sample (event, false);
3863 map.gui_stretch_tempo_end (&map.tempo_section_at_sample (_tempo->sample() - 1), map.sample_at_quarter_note (_grab_qn), pf);
3866 sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3868 if (_tempo->clamped()) {
3869 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3872 show_verbose_cursor_text (sstr.str());
3876 TempoEndDrag::finished (GdkEvent* event, bool movement_occurred)
3878 if (!movement_occurred || !_drag_valid) {
3882 TempoMap& tmap (_editor->session()->tempo_map());
3884 XMLNode &after = tmap.get_state();
3885 _editor->session()->add_command(new MementoCommand<TempoMap>(tmap, _before_state, &after));
3886 _editor->commit_reversible_command ();
3888 TempoSection* prev = 0;
3889 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3890 _editor->tempo_curve_selected (prev, false);
3893 if (_tempo->clamped()) {
3894 _editor->tempo_curve_selected (_tempo, false);
3900 TempoEndDrag::aborted (bool moved)
3903 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3907 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3908 : Drag (e, &c.track_canvas_item(), false)
3913 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3916 /** Do all the things we do when dragging the playhead to make it look as though
3917 * we have located, without actually doing the locate (because that would cause
3918 * the diskstream buffers to be refilled, which is too slow).
3921 CursorDrag::fake_locate (samplepos_t t)
3923 if (_editor->session () == 0) {
3927 _editor->playhead_cursor->set_position (t);
3929 Session* s = _editor->session ();
3930 if (s->timecode_transmission_suspended ()) {
3931 samplepos_t const f = _editor->playhead_cursor->current_sample ();
3932 /* This is asynchronous so it will be sent "now"
3934 s->send_mmc_locate (f);
3935 /* These are synchronous and will be sent during the next
3938 s->queue_full_time_code ();
3939 s->queue_song_position_pointer ();
3942 show_verbose_cursor_time (t);
3943 _editor->UpdateAllTransportClocks (t);
3947 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3949 Drag::start_grab (event, c);
3950 setup_snap_delta (MusicSample (_editor->playhead_cursor->current_sample(), 0));
3952 _grab_zoom = _editor->samples_per_pixel;
3954 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3956 _editor->snap_to_with_modifier (where, event);
3957 _editor->_dragging_playhead = true;
3958 _editor->_control_scroll_target = where.sample;
3960 Session* s = _editor->session ();
3962 /* grab the track canvas item as well */
3964 _cursor.track_canvas_item().grab();
3967 if (_was_rolling && _stop) {
3971 if (s->is_auditioning()) {
3972 s->cancel_audition ();
3976 if (AudioEngine::instance()->running()) {
3978 /* do this only if we're the engine is connected
3979 * because otherwise this request will never be
3980 * serviced and we'll busy wait forever. likewise,
3981 * notice if we are disconnected while waiting for the
3982 * request to be serviced.
3985 s->request_suspend_timecode_transmission ();
3986 while (AudioEngine::instance()->running() && !s->timecode_transmission_suspended ()) {
3987 /* twiddle our thumbs */
3992 fake_locate (where.sample - snap_delta (event->button.state));
3998 CursorDrag::motion (GdkEvent* event, bool)
4000 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4002 _editor->snap_to_with_modifier (where, event);
4004 if (where.sample != last_pointer_sample()) {
4005 fake_locate (where.sample - snap_delta (event->button.state));
4008 //maybe do zooming, too, if the option is enabled
4009 if (UIConfiguration::instance ().get_use_time_rulers_to_zoom_with_vertical_drag () ) {
4011 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
4012 //we use screen coordinates for this, not canvas-based grab_x
4013 double mx = event->button.x;
4014 double dx = fabs(mx - _last_mx);
4015 double my = event->button.y;
4016 double dy = fabs(my - _last_my);
4019 //do zooming in windowed "steps" so it feels more reversible ?
4020 const int stepsize = 2; //stepsize ==1 means "trigger on every pixel of movement"
4021 int y_delta = grab_y() - current_pointer_y();
4022 y_delta = y_delta / stepsize;
4024 //if all requirements are met, do the actual zoom
4025 const double scale = 1.2;
4026 if ( (dy>dx) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
4027 if ( _last_y_delta > y_delta ) {
4028 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
4030 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
4032 _last_y_delta = y_delta;
4043 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
4045 _editor->_dragging_playhead = false;
4047 _cursor.track_canvas_item().ungrab();
4049 if (!movement_occurred && _stop) {
4053 motion (event, false);
4055 Session* s = _editor->session ();
4057 s->request_locate (_editor->playhead_cursor->current_sample (), _was_rolling);
4058 _editor->_pending_locate_request = true;
4059 s->request_resume_timecode_transmission ();
4064 CursorDrag::aborted (bool)
4066 _cursor.track_canvas_item().ungrab();
4068 if (_editor->_dragging_playhead) {
4069 _editor->session()->request_resume_timecode_transmission ();
4070 _editor->_dragging_playhead = false;
4073 _editor->playhead_cursor->set_position (adjusted_sample (grab_sample (), 0, false).sample);
4076 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4077 : RegionDrag (e, i, p, v)
4079 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
4083 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4085 Drag::start_grab (event, cursor);
4087 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4088 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4089 setup_snap_delta (MusicSample (r->position(), 0));
4091 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
4095 FadeInDrag::setup_pointer_sample_offset ()
4097 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4098 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4099 _pointer_sample_offset = raw_grab_sample() - ((samplecnt_t) r->fade_in()->back()->when + r->position());
4103 FadeInDrag::motion (GdkEvent* event, bool)
4105 samplecnt_t fade_length;
4107 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4108 _editor->snap_to_with_modifier (pos, event);
4110 pos.sample -= snap_delta (event->button.state);
4112 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4114 if (pos.sample < (region->position() + 64)) {
4115 fade_length = 64; // this should be a minimum defined somewhere
4116 } else if (pos.sample > region->position() + region->length() - region->fade_out()->back()->when) {
4117 fade_length = region->length() - region->fade_out()->back()->when - 1;
4119 fade_length = pos.sample - region->position();
4122 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4124 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4130 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
4133 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
4137 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
4139 if (!movement_occurred) {
4143 samplecnt_t fade_length;
4144 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4146 _editor->snap_to_with_modifier (pos, event);
4147 pos.sample -= snap_delta (event->button.state);
4149 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4151 if (pos.sample < (region->position() + 64)) {
4152 fade_length = 64; // this should be a minimum defined somewhere
4153 } else if (pos.sample >= region->position() + region->length() - region->fade_out()->back()->when) {
4154 fade_length = region->length() - region->fade_out()->back()->when - 1;
4156 fade_length = pos.sample - region->position();
4159 bool in_command = false;
4161 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4163 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4169 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
4170 XMLNode &before = alist->get_state();
4172 tmp->audio_region()->set_fade_in_length (fade_length);
4173 tmp->audio_region()->set_fade_in_active (true);
4176 _editor->begin_reversible_command (_("change fade in length"));
4179 XMLNode &after = alist->get_state();
4180 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4184 _editor->commit_reversible_command ();
4189 FadeInDrag::aborted (bool)
4191 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4192 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4198 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
4202 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4203 : RegionDrag (e, i, p, v)
4205 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
4209 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4211 Drag::start_grab (event, cursor);
4213 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4214 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4215 setup_snap_delta (MusicSample (r->last_sample(), 0));
4217 show_verbose_cursor_duration (r->last_sample() - r->fade_out()->back()->when, r->last_sample());
4221 FadeOutDrag::setup_pointer_sample_offset ()
4223 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4224 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4225 _pointer_sample_offset = raw_grab_sample() - (r->length() - (samplecnt_t) r->fade_out()->back()->when + r->position());
4229 FadeOutDrag::motion (GdkEvent* event, bool)
4231 samplecnt_t fade_length;
4232 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4234 _editor->snap_to_with_modifier (pos, event);
4235 pos.sample -= snap_delta (event->button.state);
4237 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4239 if (pos.sample > (region->last_sample() - 64)) {
4240 fade_length = 64; // this should really be a minimum fade defined somewhere
4241 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4242 fade_length = region->length() - region->fade_in()->back()->when - 1;
4244 fade_length = region->last_sample() - pos.sample;
4247 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4249 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4255 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
4258 show_verbose_cursor_duration (region->last_sample() - fade_length, region->last_sample());
4262 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
4264 if (!movement_occurred) {
4268 samplecnt_t fade_length;
4269 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4271 _editor->snap_to_with_modifier (pos, event);
4272 pos.sample -= snap_delta (event->button.state);
4274 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4276 if (pos.sample > (region->last_sample() - 64)) {
4277 fade_length = 64; // this should really be a minimum fade defined somewhere
4278 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4279 fade_length = region->length() - region->fade_in()->back()->when - 1;
4281 fade_length = region->last_sample() - pos.sample;
4284 bool in_command = false;
4286 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4288 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4294 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
4295 XMLNode &before = alist->get_state();
4297 tmp->audio_region()->set_fade_out_length (fade_length);
4298 tmp->audio_region()->set_fade_out_active (true);
4301 _editor->begin_reversible_command (_("change fade out length"));
4304 XMLNode &after = alist->get_state();
4305 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4309 _editor->commit_reversible_command ();
4314 FadeOutDrag::aborted (bool)
4316 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4317 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4323 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
4327 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
4329 , _selection_changed (false)
4331 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
4332 Gtk::Window* toplevel = _editor->current_toplevel();
4333 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
4337 _points.push_back (ArdourCanvas::Duple (0, 0));
4339 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4342 MarkerDrag::~MarkerDrag ()
4344 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4349 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4351 location = new Location (*l);
4352 markers.push_back (m);
4357 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4359 Drag::start_grab (event, cursor);
4363 Location *location = _editor->find_location_from_marker (_marker, is_start);
4364 _editor->_dragging_edit_point = true;
4366 update_item (location);
4368 // _drag_line->show();
4369 // _line->raise_to_top();
4372 show_verbose_cursor_time (location->start());
4374 show_verbose_cursor_time (location->end());
4376 setup_snap_delta (MusicSample (is_start ? location->start() : location->end(), 0));
4378 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4381 case Selection::Toggle:
4382 /* we toggle on the button release */
4384 case Selection::Set:
4385 if (!_editor->selection->selected (_marker)) {
4386 _editor->selection->set (_marker);
4387 _selection_changed = true;
4390 case Selection::Extend:
4392 Locations::LocationList ll;
4393 list<ArdourMarker*> to_add;
4395 _editor->selection->markers.range (s, e);
4396 s = min (_marker->position(), s);
4397 e = max (_marker->position(), e);
4400 if (e < max_samplepos) {
4403 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4404 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4405 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4408 to_add.push_back (lm->start);
4411 to_add.push_back (lm->end);
4415 if (!to_add.empty()) {
4416 _editor->selection->add (to_add);
4417 _selection_changed = true;
4421 case Selection::Add:
4422 _editor->selection->add (_marker);
4423 _selection_changed = true;
4428 /* Set up copies for us to manipulate during the drag
4431 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4433 Location* l = _editor->find_location_from_marker (*i, is_start);
4440 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4442 /* range: check that the other end of the range isn't
4445 CopiedLocationInfo::iterator x;
4446 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4447 if (*(*x).location == *l) {
4451 if (x == _copied_locations.end()) {
4452 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4454 (*x).markers.push_back (*i);
4455 (*x).move_both = true;
4463 MarkerDrag::setup_pointer_sample_offset ()
4466 Location *location = _editor->find_location_from_marker (_marker, is_start);
4467 _pointer_sample_offset = raw_grab_sample() - (is_start ? location->start() : location->end());
4471 MarkerDrag::motion (GdkEvent* event, bool)
4473 samplecnt_t f_delta = 0;
4475 bool move_both = false;
4476 Location *real_location;
4477 Location *copy_location = 0;
4478 samplecnt_t const sd = snap_delta (event->button.state);
4480 samplecnt_t const newframe = adjusted_sample (_drags->current_pointer_sample () + sd, event, true).sample - sd;
4481 samplepos_t next = newframe;
4483 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4487 CopiedLocationInfo::iterator x;
4489 /* find the marker we're dragging, and compute the delta */
4491 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4493 copy_location = (*x).location;
4495 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4497 /* this marker is represented by this
4498 * CopiedLocationMarkerInfo
4501 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4506 if (real_location->is_mark()) {
4507 f_delta = newframe - copy_location->start();
4511 switch (_marker->type()) {
4512 case ArdourMarker::SessionStart:
4513 case ArdourMarker::RangeStart:
4514 case ArdourMarker::LoopStart:
4515 case ArdourMarker::PunchIn:
4516 f_delta = newframe - copy_location->start();
4519 case ArdourMarker::SessionEnd:
4520 case ArdourMarker::RangeEnd:
4521 case ArdourMarker::LoopEnd:
4522 case ArdourMarker::PunchOut:
4523 f_delta = newframe - copy_location->end();
4526 /* what kind of marker is this ? */
4535 if (x == _copied_locations.end()) {
4536 /* hmm, impossible - we didn't find the dragged marker */
4540 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4542 /* now move them all */
4544 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4546 copy_location = x->location;
4548 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4552 if (real_location->locked()) {
4556 if (copy_location->is_mark()) {
4559 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4563 samplepos_t new_start = copy_location->start() + f_delta;
4564 samplepos_t new_end = copy_location->end() + f_delta;
4566 if (is_start) { // start-of-range marker
4568 if (move_both || (*x).move_both) {
4569 copy_location->set_start (new_start, false, true, divisions);
4570 copy_location->set_end (new_end, false, true, divisions);
4571 } else if (new_start < copy_location->end()) {
4572 copy_location->set_start (new_start, false, true, divisions);
4573 } else if (newframe > 0) {
4574 //_editor->snap_to (next, RoundUpAlways, true);
4575 copy_location->set_end (next, false, true, divisions);
4576 copy_location->set_start (newframe, false, true, divisions);
4579 } else { // end marker
4581 if (move_both || (*x).move_both) {
4582 copy_location->set_end (new_end, divisions);
4583 copy_location->set_start (new_start, false, true, divisions);
4584 } else if (new_end > copy_location->start()) {
4585 copy_location->set_end (new_end, false, true, divisions);
4586 } else if (newframe > 0) {
4587 //_editor->snap_to (next, RoundDownAlways, true);
4588 copy_location->set_start (next, false, true, divisions);
4589 copy_location->set_end (newframe, false, true, divisions);
4594 update_item (copy_location);
4596 /* now lookup the actual GUI items used to display this
4597 * location and move them to wherever the copy of the location
4598 * is now. This means that the logic in ARDOUR::Location is
4599 * still enforced, even though we are not (yet) modifying
4600 * the real Location itself.
4603 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4606 lm->set_position (copy_location->start(), copy_location->end());
4611 assert (!_copied_locations.empty());
4613 show_verbose_cursor_time (newframe);
4617 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4619 if (!movement_occurred) {
4621 if (was_double_click()) {
4622 _editor->rename_marker (_marker);
4626 /* just a click, do nothing but finish
4627 off the selection process
4630 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4632 case Selection::Set:
4633 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4634 _editor->selection->set (_marker);
4635 _selection_changed = true;
4639 case Selection::Toggle:
4640 /* we toggle on the button release, click only */
4641 _editor->selection->toggle (_marker);
4642 _selection_changed = true;
4646 case Selection::Extend:
4647 case Selection::Add:
4651 if (_selection_changed) {
4652 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4653 _editor->commit_reversible_selection_op();
4659 _editor->_dragging_edit_point = false;
4661 XMLNode &before = _editor->session()->locations()->get_state();
4662 bool in_command = false;
4664 MarkerSelection::iterator i;
4665 CopiedLocationInfo::iterator x;
4666 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4669 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4670 x != _copied_locations.end() && i != _editor->selection->markers.end();
4673 Location * location = _editor->find_location_from_marker (*i, is_start);
4677 if (location->locked()) {
4681 _editor->begin_reversible_command ( _("move marker") );
4684 if (location->is_mark()) {
4685 location->set_start (((*x).location)->start(), false, true, divisions);
4687 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4690 if (location->is_session_range()) {
4691 _editor->session()->set_end_is_free (false);
4697 XMLNode &after = _editor->session()->locations()->get_state();
4698 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4699 _editor->commit_reversible_command ();
4704 MarkerDrag::aborted (bool movement_occurred)
4706 if (!movement_occurred) {
4710 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4712 /* move all markers to their original location */
4715 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4718 Location * location = _editor->find_location_from_marker (*m, is_start);
4721 (*m)->set_position (is_start ? location->start() : location->end());
4728 MarkerDrag::update_item (Location*)
4733 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4735 , _fixed_grab_x (0.0)
4736 , _fixed_grab_y (0.0)
4737 , _cumulative_x_drag (0.0)
4738 , _cumulative_y_drag (0.0)
4742 if (_zero_gain_fraction < 0.0) {
4743 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4746 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4748 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4754 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4756 Drag::start_grab (event, _editor->cursors()->fader);
4758 // start the grab at the center of the control point so
4759 // the point doesn't 'jump' to the mouse after the first drag
4760 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4761 _fixed_grab_y = _point->get_y();
4763 setup_snap_delta (MusicSample (_editor->pixel_to_sample (_fixed_grab_x), 0));
4765 float const fraction = 1 - (_point->get_y() / _point->line().height());
4766 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4768 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4770 if (!_point->can_slide ()) {
4771 _x_constrained = true;
4776 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4778 double dx = _drags->current_pointer_x() - last_pointer_x();
4779 double dy = current_pointer_y() - last_pointer_y();
4780 bool need_snap = true;
4782 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4788 /* coordinate in pixels relative to the start of the region (for region-based automation)
4789 or track (for track-based automation) */
4790 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4791 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4793 // calculate zero crossing point. back off by .01 to stay on the
4794 // positive side of zero
4795 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4797 if (_x_constrained) {
4800 if (_y_constrained) {
4804 _cumulative_x_drag = cx - _fixed_grab_x;
4805 _cumulative_y_drag = cy - _fixed_grab_y;
4809 cy = min ((double) _point->line().height(), cy);
4811 // make sure we hit zero when passing through
4812 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4816 MusicSample cx_mf (_editor->pixel_to_sample (cx) + snap_delta (event->button.state), 0);
4818 if (!_x_constrained && need_snap) {
4819 _editor->snap_to_with_modifier (cx_mf, event);
4822 cx_mf.sample -= snap_delta (event->button.state);
4823 cx_mf.sample = min (cx_mf.sample, _point->line().maximum_time() + _point->line().offset());
4825 float const fraction = 1.0 - (cy / _point->line().height());
4828 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4829 _editor->begin_reversible_command (_("automation event move"));
4830 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4832 pair<float, float> result;
4833 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_mf.sample), fraction, false, _pushing, _final_index);
4834 show_verbose_cursor_text (_point->line().get_verbose_cursor_relative_string (result.first, result.second));
4838 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4840 if (!movement_occurred) {
4843 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4844 _editor->reset_point_selection ();
4848 _point->line().end_drag (_pushing, _final_index);
4849 _editor->commit_reversible_command ();
4854 ControlPointDrag::aborted (bool)
4856 _point->line().reset ();
4860 ControlPointDrag::active (Editing::MouseMode m)
4862 if (m == Editing::MouseDraw) {
4863 /* always active in mouse draw */
4867 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4868 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4871 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4874 , _fixed_grab_x (0.0)
4875 , _fixed_grab_y (0.0)
4876 , _cumulative_y_drag (0)
4880 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4884 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4886 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4889 _item = &_line->grab_item ();
4891 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4892 origin, and ditto for y.
4895 double mx = event->button.x;
4896 double my = event->button.y;
4898 _line->grab_item().canvas_to_item (mx, my);
4900 samplecnt_t const sample_within_region = (samplecnt_t) floor (mx * _editor->samples_per_pixel);
4902 if (!_line->control_points_adjacent (sample_within_region, _before, _after)) {
4903 /* no adjacent points */
4907 Drag::start_grab (event, _editor->cursors()->fader);
4909 /* store grab start in item sample */
4910 double const bx = _line->nth (_before)->get_x();
4911 double const ax = _line->nth (_after)->get_x();
4912 double const click_ratio = (ax - mx) / (ax - bx);
4914 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4919 double fraction = 1.0 - (cy / _line->height());
4921 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4925 LineDrag::motion (GdkEvent* event, bool first_move)
4927 double dy = current_pointer_y() - last_pointer_y();
4929 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4933 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4935 _cumulative_y_drag = cy - _fixed_grab_y;
4938 cy = min ((double) _line->height(), cy);
4940 double const fraction = 1.0 - (cy / _line->height());
4944 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4946 _editor->begin_reversible_command (_("automation range move"));
4947 _line->start_drag_line (_before, _after, initial_fraction);
4950 /* we are ignoring x position for this drag, so we can just pass in anything */
4951 pair<float, float> result;
4953 result = _line->drag_motion (0, fraction, true, false, ignored);
4954 show_verbose_cursor_text (_line->get_verbose_cursor_relative_string (result.first, result.second));
4958 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4960 if (movement_occurred) {
4961 motion (event, false);
4962 _line->end_drag (false, 0);
4963 _editor->commit_reversible_command ();
4965 /* add a new control point on the line */
4967 AutomationTimeAxisView* atv;
4969 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4970 samplepos_t where = grab_sample ();
4973 double cy = _fixed_grab_y;
4975 _line->grab_item().item_to_canvas (cx, cy);
4977 atv->add_automation_event (event, where, cy, false);
4978 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4979 AudioRegionView* arv;
4981 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4982 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4989 LineDrag::aborted (bool)
4994 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4998 _region_view_grab_x (0.0),
4999 _cumulative_x_drag (0),
5003 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
5007 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
5009 Drag::start_grab (event);
5011 _line = reinterpret_cast<Line*> (_item);
5014 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
5016 double cx = event->button.x;
5017 double cy = event->button.y;
5019 _item->parent()->canvas_to_item (cx, cy);
5021 /* store grab start in parent sample */
5022 _region_view_grab_x = cx;
5024 _before = *(float*) _item->get_data ("position");
5026 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5028 _max_x = _editor->sample_to_pixel(_arv->get_duration());
5032 FeatureLineDrag::motion (GdkEvent*, bool)
5034 double dx = _drags->current_pointer_x() - last_pointer_x();
5036 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
5038 _cumulative_x_drag += dx;
5040 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
5049 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
5051 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
5053 float *pos = new float;
5056 _line->set_data ("position", pos);
5062 FeatureLineDrag::finished (GdkEvent*, bool)
5064 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5065 _arv->update_transient(_before, _before);
5069 FeatureLineDrag::aborted (bool)
5074 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5076 , _vertical_only (false)
5078 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
5082 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5084 Drag::start_grab (event);
5085 show_verbose_cursor_time (adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
5089 RubberbandSelectDrag::motion (GdkEvent* event, bool)
5095 samplepos_t const pf = adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
5096 MusicSample grab (grab_sample (), 0);
5098 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5099 _editor->snap_to_with_modifier (grab, event);
5101 grab.sample = raw_grab_sample ();
5104 /* base start and end on initial click position */
5106 if (pf < grab.sample) {
5111 start = grab.sample;
5114 if (current_pointer_y() < grab_y()) {
5115 y1 = current_pointer_y();
5118 y2 = current_pointer_y();
5122 if (start != end || y1 != y2) {
5124 double x1 = _editor->sample_to_pixel (start);
5125 double x2 = _editor->sample_to_pixel (end);
5126 const double min_dimension = 2.0;
5128 if (_vertical_only) {
5129 /* fixed 10 pixel width */
5133 x2 = min (x1 - min_dimension, x2);
5135 x2 = max (x1 + min_dimension, x2);
5140 y2 = min (y1 - min_dimension, y2);
5142 y2 = max (y1 + min_dimension, y2);
5145 /* translate rect into item space and set */
5147 ArdourCanvas::Rect r (x1, y1, x2, y2);
5149 /* this drag is a _trackview_only == true drag, so the y1 and
5150 * y2 (computed using current_pointer_y() and grab_y()) will be
5151 * relative to the top of the trackview group). The
5152 * rubberband rect has the same parent/scroll offset as the
5153 * the trackview group, so we can use the "r" rect directly
5154 * to set the shape of the rubberband.
5157 _editor->rubberband_rect->set (r);
5158 _editor->rubberband_rect->show();
5159 _editor->rubberband_rect->raise_to_top();
5161 show_verbose_cursor_time (pf);
5163 do_select_things (event, true);
5168 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
5172 samplepos_t grab = grab_sample ();
5173 samplepos_t lpf = last_pointer_sample ();
5175 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5176 grab = raw_grab_sample ();
5177 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
5191 if (current_pointer_y() < grab_y()) {
5192 y1 = current_pointer_y();
5195 y2 = current_pointer_y();
5199 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
5203 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
5205 if (movement_occurred) {
5207 motion (event, false);
5208 do_select_things (event, false);
5214 bool do_deselect = true;
5215 MidiTimeAxisView* mtv;
5217 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
5219 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
5220 /* nothing selected */
5221 add_midi_region (mtv, true);
5222 do_deselect = false;
5226 /* do not deselect if Primary or Tertiary (toggle-select or
5227 * extend-select are pressed.
5230 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
5231 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
5238 _editor->rubberband_rect->hide();
5242 RubberbandSelectDrag::aborted (bool)
5244 _editor->rubberband_rect->hide ();
5247 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
5248 : RegionDrag (e, i, p, v)
5250 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
5254 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5256 Drag::start_grab (event, cursor);
5258 _editor->get_selection().add (_primary);
5260 MusicSample where (_primary->region()->position(), 0);
5261 setup_snap_delta (where);
5263 show_verbose_cursor_duration (where.sample, adjusted_current_sample (event), 0);
5267 TimeFXDrag::motion (GdkEvent* event, bool)
5269 RegionView* rv = _primary;
5270 StreamView* cv = rv->get_time_axis_view().view ();
5271 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
5272 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
5273 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
5274 MusicSample pf (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
5276 _editor->snap_to_with_modifier (pf, event);
5277 pf.sample -= snap_delta (event->button.state);
5279 if (pf.sample > rv->region()->position()) {
5280 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf.sample, layers, layer);
5283 show_verbose_cursor_duration (_primary->region()->position(), pf.sample, 0);
5287 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
5289 /* this may have been a single click, no drag. We still want the dialog
5290 to show up in that case, so that the user can manually edit the
5291 parameters for the timestretch.
5294 float fraction = 1.0;
5296 if (movement_occurred) {
5298 motion (event, false);
5300 _primary->get_time_axis_view().hide_timestretch ();
5302 samplepos_t adjusted_sample_pos = adjusted_current_sample (event);
5304 if (adjusted_sample_pos < _primary->region()->position()) {
5305 /* backwards drag of the left edge - not usable */
5309 samplecnt_t newlen = adjusted_sample_pos - _primary->region()->position();
5311 fraction = (double) newlen / (double) _primary->region()->length();
5313 #ifndef USE_RUBBERBAND
5314 // Soundtouch uses fraction / 100 instead of normal (/ 1)
5315 if (_primary->region()->data_type() == DataType::AUDIO) {
5316 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
5321 if (!_editor->get_selection().regions.empty()) {
5322 /* primary will already be included in the selection, and edit
5323 group shared editing will propagate selection across
5324 equivalent regions, so just use the current region
5328 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
5329 error << _("An error occurred while executing time stretch operation") << endmsg;
5335 TimeFXDrag::aborted (bool)
5337 _primary->get_time_axis_view().hide_timestretch ();
5340 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5343 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5347 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5349 Drag::start_grab (event);
5353 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5355 _editor->scrub (adjusted_current_sample (0, false), _drags->current_pointer_x ());
5359 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5361 if (movement_occurred && _editor->session()) {
5362 /* make sure we stop */
5363 _editor->session()->request_transport_speed (0.0);
5368 ScrubDrag::aborted (bool)
5373 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5377 , _track_selection_at_start (e)
5378 , _time_selection_at_start (!_editor->get_selection().time.empty())
5380 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5382 if (_time_selection_at_start) {
5383 start_at_start = _editor->get_selection().time.start();
5384 end_at_start = _editor->get_selection().time.end_sample();
5389 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5391 if (_editor->session() == 0) {
5395 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5397 switch (_operation) {
5398 case CreateSelection:
5399 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5404 cursor = _editor->cursors()->selector;
5405 Drag::start_grab (event, cursor);
5408 case SelectionStartTrim:
5409 if (_editor->clicked_axisview) {
5410 _editor->clicked_axisview->order_selection_trims (_item, true);
5412 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5415 case SelectionEndTrim:
5416 if (_editor->clicked_axisview) {
5417 _editor->clicked_axisview->order_selection_trims (_item, false);
5419 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5423 Drag::start_grab (event, cursor);
5426 case SelectionExtend:
5427 Drag::start_grab (event, cursor);
5431 if (_operation == SelectionMove) {
5432 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5434 show_verbose_cursor_time (adjusted_current_sample (event));
5439 SelectionDrag::setup_pointer_sample_offset ()
5441 switch (_operation) {
5442 case CreateSelection:
5443 _pointer_sample_offset = 0;
5446 case SelectionStartTrim:
5448 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].start;
5451 case SelectionEndTrim:
5452 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].end;
5455 case SelectionExtend:
5461 SelectionDrag::motion (GdkEvent* event, bool first_move)
5463 samplepos_t start = 0;
5464 samplepos_t end = 0;
5465 samplecnt_t length = 0;
5466 samplecnt_t distance = 0;
5467 MusicSample start_mf (0, 0);
5468 samplepos_t const pending_position = adjusted_current_sample (event);
5470 if (_operation != CreateSelection && pending_position == last_pointer_sample()) {
5475 _track_selection_at_start = _editor->selection->tracks;
5478 switch (_operation) {
5479 case CreateSelection:
5481 MusicSample grab (grab_sample (), 0);
5483 grab.sample = adjusted_current_sample (event, false);
5484 if (grab.sample < pending_position) {
5485 _editor->snap_to (grab, RoundDownMaybe);
5487 _editor->snap_to (grab, RoundUpMaybe);
5491 if (pending_position < grab.sample) {
5492 start = pending_position;
5495 end = pending_position;
5496 start = grab.sample;
5499 /* first drag: Either add to the selection
5500 or create a new selection
5507 /* adding to the selection */
5508 _editor->set_selected_track_as_side_effect (Selection::Add);
5509 _editor->clicked_selection = _editor->selection->add (start, end);
5516 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5517 _editor->set_selected_track_as_side_effect (Selection::Set);
5520 _editor->clicked_selection = _editor->selection->set (start, end);
5524 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5525 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5526 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5528 _editor->selection->add (atest);
5532 /* select all tracks within the rectangle that we've marked out so far */
5533 TrackViewList new_selection;
5534 TrackViewList& all_tracks (_editor->track_views);
5536 ArdourCanvas::Coord const top = grab_y();
5537 ArdourCanvas::Coord const bottom = current_pointer_y();
5539 if (top >= 0 && bottom >= 0) {
5541 //first, find the tracks that are covered in the y range selection
5542 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5543 if ((*i)->covered_by_y_range (top, bottom)) {
5544 new_selection.push_back (*i);
5548 //now compare our list with the current selection, and add as necessary
5549 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5550 TrackViewList tracks_to_add;
5551 TrackViewList tracks_to_remove;
5552 vector<RouteGroup*> selected_route_groups;
5555 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5556 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5557 tracks_to_remove.push_back (*i);
5559 RouteGroup* rg = (*i)->route_group();
5560 if (rg && rg->is_active() && rg->is_select()) {
5561 selected_route_groups.push_back (rg);
5567 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5568 if (!_editor->selection->tracks.contains (*i)) {
5569 tracks_to_add.push_back (*i);
5570 RouteGroup* rg = (*i)->route_group();
5572 if (rg && rg->is_active() && rg->is_select()) {
5573 selected_route_groups.push_back (rg);
5578 _editor->selection->add (tracks_to_add);
5580 if (!tracks_to_remove.empty()) {
5582 /* check all these to-be-removed tracks against the
5583 * possibility that they are selected by being
5584 * in the same group as an approved track.
5587 for (TrackViewList::iterator i = tracks_to_remove.begin(); i != tracks_to_remove.end(); ) {
5588 RouteGroup* rg = (*i)->route_group();
5590 if (rg && find (selected_route_groups.begin(), selected_route_groups.end(), rg) != selected_route_groups.end()) {
5591 i = tracks_to_remove.erase (i);
5597 /* remove whatever is left */
5599 _editor->selection->remove (tracks_to_remove);
5605 case SelectionStartTrim:
5607 end = _editor->selection->time[_editor->clicked_selection].end;
5609 if (pending_position > end) {
5612 start = pending_position;
5616 case SelectionEndTrim:
5618 start = _editor->selection->time[_editor->clicked_selection].start;
5620 if (pending_position < start) {
5623 end = pending_position;
5630 start = _editor->selection->time[_editor->clicked_selection].start;
5631 end = _editor->selection->time[_editor->clicked_selection].end;
5633 length = end - start;
5634 distance = pending_position - start;
5635 start = pending_position;
5637 start_mf.sample = start;
5638 _editor->snap_to (start_mf);
5640 end = start_mf.sample + length;
5644 case SelectionExtend:
5649 switch (_operation) {
5651 if (_time_selection_at_start) {
5652 _editor->selection->move_time (distance);
5656 _editor->selection->replace (_editor->clicked_selection, start, end);
5660 if (_operation == SelectionMove) {
5661 show_verbose_cursor_time(start);
5663 show_verbose_cursor_time(pending_position);
5668 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5670 Session* s = _editor->session();
5672 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5673 if (movement_occurred) {
5674 motion (event, false);
5675 /* XXX this is not object-oriented programming at all. ick */
5676 if (_editor->selection->time.consolidate()) {
5677 _editor->selection->TimeChanged ();
5680 /* XXX what if its a music time selection? */
5682 if (s->get_play_range() && s->transport_rolling()) {
5683 s->request_play_range (&_editor->selection->time, true);
5684 } else if (!s->config.get_external_sync()) {
5685 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5686 s->request_locate (_editor->get_selection().time.start());
5690 if (_editor->get_selection().time.length() != 0) {
5691 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_sample());
5693 s->clear_range_selection ();
5698 /* just a click, no pointer movement.
5701 if (was_double_click()) {
5702 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5703 _editor->temporal_zoom_selection (Both);
5708 if (_operation == SelectionExtend) {
5709 if (_time_selection_at_start) {
5710 samplepos_t pos = adjusted_current_sample (event, false);
5711 samplepos_t start = min (pos, start_at_start);
5712 samplepos_t end = max (pos, end_at_start);
5713 _editor->selection->set (start, end);
5716 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5717 if (_editor->clicked_selection) {
5718 _editor->selection->remove (_editor->clicked_selection);
5721 if (!_editor->clicked_selection) {
5722 _editor->selection->clear_time();
5727 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5728 _editor->selection->set (_editor->clicked_axisview);
5731 if (s && s->get_play_range () && s->transport_rolling()) {
5732 s->request_stop (false, false);
5737 _editor->stop_canvas_autoscroll ();
5738 _editor->clicked_selection = 0;
5739 _editor->commit_reversible_selection_op ();
5743 SelectionDrag::aborted (bool)
5748 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5749 : Drag (e, i, false),
5753 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5755 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5756 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5757 physical_screen_height (_editor->current_toplevel()->get_window())));
5758 _drag_rect->hide ();
5760 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5761 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5764 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5766 /* normal canvas items will be cleaned up when their parent group is deleted. But
5767 this item is created as the child of a long-lived parent group, and so we
5768 need to explicitly delete it.
5774 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5776 if (_editor->session() == 0) {
5780 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5782 if (!_editor->temp_location) {
5783 _editor->temp_location = new Location (*_editor->session());
5786 switch (_operation) {
5787 case CreateSkipMarker:
5788 case CreateRangeMarker:
5789 case CreateTransportMarker:
5790 case CreateCDMarker:
5792 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5797 cursor = _editor->cursors()->selector;
5801 Drag::start_grab (event, cursor);
5803 show_verbose_cursor_time (adjusted_current_sample (event));
5807 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5809 samplepos_t start = 0;
5810 samplepos_t end = 0;
5811 ArdourCanvas::Rectangle *crect;
5813 switch (_operation) {
5814 case CreateSkipMarker:
5815 crect = _editor->range_bar_drag_rect;
5817 case CreateRangeMarker:
5818 crect = _editor->range_bar_drag_rect;
5820 case CreateTransportMarker:
5821 crect = _editor->transport_bar_drag_rect;
5823 case CreateCDMarker:
5824 crect = _editor->cd_marker_bar_drag_rect;
5827 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5832 samplepos_t const pf = adjusted_current_sample (event);
5834 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5835 MusicSample grab (grab_sample (), 0);
5836 _editor->snap_to (grab);
5838 if (pf < grab_sample()) {
5843 start = grab.sample;
5846 /* first drag: Either add to the selection
5847 or create a new selection.
5852 _editor->temp_location->set (start, end);
5856 update_item (_editor->temp_location);
5858 //_drag_rect->raise_to_top();
5864 _editor->temp_location->set (start, end);
5866 double x1 = _editor->sample_to_pixel (start);
5867 double x2 = _editor->sample_to_pixel (end);
5871 update_item (_editor->temp_location);
5874 show_verbose_cursor_time (pf);
5879 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5881 Location * newloc = 0;
5885 if (movement_occurred) {
5886 motion (event, false);
5889 switch (_operation) {
5890 case CreateSkipMarker:
5891 case CreateRangeMarker:
5892 case CreateCDMarker:
5894 XMLNode &before = _editor->session()->locations()->get_state();
5895 if (_operation == CreateSkipMarker) {
5896 _editor->begin_reversible_command (_("new skip marker"));
5897 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5898 flags = Location::IsRangeMarker | Location::IsSkip;
5899 _editor->range_bar_drag_rect->hide();
5900 } else if (_operation == CreateCDMarker) {
5901 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5902 _editor->begin_reversible_command (_("new CD marker"));
5903 flags = Location::IsRangeMarker | Location::IsCDMarker;
5904 _editor->cd_marker_bar_drag_rect->hide();
5906 _editor->begin_reversible_command (_("new skip marker"));
5907 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5908 flags = Location::IsRangeMarker;
5909 _editor->range_bar_drag_rect->hide();
5911 newloc = new Location (
5912 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5913 , _editor->get_grid_music_divisions (event->button.state));
5915 _editor->session()->locations()->add (newloc, true);
5916 XMLNode &after = _editor->session()->locations()->get_state();
5917 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5918 _editor->commit_reversible_command ();
5922 case CreateTransportMarker:
5923 // popup menu to pick loop or punch
5924 _editor->new_transport_marker_context_menu (&event->button, _item);
5930 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5932 if (_operation == CreateTransportMarker) {
5934 /* didn't drag, so just locate */
5936 _editor->session()->request_locate (grab_sample(), _editor->session()->transport_rolling());
5938 } else if (_operation == CreateCDMarker) {
5940 /* didn't drag, but mark is already created so do
5943 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5948 _editor->session()->locations()->marks_either_side (grab_sample(), start, end);
5950 if (end == max_samplepos) {
5951 end = _editor->session()->current_end_sample ();
5954 if (start == max_samplepos) {
5955 start = _editor->session()->current_start_sample ();
5958 switch (_editor->mouse_mode) {
5960 /* find the two markers on either side and then make the selection from it */
5961 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5965 /* find the two markers on either side of the click and make the range out of it */
5966 _editor->selection->set (start, end);
5975 _editor->stop_canvas_autoscroll ();
5979 RangeMarkerBarDrag::aborted (bool movement_occurred)
5981 if (movement_occurred) {
5982 _drag_rect->hide ();
5987 RangeMarkerBarDrag::update_item (Location* location)
5989 double const x1 = _editor->sample_to_pixel (location->start());
5990 double const x2 = _editor->sample_to_pixel (location->end());
5992 _drag_rect->set_x0 (x1);
5993 _drag_rect->set_x1 (x2);
5996 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5998 , _cumulative_dx (0)
5999 , _cumulative_dy (0)
6001 , _was_selected (false)
6004 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
6006 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
6008 _region = &_primary->region_view ();
6009 _note_height = _region->midi_stream_view()->note_height ();
6013 NoteDrag::setup_pointer_sample_offset ()
6015 _pointer_sample_offset = raw_grab_sample()
6016 - _editor->session()->tempo_map().sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6020 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
6022 Drag::start_grab (event);
6024 if (ArdourKeyboard::indicates_copy (event->button.state)) {
6030 setup_snap_delta (MusicSample (_region->source_beats_to_absolute_samples (_primary->note()->time ()), 0));
6032 if (!(_was_selected = _primary->selected())) {
6034 /* tertiary-click means extend selection - we'll do that on button release,
6035 so don't add it here, because otherwise we make it hard to figure
6036 out the "extend-to" range.
6039 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
6042 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
6045 _region->note_selected (_primary, true);
6047 _editor->get_selection().clear_points();
6048 _region->unique_select (_primary);
6054 /** @return Current total drag x change in quarter notes */
6056 NoteDrag::total_dx (GdkEvent * event) const
6058 if (_x_constrained) {
6062 TempoMap& map (_editor->session()->tempo_map());
6065 sampleoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
6067 /* primary note time */
6068 sampleoffset_t const n = map.sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6070 /* primary note time in quarter notes */
6071 double const n_qn = _region->session_relative_qn (_primary->note()->time().to_double());
6073 /* new time of the primary note in session samples */
6074 sampleoffset_t st = n + dx + snap_delta (event->button.state);
6076 /* possibly snap and return corresponding delta in quarter notes */
6077 MusicSample snap (st, 0);
6078 _editor->snap_to_with_modifier (snap, event);
6079 double ret = map.exact_qn_at_sample (snap.sample, snap.division) - n_qn - snap_delta_music (event->button.state);
6081 /* prevent the earliest note being dragged earlier than the region's start position */
6082 if (_earliest + ret < _region->midi_region()->start_beats()) {
6083 ret -= (_earliest + ret) - _region->midi_region()->start_beats();
6089 /** @return Current total drag y change in note number */
6091 NoteDrag::total_dy () const
6093 if (_y_constrained) {
6097 double const y = _region->midi_view()->y_position ();
6098 /* new current note */
6099 uint8_t n = _region->y_to_note (current_pointer_y () - y);
6101 MidiStreamView* msv = _region->midi_stream_view ();
6102 n = max (msv->lowest_note(), n);
6103 n = min (msv->highest_note(), n);
6104 /* and work out delta */
6105 return n - _region->y_to_note (grab_y() - y);
6109 NoteDrag::motion (GdkEvent * event, bool first_move)
6112 _earliest = _region->earliest_in_selection().to_double();
6114 /* make copies of all the selected notes */
6115 _primary = _region->copy_selection (_primary);
6119 /* Total change in x and y since the start of the drag */
6120 double const dx_qn = total_dx (event);
6121 int8_t const dy = total_dy ();
6123 /* Now work out what we have to do to the note canvas items to set this new drag delta */
6124 double const tdx = _x_constrained ? 0 : dx_qn - _cumulative_dx;
6125 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
6128 _cumulative_dx = dx_qn;
6129 _cumulative_dy += tdy;
6131 int8_t note_delta = total_dy();
6135 _region->move_copies (dx_qn, tdy, note_delta);
6137 _region->move_selection (dx_qn, tdy, note_delta);
6140 /* the new note value may be the same as the old one, but we
6141 * don't know what that means because the selection may have
6142 * involved more than one note and we might be doing something
6143 * odd with them. so show the note value anyway, always.
6146 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
6148 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
6154 NoteDrag::finished (GdkEvent* ev, bool moved)
6157 /* no motion - select note */
6159 if (_editor->current_mouse_mode() == Editing::MouseContent ||
6160 _editor->current_mouse_mode() == Editing::MouseDraw) {
6162 bool changed = false;
6164 if (_was_selected) {
6165 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6167 _region->note_deselected (_primary);
6170 _editor->get_selection().clear_points();
6171 _region->unique_select (_primary);
6175 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
6176 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6178 if (!extend && !add && _region->selection_size() > 1) {
6179 _editor->get_selection().clear_points();
6180 _region->unique_select (_primary);
6182 } else if (extend) {
6183 _region->note_selected (_primary, true, true);
6186 /* it was added during button press */
6193 _editor->begin_reversible_selection_op(X_("Select Note Release"));
6194 _editor->commit_reversible_selection_op();
6198 _region->note_dropped (_primary, total_dx (ev), total_dy(), _copy);
6203 NoteDrag::aborted (bool)
6208 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
6209 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
6210 : Drag (editor, atv->base_item ())
6212 , _y_origin (atv->y_position())
6213 , _nothing_to_drag (false)
6215 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6216 setup (atv->lines ());
6219 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
6220 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
6221 : Drag (editor, rv->get_canvas_group ())
6223 , _y_origin (rv->get_time_axis_view().y_position())
6224 , _nothing_to_drag (false)
6227 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6229 list<boost::shared_ptr<AutomationLine> > lines;
6231 AudioRegionView* audio_view;
6232 AutomationRegionView* automation_view;
6233 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
6234 lines.push_back (audio_view->get_gain_line ());
6235 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
6236 lines.push_back (automation_view->line ());
6239 error << _("Automation range drag created for invalid region type") << endmsg;
6245 /** @param lines AutomationLines to drag.
6246 * @param offset Offset from the session start to the points in the AutomationLines.
6249 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
6251 /* find the lines that overlap the ranges being dragged */
6252 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
6253 while (i != lines.end ()) {
6254 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
6257 pair<samplepos_t, samplepos_t> r = (*i)->get_point_x_range ();
6259 /* check this range against all the AudioRanges that we are using */
6260 list<AudioRange>::const_iterator k = _ranges.begin ();
6261 while (k != _ranges.end()) {
6262 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
6268 /* add it to our list if it overlaps at all */
6269 if (k != _ranges.end()) {
6274 _lines.push_back (n);
6280 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
6284 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
6286 return 1.0 - ((global_y - _y_origin) / line->height());
6290 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
6292 const double v = list->eval(x);
6293 return _integral ? rint(v) : v;
6297 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6299 Drag::start_grab (event, cursor);
6301 /* Get line states before we start changing things */
6302 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6303 i->state = &i->line->get_state ();
6304 i->original_fraction = y_fraction (i->line, current_pointer_y());
6307 if (_ranges.empty()) {
6309 /* No selected time ranges: drag all points */
6310 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6311 uint32_t const N = i->line->npoints ();
6312 for (uint32_t j = 0; j < N; ++j) {
6313 i->points.push_back (i->line->nth (j));
6319 if (_nothing_to_drag) {
6325 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
6327 if (_nothing_to_drag && !first_move) {
6332 _editor->begin_reversible_command (_("automation range move"));
6334 if (!_ranges.empty()) {
6336 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
6338 samplecnt_t const half = (i->start + i->end) / 2;
6340 /* find the line that this audio range starts in */
6341 list<Line>::iterator j = _lines.begin();
6342 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
6346 if (j != _lines.end()) {
6347 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6349 /* j is the line that this audio range starts in; fade into it;
6350 64 samples length plucked out of thin air.
6353 samplepos_t a = i->start + 64;
6358 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
6359 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
6361 XMLNode &before = the_list->get_state();
6362 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6363 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6365 if (add_p || add_q) {
6366 _editor->session()->add_command (
6367 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6371 /* same thing for the end */
6374 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
6378 if (j != _lines.end()) {
6379 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6381 /* j is the line that this audio range starts in; fade out of it;
6382 64 samples length plucked out of thin air.
6385 samplepos_t b = i->end - 64;
6390 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6391 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6393 XMLNode &before = the_list->get_state();
6394 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6395 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6397 if (add_p || add_q) {
6398 _editor->session()->add_command (
6399 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6404 _nothing_to_drag = true;
6406 /* Find all the points that should be dragged and put them in the relevant
6407 points lists in the Line structs.
6410 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6412 uint32_t const N = i->line->npoints ();
6413 for (uint32_t j = 0; j < N; ++j) {
6415 /* here's a control point on this line */
6416 ControlPoint* p = i->line->nth (j);
6417 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6419 /* see if it's inside a range */
6420 list<AudioRange>::const_iterator k = _ranges.begin ();
6421 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6425 if (k != _ranges.end()) {
6426 /* dragging this point */
6427 _nothing_to_drag = false;
6428 i->points.push_back (p);
6434 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6435 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6439 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6440 float const f = y_fraction (l->line, current_pointer_y());
6441 /* we are ignoring x position for this drag, so we can just pass in anything */
6442 pair<float, float> result;
6444 result = l->line->drag_motion (0, f, true, false, ignored);
6445 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (result.first, result.second));
6450 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6452 if (_nothing_to_drag || !motion_occurred) {
6456 motion (event, false);
6457 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6458 i->line->end_drag (false, 0);
6461 _editor->commit_reversible_command ();
6465 AutomationRangeDrag::aborted (bool)
6467 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6472 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6474 , initial_time_axis_view (itav)
6476 /* note that time_axis_view may be null if the regionview was created
6477 * as part of a copy operation.
6479 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6480 layer = v->region()->layer ();
6481 initial_y = v->get_canvas_group()->position().y;
6482 initial_playlist = v->region()->playlist ();
6483 initial_position = v->region()->position ();
6484 initial_end = v->region()->position () + v->region()->length ();
6487 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6488 : Drag (e, i->canvas_item ())
6491 , _cumulative_dx (0)
6493 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6494 _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time()),
6499 PatchChangeDrag::motion (GdkEvent* ev, bool)
6501 samplepos_t f = adjusted_current_sample (ev);
6502 boost::shared_ptr<Region> r = _region_view->region ();
6503 f = max (f, r->position ());
6504 f = min (f, r->last_sample ());
6506 samplecnt_t const dxf = f - grab_sample(); // permitted dx in samples
6507 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6508 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6509 _cumulative_dx = dxu;
6513 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6515 if (!movement_occurred) {
6516 if (was_double_click()) {
6517 _region_view->edit_patch_change (_patch_change);
6522 boost::shared_ptr<Region> r (_region_view->region ());
6523 samplepos_t f = adjusted_current_sample (ev);
6524 f = max (f, r->position ());
6525 f = min (f, r->last_sample ());
6527 _region_view->move_patch_change (
6529 _region_view->region_samples_to_region_beats (f - (r->position() - r->start()))
6534 PatchChangeDrag::aborted (bool)
6536 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6540 PatchChangeDrag::setup_pointer_sample_offset ()
6542 boost::shared_ptr<Region> region = _region_view->region ();
6543 _pointer_sample_offset = raw_grab_sample() - _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time());
6546 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6547 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6554 MidiRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6556 _region_view->update_drag_selection (
6558 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6562 MidiRubberbandSelectDrag::deselect_things ()
6567 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6568 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6571 _vertical_only = true;
6575 MidiVerticalSelectDrag::select_things (int button_state, samplepos_t /*x1*/, samplepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6577 double const y = _region_view->midi_view()->y_position ();
6579 y1 = max (0.0, y1 - y);
6580 y2 = max (0.0, y2 - y);
6582 _region_view->update_vertical_drag_selection (
6585 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6590 MidiVerticalSelectDrag::deselect_things ()
6595 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6596 : RubberbandSelectDrag (e, i)
6602 EditorRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool drag_in_progress)
6604 if (drag_in_progress) {
6605 /* We just want to select things at the end of the drag, not during it */
6609 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6611 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6613 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6615 _editor->commit_reversible_selection_op ();
6619 EditorRubberbandSelectDrag::deselect_things ()
6621 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6623 _editor->selection->clear_tracks();
6624 _editor->selection->clear_regions();
6625 _editor->selection->clear_points ();
6626 _editor->selection->clear_lines ();
6627 _editor->selection->clear_midi_notes ();
6629 _editor->commit_reversible_selection_op();
6632 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6637 _note[0] = _note[1] = 0;
6640 NoteCreateDrag::~NoteCreateDrag ()
6646 NoteCreateDrag::grid_samples (samplepos_t t) const
6649 const Evoral::Beats grid_beats = _region_view->get_grid_beats (t);
6650 const Evoral::Beats t_beats = _region_view->region_samples_to_region_beats (t);
6652 return _region_view->region_beats_to_region_samples (t_beats + grid_beats)
6653 - _region_view->region_beats_to_region_samples (t_beats);
6657 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6659 Drag::start_grab (event, cursor);
6661 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6662 TempoMap& map (_editor->session()->tempo_map());
6664 const samplepos_t pf = _drags->current_pointer_sample ();
6665 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6667 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6669 double eqaf = map.exact_qn_at_sample (pf, divisions);
6671 if (divisions != 0) {
6673 const double qaf = map.quarter_note_at_sample (pf);
6675 /* Hack so that we always snap to the note that we are over, instead of snapping
6676 to the next one if we're more than halfway through the one we're over.
6679 const double rem = eqaf - qaf;
6681 eqaf -= grid_beats.to_double();
6685 _note[0] = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6686 /* minimum initial length is grid beats */
6687 _note[1] = map.sample_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6689 double const x0 = _editor->sample_to_pixel (_note[0]);
6690 double const x1 = _editor->sample_to_pixel (_note[1]);
6691 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6693 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6694 _drag_rect->set_outline_all ();
6695 _drag_rect->set_outline_color (0xffffff99);
6696 _drag_rect->set_fill_color (0xffffff66);
6700 NoteCreateDrag::motion (GdkEvent* event, bool)
6702 TempoMap& map (_editor->session()->tempo_map());
6703 const samplepos_t pf = _drags->current_pointer_sample ();
6704 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6705 double eqaf = map.exact_qn_at_sample (pf, divisions);
6707 if (divisions != 0) {
6709 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6711 const double qaf = map.quarter_note_at_sample (pf);
6712 /* Hack so that we always snap to the note that we are over, instead of snapping
6713 to the next one if we're more than halfway through the one we're over.
6716 const double rem = eqaf - qaf;
6718 eqaf -= grid_beats.to_double();
6721 eqaf += grid_beats.to_double();
6723 _note[1] = max ((samplepos_t)0, map.sample_at_quarter_note (eqaf) - _region_view->region()->position ());
6725 double const x0 = _editor->sample_to_pixel (_note[0]);
6726 double const x1 = _editor->sample_to_pixel (_note[1]);
6727 _drag_rect->set_x0 (std::min(x0, x1));
6728 _drag_rect->set_x1 (std::max(x0, x1));
6732 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6734 /* we create a note even if there was no movement */
6735 samplepos_t const start = min (_note[0], _note[1]);
6736 samplepos_t const start_sess_rel = start + _region_view->region()->position();
6737 samplecnt_t length = max (_editor->pixel_to_sample (1.0), (samplecnt_t) fabs ((double)(_note[0] - _note[1])));
6738 samplecnt_t const g = grid_samples (start_sess_rel);
6740 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6744 TempoMap& map (_editor->session()->tempo_map());
6745 const double qn_length = map.quarter_notes_between_samples (start_sess_rel, start_sess_rel + length);
6746 Evoral::Beats qn_length_beats = max (Evoral::Beats::ticks(1), Evoral::Beats (qn_length));
6748 _editor->begin_reversible_command (_("Create Note"));
6749 _region_view->clear_editor_note_selection();
6750 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6751 _editor->commit_reversible_command ();
6755 NoteCreateDrag::y_to_region (double y) const
6758 _region_view->get_canvas_group()->canvas_to_item (x, y);
6763 NoteCreateDrag::aborted (bool)
6768 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6776 HitCreateDrag::~HitCreateDrag ()
6781 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6783 Drag::start_grab (event, cursor);
6785 TempoMap& map (_editor->session()->tempo_map());
6787 _y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6789 const samplepos_t pf = _drags->current_pointer_sample ();
6790 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6792 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6794 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6796 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6800 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6801 Evoral::Beats length = _region_view->get_grid_beats (pf);
6803 _editor->begin_reversible_command (_("Create Hit"));
6804 _region_view->clear_editor_note_selection();
6805 _region_view->create_note_at (start, _y, length, event->button.state, false);
6811 HitCreateDrag::motion (GdkEvent* event, bool)
6813 TempoMap& map (_editor->session()->tempo_map());
6815 const samplepos_t pf = _drags->current_pointer_sample ();
6816 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6818 if (divisions == 0) {
6822 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6823 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position ();
6825 if (_last_pos == start) {
6829 Evoral::Beats length = _region_view->get_grid_beats (pf);
6831 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6833 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6837 _region_view->create_note_at (start, _y, length, event->button.state, false);
6843 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6845 _editor->commit_reversible_command ();
6850 HitCreateDrag::y_to_region (double y) const
6853 _region_view->get_canvas_group()->canvas_to_item (x, y);
6858 HitCreateDrag::aborted (bool)
6863 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6868 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6872 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6874 Drag::start_grab (event, cursor);
6878 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6884 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6887 distance = _drags->current_pointer_x() - grab_x();
6888 len = ar->fade_in()->back()->when;
6890 distance = grab_x() - _drags->current_pointer_x();
6891 len = ar->fade_out()->back()->when;
6894 /* how long should it be ? */
6896 new_length = len + _editor->pixel_to_sample (distance);
6898 /* now check with the region that this is legal */
6900 new_length = ar->verify_xfade_bounds (new_length, start);
6903 arv->reset_fade_in_shape_width (ar, new_length);
6905 arv->reset_fade_out_shape_width (ar, new_length);
6910 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6916 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6919 distance = _drags->current_pointer_x() - grab_x();
6920 len = ar->fade_in()->back()->when;
6922 distance = grab_x() - _drags->current_pointer_x();
6923 len = ar->fade_out()->back()->when;
6926 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6928 _editor->begin_reversible_command ("xfade trim");
6929 ar->playlist()->clear_owned_changes ();
6932 ar->set_fade_in_length (new_length);
6934 ar->set_fade_out_length (new_length);
6937 /* Adjusting the xfade may affect other regions in the playlist, so we need
6938 to get undo Commands from the whole playlist rather than just the
6942 vector<Command*> cmds;
6943 ar->playlist()->rdiff (cmds);
6944 _editor->session()->add_commands (cmds);
6945 _editor->commit_reversible_command ();
6950 CrossfadeEdgeDrag::aborted (bool)
6953 // arv->redraw_start_xfade ();
6955 // arv->redraw_end_xfade ();
6959 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, samplepos_t pos)
6960 : Drag (e, item, true)
6961 , line (new EditorCursor (*e))
6963 line->set_position (pos);
6965 line->track_canvas_item().reparent (_editor->_drag_motion_group);
6968 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)
6983 MusicSample pos (_drags->current_pointer_sample(), 0);
6984 _editor->snap_to_with_modifier (pos, event);
6986 line->set_position (pos.sample);
6990 RegionCutDrag::finished (GdkEvent* event, bool)
6992 _editor->get_track_canvas()->canvas()->re_enter();
6995 MusicSample pos (_drags->current_pointer_sample(), 0);
6996 _editor->snap_to_with_modifier (pos, event);
6999 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos.sample);
7005 _editor->split_regions_at (pos, rs, false);
7009 RegionCutDrag::aborted (bool)