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 // if ( UIConfiguration::instance().get_snap_to_region_start() || UIConfiguration::instance().get_snap_to_region_end() || UIConfiguration::instance().get_snap_to_region_sync() ) {
297 // _editor->build_region_boundary_cache ();
301 /** Call to end a drag `successfully'. Ungrabs item and calls
302 * subclass' finished() method.
304 * @param event GDK event, or 0.
305 * @return true if some movement occurred, otherwise false.
308 Drag::end_grab (GdkEvent* event)
310 _editor->stop_canvas_autoscroll ();
314 finished (event, _move_threshold_passed);
316 _editor->verbose_cursor()->hide ();
319 return _move_threshold_passed;
323 Drag::adjusted_sample (samplepos_t f, GdkEvent const * event, bool snap) const
325 MusicSample pos (0, 0);
327 if (f > _pointer_sample_offset) {
328 pos.sample = f - _pointer_sample_offset;
332 _editor->snap_to_with_modifier (pos, event);
339 Drag::adjusted_current_sample (GdkEvent const * event, bool snap) const
341 return adjusted_sample (_drags->current_pointer_sample (), event, snap).sample;
345 Drag::snap_delta (guint state) const
347 if (ArdourKeyboard::indicates_snap_delta (state)) {
354 Drag::snap_delta_music (guint state) const
356 if (ArdourKeyboard::indicates_snap_delta (state)) {
357 return _snap_delta_music;
364 Drag::current_pointer_x() const
366 return _drags->current_pointer_x ();
370 Drag::current_pointer_y () const
372 if (!_trackview_only) {
373 return _drags->current_pointer_y ();
376 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
380 Drag::setup_snap_delta (MusicSample pos)
382 TempoMap& map (_editor->session()->tempo_map());
383 MusicSample snap (pos);
384 _editor->snap_to (snap, ARDOUR::RoundNearest, ARDOUR::SnapToAny_Visual, true);
385 _snap_delta = snap.sample - pos.sample;
387 _snap_delta_music = 0.0;
389 if (_snap_delta != 0) {
390 _snap_delta_music = map.exact_qn_at_sample (snap.sample, snap.division) - map.exact_qn_at_sample (pos.sample, pos.division);
395 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
397 /* check to see if we have moved in any way that matters since the last motion event */
398 if (_move_threshold_passed &&
399 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
400 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
404 pair<samplecnt_t, int> const threshold = move_threshold ();
406 bool const old_move_threshold_passed = _move_threshold_passed;
408 if (!_move_threshold_passed) {
410 bool const xp = (::llabs (_drags->current_pointer_sample () - _raw_grab_sample) >= threshold.first);
411 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
413 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
416 if (active (_editor->mouse_mode) && _move_threshold_passed) {
418 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
420 if (old_move_threshold_passed != _move_threshold_passed) {
424 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
425 _initially_vertical = true;
427 _initially_vertical = false;
429 /** check constraints for this drag.
430 * Note that the current convention is to use "contains" for
431 * key modifiers during motion and "equals" when initiating a drag.
432 * In this case we haven't moved yet, so "equals" applies here.
434 if (Config->get_edit_mode() != Lock) {
435 if (event->motion.state & Gdk::BUTTON2_MASK) {
436 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
437 if (_constraint_pressed) {
438 _x_constrained = false;
439 _y_constrained = true;
441 _x_constrained = true;
442 _y_constrained = false;
444 } else if (_constraint_pressed) {
445 // if dragging normally, the motion is constrained to the first direction of movement.
446 if (_initially_vertical) {
447 _x_constrained = true;
448 _y_constrained = false;
450 _x_constrained = false;
451 _y_constrained = true;
455 if (event->button.state & Gdk::BUTTON2_MASK) {
456 _x_constrained = false;
458 _x_constrained = true;
460 _y_constrained = false;
464 if (!from_autoscroll) {
465 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
468 if (!_editor->autoscroll_active() || from_autoscroll) {
471 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
473 motion (event, first_move && !_starting_point_passed);
475 if (first_move && !_starting_point_passed) {
476 _starting_point_passed = true;
479 _last_pointer_x = _drags->current_pointer_x ();
480 _last_pointer_y = current_pointer_y ();
481 _last_pointer_sample = adjusted_current_sample (event, false);
491 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
499 aborted (_move_threshold_passed);
501 _editor->stop_canvas_autoscroll ();
502 _editor->verbose_cursor()->hide ();
506 Drag::show_verbose_cursor_time (samplepos_t sample)
508 _editor->verbose_cursor()->set_time (sample);
509 _editor->verbose_cursor()->show ();
513 Drag::show_verbose_cursor_duration (samplepos_t start, samplepos_t end, double /*xoffset*/)
515 _editor->verbose_cursor()->set_duration (start, end);
516 _editor->verbose_cursor()->show ();
520 Drag::show_verbose_cursor_text (string const & text)
522 _editor->verbose_cursor()->set (text);
523 _editor->verbose_cursor()->show ();
526 boost::shared_ptr<Region>
527 Drag::add_midi_region (MidiTimeAxisView* view, bool commit)
529 if (_editor->session()) {
530 const TempoMap& map (_editor->session()->tempo_map());
531 samplecnt_t pos = grab_sample();
532 /* not that the frame rate used here can be affected by pull up/down which
535 samplecnt_t len = map.sample_at_beat (max (0.0, map.beat_at_sample (pos)) + 1.0) - pos;
536 return view->add_region (grab_sample(), len, commit);
539 return boost::shared_ptr<Region>();
542 struct TimeAxisViewStripableSorter {
543 bool operator() (TimeAxisView* tav_a, TimeAxisView* tav_b) {
544 boost::shared_ptr<ARDOUR::Stripable> const& a = tav_a->stripable ();
545 boost::shared_ptr<ARDOUR::Stripable> const& b = tav_b->stripable ();
546 return ARDOUR::Stripable::Sorter () (a, b);
550 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
555 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
557 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
558 as some of the regions we are dragging may be on such tracks.
561 TrackViewList track_views = _editor->track_views;
562 track_views.sort (TimeAxisViewStripableSorter ());
564 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
565 _time_axis_views.push_back (*i);
567 TimeAxisView::Children children_list = (*i)->get_child_list ();
568 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
569 _time_axis_views.push_back (j->get());
573 /* the list of views can be empty at this point if this is a region list-insert drag
576 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
577 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
580 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
584 RegionDrag::region_going_away (RegionView* v)
586 list<DraggingView>::iterator i = _views.begin ();
587 while (i != _views.end() && i->view != v) {
591 if (i != _views.end()) {
596 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
597 * or -1 if it is not found.
600 RegionDrag::find_time_axis_view (TimeAxisView* t) const
603 int const N = _time_axis_views.size ();
604 while (i < N && _time_axis_views[i] != t) {
608 if (_time_axis_views[i] != t) {
615 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
616 : RegionDrag (e, i, p, v)
618 , _ignore_video_lock (false)
619 , _last_position (0, 0)
621 , _last_pointer_time_axis_view (0)
622 , _last_pointer_layer (0)
627 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
631 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
633 Drag::start_grab (event, cursor);
634 setup_snap_delta (_last_position);
636 show_verbose_cursor_time (_last_position.sample);
638 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
640 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
641 assert(_last_pointer_time_axis_view >= 0);
642 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
645 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
646 _ignore_video_lock = true;
650 /* cross track dragging seems broken here. disabled for now. */
651 _y_constrained = true;
656 RegionMotionDrag::compute_x_delta (GdkEvent const * event, MusicSample* pending_region_position)
658 /* compute the amount of pointer motion in samples, and where
659 the region would be if we moved it by that much.
661 if (_x_constrained) {
662 *pending_region_position = _last_position;
666 *pending_region_position = adjusted_sample (_drags->current_pointer_sample (), event, false);
668 samplecnt_t sync_offset;
671 sync_offset = _primary->region()->sync_offset (sync_dir);
673 /* we don't handle a sync point that lies before zero.
675 if (sync_dir >= 0 || (sync_dir < 0 && pending_region_position->sample >= sync_offset)) {
677 samplecnt_t const sd = snap_delta (event->button.state);
678 MusicSample sync_snap (pending_region_position->sample + (sync_dir * sync_offset) + sd, 0);
679 _editor->snap_to_with_modifier (sync_snap, event);
680 if (sync_offset == 0 && sd == 0) {
681 *pending_region_position = sync_snap;
683 pending_region_position->set (_primary->region()->adjust_to_sync (sync_snap.sample) - sd, 0);
686 *pending_region_position = _last_position;
689 if (pending_region_position->sample > max_samplepos - _primary->region()->length()) {
690 *pending_region_position = _last_position;
695 bool const x_move_allowed = !_x_constrained;
697 if ((pending_region_position->sample != _last_position.sample) && x_move_allowed) {
699 /* x movement since last time (in pixels) */
700 dx = _editor->sample_to_pixel_unrounded (pending_region_position->sample - _last_position.sample);
702 /* total x movement */
703 samplecnt_t total_dx = _editor->pixel_to_sample (_total_x_delta + dx);
705 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
706 sampleoffset_t const off = i->view->region()->position() + total_dx;
708 dx = dx - _editor->sample_to_pixel_unrounded (off);
709 *pending_region_position = MusicSample (pending_region_position->sample - off, 0);
715 _editor->set_snapped_cursor_position(pending_region_position->sample);
721 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
727 const int tavsize = _time_axis_views.size();
728 const int dt = delta > 0 ? +1 : -1;
730 int target = start + delta - skip;
732 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
733 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
735 while (current >= 0 && current != target) {
737 if (current < 0 && dt < 0) {
740 if (current >= tavsize && dt > 0) {
743 if (current < 0 || current >= tavsize) {
747 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
748 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
752 if (distance_only && current == start + delta) {
760 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
762 if (_y_constrained) {
766 const int tavsize = _time_axis_views.size();
767 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
768 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
769 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
771 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
772 /* already in the drop zone */
773 if (delta_track >= 0) {
774 /* downward motion - OK if others are still not in the dropzone */
783 } else if (n >= tavsize) {
784 /* downward motion into drop zone. That's fine. */
788 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
789 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
790 /* not a track, or the wrong type */
794 double const l = i->layer + delta_layer;
796 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
797 mode to allow the user to place a region below another on layer 0.
799 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
800 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
801 If it has, the layers will be munged later anyway, so it's ok.
807 /* all regions being dragged are ok with this change */
811 struct DraggingViewSorter {
812 bool operator() (const DraggingView& a, const DraggingView& b) {
813 return a.time_axis_view < b.time_axis_view;
818 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
820 double delta_layer = 0;
821 int delta_time_axis_view = 0;
822 int current_pointer_time_axis_view = -1;
824 assert (!_views.empty ());
826 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
828 /* Find the TimeAxisView that the pointer is now over */
829 const double cur_y = current_pointer_y ();
830 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
831 TimeAxisView* tv = r.first;
833 if (!tv && cur_y < 0) {
834 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
838 /* find drop-zone y-position */
839 Coord last_track_bottom_edge;
840 last_track_bottom_edge = 0;
841 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
842 if (!(*t)->hidden()) {
843 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
848 if (tv && tv->view()) {
849 /* the mouse is over a track */
850 double layer = r.second;
852 if (first_move && tv->view()->layer_display() == Stacked) {
853 tv->view()->set_layer_display (Expanded);
856 /* Here's the current pointer position in terms of time axis view and layer */
857 current_pointer_time_axis_view = find_time_axis_view (tv);
858 assert(current_pointer_time_axis_view >= 0);
860 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
862 /* Work out the change in y */
864 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
865 if (!rtv || !rtv->is_track()) {
866 /* ignore non-tracks early on. we can't move any regions on them */
867 } else if (_last_pointer_time_axis_view < 0) {
868 /* Was in the drop-zone, now over a track.
869 * Hence it must be an upward move (from the bottom)
871 * track_index is still -1, so delta must be set to
872 * move up the correct number of tracks from the bottom.
874 * This is necessary because steps may be skipped if
875 * the bottom-most track is not a valid target and/or
876 * if there are hidden tracks at the bottom.
877 * Hence the initial offset (_ddropzone) as well as the
878 * last valid pointer position (_pdropzone) need to be
879 * taken into account.
881 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
883 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
886 /* TODO needs adjustment per DraggingView,
888 * e.g. select one region on the top-layer of a track
889 * and one region which is at the bottom-layer of another track
892 * Indicated drop-zones and layering is wrong.
893 * and may infer additional layers on the target-track
894 * (depending how many layers the original track had).
896 * Or select two regions (different layers) on a same track,
897 * move across a non-layer track.. -> layering info is lost.
898 * on drop either of the regions may be on top.
900 * Proposed solution: screw it :) well,
901 * don't use delta_layer, use an absolute value
902 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
903 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
904 * 3) iterate over all DraggingView, find the one that is over the track with most layers
905 * 4) proportionally scale layer to layers available on target
907 delta_layer = current_pointer_layer - _last_pointer_layer;
910 /* for automation lanes, there is a TimeAxisView but no ->view()
911 * if (!tv) -> dropzone
913 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
914 /* Moving into the drop-zone.. */
915 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
916 /* delta_time_axis_view may not be sufficient to move into the DZ
917 * the mouse may enter it, but it may not be a valid move due to
920 * -> remember the delta needed to move into the dropzone
922 _ddropzone = delta_time_axis_view;
923 /* ..but subtract hidden tracks (or routes) at the bottom.
924 * we silently move mover them
926 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
927 - _time_axis_views.size();
929 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
930 /* move around inside the zone.
931 * This allows to move further down until all regions are in the zone.
933 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
934 assert(ptr_y >= last_track_bottom_edge);
935 assert(_ddropzone > 0);
937 /* calculate mouse position in 'tracks' below last track. */
938 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
939 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
941 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
943 delta_time_axis_view = dzpos - _pdropzone;
944 } else if (dzpos < _pdropzone && _ndropzone > 0) {
945 // move up inside the DZ
946 delta_time_axis_view = dzpos - _pdropzone;
950 /* Work out the change in x */
951 TempoMap& tmap = _editor->session()->tempo_map();
952 MusicSample pending_region_position (0, 0);
953 double const x_delta = compute_x_delta (event, &pending_region_position);
955 double const last_pos_qn = tmap.exact_qn_at_sample (_last_position.sample, _last_position.division);
956 double const qn_delta = tmap.exact_qn_at_sample (pending_region_position.sample, pending_region_position.division) - last_pos_qn;
958 _last_position = pending_region_position;
960 /* calculate hidden tracks in current y-axis delta */
962 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
963 /* The mouse is more than one track below the dropzone.
964 * distance calculation is not needed (and would not work, either
965 * because the dropzone is "packed").
967 * Except when [partially] moving regions out of dropzone in a large step.
968 * (the mouse may or may not remain in the DZ)
969 * Hidden tracks at the bottom of the TAV need to be skipped.
971 * This also handles the case if the mouse entered the DZ
972 * in a large step (exessive delta), either due to fast-movement,
973 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
975 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
976 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
978 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
979 -_time_axis_views.size() - dt;
982 else if (_last_pointer_time_axis_view < 0) {
983 /* Moving out of the zone. Check for hidden tracks at the bottom. */
984 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
985 -_time_axis_views.size() - delta_time_axis_view;
987 /* calculate hidden tracks that are skipped by the pointer movement */
988 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
989 - _last_pointer_time_axis_view
990 - delta_time_axis_view;
993 /* Verify change in y */
994 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
995 /* this y movement is not allowed, so do no y movement this time */
996 delta_time_axis_view = 0;
1001 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1002 /* haven't reached next snap point, and we're not switching
1003 trackviews nor layers. nothing to do.
1008 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1009 PlaylistDropzoneMap playlist_dropzone_map;
1010 _ndropzone = 0; // number of elements currently in the dropzone
1013 /* sort views by time_axis.
1014 * This retains track order in the dropzone, regardless
1015 * of actual selection order
1017 _views.sort (DraggingViewSorter());
1019 /* count number of distinct tracks of all regions
1020 * being dragged, used for dropzone.
1022 int prev_track = -1;
1023 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1024 if (i->time_axis_view != prev_track) {
1025 prev_track = i->time_axis_view;
1031 _views.back().time_axis_view -
1032 _views.front().time_axis_view;
1034 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1035 - _views.back().time_axis_view;
1037 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1041 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1043 RegionView* rv = i->view;
1048 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1055 /* reparent the regionview into a group above all
1059 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1060 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1061 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1062 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1063 /* move the item so that it continues to appear at the
1064 same location now that its parent has changed.
1066 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1069 /* If we have moved tracks, we'll fudge the layer delta so that the
1070 region gets moved back onto layer 0 on its new track; this avoids
1071 confusion when dragging regions from non-zero layers onto different
1074 double this_delta_layer = delta_layer;
1075 if (delta_time_axis_view != 0) {
1076 this_delta_layer = - i->layer;
1079 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1081 int track_index = i->time_axis_view + this_delta_time_axis_view;
1082 assert(track_index >= 0);
1084 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1085 /* Track is in the Dropzone */
1087 i->time_axis_view = track_index;
1088 assert(i->time_axis_view >= (int) _time_axis_views.size());
1091 double yposition = 0;
1092 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1093 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1096 /* store index of each new playlist as a negative count, starting at -1 */
1098 if (pdz == playlist_dropzone_map.end()) {
1099 /* compute where this new track (which doesn't exist yet) will live
1102 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1104 /* How high is this region view ? */
1106 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1107 ArdourCanvas::Rect bbox;
1110 bbox = obbox.get ();
1113 last_track_bottom_edge += bbox.height();
1115 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1118 yposition = pdz->second;
1121 /* values are zero or negative, hence the use of min() */
1122 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1125 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1127 mrv->apply_note_range (60, 71, true);
1131 /* The TimeAxisView that this region is now over */
1132 TimeAxisView* current_tv = _time_axis_views[track_index];
1134 /* Ensure it is moved from stacked -> expanded if appropriate */
1135 if (current_tv->view()->layer_display() == Stacked) {
1136 current_tv->view()->set_layer_display (Expanded);
1139 /* We're only allowed to go -ve in layer on Expanded views */
1140 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1141 this_delta_layer = - i->layer;
1145 rv->set_height (current_tv->view()->child_height ());
1147 /* Update show/hidden status as the region view may have come from a hidden track,
1148 or have moved to one.
1150 if (current_tv->hidden ()) {
1151 rv->get_canvas_group()->hide ();
1153 rv->get_canvas_group()->show ();
1156 /* Update the DraggingView */
1157 i->time_axis_view = track_index;
1158 i->layer += this_delta_layer;
1161 _editor->mouse_brush_insert_region (rv, pending_region_position.sample);
1165 /* Get the y coordinate of the top of the track that this region is now over */
1166 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1168 /* And adjust for the layer that it should be on */
1169 StreamView* cv = current_tv->view ();
1170 switch (cv->layer_display ()) {
1174 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1177 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1181 /* need to get the parent of the regionview
1182 * canvas group and get its position in
1183 * equivalent coordinate space as the trackview
1184 * we are now dragging over.
1187 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1191 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1193 MidiStreamView* msv;
1194 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1195 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1200 /* Now move the region view */
1201 if (rv->region()->position_lock_style() == MusicTime) {
1202 double const last_qn = tmap.quarter_note_at_sample (rv->get_position());
1203 samplepos_t const x_pos_music = tmap.sample_at_quarter_note (last_qn + qn_delta);
1205 rv->set_position (x_pos_music, 0);
1206 rv->move (0, y_delta);
1208 rv->move (x_delta, y_delta);
1211 } /* foreach region */
1213 _total_x_delta += x_delta;
1215 if (x_delta != 0 && !_brushing) {
1216 show_verbose_cursor_time (_last_position.sample);
1219 /* keep track of pointer movement */
1221 /* the pointer is currently over a time axis view */
1223 if (_last_pointer_time_axis_view < 0) {
1224 /* last motion event was not over a time axis view
1225 * or last y-movement out of the dropzone was not valid
1228 if (delta_time_axis_view < 0) {
1229 /* in the drop zone, moving up */
1231 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1232 * We do not use negative _last_pointer_time_axis_view because
1233 * the dropzone is "packed" (the actual track offset is ignored)
1235 * As opposed to the actual number
1236 * of elements in the dropzone (_ndropzone)
1237 * _pdropzone is not constrained. This is necessary
1238 * to allow moving multiple regions with y-distance
1241 * There can be 0 elements in the dropzone,
1242 * even though the drag-pointer is inside the DZ.
1245 * [ Audio-track, Midi-track, Audio-track, DZ ]
1246 * move regions from both audio tracks at the same time into the
1247 * DZ by grabbing the region in the bottom track.
1249 assert(current_pointer_time_axis_view >= 0);
1250 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1254 /* only move out of the zone if the movement is OK */
1255 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1256 assert(delta_time_axis_view < 0);
1257 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1258 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1259 * the current position can be calculated as follows:
1261 // a well placed oofus attack can still throw this off.
1262 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1263 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1266 /* last motion event was also over a time axis view */
1267 _last_pointer_time_axis_view += delta_time_axis_view;
1268 assert(_last_pointer_time_axis_view >= 0);
1273 /* the pointer is not over a time axis view */
1274 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1275 _pdropzone += delta_time_axis_view - delta_skip;
1276 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1279 _last_pointer_layer += delta_layer;
1283 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1285 if (_copy && first_move) {
1286 if (_x_constrained && !_brushing) {
1287 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1288 } else if (!_brushing) {
1289 _editor->begin_reversible_command (Operations::region_copy);
1290 } else if (_brushing) {
1291 _editor->begin_reversible_command (Operations::drag_region_brush);
1293 /* duplicate the regionview(s) and region(s) */
1295 list<DraggingView> new_regionviews;
1297 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1299 RegionView* rv = i->view;
1300 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1301 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1303 const boost::shared_ptr<const Region> original = rv->region();
1304 boost::shared_ptr<Region> region_copy;
1306 region_copy = RegionFactory::create (original, true);
1308 /* need to set this so that the drop zone code can work. This doesn't
1309 actually put the region into the playlist, but just sets a weak pointer
1312 region_copy->set_playlist (original->playlist());
1316 boost::shared_ptr<AudioRegion> audioregion_copy
1317 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1319 nrv = new AudioRegionView (*arv, audioregion_copy);
1321 boost::shared_ptr<MidiRegion> midiregion_copy
1322 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1323 nrv = new MidiRegionView (*mrv, midiregion_copy);
1328 nrv->get_canvas_group()->show ();
1329 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1331 /* swap _primary to the copy */
1333 if (rv == _primary) {
1337 /* ..and deselect the one we copied */
1339 rv->set_selected (false);
1342 if (!new_regionviews.empty()) {
1344 /* reflect the fact that we are dragging the copies */
1346 _views = new_regionviews;
1348 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1351 } else if (!_copy && first_move) {
1352 if (_x_constrained && !_brushing) {
1353 _editor->begin_reversible_command (_("fixed time region drag"));
1354 } else if (!_brushing) {
1355 _editor->begin_reversible_command (Operations::region_drag);
1356 } else if (_brushing) {
1357 _editor->begin_reversible_command (Operations::drag_region_brush);
1360 RegionMotionDrag::motion (event, first_move);
1364 RegionMotionDrag::finished (GdkEvent *, bool)
1366 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1367 if (!(*i)->view()) {
1371 if ((*i)->view()->layer_display() == Expanded) {
1372 (*i)->view()->set_layer_display (Stacked);
1378 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1380 RegionMotionDrag::finished (ev, movement_occurred);
1382 if (!movement_occurred) {
1386 if (was_double_click() && !_views.empty()) {
1387 DraggingView dv = _views.front();
1388 _editor->edit_region (dv.view);
1394 assert (!_views.empty ());
1396 /* We might have hidden region views so that they weren't visible during the drag
1397 (when they have been reparented). Now everything can be shown again, as region
1398 views are back in their track parent groups.
1400 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1401 i->view->get_canvas_group()->show ();
1404 bool const changed_position = (_last_position.sample != _primary->region()->position());
1405 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1429 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1431 if (!ARDOUR_UI_UTILS::engine_is_running ()) {
1435 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1438 TimeAxisView* tav = 0;
1440 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1441 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1442 uint32_t output_chan = region->n_channels();
1443 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1444 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1446 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1447 tav =_editor->time_axis_view_from_stripable (audio_tracks.front());
1449 ChanCount one_midi_port (DataType::MIDI, 1);
1450 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1451 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1452 Config->get_strict_io () || Profile->get_mixbus (),
1453 boost::shared_ptr<ARDOUR::PluginInfo>(),
1454 (ARDOUR::Plugin::PresetRecord*) 0,
1455 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1456 tav = _editor->time_axis_view_from_stripable (midi_tracks.front());
1460 tav->set_height (original->current_height());
1463 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1466 return dynamic_cast<RouteTimeAxisView*> (tav);
1470 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, MusicSample last_position, int32_t const ev_state)
1472 RegionSelection new_views;
1473 PlaylistSet modified_playlists;
1474 RouteTimeAxisView* new_time_axis_view = 0;
1475 samplecnt_t const drag_delta = _primary->region()->position() - _last_position.sample;
1477 TempoMap& tmap (_editor->session()->tempo_map());
1478 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1479 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1482 /* all changes were made during motion event handlers */
1484 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1488 _editor->commit_reversible_command ();
1492 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1493 PlaylistMapping playlist_mapping;
1495 /* insert the regions into their new playlists */
1496 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1498 RouteTimeAxisView* dest_rtv = 0;
1500 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1504 MusicSample where (0, 0);
1505 double quarter_note;
1507 if (changed_position && !_x_constrained) {
1508 where.set (i->view->region()->position() - drag_delta, 0);
1509 quarter_note = i->view->region()->quarter_note() - qn_delta;
1511 /* region has not moved - divisor will not affect musical pos */
1512 where.set (i->view->region()->position(), 0);
1513 quarter_note = i->view->region()->quarter_note();
1516 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1517 /* dragged to drop zone */
1519 PlaylistMapping::iterator pm;
1521 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1522 /* first region from this original playlist: create a new track */
1523 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1524 if(!new_time_axis_view) {
1528 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1529 dest_rtv = new_time_axis_view;
1531 /* we already created a new track for regions from this playlist, use it */
1532 dest_rtv = pm->second;
1535 /* destination time axis view is the one we dragged to */
1536 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1539 if (dest_rtv != 0) {
1540 RegionView* new_view;
1541 if (i->view == _primary && !_x_constrained) {
1542 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, last_position, last_pos_qn,
1543 modified_playlists, true);
1545 if (i->view->region()->position_lock_style() == AudioTime) {
1546 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1547 modified_playlists);
1549 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1550 modified_playlists, true);
1554 if (new_view != 0) {
1555 new_views.push_back (new_view);
1559 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1560 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1563 list<DraggingView>::const_iterator next = i;
1569 /* If we've created new regions either by copying or moving
1570 to a new track, we want to replace the old selection with the new ones
1573 if (new_views.size() > 0) {
1574 _editor->selection->set (new_views);
1577 /* write commands for the accumulated diffs for all our modified playlists */
1578 add_stateful_diff_commands_for_playlists (modified_playlists);
1580 _editor->commit_reversible_command ();
1584 RegionMoveDrag::finished_no_copy (
1585 bool const changed_position,
1586 bool const changed_tracks,
1587 MusicSample last_position,
1588 int32_t const ev_state
1591 RegionSelection new_views;
1592 PlaylistSet modified_playlists;
1593 PlaylistSet frozen_playlists;
1594 set<RouteTimeAxisView*> views_to_update;
1595 RouteTimeAxisView* new_time_axis_view = 0;
1596 samplecnt_t const drag_delta = _primary->region()->position() - last_position.sample;
1598 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1599 PlaylistMapping playlist_mapping;
1601 TempoMap& tmap (_editor->session()->tempo_map());
1602 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1603 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1605 std::set<boost::shared_ptr<const Region> > uniq;
1606 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1608 RegionView* rv = i->view;
1609 RouteTimeAxisView* dest_rtv = 0;
1611 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1616 if (uniq.find (rv->region()) != uniq.end()) {
1617 /* prevent duplicate moves when selecting regions from shared playlists */
1621 uniq.insert(rv->region());
1623 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1624 /* dragged to drop zone */
1626 PlaylistMapping::iterator pm;
1628 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1629 /* first region from this original playlist: create a new track */
1630 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1631 if(!new_time_axis_view) { // New track creation failed
1635 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1636 dest_rtv = new_time_axis_view;
1638 /* we already created a new track for regions from this playlist, use it */
1639 dest_rtv = pm->second;
1643 /* destination time axis view is the one we dragged to */
1644 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1649 double const dest_layer = i->layer;
1651 views_to_update.insert (dest_rtv);
1653 MusicSample where (0, 0);
1654 double quarter_note;
1656 if (changed_position && !_x_constrained) {
1657 where.set (rv->region()->position() - drag_delta, 0);
1658 quarter_note = i->view->region()->quarter_note() - qn_delta;
1660 where.set (rv->region()->position(), 0);
1661 quarter_note = i->view->region()->quarter_note();
1664 if (changed_tracks) {
1666 /* insert into new playlist */
1667 RegionView* new_view;
1668 if (rv == _primary && !_x_constrained) {
1669 new_view = insert_region_into_playlist (
1670 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, last_position, last_pos_qn,
1671 modified_playlists, true
1674 if (rv->region()->position_lock_style() == AudioTime) {
1676 new_view = insert_region_into_playlist (
1677 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1681 new_view = insert_region_into_playlist (
1682 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1683 modified_playlists, true
1688 if (new_view == 0) {
1693 new_views.push_back (new_view);
1695 /* remove from old playlist */
1697 /* the region that used to be in the old playlist is not
1698 moved to the new one - we use a copy of it. as a result,
1699 any existing editor for the region should no longer be
1702 rv->hide_region_editor();
1705 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1709 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1711 /* this movement may result in a crossfade being modified, or a layering change,
1712 so we need to get undo data from the playlist as well as the region.
1715 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1717 playlist->clear_changes ();
1720 rv->region()->clear_changes ();
1723 motion on the same track. plonk the previously reparented region
1724 back to its original canvas group (its streamview).
1725 No need to do anything for copies as they are fake regions which will be deleted.
1728 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1729 rv->get_canvas_group()->set_y_position (i->initial_y);
1732 /* just change the model */
1733 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1734 playlist->set_layer (rv->region(), dest_layer);
1737 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1739 r = frozen_playlists.insert (playlist);
1742 playlist->freeze ();
1744 if (rv == _primary) {
1745 rv->region()->set_position (where.sample, last_position.division);
1747 if (rv->region()->position_lock_style() == AudioTime) {
1748 /* move by sample offset */
1749 rv->region()->set_position (where.sample, 0);
1751 /* move by music offset */
1752 rv->region()->set_position_music (rv->region()->quarter_note() - qn_delta);
1755 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1758 if (changed_tracks) {
1760 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1761 was selected in all of them, then removing it from a playlist will have removed all
1762 trace of it from _views (i.e. there were N regions selected, we removed 1,
1763 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1764 corresponding regionview, and _views is now empty).
1766 This could have invalidated any and all iterators into _views.
1768 The heuristic we use here is: if the region selection is empty, break out of the loop
1769 here. if the region selection is not empty, then restart the loop because we know that
1770 we must have removed at least the region(view) we've just been working on as well as any
1771 that we processed on previous iterations.
1773 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1774 we can just iterate.
1778 if (_views.empty()) {
1789 /* If we've created new regions either by copying or moving
1790 to a new track, we want to replace the old selection with the new ones
1793 if (new_views.size() > 0) {
1794 _editor->selection->set (new_views);
1797 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1801 /* write commands for the accumulated diffs for all our modified playlists */
1802 add_stateful_diff_commands_for_playlists (modified_playlists);
1803 /* applies to _brushing */
1804 _editor->commit_reversible_command ();
1806 /* We have futzed with the layering of canvas items on our streamviews.
1807 If any region changed layer, this will have resulted in the stream
1808 views being asked to set up their region views, and all will be well.
1809 If not, we might now have badly-ordered region views. Ask the StreamViews
1810 involved to sort themselves out, just in case.
1813 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1814 (*i)->view()->playlist_layered ((*i)->track ());
1818 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1819 * @param region Region to remove.
1820 * @param playlist playlist To remove from.
1821 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1822 * that clear_changes () is only called once per playlist.
1825 RegionMoveDrag::remove_region_from_playlist (
1826 boost::shared_ptr<Region> region,
1827 boost::shared_ptr<Playlist> playlist,
1828 PlaylistSet& modified_playlists
1831 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1834 playlist->clear_changes ();
1837 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1841 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1842 * clearing the playlist's diff history first if necessary.
1843 * @param region Region to insert.
1844 * @param dest_rtv Destination RouteTimeAxisView.
1845 * @param dest_layer Destination layer.
1846 * @param where Destination position.
1847 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1848 * that clear_changes () is only called once per playlist.
1849 * @return New RegionView, or 0 if no insert was performed.
1852 RegionMoveDrag::insert_region_into_playlist (
1853 boost::shared_ptr<Region> region,
1854 RouteTimeAxisView* dest_rtv,
1857 double quarter_note,
1858 PlaylistSet& modified_playlists,
1862 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1863 if (!dest_playlist) {
1867 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1868 _new_region_view = 0;
1869 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1871 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1872 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1874 dest_playlist->clear_changes ();
1877 dest_playlist->add_region (region, where.sample, 1.0, false, where.division, quarter_note, true);
1879 dest_playlist->add_region (region, where.sample, 1.0, false, where.division);
1882 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1883 dest_playlist->set_layer (region, dest_layer);
1888 assert (_new_region_view);
1890 return _new_region_view;
1894 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1896 _new_region_view = rv;
1900 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1902 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1903 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1905 _editor->session()->add_command (c);
1914 RegionMoveDrag::aborted (bool movement_occurred)
1918 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1919 list<DraggingView>::const_iterator next = i;
1928 RegionMotionDrag::aborted (movement_occurred);
1933 RegionMotionDrag::aborted (bool)
1935 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1937 StreamView* sview = (*i)->view();
1940 if (sview->layer_display() == Expanded) {
1941 sview->set_layer_display (Stacked);
1946 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1947 RegionView* rv = i->view;
1948 TimeAxisView* tv = &(rv->get_time_axis_view ());
1949 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1951 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1952 rv->get_canvas_group()->set_y_position (0);
1954 rv->move (-_total_x_delta, 0);
1955 rv->set_height (rtv->view()->child_height ());
1959 /** @param b true to brush, otherwise false.
1960 * @param c true to make copies of the regions being moved, otherwise false.
1962 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1963 : RegionMotionDrag (e, i, p, v, b)
1965 , _new_region_view (0)
1967 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1969 _last_position = MusicSample (_primary->region()->position(), 0);
1973 RegionMoveDrag::setup_pointer_sample_offset ()
1975 _pointer_sample_offset = raw_grab_sample() - _last_position.sample;
1978 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, samplepos_t pos)
1979 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1981 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1983 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1984 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1986 _primary = v->view()->create_region_view (r, false, false);
1988 _primary->get_canvas_group()->show ();
1989 _primary->set_position (pos, 0);
1990 _views.push_back (DraggingView (_primary, this, v));
1992 _last_position = MusicSample (pos, 0);
1994 _item = _primary->get_canvas_group ();
1998 RegionInsertDrag::finished (GdkEvent * event, bool)
2000 int pos = _views.front().time_axis_view;
2001 assert(pos >= 0 && pos < (int)_time_axis_views.size());
2003 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
2005 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
2006 _primary->get_canvas_group()->set_y_position (0);
2008 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
2010 _editor->begin_reversible_command (Operations::insert_region);
2011 playlist->clear_changes ();
2012 _editor->snap_to_with_modifier (_last_position, event);
2014 playlist->add_region (_primary->region (), _last_position.sample, 1.0, false, _last_position.division);
2016 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2017 if (Config->get_edit_mode() == Ripple) {
2018 playlist->ripple (_last_position.sample, _primary->region()->length(), _primary->region());
2021 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2022 _editor->commit_reversible_command ();
2030 RegionInsertDrag::aborted (bool)
2037 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2038 : RegionMoveDrag (e, i, p, v, false, false)
2040 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2043 struct RegionSelectionByPosition {
2044 bool operator() (RegionView*a, RegionView* b) {
2045 return a->region()->position () < b->region()->position();
2050 RegionSpliceDrag::motion (GdkEvent* event, bool)
2052 /* Which trackview is this ? */
2054 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2055 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2057 /* The region motion is only processed if the pointer is over
2061 if (!tv || !tv->is_track()) {
2062 /* To make sure we hide the verbose canvas cursor when the mouse is
2063 not held over an audio track.
2065 _editor->verbose_cursor()->hide ();
2068 _editor->verbose_cursor()->show ();
2073 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2079 RegionSelection copy;
2080 _editor->selection->regions.by_position(copy);
2082 samplepos_t const pf = adjusted_current_sample (event);
2084 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2086 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2092 boost::shared_ptr<Playlist> playlist;
2094 if ((playlist = atv->playlist()) == 0) {
2098 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2103 if (pf < (*i)->region()->last_sample() + 1) {
2107 if (pf > (*i)->region()->first_sample()) {
2113 playlist->shuffle ((*i)->region(), dir);
2118 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2120 RegionMoveDrag::finished (event, movement_occurred);
2124 RegionSpliceDrag::aborted (bool)
2134 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, samplepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2137 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<samplepos_t>(where, max_samplepos));
2139 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2140 RegionSelection to_ripple;
2141 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2142 if ((*i)->position() >= where) {
2143 to_ripple.push_back (rtv->view()->find_view(*i));
2147 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2148 if (!exclude.contains (*i)) {
2149 // the selection has already been added to _views
2151 if (drag_in_progress) {
2152 // do the same things that RegionMotionDrag::motion does when
2153 // first_move is true, for the region views that we're adding
2154 // to _views this time
2157 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2158 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2159 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2160 rvg->reparent (_editor->_drag_motion_group);
2162 // we only need to move in the y direction
2163 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2168 _views.push_back (DraggingView (*i, this, tav));
2174 RegionRippleDrag::remove_unselected_from_views(samplecnt_t amount, bool move_regions)
2177 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2178 // we added all the regions after the selection
2180 std::list<DraggingView>::iterator to_erase = i++;
2181 if (!_editor->selection->regions.contains (to_erase->view)) {
2182 // restore the non-selected regions to their original playlist & positions,
2183 // and then ripple them back by the length of the regions that were dragged away
2184 // do the same things as RegionMotionDrag::aborted
2186 RegionView *rv = to_erase->view;
2187 TimeAxisView* tv = &(rv->get_time_axis_view ());
2188 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2191 // plonk them back onto their own track
2192 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2193 rv->get_canvas_group()->set_y_position (0);
2197 // move the underlying region to match the view
2198 rv->region()->set_position (rv->region()->position() + amount);
2200 // restore the view to match the underlying region's original position
2201 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2204 rv->set_height (rtv->view()->child_height ());
2205 _views.erase (to_erase);
2211 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2213 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2215 return allow_moves_across_tracks;
2223 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2224 : RegionMoveDrag (e, i, p, v, false, false)
2226 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2227 // compute length of selection
2228 RegionSelection selected_regions = _editor->selection->regions;
2229 selection_length = selected_regions.end_sample() - selected_regions.start();
2231 // we'll only allow dragging to another track in ripple mode if all the regions
2232 // being dragged start off on the same track
2233 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2236 exclude = new RegionList;
2237 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2238 exclude->push_back((*i)->region());
2241 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2242 RegionSelection copy;
2243 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2245 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2246 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2248 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2249 // find ripple start point on each applicable playlist
2250 RegionView *first_selected_on_this_track = NULL;
2251 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2252 if ((*i)->region()->playlist() == (*pi)) {
2253 // region is on this playlist - it's the first, because they're sorted
2254 first_selected_on_this_track = *i;
2258 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2259 add_all_after_to_views (
2260 &first_selected_on_this_track->get_time_axis_view(),
2261 first_selected_on_this_track->region()->position(),
2262 selected_regions, false);
2265 if (allow_moves_across_tracks) {
2266 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2274 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2276 /* Which trackview is this ? */
2278 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2279 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2281 /* The region motion is only processed if the pointer is over
2285 if (!tv || !tv->is_track()) {
2286 /* To make sure we hide the verbose canvas cursor when the mouse is
2287 not held over an audiotrack.
2289 _editor->verbose_cursor()->hide ();
2293 samplepos_t where = adjusted_current_sample (event);
2294 assert (where >= 0);
2295 MusicSample after (0, 0);
2296 double delta = compute_x_delta (event, &after);
2298 samplecnt_t amount = _editor->pixel_to_sample (delta);
2300 if (allow_moves_across_tracks) {
2301 // all the originally selected regions were on the same track
2303 samplecnt_t adjust = 0;
2304 if (prev_tav && tv != prev_tav) {
2305 // dragged onto a different track
2306 // remove the unselected regions from _views, restore them to their original positions
2307 // and add the regions after the drop point on the new playlist to _views instead.
2308 // undo the effect of rippling the previous playlist, and include the effect of removing
2309 // the dragged region(s) from this track
2311 remove_unselected_from_views (prev_amount, false);
2312 // ripple previous playlist according to the regions that have been removed onto the new playlist
2313 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2316 // move just the selected regions
2317 RegionMoveDrag::motion(event, first_move);
2319 // ensure that the ripple operation on the new playlist inserts selection_length time
2320 adjust = selection_length;
2321 // ripple the new current playlist
2322 tv->playlist()->ripple (where, amount+adjust, exclude);
2324 // add regions after point where drag entered this track to subsequent ripples
2325 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2328 // motion on same track
2329 RegionMoveDrag::motion(event, first_move);
2333 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2334 prev_position = where;
2336 // selection encompasses multiple tracks - just drag
2337 // cross-track drags are forbidden
2338 RegionMoveDrag::motion(event, first_move);
2341 if (!_x_constrained) {
2342 prev_amount += amount;
2345 _last_position = after;
2349 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2351 if (!movement_occurred) {
2355 if (was_double_click() && !_views.empty()) {
2356 DraggingView dv = _views.front();
2357 _editor->edit_region (dv.view);
2363 _editor->begin_reversible_command(_("Ripple drag"));
2365 // remove the regions being rippled from the dragging view, updating them to
2366 // their new positions
2367 remove_unselected_from_views (prev_amount, true);
2369 if (allow_moves_across_tracks) {
2371 // if regions were dragged across tracks, we've rippled any later
2372 // regions on the track the regions were dragged off, so we need
2373 // to add the original track to the undo record
2374 orig_tav->playlist()->clear_changes();
2375 vector<Command*> cmds;
2376 orig_tav->playlist()->rdiff (cmds);
2377 _editor->session()->add_commands (cmds);
2379 if (prev_tav && prev_tav != orig_tav) {
2380 prev_tav->playlist()->clear_changes();
2381 vector<Command*> cmds;
2382 prev_tav->playlist()->rdiff (cmds);
2383 _editor->session()->add_commands (cmds);
2386 // selection spanned multiple tracks - all will need adding to undo record
2388 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2389 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2391 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2392 (*pi)->clear_changes();
2393 vector<Command*> cmds;
2394 (*pi)->rdiff (cmds);
2395 _editor->session()->add_commands (cmds);
2399 // other modified playlists are added to undo by RegionMoveDrag::finished()
2400 RegionMoveDrag::finished (event, movement_occurred);
2401 _editor->commit_reversible_command();
2405 RegionRippleDrag::aborted (bool movement_occurred)
2407 RegionMoveDrag::aborted (movement_occurred);
2412 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2414 _view (dynamic_cast<MidiTimeAxisView*> (v))
2416 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2422 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2426 _editor->begin_reversible_command (_("create region"));
2427 _region = add_midi_region (_view, false);
2428 _view->playlist()->freeze ();
2432 samplepos_t const f = adjusted_current_sample (event);
2433 if (f <= grab_sample()) {
2434 _region->set_initial_position (f);
2437 /* Don't use a zero-length region, and subtract 1 sample from the snapped length
2438 so that if this region is duplicated, its duplicate starts on
2439 a snap point rather than 1 sample after a snap point. Otherwise things get
2440 a bit confusing as if a region starts 1 sample after a snap point, one cannot
2441 place snapped notes at the start of the region.
2443 if (f != grab_sample()) {
2444 samplecnt_t const len = (samplecnt_t) fabs ((double)(f - grab_sample () - 1));
2445 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2452 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2454 if (!movement_occurred) {
2455 add_midi_region (_view, true);
2457 _view->playlist()->thaw ();
2458 _editor->commit_reversible_command();
2463 RegionCreateDrag::aborted (bool)
2466 _view->playlist()->thaw ();
2472 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2477 , _was_selected (false)
2480 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2484 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2486 Gdk::Cursor* cursor;
2487 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2489 float x_fraction = cnote->mouse_x_fraction ();
2491 if (x_fraction > 0.0 && x_fraction < 0.25) {
2492 cursor = _editor->cursors()->left_side_trim;
2495 cursor = _editor->cursors()->right_side_trim;
2499 Drag::start_grab (event, cursor);
2501 region = &cnote->region_view();
2504 temp = region->snap_to_pixel (cnote->x0 (), true);
2505 _snap_delta = temp - cnote->x0 ();
2509 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2514 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2515 if (ms.size() > 1) {
2516 /* has to be relative, may make no sense otherwise */
2520 if (!(_was_selected = cnote->selected())) {
2522 /* tertiary-click means extend selection - we'll do that on button release,
2523 so don't add it here, because otherwise we make it hard to figure
2524 out the "extend-to" range.
2527 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2530 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2533 region->note_selected (cnote, true);
2535 _editor->get_selection().clear_points();
2536 region->unique_select (cnote);
2543 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2545 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2547 _editor->begin_reversible_command (_("resize notes"));
2549 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2550 MidiRegionSelection::iterator next;
2553 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2555 mrv->begin_resizing (at_front);
2561 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2562 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2564 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2568 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2570 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2571 if (_editor->snap_mode () != SnapOff) {
2575 if (_editor->snap_mode () == SnapOff) {
2577 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2578 if (apply_snap_delta) {
2584 if (apply_snap_delta) {
2588 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2594 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2596 if (!movement_occurred) {
2597 /* no motion - select note */
2598 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2599 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2600 _editor->current_mouse_mode() == Editing::MouseDraw) {
2602 bool changed = false;
2604 if (_was_selected) {
2605 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2607 region->note_deselected (cnote);
2610 _editor->get_selection().clear_points();
2611 region->unique_select (cnote);
2615 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2616 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2618 if (!extend && !add && region->selection_size() > 1) {
2619 _editor->get_selection().clear_points();
2620 region->unique_select (cnote);
2622 } else if (extend) {
2623 region->note_selected (cnote, true, true);
2626 /* it was added during button press */
2632 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2633 _editor->commit_reversible_selection_op();
2640 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2641 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2642 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2644 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2647 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2649 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2650 if (_editor->snap_mode () != SnapOff) {
2654 if (_editor->snap_mode () == SnapOff) {
2656 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2657 if (apply_snap_delta) {
2663 if (apply_snap_delta) {
2667 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2671 _editor->commit_reversible_command ();
2675 NoteResizeDrag::aborted (bool)
2677 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2678 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2679 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2681 mrv->abort_resizing ();
2686 AVDraggingView::AVDraggingView (RegionView* v)
2689 initial_position = v->region()->position ();
2692 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2695 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2698 TrackViewList empty;
2700 _editor->get_regions_after(rs, (samplepos_t) 0, empty);
2701 std::list<RegionView*> views = rs.by_layer();
2704 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2705 RegionView* rv = (*i);
2706 if (!rv->region()->video_locked()) {
2709 if (rv->region()->locked()) {
2712 _views.push_back (AVDraggingView (rv));
2717 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2719 Drag::start_grab (event);
2720 if (_editor->session() == 0) {
2724 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2730 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2734 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2735 _max_backwards_drag = (
2736 ARDOUR_UI::instance()->video_timeline->get_duration()
2737 + ARDOUR_UI::instance()->video_timeline->get_offset()
2738 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2741 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2742 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2743 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv (i->initial_position);
2746 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2749 Timecode::Time timecode;
2750 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2751 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);
2752 show_verbose_cursor_text (buf);
2756 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2758 if (_editor->session() == 0) {
2761 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2765 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2769 samplecnt_t dt = adjusted_current_sample (event) - raw_grab_sample() + _pointer_sample_offset;
2770 dt = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2772 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2773 dt = - _max_backwards_drag;
2776 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2777 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2779 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2780 RegionView* rv = i->view;
2781 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2784 rv->region()->clear_changes ();
2785 rv->region()->suspend_property_changes();
2787 rv->region()->set_position(i->initial_position + dt);
2788 rv->region_changed(ARDOUR::Properties::position);
2791 const samplepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2792 Timecode::Time timecode;
2793 Timecode::Time timediff;
2795 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2796 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2797 snprintf (buf, sizeof (buf),
2798 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2799 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2800 , _("Video Start:"),
2801 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2803 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2805 show_verbose_cursor_text (buf);
2809 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2811 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2818 if (!movement_occurred || ! _editor->session()) {
2822 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2824 _editor->begin_reversible_command (_("Move Video"));
2826 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2827 ARDOUR_UI::instance()->video_timeline->save_undo();
2828 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2829 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2831 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2832 i->view->drag_end();
2833 i->view->region()->resume_property_changes ();
2835 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2838 _editor->session()->maybe_update_session_range(
2839 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::sampleoffset_t) 0),
2840 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::sampleoffset_t) 0)
2844 _editor->commit_reversible_command ();
2848 VideoTimeLineDrag::aborted (bool)
2850 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2853 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2854 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2856 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2857 i->view->region()->resume_property_changes ();
2858 i->view->region()->set_position(i->initial_position);
2862 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2863 : RegionDrag (e, i, p, v)
2864 , _operation (StartTrim)
2865 , _preserve_fade_anchor (preserve_fade_anchor)
2866 , _jump_position_when_done (false)
2868 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2872 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2874 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2876 samplepos_t const region_start = _primary->region()->position();
2877 samplepos_t const region_end = _primary->region()->last_sample();
2878 samplecnt_t const region_length = _primary->region()->length();
2880 samplepos_t const pf = adjusted_current_sample (event);
2881 setup_snap_delta (MusicSample(region_start, 0));
2883 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2884 /* Move the contents of the region around without changing the region bounds */
2885 _operation = ContentsTrim;
2886 Drag::start_grab (event, _editor->cursors()->trimmer);
2888 /* These will get overridden for a point trim.*/
2889 if (pf < (region_start + region_length/2)) {
2890 /* closer to front */
2891 _operation = StartTrim;
2892 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2893 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2895 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2899 _operation = EndTrim;
2900 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2901 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2903 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2907 /* jump trim disabled for now
2908 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2909 _jump_position_when_done = true;
2913 switch (_operation) {
2915 show_verbose_cursor_time (region_start);
2918 show_verbose_cursor_duration (region_start, region_end);
2921 show_verbose_cursor_time (pf);
2925 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2926 i->view->region()->suspend_property_changes ();
2931 TrimDrag::motion (GdkEvent* event, bool first_move)
2933 RegionView* rv = _primary;
2935 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2936 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2937 sampleoffset_t sample_delta = 0;
2939 MusicSample adj_sample = adjusted_sample (_drags->current_pointer_sample () + snap_delta (event->button.state), event, true);
2940 samplecnt_t dt = adj_sample.sample - raw_grab_sample () + _pointer_sample_offset - snap_delta (event->button.state);
2946 switch (_operation) {
2948 trim_type = "Region start trim";
2951 trim_type = "Region end trim";
2954 trim_type = "Region content trim";
2961 _editor->begin_reversible_command (trim_type);
2963 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2964 RegionView* rv = i->view;
2965 rv->region()->playlist()->clear_owned_changes ();
2967 if (_operation == StartTrim) {
2968 rv->trim_front_starting ();
2971 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2974 arv->temporarily_hide_envelope ();
2978 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2979 insert_result = _editor->motion_frozen_playlists.insert (pl);
2981 if (insert_result.second) {
2985 MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (rv);
2986 /* a MRV start trim may change the source length. ensure we cover all playlists here */
2987 if (mrv && _operation == StartTrim) {
2988 vector<boost::shared_ptr<Playlist> > all_playlists;
2989 _editor->session()->playlists->get (all_playlists);
2990 for (vector<boost::shared_ptr<Playlist> >::iterator x = all_playlists.begin(); x != all_playlists.end(); ++x) {
2992 if ((*x)->uses_source (rv->region()->source(0))) {
2993 insert_result = _editor->motion_frozen_playlists.insert (*x);
2994 if (insert_result.second) {
2995 (*x)->clear_owned_changes ();
3005 bool non_overlap_trim = false;
3007 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
3008 non_overlap_trim = true;
3011 /* contstrain trim to fade length */
3012 if (_preserve_fade_anchor) {
3013 switch (_operation) {
3015 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3016 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3018 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3019 if (ar->locked()) continue;
3020 samplecnt_t len = ar->fade_in()->back()->when;
3021 if (len < dt) dt = min(dt, len);
3025 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3026 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3028 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3029 if (ar->locked()) continue;
3030 samplecnt_t len = ar->fade_out()->back()->when;
3031 if (len < -dt) dt = max(dt, -len);
3039 switch (_operation) {
3041 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3042 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3043 , adj_sample.division);
3045 if (changed && _preserve_fade_anchor) {
3046 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3048 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3049 samplecnt_t len = ar->fade_in()->back()->when;
3050 samplecnt_t diff = ar->first_sample() - i->initial_position;
3051 samplepos_t new_length = len - diff;
3052 i->anchored_fade_length = min (ar->length(), new_length);
3053 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3054 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3061 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3062 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, adj_sample.division);
3063 if (changed && _preserve_fade_anchor) {
3064 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3066 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3067 samplecnt_t len = ar->fade_out()->back()->when;
3068 samplecnt_t diff = 1 + ar->last_sample() - i->initial_end;
3069 samplepos_t new_length = len + diff;
3070 i->anchored_fade_length = min (ar->length(), new_length);
3071 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3072 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3080 sample_delta = (last_pointer_sample() - adjusted_current_sample(event));
3082 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3083 i->view->move_contents (sample_delta);
3089 switch (_operation) {
3091 show_verbose_cursor_time (rv->region()->position());
3094 show_verbose_cursor_duration (rv->region()->position(), rv->region()->last_sample());
3097 // show_verbose_cursor_time (sample_delta);
3103 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3105 if (movement_occurred) {
3106 motion (event, false);
3108 if (_operation == StartTrim) {
3109 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3111 /* This must happen before the region's StatefulDiffCommand is created, as it may
3112 `correct' (ahem) the region's _start from being negative to being zero. It
3113 needs to be zero in the undo record.
3115 i->view->trim_front_ending ();
3117 if (_preserve_fade_anchor) {
3118 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3120 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3121 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3122 ar->set_fade_in_length(i->anchored_fade_length);
3123 ar->set_fade_in_active(true);
3126 if (_jump_position_when_done) {
3127 i->view->region()->set_position (i->initial_position);
3130 } else if (_operation == EndTrim) {
3131 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3132 if (_preserve_fade_anchor) {
3133 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3135 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3136 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3137 ar->set_fade_out_length(i->anchored_fade_length);
3138 ar->set_fade_out_active(true);
3141 if (_jump_position_when_done) {
3142 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3147 if (!_editor->selection->selected (_primary)) {
3148 _primary->thaw_after_trim ();
3150 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3151 i->view->thaw_after_trim ();
3152 i->view->enable_display (true);
3156 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3157 /* Trimming one region may affect others on the playlist, so we need
3158 to get undo Commands from the whole playlist rather than just the
3159 region. Use motion_frozen_playlists (a set) to make sure we don't
3160 diff a given playlist more than once.
3163 vector<Command*> cmds;
3165 _editor->session()->add_commands (cmds);
3169 _editor->motion_frozen_playlists.clear ();
3170 _editor->commit_reversible_command();
3173 /* no mouse movement */
3174 if (adjusted_current_sample (event) != adjusted_sample (_drags->current_pointer_sample(), event, false).sample) {
3175 _editor->point_trim (event, adjusted_current_sample (event));
3179 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3180 i->view->region()->resume_property_changes ();
3185 TrimDrag::aborted (bool movement_occurred)
3187 /* Our motion method is changing model state, so use the Undo system
3188 to cancel. Perhaps not ideal, as this will leave an Undo point
3189 behind which may be slightly odd from the user's point of view.
3193 finished (&ev, true);
3195 if (movement_occurred) {
3196 _editor->session()->undo (1);
3199 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3200 i->view->region()->resume_property_changes ();
3205 TrimDrag::setup_pointer_sample_offset ()
3207 list<DraggingView>::iterator i = _views.begin ();
3208 while (i != _views.end() && i->view != _primary) {
3212 if (i == _views.end()) {
3216 switch (_operation) {
3218 _pointer_sample_offset = raw_grab_sample() - i->initial_position;
3221 _pointer_sample_offset = raw_grab_sample() - i->initial_end;
3228 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3231 , _old_grid_type (e->grid_type())
3232 , _old_snap_mode (e->snap_mode())
3235 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3236 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3238 _real_section = &_marker->meter();
3243 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3245 Drag::start_grab (event, cursor);
3246 show_verbose_cursor_time (adjusted_current_sample(event));
3250 MeterMarkerDrag::setup_pointer_sample_offset ()
3252 _pointer_sample_offset = raw_grab_sample() - _marker->meter().sample();
3256 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3259 // create a dummy marker to catch events, then hide it.
3262 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3264 _marker = new MeterMarker (
3266 *_editor->meter_group,
3267 UIConfiguration::instance().color ("meter marker"),
3269 *new MeterSection (_marker->meter())
3272 /* use the new marker for the grab */
3273 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3276 TempoMap& map (_editor->session()->tempo_map());
3277 /* get current state */
3278 before_state = &map.get_state();
3281 _editor->begin_reversible_command (_("move meter mark"));
3283 _editor->begin_reversible_command (_("copy meter mark"));
3285 Timecode::BBT_Time bbt = _real_section->bbt();
3287 /* we can't add a meter where one currently exists */
3288 if (_real_section->sample() < adjusted_current_sample (event, false)) {
3293 const samplepos_t sample = map.sample_at_bbt (bbt);
3294 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3295 , bbt, sample, _real_section->position_lock_style());
3296 if (!_real_section) {
3302 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3303 if (_real_section->position_lock_style() != AudioTime) {
3304 _editor->set_grid_to (GridTypeBar);
3305 _editor->set_snap_mode (SnapMagnetic);
3309 samplepos_t pf = adjusted_current_sample (event);
3311 if (_real_section->position_lock_style() == AudioTime && _editor->grid_musical()) {
3312 /* never snap to music for audio locked */
3313 pf = adjusted_current_sample (event, false);
3316 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3318 /* fake marker meeds to stay under the mouse, unlike the real one. */
3319 _marker->set_position (adjusted_current_sample (event, false));
3321 show_verbose_cursor_time (_real_section->sample());
3322 _editor->set_snapped_cursor_position(_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_grid_to (_old_grid_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_grid_to (_old_grid_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->grid_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());
3488 _editor->set_snapped_cursor_position(_real_section->sample());
3490 _marker->set_position (adjusted_current_sample (event, false));
3494 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3496 if (!_real_section->active()) {
3499 if (!movement_occurred) {
3500 if (was_double_click()) {
3501 _editor->edit_tempo_marker (*_marker);
3506 TempoMap& map (_editor->session()->tempo_map());
3508 XMLNode &after = map.get_state();
3509 _editor->session()->add_command (new MementoCommand<TempoMap>(map, _before_state, &after));
3510 _editor->commit_reversible_command ();
3512 // delete the dummy marker we used for visual representation while moving.
3513 // a new visual marker will show up automatically.
3518 TempoMarkerDrag::aborted (bool moved)
3520 _marker->set_position (_marker->tempo().sample());
3522 TempoMap& map (_editor->session()->tempo_map());
3523 map.set_state (*_before_state, Stateful::current_state_version);
3524 // delete the dummy (hidden) marker we used for events while moving.
3529 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3534 , _drag_valid (true)
3536 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3541 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3543 Drag::start_grab (event, cursor);
3544 TempoMap& map (_editor->session()->tempo_map());
3545 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3547 if (adjusted_current_sample (event, false) <= _tempo->sample()) {
3548 _drag_valid = false;
3552 _editor->tempo_curve_selected (_tempo, true);
3555 if (_tempo->clamped()) {
3556 TempoSection* prev = map.previous_tempo_section (_tempo);
3558 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3562 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3563 show_verbose_cursor_text (sstr.str());
3567 BBTRulerDrag::setup_pointer_sample_offset ()
3569 TempoMap& map (_editor->session()->tempo_map());
3570 /* get current state */
3571 _before_state = &map.get_state();
3573 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3574 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3577 if (divisions > 0) {
3578 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3580 /* while it makes some sense for the user to determine the division to 'grab',
3581 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3582 and the result over steep tempo curves. Use sixteenths.
3584 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3587 _grab_qn = map.quarter_note_at_beat (beat);
3589 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3594 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3601 _editor->begin_reversible_command (_("stretch tempo"));
3604 TempoMap& map (_editor->session()->tempo_map());
3607 if (_editor->grid_musical()) {
3608 pf = adjusted_current_sample (event, false);
3610 pf = adjusted_current_sample (event);
3613 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3614 /* adjust previous tempo to match pointer sample */
3615 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.sample_at_quarter_note (_grab_qn), pf, _grab_qn, map.quarter_note_at_sample (pf));
3619 if (_tempo->clamped()) {
3620 TempoSection* prev = map.previous_tempo_section (_tempo);
3622 _editor->tempo_curve_selected (prev, true);
3623 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3626 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3627 show_verbose_cursor_text (sstr.str());
3631 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3633 if (!movement_occurred) {
3637 TempoMap& map (_editor->session()->tempo_map());
3639 _editor->tempo_curve_selected (_tempo, false);
3640 if (_tempo->clamped()) {
3641 TempoSection* prev_tempo = map.previous_tempo_section (_tempo);
3643 _editor->tempo_curve_selected (prev_tempo, false);
3647 if (!movement_occurred || !_drag_valid) {
3651 XMLNode &after = map.get_state();
3652 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3653 _editor->commit_reversible_command ();
3658 BBTRulerDrag::aborted (bool moved)
3661 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3665 TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i)
3671 , _drag_valid (true)
3674 DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n");
3679 TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3681 Drag::start_grab (event, cursor);
3682 TempoMap& map (_editor->session()->tempo_map());
3683 /* get current state */
3684 _before_state = &map.get_state();
3685 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3687 if (_tempo->locked_to_meter()) {
3688 _drag_valid = false;
3692 _next_tempo = map.next_tempo_section (_tempo);
3694 if (!map.next_tempo_section (_next_tempo)) {
3695 _drag_valid = false;
3696 finished (event, false);
3700 _editor->tempo_curve_selected (_tempo, true);
3701 _editor->tempo_curve_selected (_next_tempo, true);
3704 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3705 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3706 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3707 show_verbose_cursor_text (sstr.str());
3709 _drag_valid = false;
3712 _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type());
3716 TempoTwistDrag::setup_pointer_sample_offset ()
3718 TempoMap& map (_editor->session()->tempo_map());
3719 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3720 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3723 if (divisions > 0) {
3724 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3726 /* while it makes some sense for the user to determine the division to 'grab',
3727 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3728 and the result over steep tempo curves. Use sixteenths.
3730 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3733 _grab_qn = map.quarter_note_at_beat (beat);
3735 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3740 TempoTwistDrag::motion (GdkEvent* event, bool first_move)
3743 if (!_next_tempo || !_drag_valid) {
3747 TempoMap& map (_editor->session()->tempo_map());
3750 _editor->begin_reversible_command (_("twist tempo"));
3755 if (_editor->grid_musical()) {
3756 pf = adjusted_current_sample (event, false);
3758 pf = adjusted_current_sample (event);
3761 /* adjust this and the next tempi to match pointer sample */
3762 double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3763 _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.sample_at_quarter_note (_grab_qn), pf);
3766 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3767 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3768 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3769 show_verbose_cursor_text (sstr.str());
3773 TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred)
3775 if (!movement_occurred || !_drag_valid) {
3779 _editor->tempo_curve_selected (_tempo, false);
3780 _editor->tempo_curve_selected (_next_tempo, false);
3782 TempoMap& map (_editor->session()->tempo_map());
3783 XMLNode &after = map.get_state();
3784 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3785 _editor->commit_reversible_command ();
3789 TempoTwistDrag::aborted (bool moved)
3792 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3796 TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i)
3801 , _drag_valid (true)
3803 DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n");
3804 TempoMarker* marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3805 _tempo = &marker->tempo();
3806 _grab_qn = _tempo->pulse() * 4.0;
3810 TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3812 Drag::start_grab (event, cursor);
3813 TempoMap& tmap (_editor->session()->tempo_map());
3815 /* get current state */
3816 _before_state = &tmap.get_state();
3818 if (_tempo->locked_to_meter()) {
3819 _drag_valid = false;
3825 TempoSection* prev = 0;
3826 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3827 _editor->tempo_curve_selected (tmap.previous_tempo_section (_tempo), true);
3828 sstr << "end: " << fixed << setprecision(3) << tmap.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3831 if (_tempo->clamped()) {
3832 _editor->tempo_curve_selected (_tempo, true);
3833 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3836 show_verbose_cursor_text (sstr.str());
3840 TempoEndDrag::setup_pointer_sample_offset ()
3842 TempoMap& map (_editor->session()->tempo_map());
3844 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3849 TempoEndDrag::motion (GdkEvent* event, bool first_move)
3855 TempoMap& map (_editor->session()->tempo_map());
3858 _editor->begin_reversible_command (_("stretch end tempo"));
3861 samplepos_t const pf = adjusted_current_sample (event, false);
3862 map.gui_stretch_tempo_end (&map.tempo_section_at_sample (_tempo->sample() - 1), map.sample_at_quarter_note (_grab_qn), pf);
3865 sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3867 if (_tempo->clamped()) {
3868 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3871 show_verbose_cursor_text (sstr.str());
3875 TempoEndDrag::finished (GdkEvent* event, bool movement_occurred)
3877 if (!movement_occurred || !_drag_valid) {
3881 TempoMap& tmap (_editor->session()->tempo_map());
3883 XMLNode &after = tmap.get_state();
3884 _editor->session()->add_command(new MementoCommand<TempoMap>(tmap, _before_state, &after));
3885 _editor->commit_reversible_command ();
3887 TempoSection* prev = 0;
3888 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3889 _editor->tempo_curve_selected (prev, false);
3892 if (_tempo->clamped()) {
3893 _editor->tempo_curve_selected (_tempo, false);
3899 TempoEndDrag::aborted (bool moved)
3902 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3906 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3907 : Drag (e, &c.track_canvas_item(), false)
3912 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3915 /** Do all the things we do when dragging the playhead to make it look as though
3916 * we have located, without actually doing the locate (because that would cause
3917 * the diskstream buffers to be refilled, which is too slow).
3920 CursorDrag::fake_locate (samplepos_t t)
3922 if (_editor->session () == 0) {
3926 _editor->playhead_cursor->set_position (t);
3928 Session* s = _editor->session ();
3929 if (s->timecode_transmission_suspended ()) {
3930 samplepos_t const f = _editor->playhead_cursor->current_sample ();
3931 /* This is asynchronous so it will be sent "now"
3933 s->send_mmc_locate (f);
3934 /* These are synchronous and will be sent during the next
3937 s->queue_full_time_code ();
3938 s->queue_song_position_pointer ();
3941 show_verbose_cursor_time (t);
3942 _editor->UpdateAllTransportClocks (t);
3946 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3948 Drag::start_grab (event, c);
3949 setup_snap_delta (MusicSample (_editor->playhead_cursor->current_sample(), 0));
3951 _grab_zoom = _editor->samples_per_pixel;
3953 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3955 _editor->snap_to_with_modifier (where, event);
3956 _editor->_dragging_playhead = true;
3957 _editor->_control_scroll_target = where.sample;
3959 Session* s = _editor->session ();
3961 /* grab the track canvas item as well */
3963 _cursor.track_canvas_item().grab();
3966 if (_was_rolling && _stop) {
3970 if (s->is_auditioning()) {
3971 s->cancel_audition ();
3975 if (AudioEngine::instance()->running()) {
3977 /* do this only if we're the engine is connected
3978 * because otherwise this request will never be
3979 * serviced and we'll busy wait forever. likewise,
3980 * notice if we are disconnected while waiting for the
3981 * request to be serviced.
3984 s->request_suspend_timecode_transmission ();
3985 while (AudioEngine::instance()->running() && !s->timecode_transmission_suspended ()) {
3986 /* twiddle our thumbs */
3991 fake_locate (where.sample - snap_delta (event->button.state));
3997 CursorDrag::motion (GdkEvent* event, bool)
3999 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4001 _editor->snap_to_with_modifier (where, event);
4003 if (where.sample != last_pointer_sample()) {
4004 fake_locate (where.sample - snap_delta (event->button.state));
4007 //maybe do zooming, too, if the option is enabled
4008 if (UIConfiguration::instance ().get_use_time_rulers_to_zoom_with_vertical_drag () ) {
4010 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
4011 //we use screen coordinates for this, not canvas-based grab_x
4012 double mx = event->button.x;
4013 double dx = fabs(mx - _last_mx);
4014 double my = event->button.y;
4015 double dy = fabs(my - _last_my);
4018 //do zooming in windowed "steps" so it feels more reversible ?
4019 const int stepsize = 2; //stepsize ==1 means "trigger on every pixel of movement"
4020 int y_delta = grab_y() - current_pointer_y();
4021 y_delta = y_delta / stepsize;
4023 //if all requirements are met, do the actual zoom
4024 const double scale = 1.2;
4025 if ( (dy>dx) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
4026 if ( _last_y_delta > y_delta ) {
4027 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
4029 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
4031 _last_y_delta = y_delta;
4042 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
4044 _editor->_dragging_playhead = false;
4046 _cursor.track_canvas_item().ungrab();
4048 if (!movement_occurred && _stop) {
4052 motion (event, false);
4054 Session* s = _editor->session ();
4056 s->request_locate (_editor->playhead_cursor->current_sample (), _was_rolling);
4057 _editor->_pending_locate_request = true;
4058 s->request_resume_timecode_transmission ();
4063 CursorDrag::aborted (bool)
4065 _cursor.track_canvas_item().ungrab();
4067 if (_editor->_dragging_playhead) {
4068 _editor->session()->request_resume_timecode_transmission ();
4069 _editor->_dragging_playhead = false;
4072 _editor->playhead_cursor->set_position (adjusted_sample (grab_sample (), 0, false).sample);
4075 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4076 : RegionDrag (e, i, p, v)
4078 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
4082 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4084 Drag::start_grab (event, cursor);
4086 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4087 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4088 setup_snap_delta (MusicSample (r->position(), 0));
4090 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
4094 FadeInDrag::setup_pointer_sample_offset ()
4096 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4097 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4098 _pointer_sample_offset = raw_grab_sample() - ((samplecnt_t) r->fade_in()->back()->when + r->position());
4102 FadeInDrag::motion (GdkEvent* event, bool)
4104 samplecnt_t fade_length;
4106 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4107 _editor->snap_to_with_modifier (pos, event);
4109 pos.sample -= snap_delta (event->button.state);
4111 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4113 if (pos.sample < (region->position() + 64)) {
4114 fade_length = 64; // this should be a minimum defined somewhere
4115 } else if (pos.sample > region->position() + region->length() - region->fade_out()->back()->when) {
4116 fade_length = region->length() - region->fade_out()->back()->when - 1;
4118 fade_length = pos.sample - region->position();
4121 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4123 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4129 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
4132 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
4136 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
4138 if (!movement_occurred) {
4142 samplecnt_t fade_length;
4143 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4145 _editor->snap_to_with_modifier (pos, event);
4146 pos.sample -= snap_delta (event->button.state);
4148 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4150 if (pos.sample < (region->position() + 64)) {
4151 fade_length = 64; // this should be a minimum defined somewhere
4152 } else if (pos.sample >= region->position() + region->length() - region->fade_out()->back()->when) {
4153 fade_length = region->length() - region->fade_out()->back()->when - 1;
4155 fade_length = pos.sample - region->position();
4158 bool in_command = false;
4160 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4162 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4168 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
4169 XMLNode &before = alist->get_state();
4171 tmp->audio_region()->set_fade_in_length (fade_length);
4172 tmp->audio_region()->set_fade_in_active (true);
4175 _editor->begin_reversible_command (_("change fade in length"));
4178 XMLNode &after = alist->get_state();
4179 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4183 _editor->commit_reversible_command ();
4188 FadeInDrag::aborted (bool)
4190 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4191 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4197 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
4201 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4202 : RegionDrag (e, i, p, v)
4204 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
4208 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4210 Drag::start_grab (event, cursor);
4212 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4213 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4214 setup_snap_delta (MusicSample (r->last_sample(), 0));
4216 show_verbose_cursor_duration (r->last_sample() - r->fade_out()->back()->when, r->last_sample());
4220 FadeOutDrag::setup_pointer_sample_offset ()
4222 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4223 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4224 _pointer_sample_offset = raw_grab_sample() - (r->length() - (samplecnt_t) r->fade_out()->back()->when + r->position());
4228 FadeOutDrag::motion (GdkEvent* event, bool)
4230 samplecnt_t fade_length;
4231 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4233 _editor->snap_to_with_modifier (pos, event);
4234 pos.sample -= snap_delta (event->button.state);
4236 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4238 if (pos.sample > (region->last_sample() - 64)) {
4239 fade_length = 64; // this should really be a minimum fade defined somewhere
4240 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4241 fade_length = region->length() - region->fade_in()->back()->when - 1;
4243 fade_length = region->last_sample() - pos.sample;
4246 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4248 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4254 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
4257 show_verbose_cursor_duration (region->last_sample() - fade_length, region->last_sample());
4261 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
4263 if (!movement_occurred) {
4267 samplecnt_t fade_length;
4268 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4270 _editor->snap_to_with_modifier (pos, event);
4271 pos.sample -= snap_delta (event->button.state);
4273 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4275 if (pos.sample > (region->last_sample() - 64)) {
4276 fade_length = 64; // this should really be a minimum fade defined somewhere
4277 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4278 fade_length = region->length() - region->fade_in()->back()->when - 1;
4280 fade_length = region->last_sample() - pos.sample;
4283 bool in_command = false;
4285 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4287 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4293 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
4294 XMLNode &before = alist->get_state();
4296 tmp->audio_region()->set_fade_out_length (fade_length);
4297 tmp->audio_region()->set_fade_out_active (true);
4300 _editor->begin_reversible_command (_("change fade out length"));
4303 XMLNode &after = alist->get_state();
4304 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4308 _editor->commit_reversible_command ();
4313 FadeOutDrag::aborted (bool)
4315 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4316 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4322 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
4326 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
4328 , _selection_changed (false)
4330 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
4331 Gtk::Window* toplevel = _editor->current_toplevel();
4332 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
4336 _points.push_back (ArdourCanvas::Duple (0, 0));
4338 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4341 MarkerDrag::~MarkerDrag ()
4343 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4348 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4350 location = new Location (*l);
4351 markers.push_back (m);
4356 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4358 Drag::start_grab (event, cursor);
4362 Location *location = _editor->find_location_from_marker (_marker, is_start);
4363 _editor->_dragging_edit_point = true;
4365 update_item (location);
4367 // _drag_line->show();
4368 // _line->raise_to_top();
4371 show_verbose_cursor_time (location->start());
4373 show_verbose_cursor_time (location->end());
4375 setup_snap_delta (MusicSample (is_start ? location->start() : location->end(), 0));
4377 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4380 case Selection::Toggle:
4381 /* we toggle on the button release */
4383 case Selection::Set:
4384 if (!_editor->selection->selected (_marker)) {
4385 _editor->selection->set (_marker);
4386 _selection_changed = true;
4389 case Selection::Extend:
4391 Locations::LocationList ll;
4392 list<ArdourMarker*> to_add;
4394 _editor->selection->markers.range (s, e);
4395 s = min (_marker->position(), s);
4396 e = max (_marker->position(), e);
4399 if (e < max_samplepos) {
4402 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4403 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4404 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4407 to_add.push_back (lm->start);
4410 to_add.push_back (lm->end);
4414 if (!to_add.empty()) {
4415 _editor->selection->add (to_add);
4416 _selection_changed = true;
4420 case Selection::Add:
4421 _editor->selection->add (_marker);
4422 _selection_changed = true;
4427 /* Set up copies for us to manipulate during the drag
4430 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4432 Location* l = _editor->find_location_from_marker (*i, is_start);
4439 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4441 /* range: check that the other end of the range isn't
4444 CopiedLocationInfo::iterator x;
4445 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4446 if (*(*x).location == *l) {
4450 if (x == _copied_locations.end()) {
4451 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4453 (*x).markers.push_back (*i);
4454 (*x).move_both = true;
4462 MarkerDrag::setup_pointer_sample_offset ()
4465 Location *location = _editor->find_location_from_marker (_marker, is_start);
4466 _pointer_sample_offset = raw_grab_sample() - (is_start ? location->start() : location->end());
4470 MarkerDrag::motion (GdkEvent* event, bool)
4472 samplecnt_t f_delta = 0;
4474 bool move_both = false;
4475 Location *real_location;
4476 Location *copy_location = 0;
4477 samplecnt_t const sd = snap_delta (event->button.state);
4479 samplecnt_t const newframe = adjusted_sample (_drags->current_pointer_sample () + sd, event, true).sample - sd;
4480 samplepos_t next = newframe;
4482 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4486 CopiedLocationInfo::iterator x;
4488 /* find the marker we're dragging, and compute the delta */
4490 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4492 copy_location = (*x).location;
4494 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4496 /* this marker is represented by this
4497 * CopiedLocationMarkerInfo
4500 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4505 if (real_location->is_mark()) {
4506 f_delta = newframe - copy_location->start();
4510 switch (_marker->type()) {
4511 case ArdourMarker::SessionStart:
4512 case ArdourMarker::RangeStart:
4513 case ArdourMarker::LoopStart:
4514 case ArdourMarker::PunchIn:
4515 f_delta = newframe - copy_location->start();
4518 case ArdourMarker::SessionEnd:
4519 case ArdourMarker::RangeEnd:
4520 case ArdourMarker::LoopEnd:
4521 case ArdourMarker::PunchOut:
4522 f_delta = newframe - copy_location->end();
4525 /* what kind of marker is this ? */
4534 if (x == _copied_locations.end()) {
4535 /* hmm, impossible - we didn't find the dragged marker */
4539 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4541 /* now move them all */
4543 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4545 copy_location = x->location;
4547 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4551 if (real_location->locked()) {
4555 if (copy_location->is_mark()) {
4558 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4562 samplepos_t new_start = copy_location->start() + f_delta;
4563 samplepos_t new_end = copy_location->end() + f_delta;
4565 if (is_start) { // start-of-range marker
4567 if (move_both || (*x).move_both) {
4568 copy_location->set_start (new_start, false, true, divisions);
4569 copy_location->set_end (new_end, false, true, divisions);
4570 } else if (new_start < copy_location->end()) {
4571 copy_location->set_start (new_start, false, true, divisions);
4572 } else if (newframe > 0) {
4573 //_editor->snap_to (next, RoundUpAlways, true);
4574 copy_location->set_end (next, false, true, divisions);
4575 copy_location->set_start (newframe, false, true, divisions);
4578 } else { // end marker
4580 if (move_both || (*x).move_both) {
4581 copy_location->set_end (new_end, divisions);
4582 copy_location->set_start (new_start, false, true, divisions);
4583 } else if (new_end > copy_location->start()) {
4584 copy_location->set_end (new_end, false, true, divisions);
4585 } else if (newframe > 0) {
4586 //_editor->snap_to (next, RoundDownAlways, true);
4587 copy_location->set_start (next, false, true, divisions);
4588 copy_location->set_end (newframe, false, true, divisions);
4593 update_item (copy_location);
4595 /* now lookup the actual GUI items used to display this
4596 * location and move them to wherever the copy of the location
4597 * is now. This means that the logic in ARDOUR::Location is
4598 * still enforced, even though we are not (yet) modifying
4599 * the real Location itself.
4602 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4605 lm->set_position (copy_location->start(), copy_location->end());
4610 assert (!_copied_locations.empty());
4612 show_verbose_cursor_time (newframe);
4613 _editor->set_snapped_cursor_position(newframe);
4617 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4619 if (!movement_occurred) {
4621 if (was_double_click()) {
4622 _editor->rename_marker (_marker);
4626 /* just a click, do nothing but finish
4627 off the selection process
4630 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4632 case Selection::Set:
4633 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4634 _editor->selection->set (_marker);
4635 _selection_changed = true;
4639 case Selection::Toggle:
4640 /* we toggle on the button release, click only */
4641 _editor->selection->toggle (_marker);
4642 _selection_changed = true;
4646 case Selection::Extend:
4647 case Selection::Add:
4651 if (_selection_changed) {
4652 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4653 _editor->commit_reversible_selection_op();
4659 _editor->_dragging_edit_point = false;
4661 XMLNode &before = _editor->session()->locations()->get_state();
4662 bool in_command = false;
4664 MarkerSelection::iterator i;
4665 CopiedLocationInfo::iterator x;
4666 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4669 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4670 x != _copied_locations.end() && i != _editor->selection->markers.end();
4673 Location * location = _editor->find_location_from_marker (*i, is_start);
4677 if (location->locked()) {
4681 _editor->begin_reversible_command ( _("move marker") );
4684 if (location->is_mark()) {
4685 location->set_start (((*x).location)->start(), false, true, divisions);
4687 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4690 if (location->is_session_range()) {
4691 _editor->session()->set_end_is_free (false);
4697 XMLNode &after = _editor->session()->locations()->get_state();
4698 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4699 _editor->commit_reversible_command ();
4704 MarkerDrag::aborted (bool movement_occurred)
4706 if (!movement_occurred) {
4710 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4712 /* move all markers to their original location */
4715 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4718 Location * location = _editor->find_location_from_marker (*m, is_start);
4721 (*m)->set_position (is_start ? location->start() : location->end());
4728 MarkerDrag::update_item (Location*)
4733 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4735 , _fixed_grab_x (0.0)
4736 , _fixed_grab_y (0.0)
4737 , _cumulative_x_drag (0.0)
4738 , _cumulative_y_drag (0.0)
4742 if (_zero_gain_fraction < 0.0) {
4743 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4746 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4748 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4754 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4756 Drag::start_grab (event, _editor->cursors()->fader);
4758 // start the grab at the center of the control point so
4759 // the point doesn't 'jump' to the mouse after the first drag
4760 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4761 _fixed_grab_y = _point->get_y();
4763 setup_snap_delta (MusicSample (_editor->pixel_to_sample (_fixed_grab_x), 0));
4765 float const fraction = 1 - (_point->get_y() / _point->line().height());
4766 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4768 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4770 if (!_point->can_slide ()) {
4771 _x_constrained = true;
4776 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4778 double dx = _drags->current_pointer_x() - last_pointer_x();
4779 double dy = current_pointer_y() - last_pointer_y();
4780 bool need_snap = true;
4782 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4788 /* coordinate in pixels relative to the start of the region (for region-based automation)
4789 or track (for track-based automation) */
4790 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4791 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4793 // calculate zero crossing point. back off by .01 to stay on the
4794 // positive side of zero
4795 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4797 if (_x_constrained) {
4800 if (_y_constrained) {
4804 _cumulative_x_drag = cx - _fixed_grab_x;
4805 _cumulative_y_drag = cy - _fixed_grab_y;
4809 cy = min ((double) _point->line().height(), cy);
4811 // make sure we hit zero when passing through
4812 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4816 MusicSample cx_mf (_editor->pixel_to_sample (cx) + snap_delta (event->button.state), 0);
4818 if (!_x_constrained && need_snap) {
4819 _editor->snap_to_with_modifier (cx_mf, event);
4822 cx_mf.sample -= snap_delta (event->button.state);
4823 cx_mf.sample = min (cx_mf.sample, _point->line().maximum_time() + _point->line().offset());
4825 float const fraction = 1.0 - (cy / _point->line().height());
4828 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4829 _editor->begin_reversible_command (_("automation event move"));
4830 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4832 pair<float, float> result;
4833 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_mf.sample), fraction, false, _pushing, _final_index);
4834 show_verbose_cursor_text (_point->line().get_verbose_cursor_relative_string (result.first, result.second));
4838 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4840 if (!movement_occurred) {
4843 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4844 _editor->reset_point_selection ();
4848 _point->line().end_drag (_pushing, _final_index);
4849 _editor->commit_reversible_command ();
4854 ControlPointDrag::aborted (bool)
4856 _point->line().reset ();
4860 ControlPointDrag::active (Editing::MouseMode m)
4862 if (m == Editing::MouseDraw) {
4863 /* always active in mouse draw */
4867 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4868 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4871 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4874 , _fixed_grab_x (0.0)
4875 , _fixed_grab_y (0.0)
4876 , _cumulative_y_drag (0)
4880 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4884 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4886 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4889 _item = &_line->grab_item ();
4891 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4892 origin, and ditto for y.
4895 double mx = event->button.x;
4896 double my = event->button.y;
4898 _line->grab_item().canvas_to_item (mx, my);
4900 samplecnt_t const sample_within_region = (samplecnt_t) floor (mx * _editor->samples_per_pixel);
4902 if (!_line->control_points_adjacent (sample_within_region, _before, _after)) {
4903 /* no adjacent points */
4907 Drag::start_grab (event, _editor->cursors()->fader);
4909 /* store grab start in item sample */
4910 double const bx = _line->nth (_before)->get_x();
4911 double const ax = _line->nth (_after)->get_x();
4912 double const click_ratio = (ax - mx) / (ax - bx);
4914 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4919 double fraction = 1.0 - (cy / _line->height());
4921 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4925 LineDrag::motion (GdkEvent* event, bool first_move)
4927 double dy = current_pointer_y() - last_pointer_y();
4929 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4933 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4935 _cumulative_y_drag = cy - _fixed_grab_y;
4938 cy = min ((double) _line->height(), cy);
4940 double const fraction = 1.0 - (cy / _line->height());
4944 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4946 _editor->begin_reversible_command (_("automation range move"));
4947 _line->start_drag_line (_before, _after, initial_fraction);
4950 /* we are ignoring x position for this drag, so we can just pass in anything */
4951 pair<float, float> result;
4953 result = _line->drag_motion (0, fraction, true, false, ignored);
4954 show_verbose_cursor_text (_line->get_verbose_cursor_relative_string (result.first, result.second));
4958 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4960 if (movement_occurred) {
4961 motion (event, false);
4962 _line->end_drag (false, 0);
4963 _editor->commit_reversible_command ();
4965 /* add a new control point on the line */
4967 AutomationTimeAxisView* atv;
4969 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4970 samplepos_t where = grab_sample ();
4973 double cy = _fixed_grab_y;
4975 _line->grab_item().item_to_canvas (cx, cy);
4977 atv->add_automation_event (event, where, cy, false);
4978 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4979 AudioRegionView* arv;
4981 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4982 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4989 LineDrag::aborted (bool)
4994 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4998 _region_view_grab_x (0.0),
4999 _cumulative_x_drag (0),
5003 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
5007 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
5009 Drag::start_grab (event);
5011 _line = reinterpret_cast<Line*> (_item);
5014 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
5016 double cx = event->button.x;
5017 double cy = event->button.y;
5019 _item->parent()->canvas_to_item (cx, cy);
5021 /* store grab start in parent sample */
5022 _region_view_grab_x = cx;
5024 _before = *(float*) _item->get_data ("position");
5026 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5028 _max_x = _editor->sample_to_pixel(_arv->get_duration());
5032 FeatureLineDrag::motion (GdkEvent*, bool)
5034 double dx = _drags->current_pointer_x() - last_pointer_x();
5036 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
5038 _cumulative_x_drag += dx;
5040 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
5049 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
5051 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
5053 float *pos = new float;
5056 _line->set_data ("position", pos);
5062 FeatureLineDrag::finished (GdkEvent*, bool)
5064 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5065 _arv->update_transient(_before, _before);
5069 FeatureLineDrag::aborted (bool)
5074 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5076 , _vertical_only (false)
5078 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
5082 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5084 Drag::start_grab (event);
5085 show_verbose_cursor_time (adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
5089 RubberbandSelectDrag::motion (GdkEvent* event, bool)
5095 samplepos_t const pf = adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
5096 MusicSample grab (grab_sample (), 0);
5098 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5099 _editor->snap_to_with_modifier (grab, event, RoundNearest, SnapToGrid_Scaled);
5101 grab.sample = raw_grab_sample ();
5104 /* base start and end on initial click position */
5106 if (pf < grab.sample) {
5111 start = grab.sample;
5114 if (current_pointer_y() < grab_y()) {
5115 y1 = current_pointer_y();
5118 y2 = current_pointer_y();
5122 if (start != end || y1 != y2) {
5124 double x1 = _editor->sample_to_pixel (start);
5125 double x2 = _editor->sample_to_pixel (end);
5126 const double min_dimension = 2.0;
5128 if (_vertical_only) {
5129 /* fixed 10 pixel width */
5133 x2 = min (x1 - min_dimension, x2);
5135 x2 = max (x1 + min_dimension, x2);
5140 y2 = min (y1 - min_dimension, y2);
5142 y2 = max (y1 + min_dimension, y2);
5145 /* translate rect into item space and set */
5147 ArdourCanvas::Rect r (x1, y1, x2, y2);
5149 /* this drag is a _trackview_only == true drag, so the y1 and
5150 * y2 (computed using current_pointer_y() and grab_y()) will be
5151 * relative to the top of the trackview group). The
5152 * rubberband rect has the same parent/scroll offset as the
5153 * the trackview group, so we can use the "r" rect directly
5154 * to set the shape of the rubberband.
5157 _editor->rubberband_rect->set (r);
5158 _editor->rubberband_rect->show();
5159 _editor->rubberband_rect->raise_to_top();
5161 show_verbose_cursor_time (pf);
5163 do_select_things (event, true);
5168 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
5172 samplepos_t grab = grab_sample ();
5173 samplepos_t lpf = last_pointer_sample ();
5175 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5176 grab = raw_grab_sample ();
5177 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
5191 if (current_pointer_y() < grab_y()) {
5192 y1 = current_pointer_y();
5195 y2 = current_pointer_y();
5199 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
5203 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
5205 if (movement_occurred) {
5207 motion (event, false);
5208 do_select_things (event, false);
5214 bool do_deselect = true;
5215 MidiTimeAxisView* mtv;
5217 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
5219 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
5220 /* nothing selected */
5221 add_midi_region (mtv, true);
5222 do_deselect = false;
5226 /* do not deselect if Primary or Tertiary (toggle-select or
5227 * extend-select are pressed.
5230 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
5231 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
5238 _editor->rubberband_rect->hide();
5242 RubberbandSelectDrag::aborted (bool)
5244 _editor->rubberband_rect->hide ();
5247 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
5248 : RegionDrag (e, i, p, v)
5250 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
5254 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5256 Drag::start_grab (event, cursor);
5258 _editor->get_selection().add (_primary);
5260 MusicSample where (_primary->region()->position(), 0);
5261 setup_snap_delta (where);
5263 show_verbose_cursor_duration (where.sample, adjusted_current_sample (event), 0);
5267 TimeFXDrag::motion (GdkEvent* event, bool)
5269 RegionView* rv = _primary;
5270 StreamView* cv = rv->get_time_axis_view().view ();
5271 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
5272 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
5273 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
5274 MusicSample pf (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
5276 _editor->snap_to_with_modifier (pf, event);
5277 pf.sample -= snap_delta (event->button.state);
5279 if (pf.sample > rv->region()->position()) {
5280 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf.sample, layers, layer);
5283 show_verbose_cursor_duration (_primary->region()->position(), pf.sample, 0);
5287 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
5289 /* this may have been a single click, no drag. We still want the dialog
5290 to show up in that case, so that the user can manually edit the
5291 parameters for the timestretch.
5294 float fraction = 1.0;
5296 if (movement_occurred) {
5298 motion (event, false);
5300 _primary->get_time_axis_view().hide_timestretch ();
5302 samplepos_t adjusted_sample_pos = adjusted_current_sample (event);
5304 if (adjusted_sample_pos < _primary->region()->position()) {
5305 /* backwards drag of the left edge - not usable */
5309 samplecnt_t newlen = adjusted_sample_pos - _primary->region()->position();
5311 fraction = (double) newlen / (double) _primary->region()->length();
5313 #ifndef USE_RUBBERBAND
5314 // Soundtouch uses fraction / 100 instead of normal (/ 1)
5315 if (_primary->region()->data_type() == DataType::AUDIO) {
5316 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
5321 if (!_editor->get_selection().regions.empty()) {
5322 /* primary will already be included in the selection, and edit
5323 group shared editing will propagate selection across
5324 equivalent regions, so just use the current region
5328 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
5329 error << _("An error occurred while executing time stretch operation") << endmsg;
5335 TimeFXDrag::aborted (bool)
5337 _primary->get_time_axis_view().hide_timestretch ();
5340 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5343 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5347 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5349 Drag::start_grab (event);
5353 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5355 _editor->scrub (adjusted_current_sample (0, false), _drags->current_pointer_x ());
5359 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5361 if (movement_occurred && _editor->session()) {
5362 /* make sure we stop */
5363 _editor->session()->request_transport_speed (0.0);
5368 ScrubDrag::aborted (bool)
5373 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5377 , _track_selection_at_start (e)
5378 , _time_selection_at_start (!_editor->get_selection().time.empty())
5380 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5382 if (_time_selection_at_start) {
5383 start_at_start = _editor->get_selection().time.start();
5384 end_at_start = _editor->get_selection().time.end_sample();
5389 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5391 if (_editor->session() == 0) {
5395 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5397 switch (_operation) {
5398 case CreateSelection:
5399 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5404 cursor = _editor->cursors()->selector;
5405 Drag::start_grab (event, cursor);
5408 case SelectionStartTrim:
5409 if (_editor->clicked_axisview) {
5410 _editor->clicked_axisview->order_selection_trims (_item, true);
5412 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5415 case SelectionEndTrim:
5416 if (_editor->clicked_axisview) {
5417 _editor->clicked_axisview->order_selection_trims (_item, false);
5419 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5423 Drag::start_grab (event, cursor);
5426 case SelectionExtend:
5427 Drag::start_grab (event, cursor);
5431 if (_operation == SelectionMove) {
5432 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5434 show_verbose_cursor_time (adjusted_current_sample (event));
5439 SelectionDrag::setup_pointer_sample_offset ()
5441 switch (_operation) {
5442 case CreateSelection:
5443 _pointer_sample_offset = 0;
5446 case SelectionStartTrim:
5448 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].start;
5451 case SelectionEndTrim:
5452 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].end;
5455 case SelectionExtend:
5461 SelectionDrag::motion (GdkEvent* event, bool first_move)
5463 samplepos_t start = 0;
5464 samplepos_t end = 0;
5465 samplecnt_t length = 0;
5466 samplecnt_t distance = 0;
5467 MusicSample start_mf (0, 0);
5468 samplepos_t const pending_position = adjusted_current_sample (event);
5470 if (_operation != CreateSelection && pending_position == last_pointer_sample()) {
5475 _track_selection_at_start = _editor->selection->tracks;
5478 switch (_operation) {
5479 case CreateSelection:
5481 MusicSample grab (grab_sample (), 0);
5483 grab.sample = adjusted_current_sample (event, false);
5484 if (grab.sample < pending_position) {
5485 _editor->snap_to (grab, RoundDownMaybe);
5487 _editor->snap_to (grab, RoundUpMaybe);
5491 if (pending_position < grab.sample) {
5492 start = pending_position;
5495 end = pending_position;
5496 start = grab.sample;
5499 /* first drag: Either add to the selection
5500 or create a new selection
5507 /* adding to the selection */
5508 _editor->set_selected_track_as_side_effect (Selection::Add);
5509 _editor->clicked_selection = _editor->selection->add (start, end);
5516 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5517 _editor->set_selected_track_as_side_effect (Selection::Set);
5520 _editor->clicked_selection = _editor->selection->set (start, end);
5524 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5525 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5526 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5528 _editor->selection->add (atest);
5532 /* select all tracks within the rectangle that we've marked out so far */
5533 TrackViewList new_selection;
5534 TrackViewList& all_tracks (_editor->track_views);
5536 ArdourCanvas::Coord const top = grab_y();
5537 ArdourCanvas::Coord const bottom = current_pointer_y();
5539 if (top >= 0 && bottom >= 0) {
5541 //first, find the tracks that are covered in the y range selection
5542 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5543 if ((*i)->covered_by_y_range (top, bottom)) {
5544 new_selection.push_back (*i);
5548 //now compare our list with the current selection, and add as necessary
5549 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5550 TrackViewList tracks_to_add;
5551 TrackViewList tracks_to_remove;
5552 vector<RouteGroup*> selected_route_groups;
5555 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5556 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5557 tracks_to_remove.push_back (*i);
5559 RouteGroup* rg = (*i)->route_group();
5560 if (rg && rg->is_active() && rg->is_select()) {
5561 selected_route_groups.push_back (rg);
5567 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5568 if (!_editor->selection->tracks.contains (*i)) {
5569 tracks_to_add.push_back (*i);
5570 RouteGroup* rg = (*i)->route_group();
5572 if (rg && rg->is_active() && rg->is_select()) {
5573 selected_route_groups.push_back (rg);
5578 _editor->selection->add (tracks_to_add);
5580 if (!tracks_to_remove.empty()) {
5582 /* check all these to-be-removed tracks against the
5583 * possibility that they are selected by being
5584 * in the same group as an approved track.
5587 for (TrackViewList::iterator i = tracks_to_remove.begin(); i != tracks_to_remove.end(); ) {
5588 RouteGroup* rg = (*i)->route_group();
5590 if (rg && find (selected_route_groups.begin(), selected_route_groups.end(), rg) != selected_route_groups.end()) {
5591 i = tracks_to_remove.erase (i);
5597 /* remove whatever is left */
5599 _editor->selection->remove (tracks_to_remove);
5605 case SelectionStartTrim:
5607 end = _editor->selection->time[_editor->clicked_selection].end;
5609 if (pending_position > end) {
5612 start = pending_position;
5616 case SelectionEndTrim:
5618 start = _editor->selection->time[_editor->clicked_selection].start;
5620 if (pending_position < start) {
5623 end = pending_position;
5630 start = _editor->selection->time[_editor->clicked_selection].start;
5631 end = _editor->selection->time[_editor->clicked_selection].end;
5633 length = end - start;
5634 distance = pending_position - start;
5635 start = pending_position;
5637 start_mf.sample = start;
5638 _editor->snap_to (start_mf);
5640 end = start_mf.sample + length;
5644 case SelectionExtend:
5649 switch (_operation) {
5651 if (_time_selection_at_start) {
5652 _editor->selection->move_time (distance);
5656 _editor->selection->replace (_editor->clicked_selection, start, end);
5660 if (_operation == SelectionMove) {
5661 show_verbose_cursor_time(start);
5663 show_verbose_cursor_time(pending_position);
5668 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5670 Session* s = _editor->session();
5672 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5673 if (movement_occurred) {
5674 motion (event, false);
5675 /* XXX this is not object-oriented programming at all. ick */
5676 if (_editor->selection->time.consolidate()) {
5677 _editor->selection->TimeChanged ();
5680 /* XXX what if its a music time selection? */
5683 //if Follow Edits is on, maybe try to follow the range selection ... also consider range-audition mode
5684 if ( !s->config.get_external_sync() && s->transport_rolling() ) {
5685 if ( s->solo_selection_active() ) {
5686 _editor->play_solo_selection(true); //play the newly selected range, and move solos to match
5687 } else if ( UIConfiguration::instance().get_follow_edits() && s->get_play_range() ) { //already rolling a selected range
5688 s->request_play_range (&_editor->selection->time, true); //play the newly selected range
5690 } else if ( !s->transport_rolling() && UIConfiguration::instance().get_follow_edits() ) {
5691 s->request_locate (_editor->get_selection().time.start());
5694 if (_editor->get_selection().time.length() != 0) {
5695 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_sample());
5697 s->clear_range_selection ();
5702 /* just a click, no pointer movement.
5705 if (was_double_click()) {
5706 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5707 _editor->temporal_zoom_selection (Both);
5712 if (_operation == SelectionExtend) {
5713 if (_time_selection_at_start) {
5714 samplepos_t pos = adjusted_current_sample (event, false);
5715 samplepos_t start = min (pos, start_at_start);
5716 samplepos_t end = max (pos, end_at_start);
5717 _editor->selection->set (start, end);
5720 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5721 if (_editor->clicked_selection) {
5722 _editor->selection->remove (_editor->clicked_selection);
5725 if (!_editor->clicked_selection) {
5726 _editor->selection->clear_time();
5731 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5732 _editor->selection->set (_editor->clicked_axisview);
5735 if (s && s->get_play_range () && s->transport_rolling()) {
5736 s->request_stop (false, false);
5741 _editor->stop_canvas_autoscroll ();
5742 _editor->clicked_selection = 0;
5743 _editor->commit_reversible_selection_op ();
5747 SelectionDrag::aborted (bool)
5752 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5753 : Drag (e, i, false),
5757 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5759 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5760 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5761 physical_screen_height (_editor->current_toplevel()->get_window())));
5762 _drag_rect->hide ();
5764 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5765 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5768 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5770 /* normal canvas items will be cleaned up when their parent group is deleted. But
5771 this item is created as the child of a long-lived parent group, and so we
5772 need to explicitly delete it.
5778 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5780 if (_editor->session() == 0) {
5784 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5786 if (!_editor->temp_location) {
5787 _editor->temp_location = new Location (*_editor->session());
5790 switch (_operation) {
5791 case CreateSkipMarker:
5792 case CreateRangeMarker:
5793 case CreateTransportMarker:
5794 case CreateCDMarker:
5796 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5801 cursor = _editor->cursors()->selector;
5805 Drag::start_grab (event, cursor);
5807 show_verbose_cursor_time (adjusted_current_sample (event));
5811 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5813 samplepos_t start = 0;
5814 samplepos_t end = 0;
5815 ArdourCanvas::Rectangle *crect;
5817 switch (_operation) {
5818 case CreateSkipMarker:
5819 crect = _editor->range_bar_drag_rect;
5821 case CreateRangeMarker:
5822 crect = _editor->range_bar_drag_rect;
5824 case CreateTransportMarker:
5825 crect = _editor->transport_bar_drag_rect;
5827 case CreateCDMarker:
5828 crect = _editor->cd_marker_bar_drag_rect;
5831 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5836 samplepos_t const pf = adjusted_current_sample (event);
5838 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5839 MusicSample grab (grab_sample (), 0);
5840 _editor->snap_to (grab);
5842 if (pf < grab_sample()) {
5847 start = grab.sample;
5850 /* first drag: Either add to the selection
5851 or create a new selection.
5856 _editor->temp_location->set (start, end);
5860 update_item (_editor->temp_location);
5862 //_drag_rect->raise_to_top();
5868 _editor->temp_location->set (start, end);
5870 double x1 = _editor->sample_to_pixel (start);
5871 double x2 = _editor->sample_to_pixel (end);
5875 update_item (_editor->temp_location);
5878 show_verbose_cursor_time (pf);
5883 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5885 Location * newloc = 0;
5889 if (movement_occurred) {
5890 motion (event, false);
5893 switch (_operation) {
5894 case CreateSkipMarker:
5895 case CreateRangeMarker:
5896 case CreateCDMarker:
5898 XMLNode &before = _editor->session()->locations()->get_state();
5899 if (_operation == CreateSkipMarker) {
5900 _editor->begin_reversible_command (_("new skip marker"));
5901 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5902 flags = Location::IsRangeMarker | Location::IsSkip;
5903 _editor->range_bar_drag_rect->hide();
5904 } else if (_operation == CreateCDMarker) {
5905 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5906 _editor->begin_reversible_command (_("new CD marker"));
5907 flags = Location::IsRangeMarker | Location::IsCDMarker;
5908 _editor->cd_marker_bar_drag_rect->hide();
5910 _editor->begin_reversible_command (_("new skip marker"));
5911 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5912 flags = Location::IsRangeMarker;
5913 _editor->range_bar_drag_rect->hide();
5915 newloc = new Location (
5916 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5917 , _editor->get_grid_music_divisions (event->button.state));
5919 _editor->session()->locations()->add (newloc, true);
5920 XMLNode &after = _editor->session()->locations()->get_state();
5921 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5922 _editor->commit_reversible_command ();
5926 case CreateTransportMarker:
5927 // popup menu to pick loop or punch
5928 _editor->new_transport_marker_context_menu (&event->button, _item);
5934 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5936 if (_operation == CreateTransportMarker) {
5938 /* didn't drag, so just locate */
5940 _editor->session()->request_locate (grab_sample(), _editor->session()->transport_rolling());
5942 } else if (_operation == CreateCDMarker) {
5944 /* didn't drag, but mark is already created so do
5947 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5952 _editor->session()->locations()->marks_either_side (grab_sample(), start, end);
5954 if (end == max_samplepos) {
5955 end = _editor->session()->current_end_sample ();
5958 if (start == max_samplepos) {
5959 start = _editor->session()->current_start_sample ();
5962 switch (_editor->mouse_mode) {
5964 /* find the two markers on either side and then make the selection from it */
5965 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5969 /* find the two markers on either side of the click and make the range out of it */
5970 _editor->selection->set (start, end);
5979 _editor->stop_canvas_autoscroll ();
5983 RangeMarkerBarDrag::aborted (bool movement_occurred)
5985 if (movement_occurred) {
5986 _drag_rect->hide ();
5991 RangeMarkerBarDrag::update_item (Location* location)
5993 double const x1 = _editor->sample_to_pixel (location->start());
5994 double const x2 = _editor->sample_to_pixel (location->end());
5996 _drag_rect->set_x0 (x1);
5997 _drag_rect->set_x1 (x2);
6000 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
6002 , _cumulative_dx (0)
6003 , _cumulative_dy (0)
6005 , _was_selected (false)
6008 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
6010 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
6012 _region = &_primary->region_view ();
6013 _note_height = _region->midi_stream_view()->note_height ();
6017 NoteDrag::setup_pointer_sample_offset ()
6019 _pointer_sample_offset = raw_grab_sample()
6020 - _editor->session()->tempo_map().sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6024 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
6026 Drag::start_grab (event);
6028 if (ArdourKeyboard::indicates_copy (event->button.state)) {
6034 setup_snap_delta (MusicSample (_region->source_beats_to_absolute_samples (_primary->note()->time ()), 0));
6036 if (!(_was_selected = _primary->selected())) {
6038 /* tertiary-click means extend selection - we'll do that on button release,
6039 so don't add it here, because otherwise we make it hard to figure
6040 out the "extend-to" range.
6043 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
6046 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
6049 _region->note_selected (_primary, true);
6051 _editor->get_selection().clear_points();
6052 _region->unique_select (_primary);
6058 /** @return Current total drag x change in quarter notes */
6060 NoteDrag::total_dx (GdkEvent * event) const
6062 if (_x_constrained) {
6066 TempoMap& map (_editor->session()->tempo_map());
6069 sampleoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
6071 /* primary note time */
6072 sampleoffset_t const n = map.sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6074 /* primary note time in quarter notes */
6075 double const n_qn = _region->session_relative_qn (_primary->note()->time().to_double());
6077 /* new time of the primary note in session samples */
6078 sampleoffset_t st = n + dx + snap_delta (event->button.state);
6080 /* possibly snap and return corresponding delta in quarter notes */
6081 MusicSample snap (st, 0);
6082 _editor->snap_to_with_modifier (snap, event, RoundNearest, SnapToGrid_Unscaled);
6083 double ret = map.exact_qn_at_sample (snap.sample, snap.division) - n_qn - snap_delta_music (event->button.state);
6085 /* prevent the earliest note being dragged earlier than the region's start position */
6086 if (_earliest + ret < _region->midi_region()->start_beats()) {
6087 ret -= (_earliest + ret) - _region->midi_region()->start_beats();
6093 /** @return Current total drag y change in note number */
6095 NoteDrag::total_dy () const
6097 if (_y_constrained) {
6101 double const y = _region->midi_view()->y_position ();
6102 /* new current note */
6103 uint8_t n = _region->y_to_note (current_pointer_y () - y);
6105 MidiStreamView* msv = _region->midi_stream_view ();
6106 n = max (msv->lowest_note(), n);
6107 n = min (msv->highest_note(), n);
6108 /* and work out delta */
6109 return n - _region->y_to_note (grab_y() - y);
6113 NoteDrag::motion (GdkEvent * event, bool first_move)
6116 _earliest = _region->earliest_in_selection().to_double();
6118 /* make copies of all the selected notes */
6119 _primary = _region->copy_selection (_primary);
6123 /* Total change in x and y since the start of the drag */
6124 double const dx_qn = total_dx (event);
6125 int8_t const dy = total_dy ();
6127 /* Now work out what we have to do to the note canvas items to set this new drag delta */
6128 double const tdx = _x_constrained ? 0 : dx_qn - _cumulative_dx;
6129 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
6132 _cumulative_dx = dx_qn;
6133 _cumulative_dy += tdy;
6135 int8_t note_delta = total_dy();
6139 _region->move_copies (dx_qn, tdy, note_delta);
6141 _region->move_selection (dx_qn, tdy, note_delta);
6144 /* the new note value may be the same as the old one, but we
6145 * don't know what that means because the selection may have
6146 * involved more than one note and we might be doing something
6147 * odd with them. so show the note value anyway, always.
6150 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
6152 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
6154 _editor->set_snapped_cursor_position( _region->source_beats_to_absolute_samples(_primary->note()->time()) );
6160 NoteDrag::finished (GdkEvent* ev, bool moved)
6163 /* no motion - select note */
6165 if (_editor->current_mouse_mode() == Editing::MouseContent ||
6166 _editor->current_mouse_mode() == Editing::MouseDraw) {
6168 bool changed = false;
6170 if (_was_selected) {
6171 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6173 _region->note_deselected (_primary);
6176 _editor->get_selection().clear_points();
6177 _region->unique_select (_primary);
6181 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
6182 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6184 if (!extend && !add && _region->selection_size() > 1) {
6185 _editor->get_selection().clear_points();
6186 _region->unique_select (_primary);
6188 } else if (extend) {
6189 _region->note_selected (_primary, true, true);
6192 /* it was added during button press */
6199 _editor->begin_reversible_selection_op(X_("Select Note Release"));
6200 _editor->commit_reversible_selection_op();
6204 _region->note_dropped (_primary, total_dx (ev), total_dy(), _copy);
6209 NoteDrag::aborted (bool)
6214 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
6215 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
6216 : Drag (editor, atv->base_item ())
6218 , _y_origin (atv->y_position())
6219 , _nothing_to_drag (false)
6221 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6222 setup (atv->lines ());
6225 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
6226 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
6227 : Drag (editor, rv->get_canvas_group ())
6229 , _y_origin (rv->get_time_axis_view().y_position())
6230 , _nothing_to_drag (false)
6233 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6235 list<boost::shared_ptr<AutomationLine> > lines;
6237 AudioRegionView* audio_view;
6238 AutomationRegionView* automation_view;
6239 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
6240 lines.push_back (audio_view->get_gain_line ());
6241 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
6242 lines.push_back (automation_view->line ());
6245 error << _("Automation range drag created for invalid region type") << endmsg;
6251 /** @param lines AutomationLines to drag.
6252 * @param offset Offset from the session start to the points in the AutomationLines.
6255 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
6257 /* find the lines that overlap the ranges being dragged */
6258 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
6259 while (i != lines.end ()) {
6260 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
6263 pair<samplepos_t, samplepos_t> r = (*i)->get_point_x_range ();
6265 /* check this range against all the AudioRanges that we are using */
6266 list<AudioRange>::const_iterator k = _ranges.begin ();
6267 while (k != _ranges.end()) {
6268 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
6274 /* add it to our list if it overlaps at all */
6275 if (k != _ranges.end()) {
6280 _lines.push_back (n);
6286 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
6290 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
6292 return 1.0 - ((global_y - _y_origin) / line->height());
6296 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
6298 const double v = list->eval(x);
6299 return _integral ? rint(v) : v;
6303 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6305 Drag::start_grab (event, cursor);
6307 /* Get line states before we start changing things */
6308 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6309 i->state = &i->line->get_state ();
6310 i->original_fraction = y_fraction (i->line, current_pointer_y());
6313 if (_ranges.empty()) {
6315 /* No selected time ranges: drag all points */
6316 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6317 uint32_t const N = i->line->npoints ();
6318 for (uint32_t j = 0; j < N; ++j) {
6319 i->points.push_back (i->line->nth (j));
6325 if (_nothing_to_drag) {
6331 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
6333 if (_nothing_to_drag && !first_move) {
6338 _editor->begin_reversible_command (_("automation range move"));
6340 if (!_ranges.empty()) {
6342 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
6344 samplecnt_t const half = (i->start + i->end) / 2;
6346 /* find the line that this audio range starts in */
6347 list<Line>::iterator j = _lines.begin();
6348 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
6352 if (j != _lines.end()) {
6353 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6355 /* j is the line that this audio range starts in; fade into it;
6356 64 samples length plucked out of thin air.
6359 samplepos_t a = i->start + 64;
6364 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
6365 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
6367 XMLNode &before = the_list->get_state();
6368 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6369 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6371 if (add_p || add_q) {
6372 _editor->session()->add_command (
6373 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6377 /* same thing for the end */
6380 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
6384 if (j != _lines.end()) {
6385 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6387 /* j is the line that this audio range starts in; fade out of it;
6388 64 samples length plucked out of thin air.
6391 samplepos_t b = i->end - 64;
6396 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6397 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6399 XMLNode &before = the_list->get_state();
6400 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6401 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6403 if (add_p || add_q) {
6404 _editor->session()->add_command (
6405 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6410 _nothing_to_drag = true;
6412 /* Find all the points that should be dragged and put them in the relevant
6413 points lists in the Line structs.
6416 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6418 uint32_t const N = i->line->npoints ();
6419 for (uint32_t j = 0; j < N; ++j) {
6421 /* here's a control point on this line */
6422 ControlPoint* p = i->line->nth (j);
6423 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6425 /* see if it's inside a range */
6426 list<AudioRange>::const_iterator k = _ranges.begin ();
6427 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6431 if (k != _ranges.end()) {
6432 /* dragging this point */
6433 _nothing_to_drag = false;
6434 i->points.push_back (p);
6440 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6441 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6445 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6446 float const f = y_fraction (l->line, current_pointer_y());
6447 /* we are ignoring x position for this drag, so we can just pass in anything */
6448 pair<float, float> result;
6450 result = l->line->drag_motion (0, f, true, false, ignored);
6451 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (result.first, result.second));
6456 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6458 if (_nothing_to_drag || !motion_occurred) {
6462 motion (event, false);
6463 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6464 i->line->end_drag (false, 0);
6467 _editor->commit_reversible_command ();
6471 AutomationRangeDrag::aborted (bool)
6473 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6478 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6480 , initial_time_axis_view (itav)
6482 /* note that time_axis_view may be null if the regionview was created
6483 * as part of a copy operation.
6485 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6486 layer = v->region()->layer ();
6487 initial_y = v->get_canvas_group()->position().y;
6488 initial_playlist = v->region()->playlist ();
6489 initial_position = v->region()->position ();
6490 initial_end = v->region()->position () + v->region()->length ();
6493 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6494 : Drag (e, i->canvas_item ())
6497 , _cumulative_dx (0)
6499 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6500 _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time()),
6505 PatchChangeDrag::motion (GdkEvent* ev, bool)
6507 samplepos_t f = adjusted_current_sample (ev);
6508 boost::shared_ptr<Region> r = _region_view->region ();
6509 f = max (f, r->position ());
6510 f = min (f, r->last_sample ());
6512 samplecnt_t const dxf = f - grab_sample(); // permitted dx in samples
6513 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6514 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6515 _cumulative_dx = dxu;
6519 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6521 if (!movement_occurred) {
6522 if (was_double_click()) {
6523 _region_view->edit_patch_change (_patch_change);
6528 boost::shared_ptr<Region> r (_region_view->region ());
6529 samplepos_t f = adjusted_current_sample (ev);
6530 f = max (f, r->position ());
6531 f = min (f, r->last_sample ());
6533 _region_view->move_patch_change (
6535 _region_view->region_samples_to_region_beats (f - (r->position() - r->start()))
6540 PatchChangeDrag::aborted (bool)
6542 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6546 PatchChangeDrag::setup_pointer_sample_offset ()
6548 boost::shared_ptr<Region> region = _region_view->region ();
6549 _pointer_sample_offset = raw_grab_sample() - _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time());
6552 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6553 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6560 MidiRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6562 _region_view->update_drag_selection (
6564 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6568 MidiRubberbandSelectDrag::deselect_things ()
6573 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6574 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6577 _vertical_only = true;
6581 MidiVerticalSelectDrag::select_things (int button_state, samplepos_t /*x1*/, samplepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6583 double const y = _region_view->midi_view()->y_position ();
6585 y1 = max (0.0, y1 - y);
6586 y2 = max (0.0, y2 - y);
6588 _region_view->update_vertical_drag_selection (
6591 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6596 MidiVerticalSelectDrag::deselect_things ()
6601 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6602 : RubberbandSelectDrag (e, i)
6608 EditorRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool drag_in_progress)
6610 if (drag_in_progress) {
6611 /* We just want to select things at the end of the drag, not during it */
6615 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6617 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6619 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6621 _editor->commit_reversible_selection_op ();
6625 EditorRubberbandSelectDrag::deselect_things ()
6627 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6629 _editor->selection->clear_tracks();
6630 _editor->selection->clear_regions();
6631 _editor->selection->clear_points ();
6632 _editor->selection->clear_lines ();
6633 _editor->selection->clear_midi_notes ();
6635 _editor->commit_reversible_selection_op();
6638 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6643 _note[0] = _note[1] = 0;
6646 NoteCreateDrag::~NoteCreateDrag ()
6652 NoteCreateDrag::grid_samples (samplepos_t t) const
6655 const Temporal::Beats grid_beats = _region_view->get_grid_beats (t);
6656 const Temporal::Beats t_beats = _region_view->region_samples_to_region_beats (t);
6658 return _region_view->region_beats_to_region_samples (t_beats + grid_beats)
6659 - _region_view->region_beats_to_region_samples (t_beats);
6663 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6665 Drag::start_grab (event, cursor);
6667 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6668 TempoMap& map (_editor->session()->tempo_map());
6670 const samplepos_t pf = _drags->current_pointer_sample ();
6671 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6673 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6675 double eqaf = map.exact_qn_at_sample (pf, divisions);
6677 if (divisions != 0) {
6679 const double qaf = map.quarter_note_at_sample (pf);
6681 /* Hack so that we always snap to the note that we are over, instead of snapping
6682 to the next one if we're more than halfway through the one we're over.
6685 const double rem = eqaf - qaf;
6687 eqaf -= grid_beats.to_double();
6691 _note[0] = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6692 /* minimum initial length is grid beats */
6693 _note[1] = map.sample_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6695 double const x0 = _editor->sample_to_pixel (_note[0]);
6696 double const x1 = _editor->sample_to_pixel (_note[1]);
6697 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6699 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6700 _drag_rect->set_outline_all ();
6701 _drag_rect->set_outline_color (0xffffff99);
6702 _drag_rect->set_fill_color (0xffffff66);
6706 NoteCreateDrag::motion (GdkEvent* event, bool)
6708 TempoMap& map (_editor->session()->tempo_map());
6709 const samplepos_t pf = _drags->current_pointer_sample ();
6710 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6711 double eqaf = map.exact_qn_at_sample (pf, divisions);
6713 if (divisions != 0) {
6715 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6717 const double qaf = map.quarter_note_at_sample (pf);
6718 /* Hack so that we always snap to the note that we are over, instead of snapping
6719 to the next one if we're more than halfway through the one we're over.
6722 const double rem = eqaf - qaf;
6724 eqaf -= grid_beats.to_double();
6727 eqaf += grid_beats.to_double();
6729 _note[1] = max ((samplepos_t)0, map.sample_at_quarter_note (eqaf) - _region_view->region()->position ());
6731 double const x0 = _editor->sample_to_pixel (_note[0]);
6732 double const x1 = _editor->sample_to_pixel (_note[1]);
6733 _drag_rect->set_x0 (std::min(x0, x1));
6734 _drag_rect->set_x1 (std::max(x0, x1));
6738 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6740 /* we create a note even if there was no movement */
6741 samplepos_t const start = min (_note[0], _note[1]);
6742 samplepos_t const start_sess_rel = start + _region_view->region()->position();
6743 samplecnt_t length = max (_editor->pixel_to_sample (1.0), (samplecnt_t) fabs ((double)(_note[0] - _note[1])));
6744 samplecnt_t const g = grid_samples (start_sess_rel);
6746 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6750 TempoMap& map (_editor->session()->tempo_map());
6751 const double qn_length = map.quarter_notes_between_samples (start_sess_rel, start_sess_rel + length);
6752 Temporal::Beats qn_length_beats = max (Temporal::Beats::ticks(1), Temporal::Beats (qn_length));
6754 _editor->begin_reversible_command (_("Create Note"));
6755 _region_view->clear_editor_note_selection();
6756 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6757 _editor->commit_reversible_command ();
6761 NoteCreateDrag::y_to_region (double y) const
6764 _region_view->get_canvas_group()->canvas_to_item (x, y);
6769 NoteCreateDrag::aborted (bool)
6774 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6782 HitCreateDrag::~HitCreateDrag ()
6787 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6789 Drag::start_grab (event, cursor);
6791 TempoMap& map (_editor->session()->tempo_map());
6793 _y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6795 const samplepos_t pf = _drags->current_pointer_sample ();
6796 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6798 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6800 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6802 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6806 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6807 Temporal::Beats length = _region_view->get_grid_beats (pf);
6809 _editor->begin_reversible_command (_("Create Hit"));
6810 _region_view->clear_editor_note_selection();
6811 _region_view->create_note_at (start, _y, length, event->button.state, false);
6817 HitCreateDrag::motion (GdkEvent* event, bool)
6819 TempoMap& map (_editor->session()->tempo_map());
6821 const samplepos_t pf = _drags->current_pointer_sample ();
6822 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6824 if (divisions == 0) {
6828 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6829 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position ();
6831 if (_last_pos == start) {
6835 Temporal::Beats length = _region_view->get_grid_beats (pf);
6837 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6839 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6843 _region_view->create_note_at (start, _y, length, event->button.state, false);
6849 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6851 _editor->commit_reversible_command ();
6856 HitCreateDrag::y_to_region (double y) const
6859 _region_view->get_canvas_group()->canvas_to_item (x, y);
6864 HitCreateDrag::aborted (bool)
6869 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6874 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6878 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6880 Drag::start_grab (event, cursor);
6884 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6890 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6893 distance = _drags->current_pointer_x() - grab_x();
6894 len = ar->fade_in()->back()->when;
6896 distance = grab_x() - _drags->current_pointer_x();
6897 len = ar->fade_out()->back()->when;
6900 /* how long should it be ? */
6902 new_length = len + _editor->pixel_to_sample (distance);
6904 /* now check with the region that this is legal */
6906 new_length = ar->verify_xfade_bounds (new_length, start);
6909 arv->reset_fade_in_shape_width (ar, new_length);
6911 arv->reset_fade_out_shape_width (ar, new_length);
6916 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6922 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6925 distance = _drags->current_pointer_x() - grab_x();
6926 len = ar->fade_in()->back()->when;
6928 distance = grab_x() - _drags->current_pointer_x();
6929 len = ar->fade_out()->back()->when;
6932 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6934 _editor->begin_reversible_command ("xfade trim");
6935 ar->playlist()->clear_owned_changes ();
6938 ar->set_fade_in_length (new_length);
6940 ar->set_fade_out_length (new_length);
6943 /* Adjusting the xfade may affect other regions in the playlist, so we need
6944 to get undo Commands from the whole playlist rather than just the
6948 vector<Command*> cmds;
6949 ar->playlist()->rdiff (cmds);
6950 _editor->session()->add_commands (cmds);
6951 _editor->commit_reversible_command ();
6956 CrossfadeEdgeDrag::aborted (bool)
6959 // arv->redraw_start_xfade ();
6961 // arv->redraw_end_xfade ();
6965 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, samplepos_t pos)
6966 : Drag (e, item, true)
6970 RegionCutDrag::~RegionCutDrag ()
6975 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6977 Drag::start_grab (event, c);
6978 motion (event, false);
6982 RegionCutDrag::motion (GdkEvent* event, bool)
6987 RegionCutDrag::finished (GdkEvent* event, bool)
6989 _editor->get_track_canvas()->canvas()->re_enter();
6992 MusicSample pos (_drags->current_pointer_sample(), 0);
6993 _editor->snap_to_with_modifier (pos, event);
6995 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos.sample);
7001 _editor->split_regions_at (pos, rs);
7005 RegionCutDrag::aborted (bool)