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 ();
2877 samplepos_t const region_start = _primary->region()->position();
2878 samplepos_t const region_end = _primary->region()->last_sample();
2879 samplecnt_t const region_length = _primary->region()->length();
2881 samplepos_t const pf = adjusted_current_sample (event);
2882 setup_snap_delta (MusicSample(region_start, 0));
2884 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2885 /* Move the contents of the region around without changing the region bounds */
2886 _operation = ContentsTrim;
2887 Drag::start_grab (event, _editor->cursors()->trimmer);
2889 /* These will get overridden for a point trim.*/
2890 if (pf < (region_start + region_length/2)) {
2891 /* closer to front */
2892 _operation = StartTrim;
2893 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2894 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2896 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2900 _operation = EndTrim;
2901 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2902 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2904 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2908 /* jump trim disabled for now
2909 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2910 _jump_position_when_done = true;
2914 switch (_operation) {
2916 show_verbose_cursor_time (region_start);
2919 show_verbose_cursor_duration (region_start, region_end);
2922 show_verbose_cursor_time (pf);
2926 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2927 i->view->region()->suspend_property_changes ();
2932 TrimDrag::motion (GdkEvent* event, bool first_move)
2934 RegionView* rv = _primary;
2936 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2937 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2938 sampleoffset_t sample_delta = 0;
2940 MusicSample adj_sample = adjusted_sample (_drags->current_pointer_sample () + snap_delta (event->button.state), event, true);
2941 samplecnt_t dt = adj_sample.sample - raw_grab_sample () + _pointer_sample_offset - snap_delta (event->button.state);
2947 switch (_operation) {
2949 trim_type = "Region start trim";
2952 trim_type = "Region end trim";
2955 trim_type = "Region content trim";
2962 _editor->begin_reversible_command (trim_type);
2964 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2965 RegionView* rv = i->view;
2966 rv->region()->playlist()->clear_owned_changes ();
2968 if (_operation == StartTrim) {
2969 rv->trim_front_starting ();
2972 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2975 arv->temporarily_hide_envelope ();
2979 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2980 insert_result = _editor->motion_frozen_playlists.insert (pl);
2982 if (insert_result.second) {
2986 MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (rv);
2987 /* a MRV start trim may change the source length. ensure we cover all playlists here */
2988 if (mrv && _operation == StartTrim) {
2989 vector<boost::shared_ptr<Playlist> > all_playlists;
2990 _editor->session()->playlists->get (all_playlists);
2991 for (vector<boost::shared_ptr<Playlist> >::iterator x = all_playlists.begin(); x != all_playlists.end(); ++x) {
2993 if ((*x)->uses_source (rv->region()->source(0))) {
2994 insert_result = _editor->motion_frozen_playlists.insert (*x);
2995 if (insert_result.second) {
2996 (*x)->clear_owned_changes ();
3006 bool non_overlap_trim = false;
3008 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
3009 non_overlap_trim = true;
3012 /* contstrain trim to fade length */
3013 if (_preserve_fade_anchor) {
3014 switch (_operation) {
3016 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3017 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3019 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3020 if (ar->locked()) continue;
3021 samplecnt_t len = ar->fade_in()->back()->when;
3022 if (len < dt) dt = min(dt, len);
3026 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3027 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3029 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3030 if (ar->locked()) continue;
3031 samplecnt_t len = ar->fade_out()->back()->when;
3032 if (len < -dt) dt = max(dt, -len);
3040 switch (_operation) {
3042 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3043 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3044 , adj_sample.division);
3046 if (changed && _preserve_fade_anchor) {
3047 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3049 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3050 samplecnt_t len = ar->fade_in()->back()->when;
3051 samplecnt_t diff = ar->first_sample() - i->initial_position;
3052 samplepos_t new_length = len - diff;
3053 i->anchored_fade_length = min (ar->length(), new_length);
3054 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3055 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3062 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3063 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, adj_sample.division);
3064 if (changed && _preserve_fade_anchor) {
3065 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3067 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3068 samplecnt_t len = ar->fade_out()->back()->when;
3069 samplecnt_t diff = 1 + ar->last_sample() - i->initial_end;
3070 samplepos_t new_length = len + diff;
3071 i->anchored_fade_length = min (ar->length(), new_length);
3072 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3073 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3081 sample_delta = (last_pointer_sample() - adjusted_current_sample(event));
3083 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3084 i->view->move_contents (sample_delta);
3090 switch (_operation) {
3092 show_verbose_cursor_time (rv->region()->position());
3095 show_verbose_cursor_duration (rv->region()->position(), rv->region()->last_sample());
3098 // show_verbose_cursor_time (sample_delta);
3104 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3106 if (movement_occurred) {
3107 motion (event, false);
3109 if (_operation == StartTrim) {
3110 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3112 /* This must happen before the region's StatefulDiffCommand is created, as it may
3113 `correct' (ahem) the region's _start from being negative to being zero. It
3114 needs to be zero in the undo record.
3116 i->view->trim_front_ending ();
3118 if (_preserve_fade_anchor) {
3119 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3121 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3122 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3123 ar->set_fade_in_length(i->anchored_fade_length);
3124 ar->set_fade_in_active(true);
3127 if (_jump_position_when_done) {
3128 i->view->region()->set_position (i->initial_position);
3131 } else if (_operation == EndTrim) {
3132 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3133 if (_preserve_fade_anchor) {
3134 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3136 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3137 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3138 ar->set_fade_out_length(i->anchored_fade_length);
3139 ar->set_fade_out_active(true);
3142 if (_jump_position_when_done) {
3143 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3148 if (!_editor->selection->selected (_primary)) {
3149 _primary->thaw_after_trim ();
3151 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3152 i->view->thaw_after_trim ();
3153 i->view->enable_display (true);
3157 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3158 /* Trimming one region may affect others on the playlist, so we need
3159 to get undo Commands from the whole playlist rather than just the
3160 region. Use motion_frozen_playlists (a set) to make sure we don't
3161 diff a given playlist more than once.
3164 vector<Command*> cmds;
3166 _editor->session()->add_commands (cmds);
3170 _editor->motion_frozen_playlists.clear ();
3171 _editor->commit_reversible_command();
3174 /* no mouse movement */
3175 if (adjusted_current_sample (event) != adjusted_sample (_drags->current_pointer_sample(), event, false).sample) {
3176 _editor->point_trim (event, adjusted_current_sample (event));
3180 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3181 i->view->region()->resume_property_changes ();
3186 TrimDrag::aborted (bool movement_occurred)
3188 /* Our motion method is changing model state, so use the Undo system
3189 to cancel. Perhaps not ideal, as this will leave an Undo point
3190 behind which may be slightly odd from the user's point of view.
3194 finished (&ev, true);
3196 if (movement_occurred) {
3197 _editor->session()->undo (1);
3200 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3201 i->view->region()->resume_property_changes ();
3206 TrimDrag::setup_pointer_sample_offset ()
3208 list<DraggingView>::iterator i = _views.begin ();
3209 while (i != _views.end() && i->view != _primary) {
3213 if (i == _views.end()) {
3217 switch (_operation) {
3219 _pointer_sample_offset = raw_grab_sample() - i->initial_position;
3222 _pointer_sample_offset = raw_grab_sample() - i->initial_end;
3229 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3232 , _old_snap_type (e->snap_type())
3233 , _old_snap_mode (e->snap_mode())
3236 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3237 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3239 _real_section = &_marker->meter();
3244 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3246 Drag::start_grab (event, cursor);
3247 show_verbose_cursor_time (adjusted_current_sample(event));
3251 MeterMarkerDrag::setup_pointer_sample_offset ()
3253 _pointer_sample_offset = raw_grab_sample() - _marker->meter().sample();
3257 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3260 // create a dummy marker to catch events, then hide it.
3263 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3265 _marker = new MeterMarker (
3267 *_editor->meter_group,
3268 UIConfiguration::instance().color ("meter marker"),
3270 *new MeterSection (_marker->meter())
3273 /* use the new marker for the grab */
3274 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3277 TempoMap& map (_editor->session()->tempo_map());
3278 /* get current state */
3279 before_state = &map.get_state();
3282 _editor->begin_reversible_command (_("move meter mark"));
3284 _editor->begin_reversible_command (_("copy meter mark"));
3286 Timecode::BBT_Time bbt = _real_section->bbt();
3288 /* we can't add a meter where one currently exists */
3289 if (_real_section->sample() < adjusted_current_sample (event, false)) {
3294 const samplepos_t sample = map.sample_at_bbt (bbt);
3295 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3296 , bbt, sample, _real_section->position_lock_style());
3297 if (!_real_section) {
3303 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3304 if (_real_section->position_lock_style() != AudioTime) {
3305 _editor->set_snap_to (SnapToBar);
3306 _editor->set_snap_mode (SnapNormal);
3310 samplepos_t pf = adjusted_current_sample (event);
3312 if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3313 /* never snap to music for audio locked */
3314 pf = adjusted_current_sample (event, false);
3317 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3319 /* fake marker meeds to stay under the mouse, unlike the real one. */
3320 _marker->set_position (adjusted_current_sample (event, false));
3322 show_verbose_cursor_time (_real_section->sample());
3326 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3328 if (!movement_occurred) {
3329 if (was_double_click()) {
3330 _editor->edit_meter_marker (*_marker);
3335 /* reinstate old snap setting */
3336 _editor->set_snap_to (_old_snap_type);
3337 _editor->set_snap_mode (_old_snap_mode);
3339 TempoMap& map (_editor->session()->tempo_map());
3341 XMLNode &after = map.get_state();
3342 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3343 _editor->commit_reversible_command ();
3345 // delete the dummy marker we used for visual representation while moving.
3346 // a new visual marker will show up automatically.
3351 MeterMarkerDrag::aborted (bool moved)
3353 _marker->set_position (_marker->meter().sample ());
3355 /* reinstate old snap setting */
3356 _editor->set_snap_to (_old_snap_type);
3357 _editor->set_snap_mode (_old_snap_mode);
3359 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3360 // delete the dummy marker we used for visual representation while moving.
3361 // a new visual marker will show up automatically.
3366 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3369 , _grab_bpm (120.0, 4.0)
3373 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3375 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3376 _real_section = &_marker->tempo();
3377 _movable = !_real_section->initial();
3378 _grab_bpm = Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), _real_section->end_note_types_per_minute());
3379 _grab_qn = _real_section->pulse() * 4.0;
3384 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3386 Drag::start_grab (event, cursor);
3387 if (!_real_section->active()) {
3388 show_verbose_cursor_text (_("inactive"));
3390 show_verbose_cursor_time (adjusted_current_sample (event));
3395 TempoMarkerDrag::setup_pointer_sample_offset ()
3397 _pointer_sample_offset = raw_grab_sample() - _real_section->sample();
3401 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3403 if (!_real_section->active()) {
3406 TempoMap& map (_editor->session()->tempo_map());
3410 // mvc drag - create a dummy marker to catch events, hide it.
3413 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3415 TempoSection section (_marker->tempo());
3417 _marker = new TempoMarker (
3419 *_editor->tempo_group,
3420 UIConfiguration::instance().color ("tempo marker"),
3422 *new TempoSection (_marker->tempo())
3425 /* use the new marker for the grab */
3426 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3429 /* get current state */
3430 _before_state = &map.get_state();
3433 _editor->begin_reversible_command (_("move tempo mark"));
3436 const Tempo tempo (_marker->tempo());
3437 const samplepos_t sample = adjusted_current_sample (event) + 1;
3439 _editor->begin_reversible_command (_("copy tempo mark"));
3441 if (_real_section->position_lock_style() == MusicTime) {
3442 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3443 _real_section = map.add_tempo (tempo, map.exact_qn_at_sample (sample, divisions), 0, MusicTime);
3445 _real_section = map.add_tempo (tempo, 0.0, sample, AudioTime);
3448 if (!_real_section) {
3455 if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) {
3456 double new_bpm = max (1.5, _grab_bpm.end_note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3458 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), new_bpm));
3459 strs << "end:" << fixed << setprecision(3) << new_bpm;
3460 show_verbose_cursor_text (strs.str());
3462 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3463 /* use vertical movement to alter tempo .. should be log */
3464 double new_bpm = max (1.5, _grab_bpm.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3466 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type(), _real_section->end_note_types_per_minute()));
3467 strs << "start:" << fixed << setprecision(3) << new_bpm;
3468 show_verbose_cursor_text (strs.str());
3470 } else if (_movable && !_real_section->locked_to_meter()) {
3473 if (_editor->snap_musical()) {
3474 /* we can't snap to a grid that we are about to move.
3475 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3477 pf = adjusted_current_sample (event, false);
3479 pf = adjusted_current_sample (event);
3482 /* snap to beat is 1, snap to bar is -1 (sorry) */
3483 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3485 map.gui_set_tempo_position (_real_section, pf, sub_num);
3487 show_verbose_cursor_time (_real_section->sample());
3489 _marker->set_position (adjusted_current_sample (event, false));
3493 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3495 if (!_real_section->active()) {
3498 if (!movement_occurred) {
3499 if (was_double_click()) {
3500 _editor->edit_tempo_marker (*_marker);
3505 TempoMap& map (_editor->session()->tempo_map());
3507 XMLNode &after = map.get_state();
3508 _editor->session()->add_command (new MementoCommand<TempoMap>(map, _before_state, &after));
3509 _editor->commit_reversible_command ();
3511 // delete the dummy marker we used for visual representation while moving.
3512 // a new visual marker will show up automatically.
3517 TempoMarkerDrag::aborted (bool moved)
3519 _marker->set_position (_marker->tempo().sample());
3521 TempoMap& map (_editor->session()->tempo_map());
3522 map.set_state (*_before_state, Stateful::current_state_version);
3523 // delete the dummy (hidden) marker we used for events while moving.
3528 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3533 , _drag_valid (true)
3535 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3540 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3542 Drag::start_grab (event, cursor);
3543 TempoMap& map (_editor->session()->tempo_map());
3544 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3546 if (adjusted_current_sample (event, false) <= _tempo->sample()) {
3547 _drag_valid = false;
3551 _editor->tempo_curve_selected (_tempo, true);
3554 if (_tempo->clamped()) {
3555 TempoSection* prev = map.previous_tempo_section (_tempo);
3557 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3561 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3562 show_verbose_cursor_text (sstr.str());
3566 BBTRulerDrag::setup_pointer_sample_offset ()
3568 TempoMap& map (_editor->session()->tempo_map());
3569 /* get current state */
3570 _before_state = &map.get_state();
3572 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3573 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3576 if (divisions > 0) {
3577 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3579 /* while it makes some sense for the user to determine the division to 'grab',
3580 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3581 and the result over steep tempo curves. Use sixteenths.
3583 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3586 _grab_qn = map.quarter_note_at_beat (beat);
3588 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3593 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3600 _editor->begin_reversible_command (_("stretch tempo"));
3603 TempoMap& map (_editor->session()->tempo_map());
3606 if (_editor->snap_musical()) {
3607 pf = adjusted_current_sample (event, false);
3609 pf = adjusted_current_sample (event);
3612 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3613 /* adjust previous tempo to match pointer sample */
3614 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.sample_at_quarter_note (_grab_qn), pf, _grab_qn, map.quarter_note_at_sample (pf));
3618 if (_tempo->clamped()) {
3619 TempoSection* prev = map.previous_tempo_section (_tempo);
3621 _editor->tempo_curve_selected (prev, true);
3622 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3625 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3626 show_verbose_cursor_text (sstr.str());
3630 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3632 if (!movement_occurred) {
3636 TempoMap& map (_editor->session()->tempo_map());
3638 _editor->tempo_curve_selected (_tempo, false);
3639 if (_tempo->clamped()) {
3640 TempoSection* prev_tempo = map.previous_tempo_section (_tempo);
3642 _editor->tempo_curve_selected (prev_tempo, false);
3646 if (!movement_occurred || !_drag_valid) {
3650 XMLNode &after = map.get_state();
3651 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3652 _editor->commit_reversible_command ();
3657 BBTRulerDrag::aborted (bool moved)
3660 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3664 TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i)
3670 , _drag_valid (true)
3673 DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n");
3678 TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3680 Drag::start_grab (event, cursor);
3681 TempoMap& map (_editor->session()->tempo_map());
3682 /* get current state */
3683 _before_state = &map.get_state();
3684 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3686 if (_tempo->locked_to_meter()) {
3687 _drag_valid = false;
3691 _next_tempo = map.next_tempo_section (_tempo);
3693 if (!map.next_tempo_section (_next_tempo)) {
3694 _drag_valid = false;
3695 finished (event, false);
3699 _editor->tempo_curve_selected (_tempo, true);
3700 _editor->tempo_curve_selected (_next_tempo, true);
3703 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3704 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3705 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3706 show_verbose_cursor_text (sstr.str());
3708 _drag_valid = false;
3711 _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type());
3715 TempoTwistDrag::setup_pointer_sample_offset ()
3717 TempoMap& map (_editor->session()->tempo_map());
3718 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3719 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3722 if (divisions > 0) {
3723 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3725 /* while it makes some sense for the user to determine the division to 'grab',
3726 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3727 and the result over steep tempo curves. Use sixteenths.
3729 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3732 _grab_qn = map.quarter_note_at_beat (beat);
3734 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3739 TempoTwistDrag::motion (GdkEvent* event, bool first_move)
3742 if (!_next_tempo || !_drag_valid) {
3746 TempoMap& map (_editor->session()->tempo_map());
3749 _editor->begin_reversible_command (_("twist tempo"));
3754 if (_editor->snap_musical()) {
3755 pf = adjusted_current_sample (event, false);
3757 pf = adjusted_current_sample (event);
3760 /* adjust this and the next tempi to match pointer sample */
3761 double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3762 _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.sample_at_quarter_note (_grab_qn), pf);
3765 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3766 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3767 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3768 show_verbose_cursor_text (sstr.str());
3772 TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred)
3774 if (!movement_occurred || !_drag_valid) {
3778 _editor->tempo_curve_selected (_tempo, false);
3779 _editor->tempo_curve_selected (_next_tempo, false);
3781 TempoMap& map (_editor->session()->tempo_map());
3782 XMLNode &after = map.get_state();
3783 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3784 _editor->commit_reversible_command ();
3788 TempoTwistDrag::aborted (bool moved)
3791 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3795 TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i)
3800 , _drag_valid (true)
3802 DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n");
3803 TempoMarker* marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3804 _tempo = &marker->tempo();
3805 _grab_qn = _tempo->pulse() * 4.0;
3809 TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3811 Drag::start_grab (event, cursor);
3812 TempoMap& tmap (_editor->session()->tempo_map());
3814 /* get current state */
3815 _before_state = &tmap.get_state();
3817 if (_tempo->locked_to_meter()) {
3818 _drag_valid = false;
3824 TempoSection* prev = 0;
3825 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3826 _editor->tempo_curve_selected (tmap.previous_tempo_section (_tempo), true);
3827 sstr << "end: " << fixed << setprecision(3) << tmap.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3830 if (_tempo->clamped()) {
3831 _editor->tempo_curve_selected (_tempo, true);
3832 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3835 show_verbose_cursor_text (sstr.str());
3839 TempoEndDrag::setup_pointer_sample_offset ()
3841 TempoMap& map (_editor->session()->tempo_map());
3843 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3848 TempoEndDrag::motion (GdkEvent* event, bool first_move)
3854 TempoMap& map (_editor->session()->tempo_map());
3857 _editor->begin_reversible_command (_("stretch end tempo"));
3860 samplepos_t const pf = adjusted_current_sample (event, false);
3861 map.gui_stretch_tempo_end (&map.tempo_section_at_sample (_tempo->sample() - 1), map.sample_at_quarter_note (_grab_qn), pf);
3864 sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3866 if (_tempo->clamped()) {
3867 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3870 show_verbose_cursor_text (sstr.str());
3874 TempoEndDrag::finished (GdkEvent* event, bool movement_occurred)
3876 if (!movement_occurred || !_drag_valid) {
3880 TempoMap& tmap (_editor->session()->tempo_map());
3882 XMLNode &after = tmap.get_state();
3883 _editor->session()->add_command(new MementoCommand<TempoMap>(tmap, _before_state, &after));
3884 _editor->commit_reversible_command ();
3886 TempoSection* prev = 0;
3887 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3888 _editor->tempo_curve_selected (prev, false);
3891 if (_tempo->clamped()) {
3892 _editor->tempo_curve_selected (_tempo, false);
3898 TempoEndDrag::aborted (bool moved)
3901 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3905 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3906 : Drag (e, &c.track_canvas_item(), false)
3911 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3914 /** Do all the things we do when dragging the playhead to make it look as though
3915 * we have located, without actually doing the locate (because that would cause
3916 * the diskstream buffers to be refilled, which is too slow).
3919 CursorDrag::fake_locate (samplepos_t t)
3921 if (_editor->session () == 0) {
3925 _editor->playhead_cursor->set_position (t);
3927 Session* s = _editor->session ();
3928 if (s->timecode_transmission_suspended ()) {
3929 samplepos_t const f = _editor->playhead_cursor->current_sample ();
3930 /* This is asynchronous so it will be sent "now"
3932 s->send_mmc_locate (f);
3933 /* These are synchronous and will be sent during the next
3936 s->queue_full_time_code ();
3937 s->queue_song_position_pointer ();
3940 show_verbose_cursor_time (t);
3941 _editor->UpdateAllTransportClocks (t);
3945 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3947 Drag::start_grab (event, c);
3948 setup_snap_delta (MusicSample (_editor->playhead_cursor->current_sample(), 0));
3950 _grab_zoom = _editor->samples_per_pixel;
3952 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3954 _editor->snap_to_with_modifier (where, event);
3955 _editor->_dragging_playhead = true;
3956 _editor->_control_scroll_target = where.sample;
3958 Session* s = _editor->session ();
3960 /* grab the track canvas item as well */
3962 _cursor.track_canvas_item().grab();
3965 if (_was_rolling && _stop) {
3969 if (s->is_auditioning()) {
3970 s->cancel_audition ();
3974 if (AudioEngine::instance()->running()) {
3976 /* do this only if we're the engine is connected
3977 * because otherwise this request will never be
3978 * serviced and we'll busy wait forever. likewise,
3979 * notice if we are disconnected while waiting for the
3980 * request to be serviced.
3983 s->request_suspend_timecode_transmission ();
3984 while (AudioEngine::instance()->running() && !s->timecode_transmission_suspended ()) {
3985 /* twiddle our thumbs */
3990 fake_locate (where.sample - snap_delta (event->button.state));
3996 CursorDrag::motion (GdkEvent* event, bool)
3998 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4000 _editor->snap_to_with_modifier (where, event);
4002 if (where.sample != last_pointer_sample()) {
4003 fake_locate (where.sample - snap_delta (event->button.state));
4006 //maybe do zooming, too, if the option is enabled
4007 if (UIConfiguration::instance ().get_use_time_rulers_to_zoom_with_vertical_drag () ) {
4009 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
4010 //we use screen coordinates for this, not canvas-based grab_x
4011 double mx = event->button.x;
4012 double dx = fabs(mx - _last_mx);
4013 double my = event->button.y;
4014 double dy = fabs(my - _last_my);
4017 //do zooming in windowed "steps" so it feels more reversible ?
4018 const int stepsize = 2; //stepsize ==1 means "trigger on every pixel of movement"
4019 int y_delta = grab_y() - current_pointer_y();
4020 y_delta = y_delta / stepsize;
4022 //if all requirements are met, do the actual zoom
4023 const double scale = 1.2;
4024 if ( (dy>dx) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
4025 if ( _last_y_delta > y_delta ) {
4026 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
4028 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
4030 _last_y_delta = y_delta;
4041 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
4043 _editor->_dragging_playhead = false;
4045 _cursor.track_canvas_item().ungrab();
4047 if (!movement_occurred && _stop) {
4051 motion (event, false);
4053 Session* s = _editor->session ();
4055 s->request_locate (_editor->playhead_cursor->current_sample (), _was_rolling);
4056 _editor->_pending_locate_request = true;
4057 s->request_resume_timecode_transmission ();
4062 CursorDrag::aborted (bool)
4064 _cursor.track_canvas_item().ungrab();
4066 if (_editor->_dragging_playhead) {
4067 _editor->session()->request_resume_timecode_transmission ();
4068 _editor->_dragging_playhead = false;
4071 _editor->playhead_cursor->set_position (adjusted_sample (grab_sample (), 0, false).sample);
4074 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4075 : RegionDrag (e, i, p, v)
4077 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
4081 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4083 Drag::start_grab (event, cursor);
4085 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4086 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4087 setup_snap_delta (MusicSample (r->position(), 0));
4089 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
4093 FadeInDrag::setup_pointer_sample_offset ()
4095 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4096 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4097 _pointer_sample_offset = raw_grab_sample() - ((samplecnt_t) r->fade_in()->back()->when + r->position());
4101 FadeInDrag::motion (GdkEvent* event, bool)
4103 samplecnt_t fade_length;
4105 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4106 _editor->snap_to_with_modifier (pos, event);
4108 pos.sample -= snap_delta (event->button.state);
4110 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4112 if (pos.sample < (region->position() + 64)) {
4113 fade_length = 64; // this should be a minimum defined somewhere
4114 } else if (pos.sample > region->position() + region->length() - region->fade_out()->back()->when) {
4115 fade_length = region->length() - region->fade_out()->back()->when - 1;
4117 fade_length = pos.sample - region->position();
4120 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4122 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4128 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
4131 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
4135 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
4137 if (!movement_occurred) {
4141 samplecnt_t fade_length;
4142 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4144 _editor->snap_to_with_modifier (pos, event);
4145 pos.sample -= snap_delta (event->button.state);
4147 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4149 if (pos.sample < (region->position() + 64)) {
4150 fade_length = 64; // this should be a minimum defined somewhere
4151 } else if (pos.sample >= region->position() + region->length() - region->fade_out()->back()->when) {
4152 fade_length = region->length() - region->fade_out()->back()->when - 1;
4154 fade_length = pos.sample - region->position();
4157 bool in_command = false;
4159 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4161 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4167 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
4168 XMLNode &before = alist->get_state();
4170 tmp->audio_region()->set_fade_in_length (fade_length);
4171 tmp->audio_region()->set_fade_in_active (true);
4174 _editor->begin_reversible_command (_("change fade in length"));
4177 XMLNode &after = alist->get_state();
4178 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4182 _editor->commit_reversible_command ();
4187 FadeInDrag::aborted (bool)
4189 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4190 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4196 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
4200 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4201 : RegionDrag (e, i, p, v)
4203 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
4207 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4209 Drag::start_grab (event, cursor);
4211 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4212 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4213 setup_snap_delta (MusicSample (r->last_sample(), 0));
4215 show_verbose_cursor_duration (r->last_sample() - r->fade_out()->back()->when, r->last_sample());
4219 FadeOutDrag::setup_pointer_sample_offset ()
4221 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4222 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4223 _pointer_sample_offset = raw_grab_sample() - (r->length() - (samplecnt_t) r->fade_out()->back()->when + r->position());
4227 FadeOutDrag::motion (GdkEvent* event, bool)
4229 samplecnt_t fade_length;
4230 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4232 _editor->snap_to_with_modifier (pos, event);
4233 pos.sample -= snap_delta (event->button.state);
4235 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4237 if (pos.sample > (region->last_sample() - 64)) {
4238 fade_length = 64; // this should really be a minimum fade defined somewhere
4239 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4240 fade_length = region->length() - region->fade_in()->back()->when - 1;
4242 fade_length = region->last_sample() - pos.sample;
4245 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4247 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4253 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
4256 show_verbose_cursor_duration (region->last_sample() - fade_length, region->last_sample());
4260 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
4262 if (!movement_occurred) {
4266 samplecnt_t fade_length;
4267 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4269 _editor->snap_to_with_modifier (pos, event);
4270 pos.sample -= snap_delta (event->button.state);
4272 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4274 if (pos.sample > (region->last_sample() - 64)) {
4275 fade_length = 64; // this should really be a minimum fade defined somewhere
4276 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4277 fade_length = region->length() - region->fade_in()->back()->when - 1;
4279 fade_length = region->last_sample() - pos.sample;
4282 bool in_command = false;
4284 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4286 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4292 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
4293 XMLNode &before = alist->get_state();
4295 tmp->audio_region()->set_fade_out_length (fade_length);
4296 tmp->audio_region()->set_fade_out_active (true);
4299 _editor->begin_reversible_command (_("change fade out length"));
4302 XMLNode &after = alist->get_state();
4303 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4307 _editor->commit_reversible_command ();
4312 FadeOutDrag::aborted (bool)
4314 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4315 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4321 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
4325 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
4327 , _selection_changed (false)
4329 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
4330 Gtk::Window* toplevel = _editor->current_toplevel();
4331 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
4335 _points.push_back (ArdourCanvas::Duple (0, 0));
4337 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4340 MarkerDrag::~MarkerDrag ()
4342 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4347 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4349 location = new Location (*l);
4350 markers.push_back (m);
4355 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4357 Drag::start_grab (event, cursor);
4361 Location *location = _editor->find_location_from_marker (_marker, is_start);
4362 _editor->_dragging_edit_point = true;
4364 update_item (location);
4366 // _drag_line->show();
4367 // _line->raise_to_top();
4370 show_verbose_cursor_time (location->start());
4372 show_verbose_cursor_time (location->end());
4374 setup_snap_delta (MusicSample (is_start ? location->start() : location->end(), 0));
4376 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4379 case Selection::Toggle:
4380 /* we toggle on the button release */
4382 case Selection::Set:
4383 if (!_editor->selection->selected (_marker)) {
4384 _editor->selection->set (_marker);
4385 _selection_changed = true;
4388 case Selection::Extend:
4390 Locations::LocationList ll;
4391 list<ArdourMarker*> to_add;
4393 _editor->selection->markers.range (s, e);
4394 s = min (_marker->position(), s);
4395 e = max (_marker->position(), e);
4398 if (e < max_samplepos) {
4401 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4402 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4403 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4406 to_add.push_back (lm->start);
4409 to_add.push_back (lm->end);
4413 if (!to_add.empty()) {
4414 _editor->selection->add (to_add);
4415 _selection_changed = true;
4419 case Selection::Add:
4420 _editor->selection->add (_marker);
4421 _selection_changed = true;
4426 /* Set up copies for us to manipulate during the drag
4429 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4431 Location* l = _editor->find_location_from_marker (*i, is_start);
4438 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4440 /* range: check that the other end of the range isn't
4443 CopiedLocationInfo::iterator x;
4444 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4445 if (*(*x).location == *l) {
4449 if (x == _copied_locations.end()) {
4450 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4452 (*x).markers.push_back (*i);
4453 (*x).move_both = true;
4461 MarkerDrag::setup_pointer_sample_offset ()
4464 Location *location = _editor->find_location_from_marker (_marker, is_start);
4465 _pointer_sample_offset = raw_grab_sample() - (is_start ? location->start() : location->end());
4469 MarkerDrag::motion (GdkEvent* event, bool)
4471 samplecnt_t f_delta = 0;
4473 bool move_both = false;
4474 Location *real_location;
4475 Location *copy_location = 0;
4476 samplecnt_t const sd = snap_delta (event->button.state);
4478 samplecnt_t const newframe = adjusted_sample (_drags->current_pointer_sample () + sd, event, true).sample - sd;
4479 samplepos_t next = newframe;
4481 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4485 CopiedLocationInfo::iterator x;
4487 /* find the marker we're dragging, and compute the delta */
4489 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4491 copy_location = (*x).location;
4493 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4495 /* this marker is represented by this
4496 * CopiedLocationMarkerInfo
4499 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4504 if (real_location->is_mark()) {
4505 f_delta = newframe - copy_location->start();
4509 switch (_marker->type()) {
4510 case ArdourMarker::SessionStart:
4511 case ArdourMarker::RangeStart:
4512 case ArdourMarker::LoopStart:
4513 case ArdourMarker::PunchIn:
4514 f_delta = newframe - copy_location->start();
4517 case ArdourMarker::SessionEnd:
4518 case ArdourMarker::RangeEnd:
4519 case ArdourMarker::LoopEnd:
4520 case ArdourMarker::PunchOut:
4521 f_delta = newframe - copy_location->end();
4524 /* what kind of marker is this ? */
4533 if (x == _copied_locations.end()) {
4534 /* hmm, impossible - we didn't find the dragged marker */
4538 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4540 /* now move them all */
4542 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4544 copy_location = x->location;
4546 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4550 if (real_location->locked()) {
4554 if (copy_location->is_mark()) {
4557 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4561 samplepos_t new_start = copy_location->start() + f_delta;
4562 samplepos_t new_end = copy_location->end() + f_delta;
4564 if (is_start) { // start-of-range marker
4566 if (move_both || (*x).move_both) {
4567 copy_location->set_start (new_start, false, true, divisions);
4568 copy_location->set_end (new_end, false, true, divisions);
4569 } else if (new_start < copy_location->end()) {
4570 copy_location->set_start (new_start, false, true, divisions);
4571 } else if (newframe > 0) {
4572 //_editor->snap_to (next, RoundUpAlways, true);
4573 copy_location->set_end (next, false, true, divisions);
4574 copy_location->set_start (newframe, false, true, divisions);
4577 } else { // end marker
4579 if (move_both || (*x).move_both) {
4580 copy_location->set_end (new_end, divisions);
4581 copy_location->set_start (new_start, false, true, divisions);
4582 } else if (new_end > copy_location->start()) {
4583 copy_location->set_end (new_end, false, true, divisions);
4584 } else if (newframe > 0) {
4585 //_editor->snap_to (next, RoundDownAlways, true);
4586 copy_location->set_start (next, false, true, divisions);
4587 copy_location->set_end (newframe, false, true, divisions);
4592 update_item (copy_location);
4594 /* now lookup the actual GUI items used to display this
4595 * location and move them to wherever the copy of the location
4596 * is now. This means that the logic in ARDOUR::Location is
4597 * still enforced, even though we are not (yet) modifying
4598 * the real Location itself.
4601 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4604 lm->set_position (copy_location->start(), copy_location->end());
4609 assert (!_copied_locations.empty());
4611 show_verbose_cursor_time (newframe);
4615 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4617 if (!movement_occurred) {
4619 if (was_double_click()) {
4620 _editor->rename_marker (_marker);
4624 /* just a click, do nothing but finish
4625 off the selection process
4628 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4630 case Selection::Set:
4631 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4632 _editor->selection->set (_marker);
4633 _selection_changed = true;
4637 case Selection::Toggle:
4638 /* we toggle on the button release, click only */
4639 _editor->selection->toggle (_marker);
4640 _selection_changed = true;
4644 case Selection::Extend:
4645 case Selection::Add:
4649 if (_selection_changed) {
4650 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4651 _editor->commit_reversible_selection_op();
4657 _editor->_dragging_edit_point = false;
4659 XMLNode &before = _editor->session()->locations()->get_state();
4660 bool in_command = false;
4662 MarkerSelection::iterator i;
4663 CopiedLocationInfo::iterator x;
4664 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4667 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4668 x != _copied_locations.end() && i != _editor->selection->markers.end();
4671 Location * location = _editor->find_location_from_marker (*i, is_start);
4675 if (location->locked()) {
4679 _editor->begin_reversible_command ( _("move marker") );
4682 if (location->is_mark()) {
4683 location->set_start (((*x).location)->start(), false, true, divisions);
4685 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4688 if (location->is_session_range()) {
4689 _editor->session()->set_end_is_free (false);
4695 XMLNode &after = _editor->session()->locations()->get_state();
4696 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4697 _editor->commit_reversible_command ();
4702 MarkerDrag::aborted (bool movement_occurred)
4704 if (!movement_occurred) {
4708 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4710 /* move all markers to their original location */
4713 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4716 Location * location = _editor->find_location_from_marker (*m, is_start);
4719 (*m)->set_position (is_start ? location->start() : location->end());
4726 MarkerDrag::update_item (Location*)
4731 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4733 , _fixed_grab_x (0.0)
4734 , _fixed_grab_y (0.0)
4735 , _cumulative_x_drag (0.0)
4736 , _cumulative_y_drag (0.0)
4740 if (_zero_gain_fraction < 0.0) {
4741 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4744 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4746 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4752 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4754 Drag::start_grab (event, _editor->cursors()->fader);
4756 // start the grab at the center of the control point so
4757 // the point doesn't 'jump' to the mouse after the first drag
4758 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4759 _fixed_grab_y = _point->get_y();
4761 setup_snap_delta (MusicSample (_editor->pixel_to_sample (_fixed_grab_x), 0));
4763 float const fraction = 1 - (_point->get_y() / _point->line().height());
4764 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4766 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4768 if (!_point->can_slide ()) {
4769 _x_constrained = true;
4774 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4776 double dx = _drags->current_pointer_x() - last_pointer_x();
4777 double dy = current_pointer_y() - last_pointer_y();
4778 bool need_snap = true;
4780 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4786 /* coordinate in pixels relative to the start of the region (for region-based automation)
4787 or track (for track-based automation) */
4788 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4789 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4791 // calculate zero crossing point. back off by .01 to stay on the
4792 // positive side of zero
4793 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4795 if (_x_constrained) {
4798 if (_y_constrained) {
4802 _cumulative_x_drag = cx - _fixed_grab_x;
4803 _cumulative_y_drag = cy - _fixed_grab_y;
4807 cy = min ((double) _point->line().height(), cy);
4809 // make sure we hit zero when passing through
4810 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4814 MusicSample cx_mf (_editor->pixel_to_sample (cx) + snap_delta (event->button.state), 0);
4816 if (!_x_constrained && need_snap) {
4817 _editor->snap_to_with_modifier (cx_mf, event);
4820 cx_mf.sample -= snap_delta (event->button.state);
4821 cx_mf.sample = min (cx_mf.sample, _point->line().maximum_time() + _point->line().offset());
4823 float const fraction = 1.0 - (cy / _point->line().height());
4826 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4827 _editor->begin_reversible_command (_("automation event move"));
4828 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4830 pair<float, float> result;
4831 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_mf.sample), fraction, false, _pushing, _final_index);
4832 show_verbose_cursor_text (_point->line().get_verbose_cursor_relative_string (result.first, result.second));
4836 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4838 if (!movement_occurred) {
4841 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4842 _editor->reset_point_selection ();
4846 _point->line().end_drag (_pushing, _final_index);
4847 _editor->commit_reversible_command ();
4852 ControlPointDrag::aborted (bool)
4854 _point->line().reset ();
4858 ControlPointDrag::active (Editing::MouseMode m)
4860 if (m == Editing::MouseDraw) {
4861 /* always active in mouse draw */
4865 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4866 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4869 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4872 , _fixed_grab_x (0.0)
4873 , _fixed_grab_y (0.0)
4874 , _cumulative_y_drag (0)
4878 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4882 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4884 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4887 _item = &_line->grab_item ();
4889 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4890 origin, and ditto for y.
4893 double mx = event->button.x;
4894 double my = event->button.y;
4896 _line->grab_item().canvas_to_item (mx, my);
4898 samplecnt_t const sample_within_region = (samplecnt_t) floor (mx * _editor->samples_per_pixel);
4900 if (!_line->control_points_adjacent (sample_within_region, _before, _after)) {
4901 /* no adjacent points */
4905 Drag::start_grab (event, _editor->cursors()->fader);
4907 /* store grab start in item sample */
4908 double const bx = _line->nth (_before)->get_x();
4909 double const ax = _line->nth (_after)->get_x();
4910 double const click_ratio = (ax - mx) / (ax - bx);
4912 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4917 double fraction = 1.0 - (cy / _line->height());
4919 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4923 LineDrag::motion (GdkEvent* event, bool first_move)
4925 double dy = current_pointer_y() - last_pointer_y();
4927 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4931 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4933 _cumulative_y_drag = cy - _fixed_grab_y;
4936 cy = min ((double) _line->height(), cy);
4938 double const fraction = 1.0 - (cy / _line->height());
4942 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4944 _editor->begin_reversible_command (_("automation range move"));
4945 _line->start_drag_line (_before, _after, initial_fraction);
4948 /* we are ignoring x position for this drag, so we can just pass in anything */
4949 pair<float, float> result;
4951 result = _line->drag_motion (0, fraction, true, false, ignored);
4952 show_verbose_cursor_text (_line->get_verbose_cursor_relative_string (result.first, result.second));
4956 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4958 if (movement_occurred) {
4959 motion (event, false);
4960 _line->end_drag (false, 0);
4961 _editor->commit_reversible_command ();
4963 /* add a new control point on the line */
4965 AutomationTimeAxisView* atv;
4967 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4968 samplepos_t where = grab_sample ();
4971 double cy = _fixed_grab_y;
4973 _line->grab_item().item_to_canvas (cx, cy);
4975 atv->add_automation_event (event, where, cy, false);
4976 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4977 AudioRegionView* arv;
4979 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4980 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4987 LineDrag::aborted (bool)
4992 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4996 _region_view_grab_x (0.0),
4997 _cumulative_x_drag (0),
5001 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
5005 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
5007 Drag::start_grab (event);
5009 _line = reinterpret_cast<Line*> (_item);
5012 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
5014 double cx = event->button.x;
5015 double cy = event->button.y;
5017 _item->parent()->canvas_to_item (cx, cy);
5019 /* store grab start in parent sample */
5020 _region_view_grab_x = cx;
5022 _before = *(float*) _item->get_data ("position");
5024 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5026 _max_x = _editor->sample_to_pixel(_arv->get_duration());
5030 FeatureLineDrag::motion (GdkEvent*, bool)
5032 double dx = _drags->current_pointer_x() - last_pointer_x();
5034 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
5036 _cumulative_x_drag += dx;
5038 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
5047 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
5049 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
5051 float *pos = new float;
5054 _line->set_data ("position", pos);
5060 FeatureLineDrag::finished (GdkEvent*, bool)
5062 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5063 _arv->update_transient(_before, _before);
5067 FeatureLineDrag::aborted (bool)
5072 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5074 , _vertical_only (false)
5076 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
5080 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5082 Drag::start_grab (event);
5083 show_verbose_cursor_time (adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
5087 RubberbandSelectDrag::motion (GdkEvent* event, bool)
5093 samplepos_t const pf = adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
5094 MusicSample grab (grab_sample (), 0);
5096 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5097 _editor->snap_to_with_modifier (grab, event);
5099 grab.sample = raw_grab_sample ();
5102 /* base start and end on initial click position */
5104 if (pf < grab.sample) {
5109 start = grab.sample;
5112 if (current_pointer_y() < grab_y()) {
5113 y1 = current_pointer_y();
5116 y2 = current_pointer_y();
5120 if (start != end || y1 != y2) {
5122 double x1 = _editor->sample_to_pixel (start);
5123 double x2 = _editor->sample_to_pixel (end);
5124 const double min_dimension = 2.0;
5126 if (_vertical_only) {
5127 /* fixed 10 pixel width */
5131 x2 = min (x1 - min_dimension, x2);
5133 x2 = max (x1 + min_dimension, x2);
5138 y2 = min (y1 - min_dimension, y2);
5140 y2 = max (y1 + min_dimension, y2);
5143 /* translate rect into item space and set */
5145 ArdourCanvas::Rect r (x1, y1, x2, y2);
5147 /* this drag is a _trackview_only == true drag, so the y1 and
5148 * y2 (computed using current_pointer_y() and grab_y()) will be
5149 * relative to the top of the trackview group). The
5150 * rubberband rect has the same parent/scroll offset as the
5151 * the trackview group, so we can use the "r" rect directly
5152 * to set the shape of the rubberband.
5155 _editor->rubberband_rect->set (r);
5156 _editor->rubberband_rect->show();
5157 _editor->rubberband_rect->raise_to_top();
5159 show_verbose_cursor_time (pf);
5161 do_select_things (event, true);
5166 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
5170 samplepos_t grab = grab_sample ();
5171 samplepos_t lpf = last_pointer_sample ();
5173 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5174 grab = raw_grab_sample ();
5175 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
5189 if (current_pointer_y() < grab_y()) {
5190 y1 = current_pointer_y();
5193 y2 = current_pointer_y();
5197 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
5201 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
5203 if (movement_occurred) {
5205 motion (event, false);
5206 do_select_things (event, false);
5212 bool do_deselect = true;
5213 MidiTimeAxisView* mtv;
5215 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
5217 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
5218 /* nothing selected */
5219 add_midi_region (mtv, true);
5220 do_deselect = false;
5224 /* do not deselect if Primary or Tertiary (toggle-select or
5225 * extend-select are pressed.
5228 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
5229 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
5236 _editor->rubberband_rect->hide();
5240 RubberbandSelectDrag::aborted (bool)
5242 _editor->rubberband_rect->hide ();
5245 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
5246 : RegionDrag (e, i, p, v)
5248 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
5252 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5254 Drag::start_grab (event, cursor);
5256 _editor->get_selection().add (_primary);
5258 MusicSample where (_primary->region()->position(), 0);
5259 setup_snap_delta (where);
5261 show_verbose_cursor_duration (where.sample, adjusted_current_sample (event), 0);
5265 TimeFXDrag::motion (GdkEvent* event, bool)
5267 RegionView* rv = _primary;
5268 StreamView* cv = rv->get_time_axis_view().view ();
5269 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
5270 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
5271 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
5272 MusicSample pf (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
5274 _editor->snap_to_with_modifier (pf, event);
5275 pf.sample -= snap_delta (event->button.state);
5277 if (pf.sample > rv->region()->position()) {
5278 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf.sample, layers, layer);
5281 show_verbose_cursor_duration (_primary->region()->position(), pf.sample, 0);
5285 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
5287 /* this may have been a single click, no drag. We still want the dialog
5288 to show up in that case, so that the user can manually edit the
5289 parameters for the timestretch.
5292 float fraction = 1.0;
5294 if (movement_occurred) {
5296 motion (event, false);
5298 _primary->get_time_axis_view().hide_timestretch ();
5300 samplepos_t adjusted_sample_pos = adjusted_current_sample (event);
5302 if (adjusted_sample_pos < _primary->region()->position()) {
5303 /* backwards drag of the left edge - not usable */
5307 samplecnt_t newlen = adjusted_sample_pos - _primary->region()->position();
5309 fraction = (double) newlen / (double) _primary->region()->length();
5311 #ifndef USE_RUBBERBAND
5312 // Soundtouch uses fraction / 100 instead of normal (/ 1)
5313 if (_primary->region()->data_type() == DataType::AUDIO) {
5314 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
5319 if (!_editor->get_selection().regions.empty()) {
5320 /* primary will already be included in the selection, and edit
5321 group shared editing will propagate selection across
5322 equivalent regions, so just use the current region
5326 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
5327 error << _("An error occurred while executing time stretch operation") << endmsg;
5333 TimeFXDrag::aborted (bool)
5335 _primary->get_time_axis_view().hide_timestretch ();
5338 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5341 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5345 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5347 Drag::start_grab (event);
5351 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5353 _editor->scrub (adjusted_current_sample (0, false), _drags->current_pointer_x ());
5357 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5359 if (movement_occurred && _editor->session()) {
5360 /* make sure we stop */
5361 _editor->session()->request_transport_speed (0.0);
5366 ScrubDrag::aborted (bool)
5371 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5375 , _track_selection_at_start (e)
5376 , _time_selection_at_start (!_editor->get_selection().time.empty())
5378 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5380 if (_time_selection_at_start) {
5381 start_at_start = _editor->get_selection().time.start();
5382 end_at_start = _editor->get_selection().time.end_sample();
5387 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5389 if (_editor->session() == 0) {
5393 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5395 switch (_operation) {
5396 case CreateSelection:
5397 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5402 cursor = _editor->cursors()->selector;
5403 Drag::start_grab (event, cursor);
5406 case SelectionStartTrim:
5407 if (_editor->clicked_axisview) {
5408 _editor->clicked_axisview->order_selection_trims (_item, true);
5410 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5413 case SelectionEndTrim:
5414 if (_editor->clicked_axisview) {
5415 _editor->clicked_axisview->order_selection_trims (_item, false);
5417 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5421 Drag::start_grab (event, cursor);
5424 case SelectionExtend:
5425 Drag::start_grab (event, cursor);
5429 if (_operation == SelectionMove) {
5430 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5432 show_verbose_cursor_time (adjusted_current_sample (event));
5437 SelectionDrag::setup_pointer_sample_offset ()
5439 switch (_operation) {
5440 case CreateSelection:
5441 _pointer_sample_offset = 0;
5444 case SelectionStartTrim:
5446 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].start;
5449 case SelectionEndTrim:
5450 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].end;
5453 case SelectionExtend:
5459 SelectionDrag::motion (GdkEvent* event, bool first_move)
5461 samplepos_t start = 0;
5462 samplepos_t end = 0;
5463 samplecnt_t length = 0;
5464 samplecnt_t distance = 0;
5465 MusicSample start_mf (0, 0);
5466 samplepos_t const pending_position = adjusted_current_sample (event);
5468 if (_operation != CreateSelection && pending_position == last_pointer_sample()) {
5473 _track_selection_at_start = _editor->selection->tracks;
5476 switch (_operation) {
5477 case CreateSelection:
5479 MusicSample grab (grab_sample (), 0);
5481 grab.sample = adjusted_current_sample (event, false);
5482 if (grab.sample < pending_position) {
5483 _editor->snap_to (grab, RoundDownMaybe);
5485 _editor->snap_to (grab, RoundUpMaybe);
5489 if (pending_position < grab.sample) {
5490 start = pending_position;
5493 end = pending_position;
5494 start = grab.sample;
5497 /* first drag: Either add to the selection
5498 or create a new selection
5505 /* adding to the selection */
5506 _editor->set_selected_track_as_side_effect (Selection::Add);
5507 _editor->clicked_selection = _editor->selection->add (start, end);
5514 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5515 _editor->set_selected_track_as_side_effect (Selection::Set);
5518 _editor->clicked_selection = _editor->selection->set (start, end);
5522 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5523 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5524 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5526 _editor->selection->add (atest);
5530 /* select all tracks within the rectangle that we've marked out so far */
5531 TrackViewList new_selection;
5532 TrackViewList& all_tracks (_editor->track_views);
5534 ArdourCanvas::Coord const top = grab_y();
5535 ArdourCanvas::Coord const bottom = current_pointer_y();
5537 if (top >= 0 && bottom >= 0) {
5539 //first, find the tracks that are covered in the y range selection
5540 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5541 if ((*i)->covered_by_y_range (top, bottom)) {
5542 new_selection.push_back (*i);
5546 //now compare our list with the current selection, and add as necessary
5547 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5548 TrackViewList tracks_to_add;
5549 TrackViewList tracks_to_remove;
5550 vector<RouteGroup*> selected_route_groups;
5553 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5554 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5555 tracks_to_remove.push_back (*i);
5557 RouteGroup* rg = (*i)->route_group();
5558 if (rg && rg->is_active() && rg->is_select()) {
5559 selected_route_groups.push_back (rg);
5565 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5566 if (!_editor->selection->tracks.contains (*i)) {
5567 tracks_to_add.push_back (*i);
5568 RouteGroup* rg = (*i)->route_group();
5570 if (rg && rg->is_active() && rg->is_select()) {
5571 selected_route_groups.push_back (rg);
5576 _editor->selection->add (tracks_to_add);
5578 if (!tracks_to_remove.empty()) {
5580 /* check all these to-be-removed tracks against the
5581 * possibility that they are selected by being
5582 * in the same group as an approved track.
5585 for (TrackViewList::iterator i = tracks_to_remove.begin(); i != tracks_to_remove.end(); ) {
5586 RouteGroup* rg = (*i)->route_group();
5588 if (rg && find (selected_route_groups.begin(), selected_route_groups.end(), rg) != selected_route_groups.end()) {
5589 i = tracks_to_remove.erase (i);
5595 /* remove whatever is left */
5597 _editor->selection->remove (tracks_to_remove);
5603 case SelectionStartTrim:
5605 end = _editor->selection->time[_editor->clicked_selection].end;
5607 if (pending_position > end) {
5610 start = pending_position;
5614 case SelectionEndTrim:
5616 start = _editor->selection->time[_editor->clicked_selection].start;
5618 if (pending_position < start) {
5621 end = pending_position;
5628 start = _editor->selection->time[_editor->clicked_selection].start;
5629 end = _editor->selection->time[_editor->clicked_selection].end;
5631 length = end - start;
5632 distance = pending_position - start;
5633 start = pending_position;
5635 start_mf.sample = start;
5636 _editor->snap_to (start_mf);
5638 end = start_mf.sample + length;
5642 case SelectionExtend:
5647 switch (_operation) {
5649 if (_time_selection_at_start) {
5650 _editor->selection->move_time (distance);
5654 _editor->selection->replace (_editor->clicked_selection, start, end);
5658 if (_operation == SelectionMove) {
5659 show_verbose_cursor_time(start);
5661 show_verbose_cursor_time(pending_position);
5666 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5668 Session* s = _editor->session();
5670 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5671 if (movement_occurred) {
5672 motion (event, false);
5673 /* XXX this is not object-oriented programming at all. ick */
5674 if (_editor->selection->time.consolidate()) {
5675 _editor->selection->TimeChanged ();
5678 /* XXX what if its a music time selection? */
5680 if (s->get_play_range() && s->transport_rolling()) {
5681 s->request_play_range (&_editor->selection->time, true);
5682 } else if (!s->config.get_external_sync()) {
5683 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5684 s->request_locate (_editor->get_selection().time.start());
5688 if (_editor->get_selection().time.length() != 0) {
5689 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_sample());
5691 s->clear_range_selection ();
5696 /* just a click, no pointer movement.
5699 if (was_double_click()) {
5700 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5701 _editor->temporal_zoom_selection (Both);
5706 if (_operation == SelectionExtend) {
5707 if (_time_selection_at_start) {
5708 samplepos_t pos = adjusted_current_sample (event, false);
5709 samplepos_t start = min (pos, start_at_start);
5710 samplepos_t end = max (pos, end_at_start);
5711 _editor->selection->set (start, end);
5714 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5715 if (_editor->clicked_selection) {
5716 _editor->selection->remove (_editor->clicked_selection);
5719 if (!_editor->clicked_selection) {
5720 _editor->selection->clear_time();
5725 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5726 _editor->selection->set (_editor->clicked_axisview);
5729 if (s && s->get_play_range () && s->transport_rolling()) {
5730 s->request_stop (false, false);
5735 _editor->stop_canvas_autoscroll ();
5736 _editor->clicked_selection = 0;
5737 _editor->commit_reversible_selection_op ();
5741 SelectionDrag::aborted (bool)
5746 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5747 : Drag (e, i, false),
5751 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5753 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5754 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5755 physical_screen_height (_editor->current_toplevel()->get_window())));
5756 _drag_rect->hide ();
5758 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5759 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5762 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5764 /* normal canvas items will be cleaned up when their parent group is deleted. But
5765 this item is created as the child of a long-lived parent group, and so we
5766 need to explicitly delete it.
5772 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5774 if (_editor->session() == 0) {
5778 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5780 if (!_editor->temp_location) {
5781 _editor->temp_location = new Location (*_editor->session());
5784 switch (_operation) {
5785 case CreateSkipMarker:
5786 case CreateRangeMarker:
5787 case CreateTransportMarker:
5788 case CreateCDMarker:
5790 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5795 cursor = _editor->cursors()->selector;
5799 Drag::start_grab (event, cursor);
5801 show_verbose_cursor_time (adjusted_current_sample (event));
5805 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5807 samplepos_t start = 0;
5808 samplepos_t end = 0;
5809 ArdourCanvas::Rectangle *crect;
5811 switch (_operation) {
5812 case CreateSkipMarker:
5813 crect = _editor->range_bar_drag_rect;
5815 case CreateRangeMarker:
5816 crect = _editor->range_bar_drag_rect;
5818 case CreateTransportMarker:
5819 crect = _editor->transport_bar_drag_rect;
5821 case CreateCDMarker:
5822 crect = _editor->cd_marker_bar_drag_rect;
5825 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5830 samplepos_t const pf = adjusted_current_sample (event);
5832 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5833 MusicSample grab (grab_sample (), 0);
5834 _editor->snap_to (grab);
5836 if (pf < grab_sample()) {
5841 start = grab.sample;
5844 /* first drag: Either add to the selection
5845 or create a new selection.
5850 _editor->temp_location->set (start, end);
5854 update_item (_editor->temp_location);
5856 //_drag_rect->raise_to_top();
5862 _editor->temp_location->set (start, end);
5864 double x1 = _editor->sample_to_pixel (start);
5865 double x2 = _editor->sample_to_pixel (end);
5869 update_item (_editor->temp_location);
5872 show_verbose_cursor_time (pf);
5877 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5879 Location * newloc = 0;
5883 if (movement_occurred) {
5884 motion (event, false);
5887 switch (_operation) {
5888 case CreateSkipMarker:
5889 case CreateRangeMarker:
5890 case CreateCDMarker:
5892 XMLNode &before = _editor->session()->locations()->get_state();
5893 if (_operation == CreateSkipMarker) {
5894 _editor->begin_reversible_command (_("new skip marker"));
5895 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5896 flags = Location::IsRangeMarker | Location::IsSkip;
5897 _editor->range_bar_drag_rect->hide();
5898 } else if (_operation == CreateCDMarker) {
5899 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5900 _editor->begin_reversible_command (_("new CD marker"));
5901 flags = Location::IsRangeMarker | Location::IsCDMarker;
5902 _editor->cd_marker_bar_drag_rect->hide();
5904 _editor->begin_reversible_command (_("new skip marker"));
5905 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5906 flags = Location::IsRangeMarker;
5907 _editor->range_bar_drag_rect->hide();
5909 newloc = new Location (
5910 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5911 , _editor->get_grid_music_divisions (event->button.state));
5913 _editor->session()->locations()->add (newloc, true);
5914 XMLNode &after = _editor->session()->locations()->get_state();
5915 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5916 _editor->commit_reversible_command ();
5920 case CreateTransportMarker:
5921 // popup menu to pick loop or punch
5922 _editor->new_transport_marker_context_menu (&event->button, _item);
5928 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5930 if (_operation == CreateTransportMarker) {
5932 /* didn't drag, so just locate */
5934 _editor->session()->request_locate (grab_sample(), _editor->session()->transport_rolling());
5936 } else if (_operation == CreateCDMarker) {
5938 /* didn't drag, but mark is already created so do
5941 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5946 _editor->session()->locations()->marks_either_side (grab_sample(), start, end);
5948 if (end == max_samplepos) {
5949 end = _editor->session()->current_end_sample ();
5952 if (start == max_samplepos) {
5953 start = _editor->session()->current_start_sample ();
5956 switch (_editor->mouse_mode) {
5958 /* find the two markers on either side and then make the selection from it */
5959 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5963 /* find the two markers on either side of the click and make the range out of it */
5964 _editor->selection->set (start, end);
5973 _editor->stop_canvas_autoscroll ();
5977 RangeMarkerBarDrag::aborted (bool movement_occurred)
5979 if (movement_occurred) {
5980 _drag_rect->hide ();
5985 RangeMarkerBarDrag::update_item (Location* location)
5987 double const x1 = _editor->sample_to_pixel (location->start());
5988 double const x2 = _editor->sample_to_pixel (location->end());
5990 _drag_rect->set_x0 (x1);
5991 _drag_rect->set_x1 (x2);
5994 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5996 , _cumulative_dx (0)
5997 , _cumulative_dy (0)
5999 , _was_selected (false)
6002 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
6004 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
6006 _region = &_primary->region_view ();
6007 _note_height = _region->midi_stream_view()->note_height ();
6011 NoteDrag::setup_pointer_sample_offset ()
6013 _pointer_sample_offset = raw_grab_sample()
6014 - _editor->session()->tempo_map().sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6018 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
6020 Drag::start_grab (event);
6022 if (ArdourKeyboard::indicates_copy (event->button.state)) {
6028 setup_snap_delta (MusicSample (_region->source_beats_to_absolute_samples (_primary->note()->time ()), 0));
6030 if (!(_was_selected = _primary->selected())) {
6032 /* tertiary-click means extend selection - we'll do that on button release,
6033 so don't add it here, because otherwise we make it hard to figure
6034 out the "extend-to" range.
6037 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
6040 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
6043 _region->note_selected (_primary, true);
6045 _editor->get_selection().clear_points();
6046 _region->unique_select (_primary);
6052 /** @return Current total drag x change in quarter notes */
6054 NoteDrag::total_dx (GdkEvent * event) const
6056 if (_x_constrained) {
6060 TempoMap& map (_editor->session()->tempo_map());
6063 sampleoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
6065 /* primary note time */
6066 sampleoffset_t const n = map.sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6068 /* primary note time in quarter notes */
6069 double const n_qn = _region->session_relative_qn (_primary->note()->time().to_double());
6071 /* new time of the primary note in session samples */
6072 sampleoffset_t st = n + dx + snap_delta (event->button.state);
6074 /* possibly snap and return corresponding delta in quarter notes */
6075 MusicSample snap (st, 0);
6076 _editor->snap_to_with_modifier (snap, event);
6077 double ret = map.exact_qn_at_sample (snap.sample, snap.division) - n_qn - snap_delta_music (event->button.state);
6079 /* prevent the earliest note being dragged earlier than the region's start position */
6080 if (_earliest + ret < _region->midi_region()->start_beats()) {
6081 ret -= (_earliest + ret) - _region->midi_region()->start_beats();
6087 /** @return Current total drag y change in note number */
6089 NoteDrag::total_dy () const
6091 if (_y_constrained) {
6095 double const y = _region->midi_view()->y_position ();
6096 /* new current note */
6097 uint8_t n = _region->y_to_note (current_pointer_y () - y);
6099 MidiStreamView* msv = _region->midi_stream_view ();
6100 n = max (msv->lowest_note(), n);
6101 n = min (msv->highest_note(), n);
6102 /* and work out delta */
6103 return n - _region->y_to_note (grab_y() - y);
6107 NoteDrag::motion (GdkEvent * event, bool first_move)
6110 _earliest = _region->earliest_in_selection().to_double();
6112 /* make copies of all the selected notes */
6113 _primary = _region->copy_selection (_primary);
6117 /* Total change in x and y since the start of the drag */
6118 double const dx_qn = total_dx (event);
6119 int8_t const dy = total_dy ();
6121 /* Now work out what we have to do to the note canvas items to set this new drag delta */
6122 double const tdx = _x_constrained ? 0 : dx_qn - _cumulative_dx;
6123 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
6126 _cumulative_dx = dx_qn;
6127 _cumulative_dy += tdy;
6129 int8_t note_delta = total_dy();
6133 _region->move_copies (dx_qn, tdy, note_delta);
6135 _region->move_selection (dx_qn, tdy, note_delta);
6138 /* the new note value may be the same as the old one, but we
6139 * don't know what that means because the selection may have
6140 * involved more than one note and we might be doing something
6141 * odd with them. so show the note value anyway, always.
6144 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
6146 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
6152 NoteDrag::finished (GdkEvent* ev, bool moved)
6155 /* no motion - select note */
6157 if (_editor->current_mouse_mode() == Editing::MouseContent ||
6158 _editor->current_mouse_mode() == Editing::MouseDraw) {
6160 bool changed = false;
6162 if (_was_selected) {
6163 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6165 _region->note_deselected (_primary);
6168 _editor->get_selection().clear_points();
6169 _region->unique_select (_primary);
6173 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
6174 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6176 if (!extend && !add && _region->selection_size() > 1) {
6177 _editor->get_selection().clear_points();
6178 _region->unique_select (_primary);
6180 } else if (extend) {
6181 _region->note_selected (_primary, true, true);
6184 /* it was added during button press */
6191 _editor->begin_reversible_selection_op(X_("Select Note Release"));
6192 _editor->commit_reversible_selection_op();
6196 _region->note_dropped (_primary, total_dx (ev), total_dy(), _copy);
6201 NoteDrag::aborted (bool)
6206 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
6207 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
6208 : Drag (editor, atv->base_item ())
6210 , _y_origin (atv->y_position())
6211 , _nothing_to_drag (false)
6213 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6214 setup (atv->lines ());
6217 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
6218 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
6219 : Drag (editor, rv->get_canvas_group ())
6221 , _y_origin (rv->get_time_axis_view().y_position())
6222 , _nothing_to_drag (false)
6225 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6227 list<boost::shared_ptr<AutomationLine> > lines;
6229 AudioRegionView* audio_view;
6230 AutomationRegionView* automation_view;
6231 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
6232 lines.push_back (audio_view->get_gain_line ());
6233 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
6234 lines.push_back (automation_view->line ());
6237 error << _("Automation range drag created for invalid region type") << endmsg;
6243 /** @param lines AutomationLines to drag.
6244 * @param offset Offset from the session start to the points in the AutomationLines.
6247 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
6249 /* find the lines that overlap the ranges being dragged */
6250 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
6251 while (i != lines.end ()) {
6252 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
6255 pair<samplepos_t, samplepos_t> r = (*i)->get_point_x_range ();
6257 /* check this range against all the AudioRanges that we are using */
6258 list<AudioRange>::const_iterator k = _ranges.begin ();
6259 while (k != _ranges.end()) {
6260 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
6266 /* add it to our list if it overlaps at all */
6267 if (k != _ranges.end()) {
6272 _lines.push_back (n);
6278 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
6282 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
6284 return 1.0 - ((global_y - _y_origin) / line->height());
6288 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
6290 const double v = list->eval(x);
6291 return _integral ? rint(v) : v;
6295 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6297 Drag::start_grab (event, cursor);
6299 /* Get line states before we start changing things */
6300 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6301 i->state = &i->line->get_state ();
6302 i->original_fraction = y_fraction (i->line, current_pointer_y());
6305 if (_ranges.empty()) {
6307 /* No selected time ranges: drag all points */
6308 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6309 uint32_t const N = i->line->npoints ();
6310 for (uint32_t j = 0; j < N; ++j) {
6311 i->points.push_back (i->line->nth (j));
6317 if (_nothing_to_drag) {
6323 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
6325 if (_nothing_to_drag && !first_move) {
6330 _editor->begin_reversible_command (_("automation range move"));
6332 if (!_ranges.empty()) {
6334 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
6336 samplecnt_t const half = (i->start + i->end) / 2;
6338 /* find the line that this audio range starts in */
6339 list<Line>::iterator j = _lines.begin();
6340 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
6344 if (j != _lines.end()) {
6345 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6347 /* j is the line that this audio range starts in; fade into it;
6348 64 samples length plucked out of thin air.
6351 samplepos_t a = i->start + 64;
6356 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
6357 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
6359 XMLNode &before = the_list->get_state();
6360 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6361 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6363 if (add_p || add_q) {
6364 _editor->session()->add_command (
6365 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6369 /* same thing for the end */
6372 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
6376 if (j != _lines.end()) {
6377 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6379 /* j is the line that this audio range starts in; fade out of it;
6380 64 samples length plucked out of thin air.
6383 samplepos_t b = i->end - 64;
6388 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6389 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6391 XMLNode &before = the_list->get_state();
6392 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6393 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6395 if (add_p || add_q) {
6396 _editor->session()->add_command (
6397 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6402 _nothing_to_drag = true;
6404 /* Find all the points that should be dragged and put them in the relevant
6405 points lists in the Line structs.
6408 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6410 uint32_t const N = i->line->npoints ();
6411 for (uint32_t j = 0; j < N; ++j) {
6413 /* here's a control point on this line */
6414 ControlPoint* p = i->line->nth (j);
6415 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6417 /* see if it's inside a range */
6418 list<AudioRange>::const_iterator k = _ranges.begin ();
6419 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6423 if (k != _ranges.end()) {
6424 /* dragging this point */
6425 _nothing_to_drag = false;
6426 i->points.push_back (p);
6432 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6433 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6437 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6438 float const f = y_fraction (l->line, current_pointer_y());
6439 /* we are ignoring x position for this drag, so we can just pass in anything */
6440 pair<float, float> result;
6442 result = l->line->drag_motion (0, f, true, false, ignored);
6443 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (result.first, result.second));
6448 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6450 if (_nothing_to_drag || !motion_occurred) {
6454 motion (event, false);
6455 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6456 i->line->end_drag (false, 0);
6459 _editor->commit_reversible_command ();
6463 AutomationRangeDrag::aborted (bool)
6465 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6470 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6472 , initial_time_axis_view (itav)
6474 /* note that time_axis_view may be null if the regionview was created
6475 * as part of a copy operation.
6477 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6478 layer = v->region()->layer ();
6479 initial_y = v->get_canvas_group()->position().y;
6480 initial_playlist = v->region()->playlist ();
6481 initial_position = v->region()->position ();
6482 initial_end = v->region()->position () + v->region()->length ();
6485 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6486 : Drag (e, i->canvas_item ())
6489 , _cumulative_dx (0)
6491 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6492 _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time()),
6497 PatchChangeDrag::motion (GdkEvent* ev, bool)
6499 samplepos_t f = adjusted_current_sample (ev);
6500 boost::shared_ptr<Region> r = _region_view->region ();
6501 f = max (f, r->position ());
6502 f = min (f, r->last_sample ());
6504 samplecnt_t const dxf = f - grab_sample(); // permitted dx in samples
6505 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6506 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6507 _cumulative_dx = dxu;
6511 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6513 if (!movement_occurred) {
6514 if (was_double_click()) {
6515 _region_view->edit_patch_change (_patch_change);
6520 boost::shared_ptr<Region> r (_region_view->region ());
6521 samplepos_t f = adjusted_current_sample (ev);
6522 f = max (f, r->position ());
6523 f = min (f, r->last_sample ());
6525 _region_view->move_patch_change (
6527 _region_view->region_samples_to_region_beats (f - (r->position() - r->start()))
6532 PatchChangeDrag::aborted (bool)
6534 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6538 PatchChangeDrag::setup_pointer_sample_offset ()
6540 boost::shared_ptr<Region> region = _region_view->region ();
6541 _pointer_sample_offset = raw_grab_sample() - _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time());
6544 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6545 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6552 MidiRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6554 _region_view->update_drag_selection (
6556 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6560 MidiRubberbandSelectDrag::deselect_things ()
6565 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6566 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6569 _vertical_only = true;
6573 MidiVerticalSelectDrag::select_things (int button_state, samplepos_t /*x1*/, samplepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6575 double const y = _region_view->midi_view()->y_position ();
6577 y1 = max (0.0, y1 - y);
6578 y2 = max (0.0, y2 - y);
6580 _region_view->update_vertical_drag_selection (
6583 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6588 MidiVerticalSelectDrag::deselect_things ()
6593 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6594 : RubberbandSelectDrag (e, i)
6600 EditorRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool drag_in_progress)
6602 if (drag_in_progress) {
6603 /* We just want to select things at the end of the drag, not during it */
6607 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6609 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6611 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6613 _editor->commit_reversible_selection_op ();
6617 EditorRubberbandSelectDrag::deselect_things ()
6619 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6621 _editor->selection->clear_tracks();
6622 _editor->selection->clear_regions();
6623 _editor->selection->clear_points ();
6624 _editor->selection->clear_lines ();
6625 _editor->selection->clear_midi_notes ();
6627 _editor->commit_reversible_selection_op();
6630 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6635 _note[0] = _note[1] = 0;
6638 NoteCreateDrag::~NoteCreateDrag ()
6644 NoteCreateDrag::grid_samples (samplepos_t t) const
6647 const Temporal::Beats grid_beats = _region_view->get_grid_beats (t);
6648 const Temporal::Beats t_beats = _region_view->region_samples_to_region_beats (t);
6650 return _region_view->region_beats_to_region_samples (t_beats + grid_beats)
6651 - _region_view->region_beats_to_region_samples (t_beats);
6655 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6657 Drag::start_grab (event, cursor);
6659 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6660 TempoMap& map (_editor->session()->tempo_map());
6662 const samplepos_t pf = _drags->current_pointer_sample ();
6663 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6665 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6667 double eqaf = map.exact_qn_at_sample (pf, divisions);
6669 if (divisions != 0) {
6671 const double qaf = map.quarter_note_at_sample (pf);
6673 /* Hack so that we always snap to the note that we are over, instead of snapping
6674 to the next one if we're more than halfway through the one we're over.
6677 const double rem = eqaf - qaf;
6679 eqaf -= grid_beats.to_double();
6683 _note[0] = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6684 /* minimum initial length is grid beats */
6685 _note[1] = map.sample_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6687 double const x0 = _editor->sample_to_pixel (_note[0]);
6688 double const x1 = _editor->sample_to_pixel (_note[1]);
6689 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6691 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6692 _drag_rect->set_outline_all ();
6693 _drag_rect->set_outline_color (0xffffff99);
6694 _drag_rect->set_fill_color (0xffffff66);
6698 NoteCreateDrag::motion (GdkEvent* event, bool)
6700 TempoMap& map (_editor->session()->tempo_map());
6701 const samplepos_t pf = _drags->current_pointer_sample ();
6702 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6703 double eqaf = map.exact_qn_at_sample (pf, divisions);
6705 if (divisions != 0) {
6707 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6709 const double qaf = map.quarter_note_at_sample (pf);
6710 /* Hack so that we always snap to the note that we are over, instead of snapping
6711 to the next one if we're more than halfway through the one we're over.
6714 const double rem = eqaf - qaf;
6716 eqaf -= grid_beats.to_double();
6719 eqaf += grid_beats.to_double();
6721 _note[1] = max ((samplepos_t)0, map.sample_at_quarter_note (eqaf) - _region_view->region()->position ());
6723 double const x0 = _editor->sample_to_pixel (_note[0]);
6724 double const x1 = _editor->sample_to_pixel (_note[1]);
6725 _drag_rect->set_x0 (std::min(x0, x1));
6726 _drag_rect->set_x1 (std::max(x0, x1));
6730 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6732 /* we create a note even if there was no movement */
6733 samplepos_t const start = min (_note[0], _note[1]);
6734 samplepos_t const start_sess_rel = start + _region_view->region()->position();
6735 samplecnt_t length = max (_editor->pixel_to_sample (1.0), (samplecnt_t) fabs ((double)(_note[0] - _note[1])));
6736 samplecnt_t const g = grid_samples (start_sess_rel);
6738 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6742 TempoMap& map (_editor->session()->tempo_map());
6743 const double qn_length = map.quarter_notes_between_samples (start_sess_rel, start_sess_rel + length);
6744 Temporal::Beats qn_length_beats = max (Temporal::Beats::ticks(1), Temporal::Beats (qn_length));
6746 _editor->begin_reversible_command (_("Create Note"));
6747 _region_view->clear_editor_note_selection();
6748 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6749 _editor->commit_reversible_command ();
6753 NoteCreateDrag::y_to_region (double y) const
6756 _region_view->get_canvas_group()->canvas_to_item (x, y);
6761 NoteCreateDrag::aborted (bool)
6766 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6774 HitCreateDrag::~HitCreateDrag ()
6779 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6781 Drag::start_grab (event, cursor);
6783 TempoMap& map (_editor->session()->tempo_map());
6785 _y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6787 const samplepos_t pf = _drags->current_pointer_sample ();
6788 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6790 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6792 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6794 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6798 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6799 Temporal::Beats length = _region_view->get_grid_beats (pf);
6801 _editor->begin_reversible_command (_("Create Hit"));
6802 _region_view->clear_editor_note_selection();
6803 _region_view->create_note_at (start, _y, length, event->button.state, false);
6809 HitCreateDrag::motion (GdkEvent* event, bool)
6811 TempoMap& map (_editor->session()->tempo_map());
6813 const samplepos_t pf = _drags->current_pointer_sample ();
6814 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6816 if (divisions == 0) {
6820 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6821 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position ();
6823 if (_last_pos == start) {
6827 Temporal::Beats length = _region_view->get_grid_beats (pf);
6829 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6831 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6835 _region_view->create_note_at (start, _y, length, event->button.state, false);
6841 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6843 _editor->commit_reversible_command ();
6848 HitCreateDrag::y_to_region (double y) const
6851 _region_view->get_canvas_group()->canvas_to_item (x, y);
6856 HitCreateDrag::aborted (bool)
6861 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6866 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6870 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6872 Drag::start_grab (event, cursor);
6876 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6882 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6885 distance = _drags->current_pointer_x() - grab_x();
6886 len = ar->fade_in()->back()->when;
6888 distance = grab_x() - _drags->current_pointer_x();
6889 len = ar->fade_out()->back()->when;
6892 /* how long should it be ? */
6894 new_length = len + _editor->pixel_to_sample (distance);
6896 /* now check with the region that this is legal */
6898 new_length = ar->verify_xfade_bounds (new_length, start);
6901 arv->reset_fade_in_shape_width (ar, new_length);
6903 arv->reset_fade_out_shape_width (ar, new_length);
6908 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6914 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6917 distance = _drags->current_pointer_x() - grab_x();
6918 len = ar->fade_in()->back()->when;
6920 distance = grab_x() - _drags->current_pointer_x();
6921 len = ar->fade_out()->back()->when;
6924 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6926 _editor->begin_reversible_command ("xfade trim");
6927 ar->playlist()->clear_owned_changes ();
6930 ar->set_fade_in_length (new_length);
6932 ar->set_fade_out_length (new_length);
6935 /* Adjusting the xfade may affect other regions in the playlist, so we need
6936 to get undo Commands from the whole playlist rather than just the
6940 vector<Command*> cmds;
6941 ar->playlist()->rdiff (cmds);
6942 _editor->session()->add_commands (cmds);
6943 _editor->commit_reversible_command ();
6948 CrossfadeEdgeDrag::aborted (bool)
6951 // arv->redraw_start_xfade ();
6953 // arv->redraw_end_xfade ();
6957 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, samplepos_t pos)
6958 : Drag (e, item, true)
6959 , line (new EditorCursor (*e))
6961 line->set_position (pos);
6963 line->track_canvas_item().reparent (_editor->_drag_motion_group);
6966 RegionCutDrag::~RegionCutDrag ()
6972 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6974 Drag::start_grab (event, c);
6975 motion (event, false);
6979 RegionCutDrag::motion (GdkEvent* event, bool)
6981 MusicSample pos (_drags->current_pointer_sample(), 0);
6982 _editor->snap_to_with_modifier (pos, event);
6984 line->set_position (pos.sample);
6988 RegionCutDrag::finished (GdkEvent* event, bool)
6990 _editor->get_track_canvas()->canvas()->re_enter();
6993 MusicSample pos (_drags->current_pointer_sample(), 0);
6994 _editor->snap_to_with_modifier (pos, event);
6997 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos.sample);
7003 _editor->split_regions_at (pos, rs, false);
7007 RegionCutDrag::aborted (bool)