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 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1434 TimeAxisView* tav = 0;
1436 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1437 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1438 uint32_t output_chan = region->n_channels();
1439 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1440 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1442 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1443 tav =_editor->time_axis_view_from_stripable (audio_tracks.front());
1445 ChanCount one_midi_port (DataType::MIDI, 1);
1446 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1447 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1448 Config->get_strict_io () || Profile->get_mixbus (),
1449 boost::shared_ptr<ARDOUR::PluginInfo>(),
1450 (ARDOUR::Plugin::PresetRecord*) 0,
1451 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1452 tav = _editor->time_axis_view_from_stripable (midi_tracks.front());
1456 tav->set_height (original->current_height());
1459 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1462 return dynamic_cast<RouteTimeAxisView*> (tav);
1466 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, MusicSample last_position, int32_t const ev_state)
1468 RegionSelection new_views;
1469 PlaylistSet modified_playlists;
1470 RouteTimeAxisView* new_time_axis_view = 0;
1471 samplecnt_t const drag_delta = _primary->region()->position() - _last_position.sample;
1473 TempoMap& tmap (_editor->session()->tempo_map());
1474 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1475 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1478 /* all changes were made during motion event handlers */
1480 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1484 _editor->commit_reversible_command ();
1488 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1489 PlaylistMapping playlist_mapping;
1491 /* insert the regions into their new playlists */
1492 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1494 RouteTimeAxisView* dest_rtv = 0;
1496 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1500 MusicSample where (0, 0);
1501 double quarter_note;
1503 if (changed_position && !_x_constrained) {
1504 where.set (i->view->region()->position() - drag_delta, 0);
1505 quarter_note = i->view->region()->quarter_note() - qn_delta;
1507 /* region has not moved - divisor will not affect musical pos */
1508 where.set (i->view->region()->position(), 0);
1509 quarter_note = i->view->region()->quarter_note();
1512 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1513 /* dragged to drop zone */
1515 PlaylistMapping::iterator pm;
1517 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1518 /* first region from this original playlist: create a new track */
1519 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1520 if(!new_time_axis_view) {
1524 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1525 dest_rtv = new_time_axis_view;
1527 /* we already created a new track for regions from this playlist, use it */
1528 dest_rtv = pm->second;
1531 /* destination time axis view is the one we dragged to */
1532 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1535 if (dest_rtv != 0) {
1536 RegionView* new_view;
1537 if (i->view == _primary && !_x_constrained) {
1538 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, last_position, last_pos_qn,
1539 modified_playlists, true);
1541 if (i->view->region()->position_lock_style() == AudioTime) {
1542 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1543 modified_playlists);
1545 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1546 modified_playlists, true);
1550 if (new_view != 0) {
1551 new_views.push_back (new_view);
1555 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1556 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1559 list<DraggingView>::const_iterator next = i;
1565 /* If we've created new regions either by copying or moving
1566 to a new track, we want to replace the old selection with the new ones
1569 if (new_views.size() > 0) {
1570 _editor->selection->set (new_views);
1573 /* write commands for the accumulated diffs for all our modified playlists */
1574 add_stateful_diff_commands_for_playlists (modified_playlists);
1576 _editor->commit_reversible_command ();
1580 RegionMoveDrag::finished_no_copy (
1581 bool const changed_position,
1582 bool const changed_tracks,
1583 MusicSample last_position,
1584 int32_t const ev_state
1587 RegionSelection new_views;
1588 PlaylistSet modified_playlists;
1589 PlaylistSet frozen_playlists;
1590 set<RouteTimeAxisView*> views_to_update;
1591 RouteTimeAxisView* new_time_axis_view = 0;
1592 samplecnt_t const drag_delta = _primary->region()->position() - last_position.sample;
1594 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1595 PlaylistMapping playlist_mapping;
1597 TempoMap& tmap (_editor->session()->tempo_map());
1598 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1599 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1601 std::set<boost::shared_ptr<const Region> > uniq;
1602 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1604 RegionView* rv = i->view;
1605 RouteTimeAxisView* dest_rtv = 0;
1607 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1612 if (uniq.find (rv->region()) != uniq.end()) {
1613 /* prevent duplicate moves when selecting regions from shared playlists */
1617 uniq.insert(rv->region());
1619 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1620 /* dragged to drop zone */
1622 PlaylistMapping::iterator pm;
1624 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1625 /* first region from this original playlist: create a new track */
1626 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1627 if(!new_time_axis_view) { // New track creation failed
1631 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1632 dest_rtv = new_time_axis_view;
1634 /* we already created a new track for regions from this playlist, use it */
1635 dest_rtv = pm->second;
1639 /* destination time axis view is the one we dragged to */
1640 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1645 double const dest_layer = i->layer;
1647 views_to_update.insert (dest_rtv);
1649 MusicSample where (0, 0);
1650 double quarter_note;
1652 if (changed_position && !_x_constrained) {
1653 where.set (rv->region()->position() - drag_delta, 0);
1654 quarter_note = i->view->region()->quarter_note() - qn_delta;
1656 where.set (rv->region()->position(), 0);
1657 quarter_note = i->view->region()->quarter_note();
1660 if (changed_tracks) {
1662 /* insert into new playlist */
1663 RegionView* new_view;
1664 if (rv == _primary && !_x_constrained) {
1665 new_view = insert_region_into_playlist (
1666 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, last_position, last_pos_qn,
1667 modified_playlists, true
1670 if (rv->region()->position_lock_style() == AudioTime) {
1672 new_view = insert_region_into_playlist (
1673 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1677 new_view = insert_region_into_playlist (
1678 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1679 modified_playlists, true
1684 if (new_view == 0) {
1689 new_views.push_back (new_view);
1691 /* remove from old playlist */
1693 /* the region that used to be in the old playlist is not
1694 moved to the new one - we use a copy of it. as a result,
1695 any existing editor for the region should no longer be
1698 rv->hide_region_editor();
1701 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1705 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1707 /* this movement may result in a crossfade being modified, or a layering change,
1708 so we need to get undo data from the playlist as well as the region.
1711 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1713 playlist->clear_changes ();
1716 rv->region()->clear_changes ();
1719 motion on the same track. plonk the previously reparented region
1720 back to its original canvas group (its streamview).
1721 No need to do anything for copies as they are fake regions which will be deleted.
1724 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1725 rv->get_canvas_group()->set_y_position (i->initial_y);
1728 /* just change the model */
1729 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1730 playlist->set_layer (rv->region(), dest_layer);
1733 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1735 r = frozen_playlists.insert (playlist);
1738 playlist->freeze ();
1740 if (rv == _primary) {
1741 rv->region()->set_position (where.sample, last_position.division);
1743 if (rv->region()->position_lock_style() == AudioTime) {
1744 /* move by sample offset */
1745 rv->region()->set_position (where.sample, 0);
1747 /* move by music offset */
1748 rv->region()->set_position_music (rv->region()->quarter_note() - qn_delta);
1751 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1754 if (changed_tracks) {
1756 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1757 was selected in all of them, then removing it from a playlist will have removed all
1758 trace of it from _views (i.e. there were N regions selected, we removed 1,
1759 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1760 corresponding regionview, and _views is now empty).
1762 This could have invalidated any and all iterators into _views.
1764 The heuristic we use here is: if the region selection is empty, break out of the loop
1765 here. if the region selection is not empty, then restart the loop because we know that
1766 we must have removed at least the region(view) we've just been working on as well as any
1767 that we processed on previous iterations.
1769 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1770 we can just iterate.
1774 if (_views.empty()) {
1785 /* If we've created new regions either by copying or moving
1786 to a new track, we want to replace the old selection with the new ones
1789 if (new_views.size() > 0) {
1790 _editor->selection->set (new_views);
1793 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1797 /* write commands for the accumulated diffs for all our modified playlists */
1798 add_stateful_diff_commands_for_playlists (modified_playlists);
1799 /* applies to _brushing */
1800 _editor->commit_reversible_command ();
1802 /* We have futzed with the layering of canvas items on our streamviews.
1803 If any region changed layer, this will have resulted in the stream
1804 views being asked to set up their region views, and all will be well.
1805 If not, we might now have badly-ordered region views. Ask the StreamViews
1806 involved to sort themselves out, just in case.
1809 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1810 (*i)->view()->playlist_layered ((*i)->track ());
1814 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1815 * @param region Region to remove.
1816 * @param playlist playlist To remove from.
1817 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1818 * that clear_changes () is only called once per playlist.
1821 RegionMoveDrag::remove_region_from_playlist (
1822 boost::shared_ptr<Region> region,
1823 boost::shared_ptr<Playlist> playlist,
1824 PlaylistSet& modified_playlists
1827 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1830 playlist->clear_changes ();
1833 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1837 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1838 * clearing the playlist's diff history first if necessary.
1839 * @param region Region to insert.
1840 * @param dest_rtv Destination RouteTimeAxisView.
1841 * @param dest_layer Destination layer.
1842 * @param where Destination position.
1843 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1844 * that clear_changes () is only called once per playlist.
1845 * @return New RegionView, or 0 if no insert was performed.
1848 RegionMoveDrag::insert_region_into_playlist (
1849 boost::shared_ptr<Region> region,
1850 RouteTimeAxisView* dest_rtv,
1853 double quarter_note,
1854 PlaylistSet& modified_playlists,
1858 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1859 if (!dest_playlist) {
1863 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1864 _new_region_view = 0;
1865 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1867 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1868 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1870 dest_playlist->clear_changes ();
1873 dest_playlist->add_region (region, where.sample, 1.0, false, where.division, quarter_note, true);
1875 dest_playlist->add_region (region, where.sample, 1.0, false, where.division);
1878 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1879 dest_playlist->set_layer (region, dest_layer);
1884 assert (_new_region_view);
1886 return _new_region_view;
1890 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1892 _new_region_view = rv;
1896 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1898 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1899 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1901 _editor->session()->add_command (c);
1910 RegionMoveDrag::aborted (bool movement_occurred)
1914 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1915 list<DraggingView>::const_iterator next = i;
1924 RegionMotionDrag::aborted (movement_occurred);
1929 RegionMotionDrag::aborted (bool)
1931 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1933 StreamView* sview = (*i)->view();
1936 if (sview->layer_display() == Expanded) {
1937 sview->set_layer_display (Stacked);
1942 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1943 RegionView* rv = i->view;
1944 TimeAxisView* tv = &(rv->get_time_axis_view ());
1945 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1947 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1948 rv->get_canvas_group()->set_y_position (0);
1950 rv->move (-_total_x_delta, 0);
1951 rv->set_height (rtv->view()->child_height ());
1955 /** @param b true to brush, otherwise false.
1956 * @param c true to make copies of the regions being moved, otherwise false.
1958 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1959 : RegionMotionDrag (e, i, p, v, b)
1961 , _new_region_view (0)
1963 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1965 _last_position = MusicSample (_primary->region()->position(), 0);
1969 RegionMoveDrag::setup_pointer_sample_offset ()
1971 _pointer_sample_offset = raw_grab_sample() - _last_position.sample;
1974 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, samplepos_t pos)
1975 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1977 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1979 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1980 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1982 _primary = v->view()->create_region_view (r, false, false);
1984 _primary->get_canvas_group()->show ();
1985 _primary->set_position (pos, 0);
1986 _views.push_back (DraggingView (_primary, this, v));
1988 _last_position = MusicSample (pos, 0);
1990 _item = _primary->get_canvas_group ();
1994 RegionInsertDrag::finished (GdkEvent * event, bool)
1996 int pos = _views.front().time_axis_view;
1997 assert(pos >= 0 && pos < (int)_time_axis_views.size());
1999 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
2001 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
2002 _primary->get_canvas_group()->set_y_position (0);
2004 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
2006 _editor->begin_reversible_command (Operations::insert_region);
2007 playlist->clear_changes ();
2008 _editor->snap_to_with_modifier (_last_position, event);
2010 playlist->add_region (_primary->region (), _last_position.sample, 1.0, false, _last_position.division);
2012 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2013 if (Config->get_edit_mode() == Ripple) {
2014 playlist->ripple (_last_position.sample, _primary->region()->length(), _primary->region());
2017 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2018 _editor->commit_reversible_command ();
2026 RegionInsertDrag::aborted (bool)
2033 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2034 : RegionMoveDrag (e, i, p, v, false, false)
2036 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2039 struct RegionSelectionByPosition {
2040 bool operator() (RegionView*a, RegionView* b) {
2041 return a->region()->position () < b->region()->position();
2046 RegionSpliceDrag::motion (GdkEvent* event, bool)
2048 /* Which trackview is this ? */
2050 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2051 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2053 /* The region motion is only processed if the pointer is over
2057 if (!tv || !tv->is_track()) {
2058 /* To make sure we hide the verbose canvas cursor when the mouse is
2059 not held over an audio track.
2061 _editor->verbose_cursor()->hide ();
2064 _editor->verbose_cursor()->show ();
2069 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2075 RegionSelection copy;
2076 _editor->selection->regions.by_position(copy);
2078 samplepos_t const pf = adjusted_current_sample (event);
2080 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2082 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2088 boost::shared_ptr<Playlist> playlist;
2090 if ((playlist = atv->playlist()) == 0) {
2094 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2099 if (pf < (*i)->region()->last_sample() + 1) {
2103 if (pf > (*i)->region()->first_sample()) {
2109 playlist->shuffle ((*i)->region(), dir);
2114 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2116 RegionMoveDrag::finished (event, movement_occurred);
2120 RegionSpliceDrag::aborted (bool)
2130 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, samplepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2133 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<samplepos_t>(where, max_samplepos));
2135 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2136 RegionSelection to_ripple;
2137 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2138 if ((*i)->position() >= where) {
2139 to_ripple.push_back (rtv->view()->find_view(*i));
2143 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2144 if (!exclude.contains (*i)) {
2145 // the selection has already been added to _views
2147 if (drag_in_progress) {
2148 // do the same things that RegionMotionDrag::motion does when
2149 // first_move is true, for the region views that we're adding
2150 // to _views this time
2153 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2154 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2155 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2156 rvg->reparent (_editor->_drag_motion_group);
2158 // we only need to move in the y direction
2159 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2164 _views.push_back (DraggingView (*i, this, tav));
2170 RegionRippleDrag::remove_unselected_from_views(samplecnt_t amount, bool move_regions)
2173 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2174 // we added all the regions after the selection
2176 std::list<DraggingView>::iterator to_erase = i++;
2177 if (!_editor->selection->regions.contains (to_erase->view)) {
2178 // restore the non-selected regions to their original playlist & positions,
2179 // and then ripple them back by the length of the regions that were dragged away
2180 // do the same things as RegionMotionDrag::aborted
2182 RegionView *rv = to_erase->view;
2183 TimeAxisView* tv = &(rv->get_time_axis_view ());
2184 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2187 // plonk them back onto their own track
2188 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2189 rv->get_canvas_group()->set_y_position (0);
2193 // move the underlying region to match the view
2194 rv->region()->set_position (rv->region()->position() + amount);
2196 // restore the view to match the underlying region's original position
2197 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2200 rv->set_height (rtv->view()->child_height ());
2201 _views.erase (to_erase);
2207 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2209 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2211 return allow_moves_across_tracks;
2219 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2220 : RegionMoveDrag (e, i, p, v, false, false)
2222 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2223 // compute length of selection
2224 RegionSelection selected_regions = _editor->selection->regions;
2225 selection_length = selected_regions.end_sample() - selected_regions.start();
2227 // we'll only allow dragging to another track in ripple mode if all the regions
2228 // being dragged start off on the same track
2229 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2232 exclude = new RegionList;
2233 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2234 exclude->push_back((*i)->region());
2237 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2238 RegionSelection copy;
2239 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2241 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2242 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2244 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2245 // find ripple start point on each applicable playlist
2246 RegionView *first_selected_on_this_track = NULL;
2247 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2248 if ((*i)->region()->playlist() == (*pi)) {
2249 // region is on this playlist - it's the first, because they're sorted
2250 first_selected_on_this_track = *i;
2254 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2255 add_all_after_to_views (
2256 &first_selected_on_this_track->get_time_axis_view(),
2257 first_selected_on_this_track->region()->position(),
2258 selected_regions, false);
2261 if (allow_moves_across_tracks) {
2262 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2270 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2272 /* Which trackview is this ? */
2274 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2275 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2277 /* The region motion is only processed if the pointer is over
2281 if (!tv || !tv->is_track()) {
2282 /* To make sure we hide the verbose canvas cursor when the mouse is
2283 not held over an audiotrack.
2285 _editor->verbose_cursor()->hide ();
2289 samplepos_t where = adjusted_current_sample (event);
2290 assert (where >= 0);
2291 MusicSample after (0, 0);
2292 double delta = compute_x_delta (event, &after);
2294 samplecnt_t amount = _editor->pixel_to_sample (delta);
2296 if (allow_moves_across_tracks) {
2297 // all the originally selected regions were on the same track
2299 samplecnt_t adjust = 0;
2300 if (prev_tav && tv != prev_tav) {
2301 // dragged onto a different track
2302 // remove the unselected regions from _views, restore them to their original positions
2303 // and add the regions after the drop point on the new playlist to _views instead.
2304 // undo the effect of rippling the previous playlist, and include the effect of removing
2305 // the dragged region(s) from this track
2307 remove_unselected_from_views (prev_amount, false);
2308 // ripple previous playlist according to the regions that have been removed onto the new playlist
2309 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2312 // move just the selected regions
2313 RegionMoveDrag::motion(event, first_move);
2315 // ensure that the ripple operation on the new playlist inserts selection_length time
2316 adjust = selection_length;
2317 // ripple the new current playlist
2318 tv->playlist()->ripple (where, amount+adjust, exclude);
2320 // add regions after point where drag entered this track to subsequent ripples
2321 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2324 // motion on same track
2325 RegionMoveDrag::motion(event, first_move);
2329 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2330 prev_position = where;
2332 // selection encompasses multiple tracks - just drag
2333 // cross-track drags are forbidden
2334 RegionMoveDrag::motion(event, first_move);
2337 if (!_x_constrained) {
2338 prev_amount += amount;
2341 _last_position = after;
2345 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2347 if (!movement_occurred) {
2351 if (was_double_click() && !_views.empty()) {
2352 DraggingView dv = _views.front();
2353 _editor->edit_region (dv.view);
2359 _editor->begin_reversible_command(_("Ripple drag"));
2361 // remove the regions being rippled from the dragging view, updating them to
2362 // their new positions
2363 remove_unselected_from_views (prev_amount, true);
2365 if (allow_moves_across_tracks) {
2367 // if regions were dragged across tracks, we've rippled any later
2368 // regions on the track the regions were dragged off, so we need
2369 // to add the original track to the undo record
2370 orig_tav->playlist()->clear_changes();
2371 vector<Command*> cmds;
2372 orig_tav->playlist()->rdiff (cmds);
2373 _editor->session()->add_commands (cmds);
2375 if (prev_tav && prev_tav != orig_tav) {
2376 prev_tav->playlist()->clear_changes();
2377 vector<Command*> cmds;
2378 prev_tav->playlist()->rdiff (cmds);
2379 _editor->session()->add_commands (cmds);
2382 // selection spanned multiple tracks - all will need adding to undo record
2384 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2385 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2387 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2388 (*pi)->clear_changes();
2389 vector<Command*> cmds;
2390 (*pi)->rdiff (cmds);
2391 _editor->session()->add_commands (cmds);
2395 // other modified playlists are added to undo by RegionMoveDrag::finished()
2396 RegionMoveDrag::finished (event, movement_occurred);
2397 _editor->commit_reversible_command();
2401 RegionRippleDrag::aborted (bool movement_occurred)
2403 RegionMoveDrag::aborted (movement_occurred);
2408 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2410 _view (dynamic_cast<MidiTimeAxisView*> (v))
2412 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2418 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2422 _editor->begin_reversible_command (_("create region"));
2423 _region = add_midi_region (_view, false);
2424 _view->playlist()->freeze ();
2428 samplepos_t const f = adjusted_current_sample (event);
2429 if (f <= grab_sample()) {
2430 _region->set_initial_position (f);
2433 /* Don't use a zero-length region, and subtract 1 sample from the snapped length
2434 so that if this region is duplicated, its duplicate starts on
2435 a snap point rather than 1 sample after a snap point. Otherwise things get
2436 a bit confusing as if a region starts 1 sample after a snap point, one cannot
2437 place snapped notes at the start of the region.
2439 if (f != grab_sample()) {
2440 samplecnt_t const len = (samplecnt_t) fabs ((double)(f - grab_sample () - 1));
2441 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2448 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2450 if (!movement_occurred) {
2451 add_midi_region (_view, true);
2453 _view->playlist()->thaw ();
2454 _editor->commit_reversible_command();
2459 RegionCreateDrag::aborted (bool)
2462 _view->playlist()->thaw ();
2468 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2473 , _was_selected (false)
2476 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2480 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2482 Gdk::Cursor* cursor;
2483 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2485 float x_fraction = cnote->mouse_x_fraction ();
2487 if (x_fraction > 0.0 && x_fraction < 0.25) {
2488 cursor = _editor->cursors()->left_side_trim;
2491 cursor = _editor->cursors()->right_side_trim;
2495 Drag::start_grab (event, cursor);
2497 region = &cnote->region_view();
2500 temp = region->snap_to_pixel (cnote->x0 (), true);
2501 _snap_delta = temp - cnote->x0 ();
2505 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2510 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2511 if (ms.size() > 1) {
2512 /* has to be relative, may make no sense otherwise */
2516 if (!(_was_selected = cnote->selected())) {
2518 /* tertiary-click means extend selection - we'll do that on button release,
2519 so don't add it here, because otherwise we make it hard to figure
2520 out the "extend-to" range.
2523 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2526 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2529 region->note_selected (cnote, true);
2531 _editor->get_selection().clear_points();
2532 region->unique_select (cnote);
2539 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2541 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2543 _editor->begin_reversible_command (_("resize notes"));
2545 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2546 MidiRegionSelection::iterator next;
2549 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2551 mrv->begin_resizing (at_front);
2557 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2558 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2560 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2564 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2566 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2567 if (_editor->snap_mode () != SnapOff) {
2571 if (_editor->snap_mode () == SnapOff) {
2573 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2574 if (apply_snap_delta) {
2580 if (apply_snap_delta) {
2584 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2590 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2592 if (!movement_occurred) {
2593 /* no motion - select note */
2594 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2595 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2596 _editor->current_mouse_mode() == Editing::MouseDraw) {
2598 bool changed = false;
2600 if (_was_selected) {
2601 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2603 region->note_deselected (cnote);
2606 _editor->get_selection().clear_points();
2607 region->unique_select (cnote);
2611 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2612 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2614 if (!extend && !add && region->selection_size() > 1) {
2615 _editor->get_selection().clear_points();
2616 region->unique_select (cnote);
2618 } else if (extend) {
2619 region->note_selected (cnote, true, true);
2622 /* it was added during button press */
2628 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2629 _editor->commit_reversible_selection_op();
2636 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2637 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2638 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2640 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2643 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2645 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2646 if (_editor->snap_mode () != SnapOff) {
2650 if (_editor->snap_mode () == SnapOff) {
2652 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2653 if (apply_snap_delta) {
2659 if (apply_snap_delta) {
2663 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2667 _editor->commit_reversible_command ();
2671 NoteResizeDrag::aborted (bool)
2673 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2674 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2675 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2677 mrv->abort_resizing ();
2682 AVDraggingView::AVDraggingView (RegionView* v)
2685 initial_position = v->region()->position ();
2688 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2691 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2694 TrackViewList empty;
2696 _editor->get_regions_after(rs, (samplepos_t) 0, empty);
2697 std::list<RegionView*> views = rs.by_layer();
2700 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2701 RegionView* rv = (*i);
2702 if (!rv->region()->video_locked()) {
2705 if (rv->region()->locked()) {
2708 _views.push_back (AVDraggingView (rv));
2713 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2715 Drag::start_grab (event);
2716 if (_editor->session() == 0) {
2720 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2726 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2730 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2731 _max_backwards_drag = (
2732 ARDOUR_UI::instance()->video_timeline->get_duration()
2733 + ARDOUR_UI::instance()->video_timeline->get_offset()
2734 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2737 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2738 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2739 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv (i->initial_position);
2742 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2745 Timecode::Time timecode;
2746 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2747 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);
2748 show_verbose_cursor_text (buf);
2752 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2754 if (_editor->session() == 0) {
2757 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2761 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2765 samplecnt_t dt = adjusted_current_sample (event) - raw_grab_sample() + _pointer_sample_offset;
2766 dt = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2768 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2769 dt = - _max_backwards_drag;
2772 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2773 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2775 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2776 RegionView* rv = i->view;
2777 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2780 rv->region()->clear_changes ();
2781 rv->region()->suspend_property_changes();
2783 rv->region()->set_position(i->initial_position + dt);
2784 rv->region_changed(ARDOUR::Properties::position);
2787 const samplepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2788 Timecode::Time timecode;
2789 Timecode::Time timediff;
2791 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2792 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2793 snprintf (buf, sizeof (buf),
2794 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2795 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2796 , _("Video Start:"),
2797 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2799 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2801 show_verbose_cursor_text (buf);
2805 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2807 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2814 if (!movement_occurred || ! _editor->session()) {
2818 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2820 _editor->begin_reversible_command (_("Move Video"));
2822 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2823 ARDOUR_UI::instance()->video_timeline->save_undo();
2824 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2825 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2827 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2828 i->view->drag_end();
2829 i->view->region()->resume_property_changes ();
2831 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2834 _editor->session()->maybe_update_session_range(
2835 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::sampleoffset_t) 0),
2836 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::sampleoffset_t) 0)
2840 _editor->commit_reversible_command ();
2844 VideoTimeLineDrag::aborted (bool)
2846 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2849 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2850 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2852 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2853 i->view->region()->resume_property_changes ();
2854 i->view->region()->set_position(i->initial_position);
2858 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2859 : RegionDrag (e, i, p, v)
2860 , _operation (StartTrim)
2861 , _preserve_fade_anchor (preserve_fade_anchor)
2862 , _jump_position_when_done (false)
2864 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2868 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2870 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2872 samplepos_t const region_start = _primary->region()->position();
2873 samplepos_t const region_end = _primary->region()->last_sample();
2874 samplecnt_t const region_length = _primary->region()->length();
2876 samplepos_t const pf = adjusted_current_sample (event);
2877 setup_snap_delta (MusicSample(region_start, 0));
2879 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2880 /* Move the contents of the region around without changing the region bounds */
2881 _operation = ContentsTrim;
2882 Drag::start_grab (event, _editor->cursors()->trimmer);
2884 /* These will get overridden for a point trim.*/
2885 if (pf < (region_start + region_length/2)) {
2886 /* closer to front */
2887 _operation = StartTrim;
2888 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2889 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2891 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2895 _operation = EndTrim;
2896 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2897 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2899 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2903 /* jump trim disabled for now
2904 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2905 _jump_position_when_done = true;
2909 switch (_operation) {
2911 show_verbose_cursor_time (region_start);
2914 show_verbose_cursor_duration (region_start, region_end);
2917 show_verbose_cursor_time (pf);
2921 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2922 i->view->region()->suspend_property_changes ();
2927 TrimDrag::motion (GdkEvent* event, bool first_move)
2929 RegionView* rv = _primary;
2931 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2932 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2933 sampleoffset_t sample_delta = 0;
2935 MusicSample adj_sample = adjusted_sample (_drags->current_pointer_sample () + snap_delta (event->button.state), event, true);
2936 samplecnt_t dt = adj_sample.sample - raw_grab_sample () + _pointer_sample_offset - snap_delta (event->button.state);
2942 switch (_operation) {
2944 trim_type = "Region start trim";
2947 trim_type = "Region end trim";
2950 trim_type = "Region content trim";
2957 _editor->begin_reversible_command (trim_type);
2959 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2960 RegionView* rv = i->view;
2961 rv->region()->playlist()->clear_owned_changes ();
2963 if (_operation == StartTrim) {
2964 rv->trim_front_starting ();
2967 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2970 arv->temporarily_hide_envelope ();
2974 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2975 insert_result = _editor->motion_frozen_playlists.insert (pl);
2977 if (insert_result.second) {
2981 MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (rv);
2982 /* a MRV start trim may change the source length. ensure we cover all playlists here */
2983 if (mrv && _operation == StartTrim) {
2984 vector<boost::shared_ptr<Playlist> > all_playlists;
2985 _editor->session()->playlists->get (all_playlists);
2986 for (vector<boost::shared_ptr<Playlist> >::iterator x = all_playlists.begin(); x != all_playlists.end(); ++x) {
2988 if ((*x)->uses_source (rv->region()->source(0))) {
2989 insert_result = _editor->motion_frozen_playlists.insert (*x);
2990 if (insert_result.second) {
2991 (*x)->clear_owned_changes ();
3001 bool non_overlap_trim = false;
3003 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
3004 non_overlap_trim = true;
3007 /* contstrain trim to fade length */
3008 if (_preserve_fade_anchor) {
3009 switch (_operation) {
3011 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3012 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3014 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3015 if (ar->locked()) continue;
3016 samplecnt_t len = ar->fade_in()->back()->when;
3017 if (len < dt) dt = min(dt, len);
3021 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3022 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3024 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3025 if (ar->locked()) continue;
3026 samplecnt_t len = ar->fade_out()->back()->when;
3027 if (len < -dt) dt = max(dt, -len);
3035 switch (_operation) {
3037 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3038 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3039 , adj_sample.division);
3041 if (changed && _preserve_fade_anchor) {
3042 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3044 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3045 samplecnt_t len = ar->fade_in()->back()->when;
3046 samplecnt_t diff = ar->first_sample() - i->initial_position;
3047 samplepos_t new_length = len - diff;
3048 i->anchored_fade_length = min (ar->length(), new_length);
3049 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3050 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3057 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3058 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, adj_sample.division);
3059 if (changed && _preserve_fade_anchor) {
3060 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3062 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3063 samplecnt_t len = ar->fade_out()->back()->when;
3064 samplecnt_t diff = 1 + ar->last_sample() - i->initial_end;
3065 samplepos_t new_length = len + diff;
3066 i->anchored_fade_length = min (ar->length(), new_length);
3067 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3068 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3076 sample_delta = (last_pointer_sample() - adjusted_current_sample(event));
3078 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3079 i->view->move_contents (sample_delta);
3085 switch (_operation) {
3087 show_verbose_cursor_time (rv->region()->position());
3090 show_verbose_cursor_duration (rv->region()->position(), rv->region()->last_sample());
3093 // show_verbose_cursor_time (sample_delta);
3099 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3101 if (movement_occurred) {
3102 motion (event, false);
3104 if (_operation == StartTrim) {
3105 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3107 /* This must happen before the region's StatefulDiffCommand is created, as it may
3108 `correct' (ahem) the region's _start from being negative to being zero. It
3109 needs to be zero in the undo record.
3111 i->view->trim_front_ending ();
3113 if (_preserve_fade_anchor) {
3114 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3116 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3117 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3118 ar->set_fade_in_length(i->anchored_fade_length);
3119 ar->set_fade_in_active(true);
3122 if (_jump_position_when_done) {
3123 i->view->region()->set_position (i->initial_position);
3126 } else if (_operation == EndTrim) {
3127 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3128 if (_preserve_fade_anchor) {
3129 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3131 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3132 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3133 ar->set_fade_out_length(i->anchored_fade_length);
3134 ar->set_fade_out_active(true);
3137 if (_jump_position_when_done) {
3138 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3143 if (!_editor->selection->selected (_primary)) {
3144 _primary->thaw_after_trim ();
3146 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3147 i->view->thaw_after_trim ();
3148 i->view->enable_display (true);
3152 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3153 /* Trimming one region may affect others on the playlist, so we need
3154 to get undo Commands from the whole playlist rather than just the
3155 region. Use motion_frozen_playlists (a set) to make sure we don't
3156 diff a given playlist more than once.
3159 vector<Command*> cmds;
3161 _editor->session()->add_commands (cmds);
3165 _editor->motion_frozen_playlists.clear ();
3166 _editor->commit_reversible_command();
3169 /* no mouse movement */
3170 if (adjusted_current_sample (event) != adjusted_sample (_drags->current_pointer_sample(), event, false).sample) {
3171 _editor->point_trim (event, adjusted_current_sample (event));
3175 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3176 i->view->region()->resume_property_changes ();
3181 TrimDrag::aborted (bool movement_occurred)
3183 /* Our motion method is changing model state, so use the Undo system
3184 to cancel. Perhaps not ideal, as this will leave an Undo point
3185 behind which may be slightly odd from the user's point of view.
3189 finished (&ev, true);
3191 if (movement_occurred) {
3192 _editor->session()->undo (1);
3195 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3196 i->view->region()->resume_property_changes ();
3201 TrimDrag::setup_pointer_sample_offset ()
3203 list<DraggingView>::iterator i = _views.begin ();
3204 while (i != _views.end() && i->view != _primary) {
3208 if (i == _views.end()) {
3212 switch (_operation) {
3214 _pointer_sample_offset = raw_grab_sample() - i->initial_position;
3217 _pointer_sample_offset = raw_grab_sample() - i->initial_end;
3224 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3227 , _old_grid_type (e->grid_type())
3228 , _old_snap_mode (e->snap_mode())
3231 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3232 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3234 _real_section = &_marker->meter();
3239 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3241 Drag::start_grab (event, cursor);
3242 show_verbose_cursor_time (adjusted_current_sample(event));
3246 MeterMarkerDrag::setup_pointer_sample_offset ()
3248 _pointer_sample_offset = raw_grab_sample() - _marker->meter().sample();
3252 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3255 // create a dummy marker to catch events, then hide it.
3258 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3260 _marker = new MeterMarker (
3262 *_editor->meter_group,
3263 UIConfiguration::instance().color ("meter marker"),
3265 *new MeterSection (_marker->meter())
3268 /* use the new marker for the grab */
3269 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3272 TempoMap& map (_editor->session()->tempo_map());
3273 /* get current state */
3274 before_state = &map.get_state();
3277 _editor->begin_reversible_command (_("move meter mark"));
3279 _editor->begin_reversible_command (_("copy meter mark"));
3281 Timecode::BBT_Time bbt = _real_section->bbt();
3283 /* we can't add a meter where one currently exists */
3284 if (_real_section->sample() < adjusted_current_sample (event, false)) {
3289 const samplepos_t sample = map.sample_at_bbt (bbt);
3290 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3291 , bbt, sample, _real_section->position_lock_style());
3292 if (!_real_section) {
3298 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3299 if (_real_section->position_lock_style() != AudioTime) {
3300 _editor->set_grid_to (GridTypeBar);
3301 _editor->set_snap_mode (SnapMagnetic);
3305 samplepos_t pf = adjusted_current_sample (event);
3307 if (_real_section->position_lock_style() == AudioTime && _editor->grid_musical()) {
3308 /* never snap to music for audio locked */
3309 pf = adjusted_current_sample (event, false);
3312 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3314 /* fake marker meeds to stay under the mouse, unlike the real one. */
3315 _marker->set_position (adjusted_current_sample (event, false));
3317 show_verbose_cursor_time (_real_section->sample());
3318 _editor->set_snapped_cursor_position(_real_section->sample());
3322 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3324 if (!movement_occurred) {
3325 if (was_double_click()) {
3326 _editor->edit_meter_marker (*_marker);
3331 /* reinstate old snap setting */
3332 _editor->set_grid_to (_old_grid_type);
3333 _editor->set_snap_mode (_old_snap_mode);
3335 TempoMap& map (_editor->session()->tempo_map());
3337 XMLNode &after = map.get_state();
3338 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3339 _editor->commit_reversible_command ();
3341 // delete the dummy marker we used for visual representation while moving.
3342 // a new visual marker will show up automatically.
3347 MeterMarkerDrag::aborted (bool moved)
3349 _marker->set_position (_marker->meter().sample ());
3351 /* reinstate old snap setting */
3352 _editor->set_grid_to (_old_grid_type);
3353 _editor->set_snap_mode (_old_snap_mode);
3355 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3356 // delete the dummy marker we used for visual representation while moving.
3357 // a new visual marker will show up automatically.
3362 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3365 , _grab_bpm (120.0, 4.0)
3369 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3371 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3372 _real_section = &_marker->tempo();
3373 _movable = !_real_section->initial();
3374 _grab_bpm = Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), _real_section->end_note_types_per_minute());
3375 _grab_qn = _real_section->pulse() * 4.0;
3380 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3382 Drag::start_grab (event, cursor);
3383 if (!_real_section->active()) {
3384 show_verbose_cursor_text (_("inactive"));
3386 show_verbose_cursor_time (adjusted_current_sample (event));
3391 TempoMarkerDrag::setup_pointer_sample_offset ()
3393 _pointer_sample_offset = raw_grab_sample() - _real_section->sample();
3397 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3399 if (!_real_section->active()) {
3402 TempoMap& map (_editor->session()->tempo_map());
3406 // mvc drag - create a dummy marker to catch events, hide it.
3409 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3411 TempoSection section (_marker->tempo());
3413 _marker = new TempoMarker (
3415 *_editor->tempo_group,
3416 UIConfiguration::instance().color ("tempo marker"),
3418 *new TempoSection (_marker->tempo())
3421 /* use the new marker for the grab */
3422 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3425 /* get current state */
3426 _before_state = &map.get_state();
3429 _editor->begin_reversible_command (_("move tempo mark"));
3432 const Tempo tempo (_marker->tempo());
3433 const samplepos_t sample = adjusted_current_sample (event) + 1;
3435 _editor->begin_reversible_command (_("copy tempo mark"));
3437 if (_real_section->position_lock_style() == MusicTime) {
3438 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3439 _real_section = map.add_tempo (tempo, map.exact_qn_at_sample (sample, divisions), 0, MusicTime);
3441 _real_section = map.add_tempo (tempo, 0.0, sample, AudioTime);
3444 if (!_real_section) {
3451 if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) {
3452 double new_bpm = max (1.5, _grab_bpm.end_note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3454 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), new_bpm));
3455 strs << "end:" << fixed << setprecision(3) << new_bpm;
3456 show_verbose_cursor_text (strs.str());
3458 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3459 /* use vertical movement to alter tempo .. should be log */
3460 double new_bpm = max (1.5, _grab_bpm.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3462 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type(), _real_section->end_note_types_per_minute()));
3463 strs << "start:" << fixed << setprecision(3) << new_bpm;
3464 show_verbose_cursor_text (strs.str());
3466 } else if (_movable && !_real_section->locked_to_meter()) {
3469 if (_editor->grid_musical()) {
3470 /* we can't snap to a grid that we are about to move.
3471 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3473 pf = adjusted_current_sample (event, false);
3475 pf = adjusted_current_sample (event);
3478 /* snap to beat is 1, snap to bar is -1 (sorry) */
3479 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3481 map.gui_set_tempo_position (_real_section, pf, sub_num);
3483 show_verbose_cursor_time (_real_section->sample());
3484 _editor->set_snapped_cursor_position(_real_section->sample());
3486 _marker->set_position (adjusted_current_sample (event, false));
3490 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3492 if (!_real_section->active()) {
3495 if (!movement_occurred) {
3496 if (was_double_click()) {
3497 _editor->edit_tempo_marker (*_marker);
3502 TempoMap& map (_editor->session()->tempo_map());
3504 XMLNode &after = map.get_state();
3505 _editor->session()->add_command (new MementoCommand<TempoMap>(map, _before_state, &after));
3506 _editor->commit_reversible_command ();
3508 // delete the dummy marker we used for visual representation while moving.
3509 // a new visual marker will show up automatically.
3514 TempoMarkerDrag::aborted (bool moved)
3516 _marker->set_position (_marker->tempo().sample());
3518 TempoMap& map (_editor->session()->tempo_map());
3519 map.set_state (*_before_state, Stateful::current_state_version);
3520 // delete the dummy (hidden) marker we used for events while moving.
3525 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3530 , _drag_valid (true)
3532 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3537 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3539 Drag::start_grab (event, cursor);
3540 TempoMap& map (_editor->session()->tempo_map());
3541 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3543 if (adjusted_current_sample (event, false) <= _tempo->sample()) {
3544 _drag_valid = false;
3548 _editor->tempo_curve_selected (_tempo, true);
3551 if (_tempo->clamped()) {
3552 TempoSection* prev = map.previous_tempo_section (_tempo);
3554 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3558 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3559 show_verbose_cursor_text (sstr.str());
3563 BBTRulerDrag::setup_pointer_sample_offset ()
3565 TempoMap& map (_editor->session()->tempo_map());
3566 /* get current state */
3567 _before_state = &map.get_state();
3569 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3570 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3573 if (divisions > 0) {
3574 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3576 /* while it makes some sense for the user to determine the division to 'grab',
3577 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3578 and the result over steep tempo curves. Use sixteenths.
3580 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3583 _grab_qn = map.quarter_note_at_beat (beat);
3585 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3590 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3597 _editor->begin_reversible_command (_("stretch tempo"));
3600 TempoMap& map (_editor->session()->tempo_map());
3603 if (_editor->grid_musical()) {
3604 pf = adjusted_current_sample (event, false);
3606 pf = adjusted_current_sample (event);
3609 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3610 /* adjust previous tempo to match pointer sample */
3611 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.sample_at_quarter_note (_grab_qn), pf, _grab_qn, map.quarter_note_at_sample (pf));
3615 if (_tempo->clamped()) {
3616 TempoSection* prev = map.previous_tempo_section (_tempo);
3618 _editor->tempo_curve_selected (prev, true);
3619 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3622 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3623 show_verbose_cursor_text (sstr.str());
3627 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3629 if (!movement_occurred) {
3633 TempoMap& map (_editor->session()->tempo_map());
3635 _editor->tempo_curve_selected (_tempo, false);
3636 if (_tempo->clamped()) {
3637 TempoSection* prev_tempo = map.previous_tempo_section (_tempo);
3639 _editor->tempo_curve_selected (prev_tempo, false);
3643 if (!movement_occurred || !_drag_valid) {
3647 XMLNode &after = map.get_state();
3648 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3649 _editor->commit_reversible_command ();
3654 BBTRulerDrag::aborted (bool moved)
3657 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3661 TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i)
3667 , _drag_valid (true)
3670 DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n");
3675 TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3677 Drag::start_grab (event, cursor);
3678 TempoMap& map (_editor->session()->tempo_map());
3679 /* get current state */
3680 _before_state = &map.get_state();
3681 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3683 if (_tempo->locked_to_meter()) {
3684 _drag_valid = false;
3688 _next_tempo = map.next_tempo_section (_tempo);
3690 if (!map.next_tempo_section (_next_tempo)) {
3691 _drag_valid = false;
3692 finished (event, false);
3696 _editor->tempo_curve_selected (_tempo, true);
3697 _editor->tempo_curve_selected (_next_tempo, true);
3700 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3701 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3702 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3703 show_verbose_cursor_text (sstr.str());
3705 _drag_valid = false;
3708 _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type());
3712 TempoTwistDrag::setup_pointer_sample_offset ()
3714 TempoMap& map (_editor->session()->tempo_map());
3715 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3716 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3719 if (divisions > 0) {
3720 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3722 /* while it makes some sense for the user to determine the division to 'grab',
3723 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3724 and the result over steep tempo curves. Use sixteenths.
3726 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3729 _grab_qn = map.quarter_note_at_beat (beat);
3731 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3736 TempoTwistDrag::motion (GdkEvent* event, bool first_move)
3739 if (!_next_tempo || !_drag_valid) {
3743 TempoMap& map (_editor->session()->tempo_map());
3746 _editor->begin_reversible_command (_("twist tempo"));
3751 if (_editor->grid_musical()) {
3752 pf = adjusted_current_sample (event, false);
3754 pf = adjusted_current_sample (event);
3757 /* adjust this and the next tempi to match pointer sample */
3758 double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3759 _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.sample_at_quarter_note (_grab_qn), pf);
3762 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3763 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3764 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3765 show_verbose_cursor_text (sstr.str());
3769 TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred)
3771 if (!movement_occurred || !_drag_valid) {
3775 _editor->tempo_curve_selected (_tempo, false);
3776 _editor->tempo_curve_selected (_next_tempo, false);
3778 TempoMap& map (_editor->session()->tempo_map());
3779 XMLNode &after = map.get_state();
3780 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3781 _editor->commit_reversible_command ();
3785 TempoTwistDrag::aborted (bool moved)
3788 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3792 TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i)
3797 , _drag_valid (true)
3799 DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n");
3800 TempoMarker* marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3801 _tempo = &marker->tempo();
3802 _grab_qn = _tempo->pulse() * 4.0;
3806 TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3808 Drag::start_grab (event, cursor);
3809 TempoMap& tmap (_editor->session()->tempo_map());
3811 /* get current state */
3812 _before_state = &tmap.get_state();
3814 if (_tempo->locked_to_meter()) {
3815 _drag_valid = false;
3821 TempoSection* prev = 0;
3822 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3823 _editor->tempo_curve_selected (tmap.previous_tempo_section (_tempo), true);
3824 sstr << "end: " << fixed << setprecision(3) << tmap.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3827 if (_tempo->clamped()) {
3828 _editor->tempo_curve_selected (_tempo, true);
3829 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3832 show_verbose_cursor_text (sstr.str());
3836 TempoEndDrag::setup_pointer_sample_offset ()
3838 TempoMap& map (_editor->session()->tempo_map());
3840 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3845 TempoEndDrag::motion (GdkEvent* event, bool first_move)
3851 TempoMap& map (_editor->session()->tempo_map());
3854 _editor->begin_reversible_command (_("stretch end tempo"));
3857 samplepos_t const pf = adjusted_current_sample (event, false);
3858 map.gui_stretch_tempo_end (&map.tempo_section_at_sample (_tempo->sample() - 1), map.sample_at_quarter_note (_grab_qn), pf);
3861 sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3863 if (_tempo->clamped()) {
3864 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3867 show_verbose_cursor_text (sstr.str());
3871 TempoEndDrag::finished (GdkEvent* event, bool movement_occurred)
3873 if (!movement_occurred || !_drag_valid) {
3877 TempoMap& tmap (_editor->session()->tempo_map());
3879 XMLNode &after = tmap.get_state();
3880 _editor->session()->add_command(new MementoCommand<TempoMap>(tmap, _before_state, &after));
3881 _editor->commit_reversible_command ();
3883 TempoSection* prev = 0;
3884 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3885 _editor->tempo_curve_selected (prev, false);
3888 if (_tempo->clamped()) {
3889 _editor->tempo_curve_selected (_tempo, false);
3895 TempoEndDrag::aborted (bool moved)
3898 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3902 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3903 : Drag (e, &c.track_canvas_item(), false)
3908 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3911 /** Do all the things we do when dragging the playhead to make it look as though
3912 * we have located, without actually doing the locate (because that would cause
3913 * the diskstream buffers to be refilled, which is too slow).
3916 CursorDrag::fake_locate (samplepos_t t)
3918 if (_editor->session () == 0) {
3922 _editor->playhead_cursor->set_position (t);
3924 Session* s = _editor->session ();
3925 if (s->timecode_transmission_suspended ()) {
3926 samplepos_t const f = _editor->playhead_cursor->current_sample ();
3927 /* This is asynchronous so it will be sent "now"
3929 s->send_mmc_locate (f);
3930 /* These are synchronous and will be sent during the next
3933 s->queue_full_time_code ();
3934 s->queue_song_position_pointer ();
3937 show_verbose_cursor_time (t);
3938 _editor->UpdateAllTransportClocks (t);
3942 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3944 Drag::start_grab (event, c);
3945 setup_snap_delta (MusicSample (_editor->playhead_cursor->current_sample(), 0));
3947 _grab_zoom = _editor->samples_per_pixel;
3949 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3951 _editor->snap_to_with_modifier (where, event);
3952 _editor->_dragging_playhead = true;
3953 _editor->_control_scroll_target = where.sample;
3955 Session* s = _editor->session ();
3957 /* grab the track canvas item as well */
3959 _cursor.track_canvas_item().grab();
3962 if (_was_rolling && _stop) {
3966 if (s->is_auditioning()) {
3967 s->cancel_audition ();
3971 if (AudioEngine::instance()->running()) {
3973 /* do this only if we're the engine is connected
3974 * because otherwise this request will never be
3975 * serviced and we'll busy wait forever. likewise,
3976 * notice if we are disconnected while waiting for the
3977 * request to be serviced.
3980 s->request_suspend_timecode_transmission ();
3981 while (AudioEngine::instance()->running() && !s->timecode_transmission_suspended ()) {
3982 /* twiddle our thumbs */
3987 fake_locate (where.sample - snap_delta (event->button.state));
3993 CursorDrag::motion (GdkEvent* event, bool)
3995 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3997 _editor->snap_to_with_modifier (where, event);
3999 if (where.sample != last_pointer_sample()) {
4000 fake_locate (where.sample - snap_delta (event->button.state));
4003 //maybe do zooming, too, if the option is enabled
4004 if (UIConfiguration::instance ().get_use_time_rulers_to_zoom_with_vertical_drag () ) {
4006 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
4007 //we use screen coordinates for this, not canvas-based grab_x
4008 double mx = event->button.x;
4009 double dx = fabs(mx - _last_mx);
4010 double my = event->button.y;
4011 double dy = fabs(my - _last_my);
4014 //do zooming in windowed "steps" so it feels more reversible ?
4015 const int stepsize = 2; //stepsize ==1 means "trigger on every pixel of movement"
4016 int y_delta = grab_y() - current_pointer_y();
4017 y_delta = y_delta / stepsize;
4019 //if all requirements are met, do the actual zoom
4020 const double scale = 1.2;
4021 if ( (dy>dx) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
4022 if ( _last_y_delta > y_delta ) {
4023 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
4025 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
4027 _last_y_delta = y_delta;
4038 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
4040 _editor->_dragging_playhead = false;
4042 _cursor.track_canvas_item().ungrab();
4044 if (!movement_occurred && _stop) {
4048 motion (event, false);
4050 Session* s = _editor->session ();
4052 s->request_locate (_editor->playhead_cursor->current_sample (), _was_rolling);
4053 _editor->_pending_locate_request = true;
4054 s->request_resume_timecode_transmission ();
4059 CursorDrag::aborted (bool)
4061 _cursor.track_canvas_item().ungrab();
4063 if (_editor->_dragging_playhead) {
4064 _editor->session()->request_resume_timecode_transmission ();
4065 _editor->_dragging_playhead = false;
4068 _editor->playhead_cursor->set_position (adjusted_sample (grab_sample (), 0, false).sample);
4071 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4072 : RegionDrag (e, i, p, v)
4074 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
4078 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4080 Drag::start_grab (event, cursor);
4082 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4083 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4084 setup_snap_delta (MusicSample (r->position(), 0));
4086 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
4090 FadeInDrag::setup_pointer_sample_offset ()
4092 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4093 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4094 _pointer_sample_offset = raw_grab_sample() - ((samplecnt_t) r->fade_in()->back()->when + r->position());
4098 FadeInDrag::motion (GdkEvent* event, bool)
4100 samplecnt_t fade_length;
4102 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4103 _editor->snap_to_with_modifier (pos, event);
4105 pos.sample -= snap_delta (event->button.state);
4107 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4109 if (pos.sample < (region->position() + 64)) {
4110 fade_length = 64; // this should be a minimum defined somewhere
4111 } else if (pos.sample > region->position() + region->length() - region->fade_out()->back()->when) {
4112 fade_length = region->length() - region->fade_out()->back()->when - 1;
4114 fade_length = pos.sample - region->position();
4117 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4119 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4125 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
4128 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
4132 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
4134 if (!movement_occurred) {
4138 samplecnt_t fade_length;
4139 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4141 _editor->snap_to_with_modifier (pos, event);
4142 pos.sample -= snap_delta (event->button.state);
4144 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4146 if (pos.sample < (region->position() + 64)) {
4147 fade_length = 64; // this should be a minimum defined somewhere
4148 } else if (pos.sample >= region->position() + region->length() - region->fade_out()->back()->when) {
4149 fade_length = region->length() - region->fade_out()->back()->when - 1;
4151 fade_length = pos.sample - region->position();
4154 bool in_command = false;
4156 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4158 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4164 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
4165 XMLNode &before = alist->get_state();
4167 tmp->audio_region()->set_fade_in_length (fade_length);
4168 tmp->audio_region()->set_fade_in_active (true);
4171 _editor->begin_reversible_command (_("change fade in length"));
4174 XMLNode &after = alist->get_state();
4175 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4179 _editor->commit_reversible_command ();
4184 FadeInDrag::aborted (bool)
4186 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4187 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4193 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
4197 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4198 : RegionDrag (e, i, p, v)
4200 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
4204 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4206 Drag::start_grab (event, cursor);
4208 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4209 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4210 setup_snap_delta (MusicSample (r->last_sample(), 0));
4212 show_verbose_cursor_duration (r->last_sample() - r->fade_out()->back()->when, r->last_sample());
4216 FadeOutDrag::setup_pointer_sample_offset ()
4218 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4219 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4220 _pointer_sample_offset = raw_grab_sample() - (r->length() - (samplecnt_t) r->fade_out()->back()->when + r->position());
4224 FadeOutDrag::motion (GdkEvent* event, bool)
4226 samplecnt_t fade_length;
4227 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4229 _editor->snap_to_with_modifier (pos, event);
4230 pos.sample -= snap_delta (event->button.state);
4232 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4234 if (pos.sample > (region->last_sample() - 64)) {
4235 fade_length = 64; // this should really be a minimum fade defined somewhere
4236 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4237 fade_length = region->length() - region->fade_in()->back()->when - 1;
4239 fade_length = region->last_sample() - pos.sample;
4242 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4244 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4250 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
4253 show_verbose_cursor_duration (region->last_sample() - fade_length, region->last_sample());
4257 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
4259 if (!movement_occurred) {
4263 samplecnt_t fade_length;
4264 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4266 _editor->snap_to_with_modifier (pos, event);
4267 pos.sample -= snap_delta (event->button.state);
4269 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4271 if (pos.sample > (region->last_sample() - 64)) {
4272 fade_length = 64; // this should really be a minimum fade defined somewhere
4273 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4274 fade_length = region->length() - region->fade_in()->back()->when - 1;
4276 fade_length = region->last_sample() - pos.sample;
4279 bool in_command = false;
4281 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4283 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4289 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
4290 XMLNode &before = alist->get_state();
4292 tmp->audio_region()->set_fade_out_length (fade_length);
4293 tmp->audio_region()->set_fade_out_active (true);
4296 _editor->begin_reversible_command (_("change fade out length"));
4299 XMLNode &after = alist->get_state();
4300 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4304 _editor->commit_reversible_command ();
4309 FadeOutDrag::aborted (bool)
4311 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4312 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4318 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
4322 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
4324 , _selection_changed (false)
4326 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
4327 Gtk::Window* toplevel = _editor->current_toplevel();
4328 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
4332 _points.push_back (ArdourCanvas::Duple (0, 0));
4334 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4337 MarkerDrag::~MarkerDrag ()
4339 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4344 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4346 location = new Location (*l);
4347 markers.push_back (m);
4352 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4354 Drag::start_grab (event, cursor);
4358 Location *location = _editor->find_location_from_marker (_marker, is_start);
4359 _editor->_dragging_edit_point = true;
4361 update_item (location);
4363 // _drag_line->show();
4364 // _line->raise_to_top();
4367 show_verbose_cursor_time (location->start());
4369 show_verbose_cursor_time (location->end());
4371 setup_snap_delta (MusicSample (is_start ? location->start() : location->end(), 0));
4373 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4376 case Selection::Toggle:
4377 /* we toggle on the button release */
4379 case Selection::Set:
4380 if (!_editor->selection->selected (_marker)) {
4381 _editor->selection->set (_marker);
4382 _selection_changed = true;
4385 case Selection::Extend:
4387 Locations::LocationList ll;
4388 list<ArdourMarker*> to_add;
4390 _editor->selection->markers.range (s, e);
4391 s = min (_marker->position(), s);
4392 e = max (_marker->position(), e);
4395 if (e < max_samplepos) {
4398 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4399 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4400 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4403 to_add.push_back (lm->start);
4406 to_add.push_back (lm->end);
4410 if (!to_add.empty()) {
4411 _editor->selection->add (to_add);
4412 _selection_changed = true;
4416 case Selection::Add:
4417 _editor->selection->add (_marker);
4418 _selection_changed = true;
4423 /* Set up copies for us to manipulate during the drag
4426 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4428 Location* l = _editor->find_location_from_marker (*i, is_start);
4435 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4437 /* range: check that the other end of the range isn't
4440 CopiedLocationInfo::iterator x;
4441 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4442 if (*(*x).location == *l) {
4446 if (x == _copied_locations.end()) {
4447 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4449 (*x).markers.push_back (*i);
4450 (*x).move_both = true;
4458 MarkerDrag::setup_pointer_sample_offset ()
4461 Location *location = _editor->find_location_from_marker (_marker, is_start);
4462 _pointer_sample_offset = raw_grab_sample() - (is_start ? location->start() : location->end());
4466 MarkerDrag::motion (GdkEvent* event, bool)
4468 samplecnt_t f_delta = 0;
4470 bool move_both = false;
4471 Location *real_location;
4472 Location *copy_location = 0;
4473 samplecnt_t const sd = snap_delta (event->button.state);
4475 samplecnt_t const newframe = adjusted_sample (_drags->current_pointer_sample () + sd, event, true).sample - sd;
4476 samplepos_t next = newframe;
4478 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4482 CopiedLocationInfo::iterator x;
4484 /* find the marker we're dragging, and compute the delta */
4486 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4488 copy_location = (*x).location;
4490 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4492 /* this marker is represented by this
4493 * CopiedLocationMarkerInfo
4496 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4501 if (real_location->is_mark()) {
4502 f_delta = newframe - copy_location->start();
4506 switch (_marker->type()) {
4507 case ArdourMarker::SessionStart:
4508 case ArdourMarker::RangeStart:
4509 case ArdourMarker::LoopStart:
4510 case ArdourMarker::PunchIn:
4511 f_delta = newframe - copy_location->start();
4514 case ArdourMarker::SessionEnd:
4515 case ArdourMarker::RangeEnd:
4516 case ArdourMarker::LoopEnd:
4517 case ArdourMarker::PunchOut:
4518 f_delta = newframe - copy_location->end();
4521 /* what kind of marker is this ? */
4530 if (x == _copied_locations.end()) {
4531 /* hmm, impossible - we didn't find the dragged marker */
4535 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4537 /* now move them all */
4539 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4541 copy_location = x->location;
4543 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4547 if (real_location->locked()) {
4551 if (copy_location->is_mark()) {
4554 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4558 samplepos_t new_start = copy_location->start() + f_delta;
4559 samplepos_t new_end = copy_location->end() + f_delta;
4561 if (is_start) { // start-of-range marker
4563 if (move_both || (*x).move_both) {
4564 copy_location->set_start (new_start, false, true, divisions);
4565 copy_location->set_end (new_end, false, true, divisions);
4566 } else if (new_start < copy_location->end()) {
4567 copy_location->set_start (new_start, false, true, divisions);
4568 } else if (newframe > 0) {
4569 //_editor->snap_to (next, RoundUpAlways, true);
4570 copy_location->set_end (next, false, true, divisions);
4571 copy_location->set_start (newframe, false, true, divisions);
4574 } else { // end marker
4576 if (move_both || (*x).move_both) {
4577 copy_location->set_end (new_end, divisions);
4578 copy_location->set_start (new_start, false, true, divisions);
4579 } else if (new_end > copy_location->start()) {
4580 copy_location->set_end (new_end, false, true, divisions);
4581 } else if (newframe > 0) {
4582 //_editor->snap_to (next, RoundDownAlways, true);
4583 copy_location->set_start (next, false, true, divisions);
4584 copy_location->set_end (newframe, false, true, divisions);
4589 update_item (copy_location);
4591 /* now lookup the actual GUI items used to display this
4592 * location and move them to wherever the copy of the location
4593 * is now. This means that the logic in ARDOUR::Location is
4594 * still enforced, even though we are not (yet) modifying
4595 * the real Location itself.
4598 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4601 lm->set_position (copy_location->start(), copy_location->end());
4606 assert (!_copied_locations.empty());
4608 show_verbose_cursor_time (newframe);
4609 _editor->set_snapped_cursor_position(newframe);
4613 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4615 if (!movement_occurred) {
4617 if (was_double_click()) {
4618 _editor->rename_marker (_marker);
4622 /* just a click, do nothing but finish
4623 off the selection process
4626 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4628 case Selection::Set:
4629 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4630 _editor->selection->set (_marker);
4631 _selection_changed = true;
4635 case Selection::Toggle:
4636 /* we toggle on the button release, click only */
4637 _editor->selection->toggle (_marker);
4638 _selection_changed = true;
4642 case Selection::Extend:
4643 case Selection::Add:
4647 if (_selection_changed) {
4648 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4649 _editor->commit_reversible_selection_op();
4655 _editor->_dragging_edit_point = false;
4657 XMLNode &before = _editor->session()->locations()->get_state();
4658 bool in_command = false;
4660 MarkerSelection::iterator i;
4661 CopiedLocationInfo::iterator x;
4662 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4665 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4666 x != _copied_locations.end() && i != _editor->selection->markers.end();
4669 Location * location = _editor->find_location_from_marker (*i, is_start);
4673 if (location->locked()) {
4677 _editor->begin_reversible_command ( _("move marker") );
4680 if (location->is_mark()) {
4681 location->set_start (((*x).location)->start(), false, true, divisions);
4683 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4686 if (location->is_session_range()) {
4687 _editor->session()->set_end_is_free (false);
4693 XMLNode &after = _editor->session()->locations()->get_state();
4694 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4695 _editor->commit_reversible_command ();
4700 MarkerDrag::aborted (bool movement_occurred)
4702 if (!movement_occurred) {
4706 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4708 /* move all markers to their original location */
4711 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4714 Location * location = _editor->find_location_from_marker (*m, is_start);
4717 (*m)->set_position (is_start ? location->start() : location->end());
4724 MarkerDrag::update_item (Location*)
4729 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4731 , _fixed_grab_x (0.0)
4732 , _fixed_grab_y (0.0)
4733 , _cumulative_x_drag (0.0)
4734 , _cumulative_y_drag (0.0)
4738 if (_zero_gain_fraction < 0.0) {
4739 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4742 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4744 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4750 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4752 Drag::start_grab (event, _editor->cursors()->fader);
4754 // start the grab at the center of the control point so
4755 // the point doesn't 'jump' to the mouse after the first drag
4756 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4757 _fixed_grab_y = _point->get_y();
4759 setup_snap_delta (MusicSample (_editor->pixel_to_sample (_fixed_grab_x), 0));
4761 float const fraction = 1 - (_point->get_y() / _point->line().height());
4762 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4764 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4766 if (!_point->can_slide ()) {
4767 _x_constrained = true;
4772 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4774 double dx = _drags->current_pointer_x() - last_pointer_x();
4775 double dy = current_pointer_y() - last_pointer_y();
4776 bool need_snap = true;
4778 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4784 /* coordinate in pixels relative to the start of the region (for region-based automation)
4785 or track (for track-based automation) */
4786 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4787 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4789 // calculate zero crossing point. back off by .01 to stay on the
4790 // positive side of zero
4791 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4793 if (_x_constrained) {
4796 if (_y_constrained) {
4800 _cumulative_x_drag = cx - _fixed_grab_x;
4801 _cumulative_y_drag = cy - _fixed_grab_y;
4805 cy = min ((double) _point->line().height(), cy);
4807 // make sure we hit zero when passing through
4808 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4812 MusicSample cx_mf (_editor->pixel_to_sample (cx) + snap_delta (event->button.state), 0);
4814 if (!_x_constrained && need_snap) {
4815 _editor->snap_to_with_modifier (cx_mf, event);
4818 cx_mf.sample -= snap_delta (event->button.state);
4819 cx_mf.sample = min (cx_mf.sample, _point->line().maximum_time() + _point->line().offset());
4821 float const fraction = 1.0 - (cy / _point->line().height());
4824 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4825 _editor->begin_reversible_command (_("automation event move"));
4826 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4828 pair<float, float> result;
4829 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_mf.sample), fraction, false, _pushing, _final_index);
4830 show_verbose_cursor_text (_point->line().get_verbose_cursor_relative_string (result.first, result.second));
4834 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4836 if (!movement_occurred) {
4839 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4840 _editor->reset_point_selection ();
4844 _point->line().end_drag (_pushing, _final_index);
4845 _editor->commit_reversible_command ();
4850 ControlPointDrag::aborted (bool)
4852 _point->line().reset ();
4856 ControlPointDrag::active (Editing::MouseMode m)
4858 if (m == Editing::MouseDraw) {
4859 /* always active in mouse draw */
4863 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4864 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4867 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4870 , _fixed_grab_x (0.0)
4871 , _fixed_grab_y (0.0)
4872 , _cumulative_y_drag (0)
4876 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4880 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4882 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4885 _item = &_line->grab_item ();
4887 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4888 origin, and ditto for y.
4891 double mx = event->button.x;
4892 double my = event->button.y;
4894 _line->grab_item().canvas_to_item (mx, my);
4896 samplecnt_t const sample_within_region = (samplecnt_t) floor (mx * _editor->samples_per_pixel);
4898 if (!_line->control_points_adjacent (sample_within_region, _before, _after)) {
4899 /* no adjacent points */
4903 Drag::start_grab (event, _editor->cursors()->fader);
4905 /* store grab start in item sample */
4906 double const bx = _line->nth (_before)->get_x();
4907 double const ax = _line->nth (_after)->get_x();
4908 double const click_ratio = (ax - mx) / (ax - bx);
4910 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4915 double fraction = 1.0 - (cy / _line->height());
4917 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4921 LineDrag::motion (GdkEvent* event, bool first_move)
4923 double dy = current_pointer_y() - last_pointer_y();
4925 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4929 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4931 _cumulative_y_drag = cy - _fixed_grab_y;
4934 cy = min ((double) _line->height(), cy);
4936 double const fraction = 1.0 - (cy / _line->height());
4940 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4942 _editor->begin_reversible_command (_("automation range move"));
4943 _line->start_drag_line (_before, _after, initial_fraction);
4946 /* we are ignoring x position for this drag, so we can just pass in anything */
4947 pair<float, float> result;
4949 result = _line->drag_motion (0, fraction, true, false, ignored);
4950 show_verbose_cursor_text (_line->get_verbose_cursor_relative_string (result.first, result.second));
4954 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4956 if (movement_occurred) {
4957 motion (event, false);
4958 _line->end_drag (false, 0);
4959 _editor->commit_reversible_command ();
4961 /* add a new control point on the line */
4963 AutomationTimeAxisView* atv;
4965 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4966 samplepos_t where = grab_sample ();
4969 double cy = _fixed_grab_y;
4971 _line->grab_item().item_to_canvas (cx, cy);
4973 atv->add_automation_event (event, where, cy, false);
4974 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4975 AudioRegionView* arv;
4977 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4978 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4985 LineDrag::aborted (bool)
4990 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4994 _region_view_grab_x (0.0),
4995 _cumulative_x_drag (0),
4999 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
5003 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
5005 Drag::start_grab (event);
5007 _line = reinterpret_cast<Line*> (_item);
5010 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
5012 double cx = event->button.x;
5013 double cy = event->button.y;
5015 _item->parent()->canvas_to_item (cx, cy);
5017 /* store grab start in parent sample */
5018 _region_view_grab_x = cx;
5020 _before = *(float*) _item->get_data ("position");
5022 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5024 _max_x = _editor->sample_to_pixel(_arv->get_duration());
5028 FeatureLineDrag::motion (GdkEvent*, bool)
5030 double dx = _drags->current_pointer_x() - last_pointer_x();
5032 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
5034 _cumulative_x_drag += dx;
5036 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
5045 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
5047 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
5049 float *pos = new float;
5052 _line->set_data ("position", pos);
5058 FeatureLineDrag::finished (GdkEvent*, bool)
5060 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5061 _arv->update_transient(_before, _before);
5065 FeatureLineDrag::aborted (bool)
5070 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5072 , _vertical_only (false)
5074 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
5078 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5080 Drag::start_grab (event);
5081 show_verbose_cursor_time (adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
5085 RubberbandSelectDrag::motion (GdkEvent* event, bool)
5091 samplepos_t const pf = adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
5092 MusicSample grab (grab_sample (), 0);
5094 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5095 _editor->snap_to_with_modifier (grab, event, RoundNearest, SnapToGrid_Scaled);
5097 grab.sample = raw_grab_sample ();
5100 /* base start and end on initial click position */
5102 if (pf < grab.sample) {
5107 start = grab.sample;
5110 if (current_pointer_y() < grab_y()) {
5111 y1 = current_pointer_y();
5114 y2 = current_pointer_y();
5118 if (start != end || y1 != y2) {
5120 double x1 = _editor->sample_to_pixel (start);
5121 double x2 = _editor->sample_to_pixel (end);
5122 const double min_dimension = 2.0;
5124 if (_vertical_only) {
5125 /* fixed 10 pixel width */
5129 x2 = min (x1 - min_dimension, x2);
5131 x2 = max (x1 + min_dimension, x2);
5136 y2 = min (y1 - min_dimension, y2);
5138 y2 = max (y1 + min_dimension, y2);
5141 /* translate rect into item space and set */
5143 ArdourCanvas::Rect r (x1, y1, x2, y2);
5145 /* this drag is a _trackview_only == true drag, so the y1 and
5146 * y2 (computed using current_pointer_y() and grab_y()) will be
5147 * relative to the top of the trackview group). The
5148 * rubberband rect has the same parent/scroll offset as the
5149 * the trackview group, so we can use the "r" rect directly
5150 * to set the shape of the rubberband.
5153 _editor->rubberband_rect->set (r);
5154 _editor->rubberband_rect->show();
5155 _editor->rubberband_rect->raise_to_top();
5157 show_verbose_cursor_time (pf);
5159 do_select_things (event, true);
5164 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
5168 samplepos_t grab = grab_sample ();
5169 samplepos_t lpf = last_pointer_sample ();
5171 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5172 grab = raw_grab_sample ();
5173 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
5187 if (current_pointer_y() < grab_y()) {
5188 y1 = current_pointer_y();
5191 y2 = current_pointer_y();
5195 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
5199 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
5201 if (movement_occurred) {
5203 motion (event, false);
5204 do_select_things (event, false);
5210 bool do_deselect = true;
5211 MidiTimeAxisView* mtv;
5213 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
5215 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
5216 /* nothing selected */
5217 add_midi_region (mtv, true);
5218 do_deselect = false;
5222 /* do not deselect if Primary or Tertiary (toggle-select or
5223 * extend-select are pressed.
5226 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
5227 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
5234 _editor->rubberband_rect->hide();
5238 RubberbandSelectDrag::aborted (bool)
5240 _editor->rubberband_rect->hide ();
5243 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
5244 : RegionDrag (e, i, p, v)
5246 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
5250 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5252 Drag::start_grab (event, cursor);
5254 _editor->get_selection().add (_primary);
5256 MusicSample where (_primary->region()->position(), 0);
5257 setup_snap_delta (where);
5259 show_verbose_cursor_duration (where.sample, adjusted_current_sample (event), 0);
5263 TimeFXDrag::motion (GdkEvent* event, bool)
5265 RegionView* rv = _primary;
5266 StreamView* cv = rv->get_time_axis_view().view ();
5267 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
5268 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
5269 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
5270 MusicSample pf (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
5272 _editor->snap_to_with_modifier (pf, event);
5273 pf.sample -= snap_delta (event->button.state);
5275 if (pf.sample > rv->region()->position()) {
5276 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf.sample, layers, layer);
5279 show_verbose_cursor_duration (_primary->region()->position(), pf.sample, 0);
5283 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
5285 /* this may have been a single click, no drag. We still want the dialog
5286 to show up in that case, so that the user can manually edit the
5287 parameters for the timestretch.
5290 float fraction = 1.0;
5292 if (movement_occurred) {
5294 motion (event, false);
5296 _primary->get_time_axis_view().hide_timestretch ();
5298 samplepos_t adjusted_sample_pos = adjusted_current_sample (event);
5300 if (adjusted_sample_pos < _primary->region()->position()) {
5301 /* backwards drag of the left edge - not usable */
5305 samplecnt_t newlen = adjusted_sample_pos - _primary->region()->position();
5307 fraction = (double) newlen / (double) _primary->region()->length();
5309 #ifndef USE_RUBBERBAND
5310 // Soundtouch uses fraction / 100 instead of normal (/ 1)
5311 if (_primary->region()->data_type() == DataType::AUDIO) {
5312 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
5317 if (!_editor->get_selection().regions.empty()) {
5318 /* primary will already be included in the selection, and edit
5319 group shared editing will propagate selection across
5320 equivalent regions, so just use the current region
5324 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
5325 error << _("An error occurred while executing time stretch operation") << endmsg;
5331 TimeFXDrag::aborted (bool)
5333 _primary->get_time_axis_view().hide_timestretch ();
5336 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5339 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5343 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5345 Drag::start_grab (event);
5349 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5351 _editor->scrub (adjusted_current_sample (0, false), _drags->current_pointer_x ());
5355 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5357 if (movement_occurred && _editor->session()) {
5358 /* make sure we stop */
5359 _editor->session()->request_transport_speed (0.0);
5364 ScrubDrag::aborted (bool)
5369 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5373 , _track_selection_at_start (e)
5374 , _time_selection_at_start (!_editor->get_selection().time.empty())
5376 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5378 if (_time_selection_at_start) {
5379 start_at_start = _editor->get_selection().time.start();
5380 end_at_start = _editor->get_selection().time.end_sample();
5385 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5387 if (_editor->session() == 0) {
5391 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5393 switch (_operation) {
5394 case CreateSelection:
5395 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5400 cursor = _editor->cursors()->selector;
5401 Drag::start_grab (event, cursor);
5404 case SelectionStartTrim:
5405 if (_editor->clicked_axisview) {
5406 _editor->clicked_axisview->order_selection_trims (_item, true);
5408 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5411 case SelectionEndTrim:
5412 if (_editor->clicked_axisview) {
5413 _editor->clicked_axisview->order_selection_trims (_item, false);
5415 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5419 Drag::start_grab (event, cursor);
5422 case SelectionExtend:
5423 Drag::start_grab (event, cursor);
5427 if (_operation == SelectionMove) {
5428 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5430 show_verbose_cursor_time (adjusted_current_sample (event));
5435 SelectionDrag::setup_pointer_sample_offset ()
5437 switch (_operation) {
5438 case CreateSelection:
5439 _pointer_sample_offset = 0;
5442 case SelectionStartTrim:
5444 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].start;
5447 case SelectionEndTrim:
5448 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].end;
5451 case SelectionExtend:
5457 SelectionDrag::motion (GdkEvent* event, bool first_move)
5459 samplepos_t start = 0;
5460 samplepos_t end = 0;
5461 samplecnt_t length = 0;
5462 samplecnt_t distance = 0;
5463 MusicSample start_mf (0, 0);
5464 samplepos_t const pending_position = adjusted_current_sample (event);
5466 if (_operation != CreateSelection && pending_position == last_pointer_sample()) {
5471 _track_selection_at_start = _editor->selection->tracks;
5474 switch (_operation) {
5475 case CreateSelection:
5477 MusicSample grab (grab_sample (), 0);
5479 grab.sample = adjusted_current_sample (event, false);
5480 if (grab.sample < pending_position) {
5481 _editor->snap_to (grab, RoundDownMaybe);
5483 _editor->snap_to (grab, RoundUpMaybe);
5487 if (pending_position < grab.sample) {
5488 start = pending_position;
5491 end = pending_position;
5492 start = grab.sample;
5495 /* first drag: Either add to the selection
5496 or create a new selection
5503 /* adding to the selection */
5504 _editor->set_selected_track_as_side_effect (Selection::Add);
5505 _editor->clicked_selection = _editor->selection->add (start, end);
5512 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5513 _editor->set_selected_track_as_side_effect (Selection::Set);
5516 _editor->clicked_selection = _editor->selection->set (start, end);
5520 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5521 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5522 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5524 _editor->selection->add (atest);
5528 /* select all tracks within the rectangle that we've marked out so far */
5529 TrackViewList new_selection;
5530 TrackViewList& all_tracks (_editor->track_views);
5532 ArdourCanvas::Coord const top = grab_y();
5533 ArdourCanvas::Coord const bottom = current_pointer_y();
5535 if (top >= 0 && bottom >= 0) {
5537 //first, find the tracks that are covered in the y range selection
5538 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5539 if ((*i)->covered_by_y_range (top, bottom)) {
5540 new_selection.push_back (*i);
5544 //now compare our list with the current selection, and add as necessary
5545 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5546 TrackViewList tracks_to_add;
5547 TrackViewList tracks_to_remove;
5548 vector<RouteGroup*> selected_route_groups;
5551 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5552 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5553 tracks_to_remove.push_back (*i);
5555 RouteGroup* rg = (*i)->route_group();
5556 if (rg && rg->is_active() && rg->is_select()) {
5557 selected_route_groups.push_back (rg);
5563 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5564 if (!_editor->selection->tracks.contains (*i)) {
5565 tracks_to_add.push_back (*i);
5566 RouteGroup* rg = (*i)->route_group();
5568 if (rg && rg->is_active() && rg->is_select()) {
5569 selected_route_groups.push_back (rg);
5574 _editor->selection->add (tracks_to_add);
5576 if (!tracks_to_remove.empty()) {
5578 /* check all these to-be-removed tracks against the
5579 * possibility that they are selected by being
5580 * in the same group as an approved track.
5583 for (TrackViewList::iterator i = tracks_to_remove.begin(); i != tracks_to_remove.end(); ) {
5584 RouteGroup* rg = (*i)->route_group();
5586 if (rg && find (selected_route_groups.begin(), selected_route_groups.end(), rg) != selected_route_groups.end()) {
5587 i = tracks_to_remove.erase (i);
5593 /* remove whatever is left */
5595 _editor->selection->remove (tracks_to_remove);
5601 case SelectionStartTrim:
5603 end = _editor->selection->time[_editor->clicked_selection].end;
5605 if (pending_position > end) {
5608 start = pending_position;
5612 case SelectionEndTrim:
5614 start = _editor->selection->time[_editor->clicked_selection].start;
5616 if (pending_position < start) {
5619 end = pending_position;
5626 start = _editor->selection->time[_editor->clicked_selection].start;
5627 end = _editor->selection->time[_editor->clicked_selection].end;
5629 length = end - start;
5630 distance = pending_position - start;
5631 start = pending_position;
5633 start_mf.sample = start;
5634 _editor->snap_to (start_mf);
5636 end = start_mf.sample + length;
5640 case SelectionExtend:
5645 switch (_operation) {
5647 if (_time_selection_at_start) {
5648 _editor->selection->move_time (distance);
5652 _editor->selection->replace (_editor->clicked_selection, start, end);
5656 if (_operation == SelectionMove) {
5657 show_verbose_cursor_time(start);
5659 show_verbose_cursor_time(pending_position);
5664 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5666 Session* s = _editor->session();
5668 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5669 if (movement_occurred) {
5670 motion (event, false);
5671 /* XXX this is not object-oriented programming at all. ick */
5672 if (_editor->selection->time.consolidate()) {
5673 _editor->selection->TimeChanged ();
5676 /* XXX what if its a music time selection? */
5679 //if Follow Edits is on, maybe try to follow the range selection ... also consider range-audition mode
5680 if ( !s->config.get_external_sync() && s->transport_rolling() ) {
5681 if ( s->solo_selection_active() ) {
5682 _editor->play_solo_selection(true); //play the newly selected range, and move solos to match
5683 } else if ( UIConfiguration::instance().get_follow_edits() && s->get_play_range() ) { //already rolling a selected range
5684 s->request_play_range (&_editor->selection->time, true); //play the newly selected range
5686 } else if ( !s->transport_rolling() && UIConfiguration::instance().get_follow_edits() ) {
5687 s->request_locate (_editor->get_selection().time.start());
5690 if (_editor->get_selection().time.length() != 0) {
5691 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_sample());
5693 s->clear_range_selection ();
5698 /* just a click, no pointer movement.
5701 if (was_double_click()) {
5702 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5703 _editor->temporal_zoom_selection (Both);
5708 if (_operation == SelectionExtend) {
5709 if (_time_selection_at_start) {
5710 samplepos_t pos = adjusted_current_sample (event, false);
5711 samplepos_t start = min (pos, start_at_start);
5712 samplepos_t end = max (pos, end_at_start);
5713 _editor->selection->set (start, end);
5716 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5717 if (_editor->clicked_selection) {
5718 _editor->selection->remove (_editor->clicked_selection);
5721 if (!_editor->clicked_selection) {
5722 _editor->selection->clear_time();
5727 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5728 _editor->selection->set (_editor->clicked_axisview);
5731 if (s && s->get_play_range () && s->transport_rolling()) {
5732 s->request_stop (false, false);
5737 _editor->stop_canvas_autoscroll ();
5738 _editor->clicked_selection = 0;
5739 _editor->commit_reversible_selection_op ();
5743 SelectionDrag::aborted (bool)
5748 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5749 : Drag (e, i, false),
5753 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5755 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5756 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5757 physical_screen_height (_editor->current_toplevel()->get_window())));
5758 _drag_rect->hide ();
5760 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5761 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5764 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5766 /* normal canvas items will be cleaned up when their parent group is deleted. But
5767 this item is created as the child of a long-lived parent group, and so we
5768 need to explicitly delete it.
5774 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5776 if (_editor->session() == 0) {
5780 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5782 if (!_editor->temp_location) {
5783 _editor->temp_location = new Location (*_editor->session());
5786 switch (_operation) {
5787 case CreateSkipMarker:
5788 case CreateRangeMarker:
5789 case CreateTransportMarker:
5790 case CreateCDMarker:
5792 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5797 cursor = _editor->cursors()->selector;
5801 Drag::start_grab (event, cursor);
5803 show_verbose_cursor_time (adjusted_current_sample (event));
5807 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5809 samplepos_t start = 0;
5810 samplepos_t end = 0;
5811 ArdourCanvas::Rectangle *crect;
5813 switch (_operation) {
5814 case CreateSkipMarker:
5815 crect = _editor->range_bar_drag_rect;
5817 case CreateRangeMarker:
5818 crect = _editor->range_bar_drag_rect;
5820 case CreateTransportMarker:
5821 crect = _editor->transport_bar_drag_rect;
5823 case CreateCDMarker:
5824 crect = _editor->cd_marker_bar_drag_rect;
5827 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5832 samplepos_t const pf = adjusted_current_sample (event);
5834 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5835 MusicSample grab (grab_sample (), 0);
5836 _editor->snap_to (grab);
5838 if (pf < grab_sample()) {
5843 start = grab.sample;
5846 /* first drag: Either add to the selection
5847 or create a new selection.
5852 _editor->temp_location->set (start, end);
5856 update_item (_editor->temp_location);
5858 //_drag_rect->raise_to_top();
5864 _editor->temp_location->set (start, end);
5866 double x1 = _editor->sample_to_pixel (start);
5867 double x2 = _editor->sample_to_pixel (end);
5871 update_item (_editor->temp_location);
5874 show_verbose_cursor_time (pf);
5879 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5881 Location * newloc = 0;
5885 if (movement_occurred) {
5886 motion (event, false);
5889 switch (_operation) {
5890 case CreateSkipMarker:
5891 case CreateRangeMarker:
5892 case CreateCDMarker:
5894 XMLNode &before = _editor->session()->locations()->get_state();
5895 if (_operation == CreateSkipMarker) {
5896 _editor->begin_reversible_command (_("new skip marker"));
5897 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5898 flags = Location::IsRangeMarker | Location::IsSkip;
5899 _editor->range_bar_drag_rect->hide();
5900 } else if (_operation == CreateCDMarker) {
5901 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5902 _editor->begin_reversible_command (_("new CD marker"));
5903 flags = Location::IsRangeMarker | Location::IsCDMarker;
5904 _editor->cd_marker_bar_drag_rect->hide();
5906 _editor->begin_reversible_command (_("new skip marker"));
5907 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5908 flags = Location::IsRangeMarker;
5909 _editor->range_bar_drag_rect->hide();
5911 newloc = new Location (
5912 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5913 , _editor->get_grid_music_divisions (event->button.state));
5915 _editor->session()->locations()->add (newloc, true);
5916 XMLNode &after = _editor->session()->locations()->get_state();
5917 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5918 _editor->commit_reversible_command ();
5922 case CreateTransportMarker:
5923 // popup menu to pick loop or punch
5924 _editor->new_transport_marker_context_menu (&event->button, _item);
5930 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5932 if (_operation == CreateTransportMarker) {
5934 /* didn't drag, so just locate */
5936 _editor->session()->request_locate (grab_sample(), _editor->session()->transport_rolling());
5938 } else if (_operation == CreateCDMarker) {
5940 /* didn't drag, but mark is already created so do
5943 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5948 _editor->session()->locations()->marks_either_side (grab_sample(), start, end);
5950 if (end == max_samplepos) {
5951 end = _editor->session()->current_end_sample ();
5954 if (start == max_samplepos) {
5955 start = _editor->session()->current_start_sample ();
5958 switch (_editor->mouse_mode) {
5960 /* find the two markers on either side and then make the selection from it */
5961 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5965 /* find the two markers on either side of the click and make the range out of it */
5966 _editor->selection->set (start, end);
5975 _editor->stop_canvas_autoscroll ();
5979 RangeMarkerBarDrag::aborted (bool movement_occurred)
5981 if (movement_occurred) {
5982 _drag_rect->hide ();
5987 RangeMarkerBarDrag::update_item (Location* location)
5989 double const x1 = _editor->sample_to_pixel (location->start());
5990 double const x2 = _editor->sample_to_pixel (location->end());
5992 _drag_rect->set_x0 (x1);
5993 _drag_rect->set_x1 (x2);
5996 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5998 , _cumulative_dx (0)
5999 , _cumulative_dy (0)
6001 , _was_selected (false)
6004 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
6006 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
6008 _region = &_primary->region_view ();
6009 _note_height = _region->midi_stream_view()->note_height ();
6013 NoteDrag::setup_pointer_sample_offset ()
6015 _pointer_sample_offset = raw_grab_sample()
6016 - _editor->session()->tempo_map().sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6020 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
6022 Drag::start_grab (event);
6024 if (ArdourKeyboard::indicates_copy (event->button.state)) {
6030 setup_snap_delta (MusicSample (_region->source_beats_to_absolute_samples (_primary->note()->time ()), 0));
6032 if (!(_was_selected = _primary->selected())) {
6034 /* tertiary-click means extend selection - we'll do that on button release,
6035 so don't add it here, because otherwise we make it hard to figure
6036 out the "extend-to" range.
6039 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
6042 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
6045 _region->note_selected (_primary, true);
6047 _editor->get_selection().clear_points();
6048 _region->unique_select (_primary);
6054 /** @return Current total drag x change in quarter notes */
6056 NoteDrag::total_dx (GdkEvent * event) const
6058 if (_x_constrained) {
6062 TempoMap& map (_editor->session()->tempo_map());
6065 sampleoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
6067 /* primary note time */
6068 sampleoffset_t const n = map.sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6070 /* primary note time in quarter notes */
6071 double const n_qn = _region->session_relative_qn (_primary->note()->time().to_double());
6073 /* new time of the primary note in session samples */
6074 sampleoffset_t st = n + dx + snap_delta (event->button.state);
6076 /* possibly snap and return corresponding delta in quarter notes */
6077 MusicSample snap (st, 0);
6078 _editor->snap_to_with_modifier (snap, event, RoundNearest, SnapToGrid_Unscaled);
6079 double ret = map.exact_qn_at_sample (snap.sample, snap.division) - n_qn - snap_delta_music (event->button.state);
6081 /* prevent the earliest note being dragged earlier than the region's start position */
6082 if (_earliest + ret < _region->midi_region()->start_beats()) {
6083 ret -= (_earliest + ret) - _region->midi_region()->start_beats();
6089 /** @return Current total drag y change in note number */
6091 NoteDrag::total_dy () const
6093 if (_y_constrained) {
6097 double const y = _region->midi_view()->y_position ();
6098 /* new current note */
6099 uint8_t n = _region->y_to_note (current_pointer_y () - y);
6101 MidiStreamView* msv = _region->midi_stream_view ();
6102 n = max (msv->lowest_note(), n);
6103 n = min (msv->highest_note(), n);
6104 /* and work out delta */
6105 return n - _region->y_to_note (grab_y() - y);
6109 NoteDrag::motion (GdkEvent * event, bool first_move)
6112 _earliest = _region->earliest_in_selection().to_double();
6114 /* make copies of all the selected notes */
6115 _primary = _region->copy_selection (_primary);
6119 /* Total change in x and y since the start of the drag */
6120 double const dx_qn = total_dx (event);
6121 int8_t const dy = total_dy ();
6123 /* Now work out what we have to do to the note canvas items to set this new drag delta */
6124 double const tdx = _x_constrained ? 0 : dx_qn - _cumulative_dx;
6125 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
6128 _cumulative_dx = dx_qn;
6129 _cumulative_dy += tdy;
6131 int8_t note_delta = total_dy();
6135 _region->move_copies (dx_qn, tdy, note_delta);
6137 _region->move_selection (dx_qn, tdy, note_delta);
6140 /* the new note value may be the same as the old one, but we
6141 * don't know what that means because the selection may have
6142 * involved more than one note and we might be doing something
6143 * odd with them. so show the note value anyway, always.
6146 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
6148 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
6150 _editor->set_snapped_cursor_position( _region->source_beats_to_absolute_samples(_primary->note()->time()) );
6156 NoteDrag::finished (GdkEvent* ev, bool moved)
6159 /* no motion - select note */
6161 if (_editor->current_mouse_mode() == Editing::MouseContent ||
6162 _editor->current_mouse_mode() == Editing::MouseDraw) {
6164 bool changed = false;
6166 if (_was_selected) {
6167 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6169 _region->note_deselected (_primary);
6172 _editor->get_selection().clear_points();
6173 _region->unique_select (_primary);
6177 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
6178 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6180 if (!extend && !add && _region->selection_size() > 1) {
6181 _editor->get_selection().clear_points();
6182 _region->unique_select (_primary);
6184 } else if (extend) {
6185 _region->note_selected (_primary, true, true);
6188 /* it was added during button press */
6195 _editor->begin_reversible_selection_op(X_("Select Note Release"));
6196 _editor->commit_reversible_selection_op();
6200 _region->note_dropped (_primary, total_dx (ev), total_dy(), _copy);
6205 NoteDrag::aborted (bool)
6210 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
6211 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
6212 : Drag (editor, atv->base_item ())
6214 , _y_origin (atv->y_position())
6215 , _nothing_to_drag (false)
6217 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6218 setup (atv->lines ());
6221 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
6222 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
6223 : Drag (editor, rv->get_canvas_group ())
6225 , _y_origin (rv->get_time_axis_view().y_position())
6226 , _nothing_to_drag (false)
6229 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6231 list<boost::shared_ptr<AutomationLine> > lines;
6233 AudioRegionView* audio_view;
6234 AutomationRegionView* automation_view;
6235 if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
6236 lines.push_back (audio_view->get_gain_line ());
6237 } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
6238 lines.push_back (automation_view->line ());
6241 error << _("Automation range drag created for invalid region type") << endmsg;
6247 /** @param lines AutomationLines to drag.
6248 * @param offset Offset from the session start to the points in the AutomationLines.
6251 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
6253 /* find the lines that overlap the ranges being dragged */
6254 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
6255 while (i != lines.end ()) {
6256 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
6259 pair<samplepos_t, samplepos_t> r = (*i)->get_point_x_range ();
6261 /* check this range against all the AudioRanges that we are using */
6262 list<AudioRange>::const_iterator k = _ranges.begin ();
6263 while (k != _ranges.end()) {
6264 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
6270 /* add it to our list if it overlaps at all */
6271 if (k != _ranges.end()) {
6276 _lines.push_back (n);
6282 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
6286 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
6288 return 1.0 - ((global_y - _y_origin) / line->height());
6292 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
6294 const double v = list->eval(x);
6295 return _integral ? rint(v) : v;
6299 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6301 Drag::start_grab (event, cursor);
6303 /* Get line states before we start changing things */
6304 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6305 i->state = &i->line->get_state ();
6306 i->original_fraction = y_fraction (i->line, current_pointer_y());
6309 if (_ranges.empty()) {
6311 /* No selected time ranges: drag all points */
6312 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6313 uint32_t const N = i->line->npoints ();
6314 for (uint32_t j = 0; j < N; ++j) {
6315 i->points.push_back (i->line->nth (j));
6321 if (_nothing_to_drag) {
6327 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
6329 if (_nothing_to_drag && !first_move) {
6334 _editor->begin_reversible_command (_("automation range move"));
6336 if (!_ranges.empty()) {
6338 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
6340 samplecnt_t const half = (i->start + i->end) / 2;
6342 /* find the line that this audio range starts in */
6343 list<Line>::iterator j = _lines.begin();
6344 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
6348 if (j != _lines.end()) {
6349 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6351 /* j is the line that this audio range starts in; fade into it;
6352 64 samples length plucked out of thin air.
6355 samplepos_t a = i->start + 64;
6360 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
6361 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
6363 XMLNode &before = the_list->get_state();
6364 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6365 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6367 if (add_p || add_q) {
6368 _editor->session()->add_command (
6369 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6373 /* same thing for the end */
6376 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
6380 if (j != _lines.end()) {
6381 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6383 /* j is the line that this audio range starts in; fade out of it;
6384 64 samples length plucked out of thin air.
6387 samplepos_t b = i->end - 64;
6392 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6393 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6395 XMLNode &before = the_list->get_state();
6396 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6397 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6399 if (add_p || add_q) {
6400 _editor->session()->add_command (
6401 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6406 _nothing_to_drag = true;
6408 /* Find all the points that should be dragged and put them in the relevant
6409 points lists in the Line structs.
6412 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6414 uint32_t const N = i->line->npoints ();
6415 for (uint32_t j = 0; j < N; ++j) {
6417 /* here's a control point on this line */
6418 ControlPoint* p = i->line->nth (j);
6419 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6421 /* see if it's inside a range */
6422 list<AudioRange>::const_iterator k = _ranges.begin ();
6423 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6427 if (k != _ranges.end()) {
6428 /* dragging this point */
6429 _nothing_to_drag = false;
6430 i->points.push_back (p);
6436 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6437 i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6441 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6442 float const f = y_fraction (l->line, current_pointer_y());
6443 /* we are ignoring x position for this drag, so we can just pass in anything */
6444 pair<float, float> result;
6446 result = l->line->drag_motion (0, f, true, false, ignored);
6447 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (result.first, result.second));
6452 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6454 if (_nothing_to_drag || !motion_occurred) {
6458 motion (event, false);
6459 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6460 i->line->end_drag (false, 0);
6463 _editor->commit_reversible_command ();
6467 AutomationRangeDrag::aborted (bool)
6469 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6474 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6476 , initial_time_axis_view (itav)
6478 /* note that time_axis_view may be null if the regionview was created
6479 * as part of a copy operation.
6481 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6482 layer = v->region()->layer ();
6483 initial_y = v->get_canvas_group()->position().y;
6484 initial_playlist = v->region()->playlist ();
6485 initial_position = v->region()->position ();
6486 initial_end = v->region()->position () + v->region()->length ();
6489 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6490 : Drag (e, i->canvas_item ())
6493 , _cumulative_dx (0)
6495 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6496 _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time()),
6501 PatchChangeDrag::motion (GdkEvent* ev, bool)
6503 samplepos_t f = adjusted_current_sample (ev);
6504 boost::shared_ptr<Region> r = _region_view->region ();
6505 f = max (f, r->position ());
6506 f = min (f, r->last_sample ());
6508 samplecnt_t const dxf = f - grab_sample(); // permitted dx in samples
6509 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6510 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6511 _cumulative_dx = dxu;
6515 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6517 if (!movement_occurred) {
6518 if (was_double_click()) {
6519 _region_view->edit_patch_change (_patch_change);
6524 boost::shared_ptr<Region> r (_region_view->region ());
6525 samplepos_t f = adjusted_current_sample (ev);
6526 f = max (f, r->position ());
6527 f = min (f, r->last_sample ());
6529 _region_view->move_patch_change (
6531 _region_view->region_samples_to_region_beats (f - (r->position() - r->start()))
6536 PatchChangeDrag::aborted (bool)
6538 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6542 PatchChangeDrag::setup_pointer_sample_offset ()
6544 boost::shared_ptr<Region> region = _region_view->region ();
6545 _pointer_sample_offset = raw_grab_sample() - _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time());
6548 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6549 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6556 MidiRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6558 _region_view->update_drag_selection (
6560 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6564 MidiRubberbandSelectDrag::deselect_things ()
6569 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6570 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6573 _vertical_only = true;
6577 MidiVerticalSelectDrag::select_things (int button_state, samplepos_t /*x1*/, samplepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6579 double const y = _region_view->midi_view()->y_position ();
6581 y1 = max (0.0, y1 - y);
6582 y2 = max (0.0, y2 - y);
6584 _region_view->update_vertical_drag_selection (
6587 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6592 MidiVerticalSelectDrag::deselect_things ()
6597 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6598 : RubberbandSelectDrag (e, i)
6604 EditorRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool drag_in_progress)
6606 if (drag_in_progress) {
6607 /* We just want to select things at the end of the drag, not during it */
6611 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6613 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6615 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6617 _editor->commit_reversible_selection_op ();
6621 EditorRubberbandSelectDrag::deselect_things ()
6623 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6625 _editor->selection->clear_tracks();
6626 _editor->selection->clear_regions();
6627 _editor->selection->clear_points ();
6628 _editor->selection->clear_lines ();
6629 _editor->selection->clear_midi_notes ();
6631 _editor->commit_reversible_selection_op();
6634 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6639 _note[0] = _note[1] = 0;
6642 NoteCreateDrag::~NoteCreateDrag ()
6648 NoteCreateDrag::grid_samples (samplepos_t t) const
6651 const Temporal::Beats grid_beats = _region_view->get_grid_beats (t);
6652 const Temporal::Beats t_beats = _region_view->region_samples_to_region_beats (t);
6654 return _region_view->region_beats_to_region_samples (t_beats + grid_beats)
6655 - _region_view->region_beats_to_region_samples (t_beats);
6659 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6661 Drag::start_grab (event, cursor);
6663 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6664 TempoMap& map (_editor->session()->tempo_map());
6666 const samplepos_t pf = _drags->current_pointer_sample ();
6667 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6669 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6671 double eqaf = map.exact_qn_at_sample (pf, divisions);
6673 if (divisions != 0) {
6675 const double qaf = map.quarter_note_at_sample (pf);
6677 /* Hack so that we always snap to the note that we are over, instead of snapping
6678 to the next one if we're more than halfway through the one we're over.
6681 const double rem = eqaf - qaf;
6683 eqaf -= grid_beats.to_double();
6687 _note[0] = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6688 /* minimum initial length is grid beats */
6689 _note[1] = map.sample_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6691 double const x0 = _editor->sample_to_pixel (_note[0]);
6692 double const x1 = _editor->sample_to_pixel (_note[1]);
6693 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6695 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6696 _drag_rect->set_outline_all ();
6697 _drag_rect->set_outline_color (0xffffff99);
6698 _drag_rect->set_fill_color (0xffffff66);
6702 NoteCreateDrag::motion (GdkEvent* event, bool)
6704 TempoMap& map (_editor->session()->tempo_map());
6705 const samplepos_t pf = _drags->current_pointer_sample ();
6706 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6707 double eqaf = map.exact_qn_at_sample (pf, divisions);
6709 if (divisions != 0) {
6711 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6713 const double qaf = map.quarter_note_at_sample (pf);
6714 /* Hack so that we always snap to the note that we are over, instead of snapping
6715 to the next one if we're more than halfway through the one we're over.
6718 const double rem = eqaf - qaf;
6720 eqaf -= grid_beats.to_double();
6723 eqaf += grid_beats.to_double();
6725 _note[1] = max ((samplepos_t)0, map.sample_at_quarter_note (eqaf) - _region_view->region()->position ());
6727 double const x0 = _editor->sample_to_pixel (_note[0]);
6728 double const x1 = _editor->sample_to_pixel (_note[1]);
6729 _drag_rect->set_x0 (std::min(x0, x1));
6730 _drag_rect->set_x1 (std::max(x0, x1));
6734 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6736 /* we create a note even if there was no movement */
6737 samplepos_t const start = min (_note[0], _note[1]);
6738 samplepos_t const start_sess_rel = start + _region_view->region()->position();
6739 samplecnt_t length = max (_editor->pixel_to_sample (1.0), (samplecnt_t) fabs ((double)(_note[0] - _note[1])));
6740 samplecnt_t const g = grid_samples (start_sess_rel);
6742 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6746 TempoMap& map (_editor->session()->tempo_map());
6747 const double qn_length = map.quarter_notes_between_samples (start_sess_rel, start_sess_rel + length);
6748 Temporal::Beats qn_length_beats = max (Temporal::Beats::ticks(1), Temporal::Beats (qn_length));
6750 _editor->begin_reversible_command (_("Create Note"));
6751 _region_view->clear_editor_note_selection();
6752 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6753 _editor->commit_reversible_command ();
6757 NoteCreateDrag::y_to_region (double y) const
6760 _region_view->get_canvas_group()->canvas_to_item (x, y);
6765 NoteCreateDrag::aborted (bool)
6770 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6778 HitCreateDrag::~HitCreateDrag ()
6783 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6785 Drag::start_grab (event, cursor);
6787 TempoMap& map (_editor->session()->tempo_map());
6789 _y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6791 const samplepos_t pf = _drags->current_pointer_sample ();
6792 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6794 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6796 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6798 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6802 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6803 Temporal::Beats length = _region_view->get_grid_beats (pf);
6805 _editor->begin_reversible_command (_("Create Hit"));
6806 _region_view->clear_editor_note_selection();
6807 _region_view->create_note_at (start, _y, length, event->button.state, false);
6813 HitCreateDrag::motion (GdkEvent* event, bool)
6815 TempoMap& map (_editor->session()->tempo_map());
6817 const samplepos_t pf = _drags->current_pointer_sample ();
6818 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6820 if (divisions == 0) {
6824 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6825 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position ();
6827 if (_last_pos == start) {
6831 Temporal::Beats length = _region_view->get_grid_beats (pf);
6833 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6835 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6839 _region_view->create_note_at (start, _y, length, event->button.state, false);
6845 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6847 _editor->commit_reversible_command ();
6852 HitCreateDrag::y_to_region (double y) const
6855 _region_view->get_canvas_group()->canvas_to_item (x, y);
6860 HitCreateDrag::aborted (bool)
6865 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6870 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6874 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6876 Drag::start_grab (event, cursor);
6880 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6886 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6889 distance = _drags->current_pointer_x() - grab_x();
6890 len = ar->fade_in()->back()->when;
6892 distance = grab_x() - _drags->current_pointer_x();
6893 len = ar->fade_out()->back()->when;
6896 /* how long should it be ? */
6898 new_length = len + _editor->pixel_to_sample (distance);
6900 /* now check with the region that this is legal */
6902 new_length = ar->verify_xfade_bounds (new_length, start);
6905 arv->reset_fade_in_shape_width (ar, new_length);
6907 arv->reset_fade_out_shape_width (ar, new_length);
6912 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6918 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6921 distance = _drags->current_pointer_x() - grab_x();
6922 len = ar->fade_in()->back()->when;
6924 distance = grab_x() - _drags->current_pointer_x();
6925 len = ar->fade_out()->back()->when;
6928 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6930 _editor->begin_reversible_command ("xfade trim");
6931 ar->playlist()->clear_owned_changes ();
6934 ar->set_fade_in_length (new_length);
6936 ar->set_fade_out_length (new_length);
6939 /* Adjusting the xfade may affect other regions in the playlist, so we need
6940 to get undo Commands from the whole playlist rather than just the
6944 vector<Command*> cmds;
6945 ar->playlist()->rdiff (cmds);
6946 _editor->session()->add_commands (cmds);
6947 _editor->commit_reversible_command ();
6952 CrossfadeEdgeDrag::aborted (bool)
6955 // arv->redraw_start_xfade ();
6957 // arv->redraw_end_xfade ();
6961 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, samplepos_t pos)
6962 : Drag (e, item, true)
6966 RegionCutDrag::~RegionCutDrag ()
6971 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6973 Drag::start_grab (event, c);
6974 motion (event, false);
6978 RegionCutDrag::motion (GdkEvent* event, bool)
6983 RegionCutDrag::finished (GdkEvent* event, bool)
6985 _editor->get_track_canvas()->canvas()->re_enter();
6988 MusicSample pos (_drags->current_pointer_sample(), 0);
6989 _editor->snap_to_with_modifier (pos, event);
6991 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos.sample);
6997 _editor->split_regions_at (pos, rs);
7001 RegionCutDrag::aborted (bool)