2 * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
3 * Copyright (C) 2009-2015 David Robillard <d@drobilla.net>
4 * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
6 * Copyright (C) 2013-2017 Nick Mainsbridge <mainsbridge@gmail.com>
7 * Copyright (C) 2013 Michael Fisher <mfisher31@gmail.com>
8 * Copyright (C) 2014-2019 Ben Loftis <ben@harrisonconsoles.com>
9 * Copyright (C) 2014 Colin Fletcher <colin.m.fletcher@googlemail.com>
10 * Copyright (C) 2015-2017 Tim Mayberry <mojofunk@gmail.com>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License along
23 * with this program; if not, write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 #include "gtk2ardour-config.h"
34 #include "pbd/memento_command.h"
35 #include "pbd/basename.h"
36 #include "pbd/stateful_diff_command.h"
38 #include "gtkmm2ext/utils.h"
40 #include "ardour/audioengine.h"
41 #include "ardour/audioregion.h"
42 #include "ardour/audio_track.h"
43 #include "ardour/dB.h"
44 #include "ardour/midi_region.h"
45 #include "ardour/midi_track.h"
46 #include "ardour/operations.h"
47 #include "ardour/profile.h"
48 #include "ardour/region_factory.h"
49 #include "ardour/session.h"
50 #include "ardour/session_playlists.h"
52 #include "canvas/canvas.h"
53 #include "canvas/scroll_group.h"
58 #include "audio_region_view.h"
59 #include "automation_region_view.h"
60 #include "midi_region_view.h"
61 #include "ardour_ui.h"
62 #include "gui_thread.h"
63 #include "control_point.h"
64 #include "region_gain_line.h"
65 #include "editor_drag.h"
66 #include "audio_time_axis.h"
67 #include "midi_time_axis.h"
68 #include "selection.h"
69 #include "midi_selection.h"
70 #include "automation_time_axis.h"
72 #include "editor_cursors.h"
73 #include "mouse_cursors.h"
74 #include "note_base.h"
75 #include "patch_change.h"
76 #include "ui_config.h"
77 #include "verbose_cursor.h"
80 using namespace ARDOUR;
83 using namespace Gtkmm2ext;
84 using namespace Editing;
85 using namespace ArdourCanvas;
87 using Gtkmm2ext::Keyboard;
89 double ControlPointDrag::_zero_gain_fraction = -1.0;
91 DragManager::DragManager (Editor* e)
94 , _current_pointer_x (0.0)
95 , _current_pointer_y (0.0)
96 , _current_pointer_sample (0)
97 , _old_follow_playhead (false)
101 DragManager::~DragManager ()
106 /** Call abort for each active drag */
108 DragManager::abort ()
112 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
117 if (!_drags.empty ()) {
118 _editor->set_follow_playhead (_old_follow_playhead, false);
122 _editor->abort_reversible_command();
128 DragManager::add (Drag* d)
130 d->set_manager (this);
131 _drags.push_back (d);
135 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
137 d->set_manager (this);
138 _drags.push_back (d);
143 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
145 /* Prevent follow playhead during the drag to be nice to the user */
146 _old_follow_playhead = _editor->follow_playhead ();
147 _editor->set_follow_playhead (false);
149 _current_pointer_sample = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
151 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
152 (*i)->start_grab (e, c);
156 /** Call end_grab for each active drag.
157 * @return true if any drag reported movement having occurred.
160 DragManager::end_grab (GdkEvent* e)
165 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
166 bool const t = (*i)->end_grab (e);
177 _editor->set_follow_playhead (_old_follow_playhead, false);
183 DragManager::mark_double_click ()
185 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
186 (*i)->set_double_click (true);
191 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
195 /* calling this implies that we expect the event to have canvas
198 * Can we guarantee that this is true?
201 _current_pointer_sample = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
203 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
204 bool const t = (*i)->motion_handler (e, from_autoscroll);
205 /* run all handlers; return true if at least one of them
206 returns true (indicating that the event has been handled).
218 DragManager::have_item (ArdourCanvas::Item* i) const
220 list<Drag*>::const_iterator j = _drags.begin ();
221 while (j != _drags.end() && (*j)->item () != i) {
225 return j != _drags.end ();
228 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
232 , _pointer_sample_offset (0)
233 , _x_constrained (false)
234 , _y_constrained (false)
235 , _was_rolling (false)
236 , _trackview_only (trackview_only)
237 , _move_threshold_passed (false)
238 , _starting_point_passed (false)
239 , _initially_vertical (false)
240 , _was_double_click (false)
243 , _last_pointer_x (0.0)
244 , _last_pointer_y (0.0)
245 , _raw_grab_sample (0)
247 , _last_pointer_sample (0)
249 , _snap_delta_music (0.0)
250 , _constraint_pressed (false)
256 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
262 _cursor_ctx = CursorContext::create (*_editor, cursor);
264 _cursor_ctx->change (cursor);
271 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
274 /* we set up x/y dragging constraints on first move */
275 _constraint_pressed = ArdourKeyboard::indicates_constraint (event->button.state);
277 _raw_grab_sample = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
279 setup_pointer_sample_offset ();
280 _grab_sample = adjusted_sample (_raw_grab_sample, event).sample;
281 _last_pointer_sample = _grab_sample;
282 _last_pointer_x = _grab_x;
284 if (_trackview_only) {
285 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
288 _last_pointer_y = _grab_y;
292 if (!_editor->cursors()->is_invalid (cursor)) {
293 /* CAIROCANVAS need a variant here that passes *cursor */
294 _cursor_ctx = CursorContext::create (*_editor, cursor);
297 if (_editor->session() && _editor->session()->transport_rolling()) {
300 _was_rolling = false;
303 // if ( UIConfiguration::instance().get_snap_to_region_start() || UIConfiguration::instance().get_snap_to_region_end() || UIConfiguration::instance().get_snap_to_region_sync() ) {
304 // _editor->build_region_boundary_cache ();
308 /** Call to end a drag `successfully'. Ungrabs item and calls
309 * subclass' finished() method.
311 * @param event GDK event, or 0.
312 * @return true if some movement occurred, otherwise false.
315 Drag::end_grab (GdkEvent* event)
317 _editor->stop_canvas_autoscroll ();
321 finished (event, _move_threshold_passed);
323 _editor->verbose_cursor()->hide ();
326 return _move_threshold_passed;
330 Drag::adjusted_sample (samplepos_t f, GdkEvent const * event, bool snap) const
332 MusicSample pos (0, 0);
334 if (f > _pointer_sample_offset) {
335 pos.sample = f - _pointer_sample_offset;
339 _editor->snap_to_with_modifier (pos, event);
346 Drag::adjusted_current_sample (GdkEvent const * event, bool snap) const
348 return adjusted_sample (_drags->current_pointer_sample (), event, snap).sample;
352 Drag::snap_delta (guint state) const
354 if (ArdourKeyboard::indicates_snap_delta (state)) {
361 Drag::snap_delta_music (guint state) const
363 if (ArdourKeyboard::indicates_snap_delta (state)) {
364 return _snap_delta_music;
371 Drag::current_pointer_x() const
373 return _drags->current_pointer_x ();
377 Drag::current_pointer_y () const
379 if (!_trackview_only) {
380 return _drags->current_pointer_y ();
383 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
387 Drag::setup_snap_delta (MusicSample pos)
389 TempoMap& map (_editor->session()->tempo_map());
390 MusicSample snap (pos);
391 _editor->snap_to (snap, ARDOUR::RoundNearest, ARDOUR::SnapToAny_Visual, true);
392 _snap_delta = snap.sample - pos.sample;
394 _snap_delta_music = 0.0;
396 if (_snap_delta != 0) {
397 _snap_delta_music = map.exact_qn_at_sample (snap.sample, snap.division) - map.exact_qn_at_sample (pos.sample, pos.division);
402 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
404 /* check to see if we have moved in any way that matters since the last motion event */
405 if (_move_threshold_passed &&
406 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
407 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
411 pair<samplecnt_t, int> const threshold = move_threshold ();
413 bool const old_move_threshold_passed = _move_threshold_passed;
415 if (!_move_threshold_passed) {
417 bool const xp = (::llabs (_drags->current_pointer_sample () - _raw_grab_sample) >= threshold.first);
418 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
420 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
423 if (active (_editor->mouse_mode) && _move_threshold_passed) {
425 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
427 if (old_move_threshold_passed != _move_threshold_passed) {
431 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
432 _initially_vertical = true;
434 _initially_vertical = false;
436 /** check constraints for this drag.
437 * Note that the current convention is to use "contains" for
438 * key modifiers during motion and "equals" when initiating a drag.
439 * In this case we haven't moved yet, so "equals" applies here.
441 if (Config->get_edit_mode() != Lock) {
442 if (event->motion.state & Gdk::BUTTON2_MASK) {
443 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
444 if (_constraint_pressed) {
445 _x_constrained = false;
446 _y_constrained = true;
448 _x_constrained = true;
449 _y_constrained = false;
451 } else if (_constraint_pressed) {
452 // if dragging normally, the motion is constrained to the first direction of movement.
453 if (_initially_vertical) {
454 _x_constrained = true;
455 _y_constrained = false;
457 _x_constrained = false;
458 _y_constrained = true;
462 if (event->button.state & Gdk::BUTTON2_MASK) {
463 _x_constrained = false;
465 _x_constrained = true;
467 _y_constrained = false;
471 if (!from_autoscroll) {
472 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
475 if (!_editor->autoscroll_active() || from_autoscroll) {
478 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
480 motion (event, first_move && !_starting_point_passed);
482 if (first_move && !_starting_point_passed) {
483 _starting_point_passed = true;
486 _last_pointer_x = _drags->current_pointer_x ();
487 _last_pointer_y = current_pointer_y ();
488 _last_pointer_sample = adjusted_current_sample (event, false);
498 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
506 aborted (_move_threshold_passed);
508 _editor->stop_canvas_autoscroll ();
509 _editor->verbose_cursor()->hide ();
513 Drag::show_verbose_cursor_time (samplepos_t sample)
515 _editor->verbose_cursor()->set_time (sample);
516 _editor->verbose_cursor()->show ();
520 Drag::show_verbose_cursor_duration (samplepos_t start, samplepos_t end, double /*xoffset*/)
522 _editor->verbose_cursor()->set_duration (start, end);
523 _editor->verbose_cursor()->show ();
527 Drag::show_verbose_cursor_text (string const & text)
529 _editor->verbose_cursor()->set (text);
530 _editor->verbose_cursor()->show ();
533 boost::shared_ptr<Region>
534 Drag::add_midi_region (MidiTimeAxisView* view, bool commit)
536 if (_editor->session()) {
537 const TempoMap& map (_editor->session()->tempo_map());
538 samplecnt_t pos = grab_sample();
539 /* not that the frame rate used here can be affected by pull up/down which
542 samplecnt_t len = map.sample_at_beat (max (0.0, map.beat_at_sample (pos)) + 1.0) - pos;
543 return view->add_region (grab_sample(), len, commit);
546 return boost::shared_ptr<Region>();
549 struct TimeAxisViewStripableSorter {
550 bool operator() (TimeAxisView* tav_a, TimeAxisView* tav_b) {
551 boost::shared_ptr<ARDOUR::Stripable> const& a = tav_a->stripable ();
552 boost::shared_ptr<ARDOUR::Stripable> const& b = tav_b->stripable ();
553 return ARDOUR::Stripable::Sorter () (a, b);
557 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
562 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
564 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
565 as some of the regions we are dragging may be on such tracks.
568 TrackViewList track_views = _editor->track_views;
569 track_views.sort (TimeAxisViewStripableSorter ());
571 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
572 _time_axis_views.push_back (*i);
574 TimeAxisView::Children children_list = (*i)->get_child_list ();
575 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
576 _time_axis_views.push_back (j->get());
580 /* the list of views can be empty at this point if this is a region list-insert drag
583 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
584 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
587 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
591 RegionDrag::region_going_away (RegionView* v)
593 list<DraggingView>::iterator i = _views.begin ();
594 while (i != _views.end() && i->view != v) {
598 if (i != _views.end()) {
603 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
604 * or -1 if it is not found.
607 RegionDrag::find_time_axis_view (TimeAxisView* t) const
610 int const N = _time_axis_views.size ();
611 while (i < N && _time_axis_views[i] != t) {
615 if (_time_axis_views[i] != t) {
622 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
623 : RegionDrag (e, i, p, v)
625 , _ignore_video_lock (false)
626 , _last_position (0, 0)
628 , _last_pointer_time_axis_view (0)
629 , _last_pointer_layer (0)
634 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
638 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
640 Drag::start_grab (event, cursor);
641 setup_snap_delta (_last_position);
643 show_verbose_cursor_time (_last_position.sample);
645 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
647 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
648 assert(_last_pointer_time_axis_view >= 0);
649 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
652 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
653 _ignore_video_lock = true;
657 /* cross track dragging seems broken here. disabled for now. */
658 _y_constrained = true;
663 RegionMotionDrag::compute_x_delta (GdkEvent const * event, MusicSample* pending_region_position)
665 /* compute the amount of pointer motion in samples, and where
666 the region would be if we moved it by that much.
668 if (_x_constrained) {
669 *pending_region_position = _last_position;
673 *pending_region_position = adjusted_sample (_drags->current_pointer_sample (), event, false);
675 samplecnt_t sync_offset;
678 sync_offset = _primary->region()->sync_offset (sync_dir);
680 /* we don't handle a sync point that lies before zero.
682 if (sync_dir >= 0 || (sync_dir < 0 && pending_region_position->sample >= sync_offset)) {
684 samplecnt_t const sd = snap_delta (event->button.state);
685 MusicSample sync_snap (pending_region_position->sample + (sync_dir * sync_offset) + sd, 0);
686 _editor->snap_to_with_modifier (sync_snap, event);
687 if (sync_offset == 0 && sd == 0) {
688 *pending_region_position = sync_snap;
690 pending_region_position->set (_primary->region()->adjust_to_sync (sync_snap.sample) - sd, 0);
693 *pending_region_position = _last_position;
696 if (pending_region_position->sample > max_samplepos - _primary->region()->length()) {
697 *pending_region_position = _last_position;
702 bool const x_move_allowed = !_x_constrained;
704 if ((pending_region_position->sample != _last_position.sample) && x_move_allowed) {
706 /* x movement since last time (in pixels) */
707 dx = _editor->sample_to_pixel_unrounded (pending_region_position->sample - _last_position.sample);
709 /* total x movement */
710 samplecnt_t total_dx = _editor->pixel_to_sample (_total_x_delta + dx);
712 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
713 sampleoffset_t const off = i->view->region()->position() + total_dx;
715 dx = dx - _editor->sample_to_pixel_unrounded (off);
716 *pending_region_position = MusicSample (pending_region_position->sample - off, 0);
722 _editor->set_snapped_cursor_position(pending_region_position->sample);
728 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
734 const int tavsize = _time_axis_views.size();
735 const int dt = delta > 0 ? +1 : -1;
737 int target = start + delta - skip;
739 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
740 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
742 while (current >= 0 && current != target) {
744 if (current < 0 && dt < 0) {
747 if (current >= tavsize && dt > 0) {
750 if (current < 0 || current >= tavsize) {
754 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
755 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
759 if (distance_only && current == start + delta) {
767 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
769 if (_y_constrained) {
773 const int tavsize = _time_axis_views.size();
774 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
775 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
776 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
778 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
779 /* already in the drop zone */
780 if (delta_track >= 0) {
781 /* downward motion - OK if others are still not in the dropzone */
790 } else if (n >= tavsize) {
791 /* downward motion into drop zone. That's fine. */
795 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
796 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
797 /* not a track, or the wrong type */
801 double const l = i->layer + delta_layer;
803 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
804 mode to allow the user to place a region below another on layer 0.
806 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
807 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
808 If it has, the layers will be munged later anyway, so it's ok.
814 /* all regions being dragged are ok with this change */
818 struct DraggingViewSorter {
819 bool operator() (const DraggingView& a, const DraggingView& b) {
820 return a.time_axis_view < b.time_axis_view;
825 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
827 double delta_layer = 0;
828 int delta_time_axis_view = 0;
829 int current_pointer_time_axis_view = -1;
831 assert (!_views.empty ());
833 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
835 /* Find the TimeAxisView that the pointer is now over */
836 const double cur_y = current_pointer_y ();
837 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
838 TimeAxisView* tv = r.first;
840 if (!tv && cur_y < 0) {
841 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
845 /* find drop-zone y-position */
846 Coord last_track_bottom_edge;
847 last_track_bottom_edge = 0;
848 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
849 if (!(*t)->hidden()) {
850 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
855 if (tv && tv->view()) {
856 /* the mouse is over a track */
857 double layer = r.second;
859 if (first_move && tv->view()->layer_display() == Stacked) {
860 tv->view()->set_layer_display (Expanded);
863 /* Here's the current pointer position in terms of time axis view and layer */
864 current_pointer_time_axis_view = find_time_axis_view (tv);
865 assert(current_pointer_time_axis_view >= 0);
867 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
869 /* Work out the change in y */
871 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
872 if (!rtv || !rtv->is_track()) {
873 /* ignore non-tracks early on. we can't move any regions on them */
874 } else if (_last_pointer_time_axis_view < 0) {
875 /* Was in the drop-zone, now over a track.
876 * Hence it must be an upward move (from the bottom)
878 * track_index is still -1, so delta must be set to
879 * move up the correct number of tracks from the bottom.
881 * This is necessary because steps may be skipped if
882 * the bottom-most track is not a valid target and/or
883 * if there are hidden tracks at the bottom.
884 * Hence the initial offset (_ddropzone) as well as the
885 * last valid pointer position (_pdropzone) need to be
886 * taken into account.
888 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
890 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
893 /* TODO needs adjustment per DraggingView,
895 * e.g. select one region on the top-layer of a track
896 * and one region which is at the bottom-layer of another track
899 * Indicated drop-zones and layering is wrong.
900 * and may infer additional layers on the target-track
901 * (depending how many layers the original track had).
903 * Or select two regions (different layers) on a same track,
904 * move across a non-layer track.. -> layering info is lost.
905 * on drop either of the regions may be on top.
907 * Proposed solution: screw it :) well,
908 * don't use delta_layer, use an absolute value
909 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
910 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
911 * 3) iterate over all DraggingView, find the one that is over the track with most layers
912 * 4) proportionally scale layer to layers available on target
914 delta_layer = current_pointer_layer - _last_pointer_layer;
917 /* for automation lanes, there is a TimeAxisView but no ->view()
918 * if (!tv) -> dropzone
920 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
921 /* Moving into the drop-zone.. */
922 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
923 /* delta_time_axis_view may not be sufficient to move into the DZ
924 * the mouse may enter it, but it may not be a valid move due to
927 * -> remember the delta needed to move into the dropzone
929 _ddropzone = delta_time_axis_view;
930 /* ..but subtract hidden tracks (or routes) at the bottom.
931 * we silently move mover them
933 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
934 - _time_axis_views.size();
936 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
937 /* move around inside the zone.
938 * This allows to move further down until all regions are in the zone.
940 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
941 assert(ptr_y >= last_track_bottom_edge);
942 assert(_ddropzone > 0);
944 /* calculate mouse position in 'tracks' below last track. */
945 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
946 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
948 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
950 delta_time_axis_view = dzpos - _pdropzone;
951 } else if (dzpos < _pdropzone && _ndropzone > 0) {
952 // move up inside the DZ
953 delta_time_axis_view = dzpos - _pdropzone;
957 /* Work out the change in x */
958 TempoMap& tmap = _editor->session()->tempo_map();
959 MusicSample pending_region_position (0, 0);
960 double const x_delta = compute_x_delta (event, &pending_region_position);
962 double const last_pos_qn = tmap.exact_qn_at_sample (_last_position.sample, _last_position.division);
963 double const qn_delta = tmap.exact_qn_at_sample (pending_region_position.sample, pending_region_position.division) - last_pos_qn;
965 _last_position = pending_region_position;
967 /* calculate hidden tracks in current y-axis delta */
969 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
970 /* The mouse is more than one track below the dropzone.
971 * distance calculation is not needed (and would not work, either
972 * because the dropzone is "packed").
974 * Except when [partially] moving regions out of dropzone in a large step.
975 * (the mouse may or may not remain in the DZ)
976 * Hidden tracks at the bottom of the TAV need to be skipped.
978 * This also handles the case if the mouse entered the DZ
979 * in a large step (exessive delta), either due to fast-movement,
980 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
982 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
983 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
985 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
986 -_time_axis_views.size() - dt;
989 else if (_last_pointer_time_axis_view < 0) {
990 /* Moving out of the zone. Check for hidden tracks at the bottom. */
991 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
992 -_time_axis_views.size() - delta_time_axis_view;
994 /* calculate hidden tracks that are skipped by the pointer movement */
995 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
996 - _last_pointer_time_axis_view
997 - delta_time_axis_view;
1000 /* Verify change in y */
1001 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
1002 /* this y movement is not allowed, so do no y movement this time */
1003 delta_time_axis_view = 0;
1008 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1009 /* haven't reached next snap point, and we're not switching
1010 trackviews nor layers. nothing to do.
1015 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1016 PlaylistDropzoneMap playlist_dropzone_map;
1017 _ndropzone = 0; // number of elements currently in the dropzone
1020 /* sort views by time_axis.
1021 * This retains track order in the dropzone, regardless
1022 * of actual selection order
1024 _views.sort (DraggingViewSorter());
1026 /* count number of distinct tracks of all regions
1027 * being dragged, used for dropzone.
1029 int prev_track = -1;
1030 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1031 if (i->time_axis_view != prev_track) {
1032 prev_track = i->time_axis_view;
1038 _views.back().time_axis_view -
1039 _views.front().time_axis_view;
1041 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1042 - _views.back().time_axis_view;
1044 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1048 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1050 RegionView* rv = i->view;
1055 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1062 /* reparent the regionview into a group above all
1066 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1067 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1068 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1069 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1070 /* move the item so that it continues to appear at the
1071 same location now that its parent has changed.
1073 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1076 /* If we have moved tracks, we'll fudge the layer delta so that the
1077 region gets moved back onto layer 0 on its new track; this avoids
1078 confusion when dragging regions from non-zero layers onto different
1081 double this_delta_layer = delta_layer;
1082 if (delta_time_axis_view != 0) {
1083 this_delta_layer = - i->layer;
1086 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1088 int track_index = i->time_axis_view + this_delta_time_axis_view;
1089 assert(track_index >= 0);
1091 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1092 /* Track is in the Dropzone */
1094 i->time_axis_view = track_index;
1095 assert(i->time_axis_view >= (int) _time_axis_views.size());
1098 double yposition = 0;
1099 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1100 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1103 /* store index of each new playlist as a negative count, starting at -1 */
1105 if (pdz == playlist_dropzone_map.end()) {
1106 /* compute where this new track (which doesn't exist yet) will live
1109 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1111 /* How high is this region view ? */
1113 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1114 ArdourCanvas::Rect bbox;
1117 bbox = obbox.get ();
1120 last_track_bottom_edge += bbox.height();
1122 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1125 yposition = pdz->second;
1128 /* values are zero or negative, hence the use of min() */
1129 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1132 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1134 mrv->apply_note_range (60, 71, true);
1138 /* The TimeAxisView that this region is now over */
1139 TimeAxisView* current_tv = _time_axis_views[track_index];
1141 /* Ensure it is moved from stacked -> expanded if appropriate */
1142 if (current_tv->view()->layer_display() == Stacked) {
1143 current_tv->view()->set_layer_display (Expanded);
1146 /* We're only allowed to go -ve in layer on Expanded views */
1147 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1148 this_delta_layer = - i->layer;
1152 rv->set_height (current_tv->view()->child_height ());
1154 /* Update show/hidden status as the region view may have come from a hidden track,
1155 or have moved to one.
1157 if (current_tv->hidden ()) {
1158 rv->get_canvas_group()->hide ();
1160 rv->get_canvas_group()->show ();
1163 /* Update the DraggingView */
1164 i->time_axis_view = track_index;
1165 i->layer += this_delta_layer;
1168 _editor->mouse_brush_insert_region (rv, pending_region_position.sample);
1172 /* Get the y coordinate of the top of the track that this region is now over */
1173 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1175 /* And adjust for the layer that it should be on */
1176 StreamView* cv = current_tv->view ();
1177 switch (cv->layer_display ()) {
1181 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1184 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1188 /* need to get the parent of the regionview
1189 * canvas group and get its position in
1190 * equivalent coordinate space as the trackview
1191 * we are now dragging over.
1194 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1198 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1200 MidiStreamView* msv;
1201 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1202 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1207 /* Now move the region view */
1208 if (rv->region()->position_lock_style() == MusicTime) {
1209 double const last_qn = tmap.quarter_note_at_sample (rv->get_position());
1210 samplepos_t const x_pos_music = tmap.sample_at_quarter_note (last_qn + qn_delta);
1212 rv->set_position (x_pos_music, 0);
1213 rv->move (0, y_delta);
1215 rv->move (x_delta, y_delta);
1218 } /* foreach region */
1220 _total_x_delta += x_delta;
1222 if (x_delta != 0 && !_brushing) {
1223 show_verbose_cursor_time (_last_position.sample);
1226 /* keep track of pointer movement */
1228 /* the pointer is currently over a time axis view */
1230 if (_last_pointer_time_axis_view < 0) {
1231 /* last motion event was not over a time axis view
1232 * or last y-movement out of the dropzone was not valid
1235 if (delta_time_axis_view < 0) {
1236 /* in the drop zone, moving up */
1238 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1239 * We do not use negative _last_pointer_time_axis_view because
1240 * the dropzone is "packed" (the actual track offset is ignored)
1242 * As opposed to the actual number
1243 * of elements in the dropzone (_ndropzone)
1244 * _pdropzone is not constrained. This is necessary
1245 * to allow moving multiple regions with y-distance
1248 * There can be 0 elements in the dropzone,
1249 * even though the drag-pointer is inside the DZ.
1252 * [ Audio-track, Midi-track, Audio-track, DZ ]
1253 * move regions from both audio tracks at the same time into the
1254 * DZ by grabbing the region in the bottom track.
1256 assert(current_pointer_time_axis_view >= 0);
1257 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1261 /* only move out of the zone if the movement is OK */
1262 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1263 assert(delta_time_axis_view < 0);
1264 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1265 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1266 * the current position can be calculated as follows:
1268 // a well placed oofus attack can still throw this off.
1269 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1270 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1273 /* last motion event was also over a time axis view */
1274 _last_pointer_time_axis_view += delta_time_axis_view;
1275 assert(_last_pointer_time_axis_view >= 0);
1280 /* the pointer is not over a time axis view */
1281 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1282 _pdropzone += delta_time_axis_view - delta_skip;
1283 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1286 _last_pointer_layer += delta_layer;
1290 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1292 if (_copy && first_move) {
1293 if (_x_constrained && !_brushing) {
1294 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1295 } else if (!_brushing) {
1296 _editor->begin_reversible_command (Operations::region_copy);
1297 } else if (_brushing) {
1298 _editor->begin_reversible_command (Operations::drag_region_brush);
1300 /* duplicate the regionview(s) and region(s) */
1302 list<DraggingView> new_regionviews;
1304 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1306 RegionView* rv = i->view;
1307 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1308 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1310 const boost::shared_ptr<const Region> original = rv->region();
1311 boost::shared_ptr<Region> region_copy;
1313 region_copy = RegionFactory::create (original, true);
1315 /* need to set this so that the drop zone code can work. This doesn't
1316 actually put the region into the playlist, but just sets a weak pointer
1319 region_copy->set_playlist (original->playlist());
1323 boost::shared_ptr<AudioRegion> audioregion_copy
1324 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1326 nrv = new AudioRegionView (*arv, audioregion_copy);
1328 boost::shared_ptr<MidiRegion> midiregion_copy
1329 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1330 nrv = new MidiRegionView (*mrv, midiregion_copy);
1335 nrv->get_canvas_group()->show ();
1336 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1338 /* swap _primary to the copy */
1340 if (rv == _primary) {
1344 /* ..and deselect the one we copied */
1346 rv->set_selected (false);
1349 if (!new_regionviews.empty()) {
1351 /* reflect the fact that we are dragging the copies */
1353 _views = new_regionviews;
1355 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1358 } else if (!_copy && first_move) {
1359 if (_x_constrained && !_brushing) {
1360 _editor->begin_reversible_command (_("fixed time region drag"));
1361 } else if (!_brushing) {
1362 _editor->begin_reversible_command (Operations::region_drag);
1363 } else if (_brushing) {
1364 _editor->begin_reversible_command (Operations::drag_region_brush);
1367 RegionMotionDrag::motion (event, first_move);
1371 RegionMotionDrag::finished (GdkEvent *, bool)
1373 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1374 if (!(*i)->view()) {
1378 if ((*i)->view()->layer_display() == Expanded) {
1379 (*i)->view()->set_layer_display (Stacked);
1385 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1387 RegionMotionDrag::finished (ev, movement_occurred);
1389 if (!movement_occurred) {
1393 if (was_double_click() && !_views.empty()) {
1394 DraggingView dv = _views.front();
1395 _editor->edit_region (dv.view);
1401 assert (!_views.empty ());
1403 /* We might have hidden region views so that they weren't visible during the drag
1404 (when they have been reparented). Now everything can be shown again, as region
1405 views are back in their track parent groups.
1407 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1408 i->view->get_canvas_group()->show ();
1411 bool const changed_position = (_last_position.sample != _primary->region()->position());
1412 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1436 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1438 if (!ARDOUR_UI_UTILS::engine_is_running ()) {
1442 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1445 TimeAxisView* tav = 0;
1447 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1448 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1449 uint32_t output_chan = region->n_channels();
1450 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1451 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1453 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1454 tav =_editor->time_axis_view_from_stripable (audio_tracks.front());
1456 ChanCount one_midi_port (DataType::MIDI, 1);
1457 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1458 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1459 Config->get_strict_io () || Profile->get_mixbus (),
1460 boost::shared_ptr<ARDOUR::PluginInfo>(),
1461 (ARDOUR::Plugin::PresetRecord*) 0,
1462 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1463 tav = _editor->time_axis_view_from_stripable (midi_tracks.front());
1467 tav->set_height (original->current_height());
1470 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1473 return dynamic_cast<RouteTimeAxisView*> (tav);
1477 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, MusicSample last_position, int32_t const ev_state)
1479 RegionSelection new_views;
1480 PlaylistSet modified_playlists;
1481 RouteTimeAxisView* new_time_axis_view = 0;
1482 samplecnt_t const drag_delta = _primary->region()->position() - _last_position.sample;
1484 TempoMap& tmap (_editor->session()->tempo_map());
1485 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1486 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1489 /* all changes were made during motion event handlers */
1491 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1495 _editor->commit_reversible_command ();
1499 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1500 PlaylistMapping playlist_mapping;
1502 /* insert the regions into their new playlists */
1503 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1505 RouteTimeAxisView* dest_rtv = 0;
1507 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1511 MusicSample where (0, 0);
1512 double quarter_note;
1514 if (changed_position && !_x_constrained) {
1515 where.set (i->view->region()->position() - drag_delta, 0);
1516 quarter_note = i->view->region()->quarter_note() - qn_delta;
1518 /* region has not moved - divisor will not affect musical pos */
1519 where.set (i->view->region()->position(), 0);
1520 quarter_note = i->view->region()->quarter_note();
1523 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1524 /* dragged to drop zone */
1526 PlaylistMapping::iterator pm;
1528 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1529 /* first region from this original playlist: create a new track */
1530 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1531 if(!new_time_axis_view) {
1535 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1536 dest_rtv = new_time_axis_view;
1538 /* we already created a new track for regions from this playlist, use it */
1539 dest_rtv = pm->second;
1542 /* destination time axis view is the one we dragged to */
1543 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1546 if (dest_rtv != 0) {
1547 RegionView* new_view;
1548 if (i->view == _primary && !_x_constrained) {
1549 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, last_position, last_pos_qn,
1550 modified_playlists, true);
1552 if (i->view->region()->position_lock_style() == AudioTime) {
1553 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1554 modified_playlists);
1556 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1557 modified_playlists, true);
1561 if (new_view != 0) {
1562 new_views.push_back (new_view);
1566 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1567 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1570 list<DraggingView>::const_iterator next = i;
1576 /* If we've created new regions either by copying or moving
1577 to a new track, we want to replace the old selection with the new ones
1580 if (new_views.size() > 0) {
1581 _editor->selection->set (new_views);
1584 /* write commands for the accumulated diffs for all our modified playlists */
1585 add_stateful_diff_commands_for_playlists (modified_playlists);
1587 _editor->commit_reversible_command ();
1591 RegionMoveDrag::finished_no_copy (
1592 bool const changed_position,
1593 bool const changed_tracks,
1594 MusicSample last_position,
1595 int32_t const ev_state
1598 RegionSelection new_views;
1599 PlaylistSet modified_playlists;
1600 PlaylistSet frozen_playlists;
1601 set<RouteTimeAxisView*> views_to_update;
1602 RouteTimeAxisView* new_time_axis_view = 0;
1603 samplecnt_t const drag_delta = _primary->region()->position() - last_position.sample;
1605 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1606 PlaylistMapping playlist_mapping;
1608 TempoMap& tmap (_editor->session()->tempo_map());
1609 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1610 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1612 std::set<boost::shared_ptr<const Region> > uniq;
1613 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1615 RegionView* rv = i->view;
1616 RouteTimeAxisView* dest_rtv = 0;
1618 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1623 if (uniq.find (rv->region()) != uniq.end()) {
1624 /* prevent duplicate moves when selecting regions from shared playlists */
1628 uniq.insert(rv->region());
1630 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1631 /* dragged to drop zone */
1633 PlaylistMapping::iterator pm;
1635 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1636 /* first region from this original playlist: create a new track */
1637 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1638 if(!new_time_axis_view) { // New track creation failed
1642 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1643 dest_rtv = new_time_axis_view;
1645 /* we already created a new track for regions from this playlist, use it */
1646 dest_rtv = pm->second;
1650 /* destination time axis view is the one we dragged to */
1651 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1656 double const dest_layer = i->layer;
1658 views_to_update.insert (dest_rtv);
1660 MusicSample where (0, 0);
1661 double quarter_note;
1663 if (changed_position && !_x_constrained) {
1664 where.set (rv->region()->position() - drag_delta, 0);
1665 quarter_note = i->view->region()->quarter_note() - qn_delta;
1667 where.set (rv->region()->position(), 0);
1668 quarter_note = i->view->region()->quarter_note();
1671 if (changed_tracks) {
1673 /* insert into new playlist */
1674 RegionView* new_view;
1675 if (rv == _primary && !_x_constrained) {
1676 new_view = insert_region_into_playlist (
1677 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, last_position, last_pos_qn,
1678 modified_playlists, true
1681 if (rv->region()->position_lock_style() == AudioTime) {
1683 new_view = insert_region_into_playlist (
1684 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1688 new_view = insert_region_into_playlist (
1689 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1690 modified_playlists, true
1695 if (new_view == 0) {
1700 new_views.push_back (new_view);
1702 /* remove from old playlist */
1704 /* the region that used to be in the old playlist is not
1705 moved to the new one - we use a copy of it. as a result,
1706 any existing editor for the region should no longer be
1709 rv->hide_region_editor();
1712 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1716 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1718 /* this movement may result in a crossfade being modified, or a layering change,
1719 so we need to get undo data from the playlist as well as the region.
1722 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1724 playlist->clear_changes ();
1727 rv->region()->clear_changes ();
1730 motion on the same track. plonk the previously reparented region
1731 back to its original canvas group (its streamview).
1732 No need to do anything for copies as they are fake regions which will be deleted.
1735 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1736 rv->get_canvas_group()->set_y_position (i->initial_y);
1739 /* just change the model */
1740 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1741 playlist->set_layer (rv->region(), dest_layer);
1744 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1746 r = frozen_playlists.insert (playlist);
1749 playlist->freeze ();
1751 if (rv == _primary) {
1752 rv->region()->set_position (where.sample, last_position.division);
1754 if (rv->region()->position_lock_style() == AudioTime) {
1755 /* move by sample offset */
1756 rv->region()->set_position (where.sample, 0);
1758 /* move by music offset */
1759 rv->region()->set_position_music (rv->region()->quarter_note() - qn_delta);
1762 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1765 if (changed_tracks) {
1767 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1768 was selected in all of them, then removing it from a playlist will have removed all
1769 trace of it from _views (i.e. there were N regions selected, we removed 1,
1770 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1771 corresponding regionview, and _views is now empty).
1773 This could have invalidated any and all iterators into _views.
1775 The heuristic we use here is: if the region selection is empty, break out of the loop
1776 here. if the region selection is not empty, then restart the loop because we know that
1777 we must have removed at least the region(view) we've just been working on as well as any
1778 that we processed on previous iterations.
1780 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1781 we can just iterate.
1785 if (_views.empty()) {
1796 /* If we've created new regions either by copying or moving
1797 to a new track, we want to replace the old selection with the new ones
1800 if (new_views.size() > 0) {
1801 _editor->selection->set (new_views);
1804 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1808 /* write commands for the accumulated diffs for all our modified playlists */
1809 add_stateful_diff_commands_for_playlists (modified_playlists);
1810 /* applies to _brushing */
1811 _editor->commit_reversible_command ();
1813 /* We have futzed with the layering of canvas items on our streamviews.
1814 If any region changed layer, this will have resulted in the stream
1815 views being asked to set up their region views, and all will be well.
1816 If not, we might now have badly-ordered region views. Ask the StreamViews
1817 involved to sort themselves out, just in case.
1820 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1821 (*i)->view()->playlist_layered ((*i)->track ());
1825 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1826 * @param region Region to remove.
1827 * @param playlist playlist To remove from.
1828 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1829 * that clear_changes () is only called once per playlist.
1832 RegionMoveDrag::remove_region_from_playlist (
1833 boost::shared_ptr<Region> region,
1834 boost::shared_ptr<Playlist> playlist,
1835 PlaylistSet& modified_playlists
1838 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1841 playlist->clear_changes ();
1844 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1848 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1849 * clearing the playlist's diff history first if necessary.
1850 * @param region Region to insert.
1851 * @param dest_rtv Destination RouteTimeAxisView.
1852 * @param dest_layer Destination layer.
1853 * @param where Destination position.
1854 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1855 * that clear_changes () is only called once per playlist.
1856 * @return New RegionView, or 0 if no insert was performed.
1859 RegionMoveDrag::insert_region_into_playlist (
1860 boost::shared_ptr<Region> region,
1861 RouteTimeAxisView* dest_rtv,
1864 double quarter_note,
1865 PlaylistSet& modified_playlists,
1869 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1870 if (!dest_playlist) {
1874 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1875 _new_region_view = 0;
1876 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1878 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1879 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1881 dest_playlist->clear_changes ();
1884 dest_playlist->add_region (region, where.sample, 1.0, false, where.division, quarter_note, true);
1886 dest_playlist->add_region (region, where.sample, 1.0, false, where.division);
1889 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1890 dest_playlist->set_layer (region, dest_layer);
1895 assert (_new_region_view);
1897 return _new_region_view;
1901 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1903 _new_region_view = rv;
1907 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1909 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1910 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1912 _editor->session()->add_command (c);
1921 RegionMoveDrag::aborted (bool movement_occurred)
1925 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1926 list<DraggingView>::const_iterator next = i;
1935 RegionMotionDrag::aborted (movement_occurred);
1940 RegionMotionDrag::aborted (bool)
1942 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1944 StreamView* sview = (*i)->view();
1947 if (sview->layer_display() == Expanded) {
1948 sview->set_layer_display (Stacked);
1953 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1954 RegionView* rv = i->view;
1955 TimeAxisView* tv = &(rv->get_time_axis_view ());
1956 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1958 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1959 rv->get_canvas_group()->set_y_position (0);
1961 rv->move (-_total_x_delta, 0);
1962 rv->set_height (rtv->view()->child_height ());
1966 /** @param b true to brush, otherwise false.
1967 * @param c true to make copies of the regions being moved, otherwise false.
1969 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1970 : RegionMotionDrag (e, i, p, v, b)
1972 , _new_region_view (0)
1974 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1976 _last_position = MusicSample (_primary->region()->position(), 0);
1980 RegionMoveDrag::setup_pointer_sample_offset ()
1982 _pointer_sample_offset = raw_grab_sample() - _last_position.sample;
1985 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, samplepos_t pos)
1986 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1988 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1990 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1991 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1993 _primary = v->view()->create_region_view (r, false, false);
1995 _primary->get_canvas_group()->show ();
1996 _primary->set_position (pos, 0);
1997 _views.push_back (DraggingView (_primary, this, v));
1999 _last_position = MusicSample (pos, 0);
2001 _item = _primary->get_canvas_group ();
2005 RegionInsertDrag::finished (GdkEvent * event, bool)
2007 int pos = _views.front().time_axis_view;
2008 assert(pos >= 0 && pos < (int)_time_axis_views.size());
2010 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
2012 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
2013 _primary->get_canvas_group()->set_y_position (0);
2015 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
2017 _editor->begin_reversible_command (Operations::insert_region);
2018 playlist->clear_changes ();
2019 _editor->snap_to_with_modifier (_last_position, event);
2021 playlist->add_region (_primary->region (), _last_position.sample, 1.0, false, _last_position.division);
2023 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2024 if (Config->get_edit_mode() == Ripple) {
2025 playlist->ripple (_last_position.sample, _primary->region()->length(), _primary->region());
2028 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2029 _editor->commit_reversible_command ();
2037 RegionInsertDrag::aborted (bool)
2044 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2045 : RegionMoveDrag (e, i, p, v, false, false)
2047 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2050 struct RegionSelectionByPosition {
2051 bool operator() (RegionView*a, RegionView* b) {
2052 return a->region()->position () < b->region()->position();
2057 RegionSpliceDrag::motion (GdkEvent* event, bool)
2059 /* Which trackview is this ? */
2061 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2062 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2064 /* The region motion is only processed if the pointer is over
2068 if (!tv || !tv->is_track()) {
2069 /* To make sure we hide the verbose canvas cursor when the mouse is
2070 not held over an audio track.
2072 _editor->verbose_cursor()->hide ();
2075 _editor->verbose_cursor()->show ();
2080 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2086 RegionSelection copy;
2087 _editor->selection->regions.by_position(copy);
2089 samplepos_t const pf = adjusted_current_sample (event);
2091 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2093 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2099 boost::shared_ptr<Playlist> playlist;
2101 if ((playlist = atv->playlist()) == 0) {
2105 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2110 if (pf < (*i)->region()->last_sample() + 1) {
2114 if (pf > (*i)->region()->first_sample()) {
2120 playlist->shuffle ((*i)->region(), dir);
2125 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2127 RegionMoveDrag::finished (event, movement_occurred);
2131 RegionSpliceDrag::aborted (bool)
2141 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, samplepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2144 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<samplepos_t>(where, max_samplepos));
2146 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2147 RegionSelection to_ripple;
2148 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2149 if ((*i)->position() >= where) {
2150 to_ripple.push_back (rtv->view()->find_view(*i));
2154 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2155 if (!exclude.contains (*i)) {
2156 // the selection has already been added to _views
2158 if (drag_in_progress) {
2159 // do the same things that RegionMotionDrag::motion does when
2160 // first_move is true, for the region views that we're adding
2161 // to _views this time
2164 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2165 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2166 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2167 rvg->reparent (_editor->_drag_motion_group);
2169 // we only need to move in the y direction
2170 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2175 _views.push_back (DraggingView (*i, this, tav));
2181 RegionRippleDrag::remove_unselected_from_views(samplecnt_t amount, bool move_regions)
2184 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2185 // we added all the regions after the selection
2187 std::list<DraggingView>::iterator to_erase = i++;
2188 if (!_editor->selection->regions.contains (to_erase->view)) {
2189 // restore the non-selected regions to their original playlist & positions,
2190 // and then ripple them back by the length of the regions that were dragged away
2191 // do the same things as RegionMotionDrag::aborted
2193 RegionView *rv = to_erase->view;
2194 TimeAxisView* tv = &(rv->get_time_axis_view ());
2195 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2198 // plonk them back onto their own track
2199 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2200 rv->get_canvas_group()->set_y_position (0);
2204 // move the underlying region to match the view
2205 rv->region()->set_position (rv->region()->position() + amount);
2207 // restore the view to match the underlying region's original position
2208 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2211 rv->set_height (rtv->view()->child_height ());
2212 _views.erase (to_erase);
2218 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2220 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2222 return allow_moves_across_tracks;
2230 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2231 : RegionMoveDrag (e, i, p, v, false, false)
2233 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2234 // compute length of selection
2235 RegionSelection selected_regions = _editor->selection->regions;
2236 selection_length = selected_regions.end_sample() - selected_regions.start();
2238 // we'll only allow dragging to another track in ripple mode if all the regions
2239 // being dragged start off on the same track
2240 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2243 exclude = new RegionList;
2244 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2245 exclude->push_back((*i)->region());
2248 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2249 RegionSelection copy;
2250 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2252 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2253 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2255 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2256 // find ripple start point on each applicable playlist
2257 RegionView *first_selected_on_this_track = NULL;
2258 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2259 if ((*i)->region()->playlist() == (*pi)) {
2260 // region is on this playlist - it's the first, because they're sorted
2261 first_selected_on_this_track = *i;
2265 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2266 add_all_after_to_views (
2267 &first_selected_on_this_track->get_time_axis_view(),
2268 first_selected_on_this_track->region()->position(),
2269 selected_regions, false);
2272 if (allow_moves_across_tracks) {
2273 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2281 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2283 /* Which trackview is this ? */
2285 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2286 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2288 /* The region motion is only processed if the pointer is over
2292 if (!tv || !tv->is_track()) {
2293 /* To make sure we hide the verbose canvas cursor when the mouse is
2294 not held over an audiotrack.
2296 _editor->verbose_cursor()->hide ();
2300 samplepos_t where = adjusted_current_sample (event);
2301 assert (where >= 0);
2302 MusicSample after (0, 0);
2303 double delta = compute_x_delta (event, &after);
2305 samplecnt_t amount = _editor->pixel_to_sample (delta);
2307 if (allow_moves_across_tracks) {
2308 // all the originally selected regions were on the same track
2310 samplecnt_t adjust = 0;
2311 if (prev_tav && tv != prev_tav) {
2312 // dragged onto a different track
2313 // remove the unselected regions from _views, restore them to their original positions
2314 // and add the regions after the drop point on the new playlist to _views instead.
2315 // undo the effect of rippling the previous playlist, and include the effect of removing
2316 // the dragged region(s) from this track
2318 remove_unselected_from_views (prev_amount, false);
2319 // ripple previous playlist according to the regions that have been removed onto the new playlist
2320 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2323 // move just the selected regions
2324 RegionMoveDrag::motion(event, first_move);
2326 // ensure that the ripple operation on the new playlist inserts selection_length time
2327 adjust = selection_length;
2328 // ripple the new current playlist
2329 tv->playlist()->ripple (where, amount+adjust, exclude);
2331 // add regions after point where drag entered this track to subsequent ripples
2332 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2335 // motion on same track
2336 RegionMoveDrag::motion(event, first_move);
2340 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2341 prev_position = where;
2343 // selection encompasses multiple tracks - just drag
2344 // cross-track drags are forbidden
2345 RegionMoveDrag::motion(event, first_move);
2348 if (!_x_constrained) {
2349 prev_amount += amount;
2352 _last_position = after;
2356 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2358 if (!movement_occurred) {
2362 if (was_double_click() && !_views.empty()) {
2363 DraggingView dv = _views.front();
2364 _editor->edit_region (dv.view);
2370 _editor->begin_reversible_command(_("Ripple drag"));
2372 // remove the regions being rippled from the dragging view, updating them to
2373 // their new positions
2374 remove_unselected_from_views (prev_amount, true);
2376 if (allow_moves_across_tracks) {
2378 // if regions were dragged across tracks, we've rippled any later
2379 // regions on the track the regions were dragged off, so we need
2380 // to add the original track to the undo record
2381 orig_tav->playlist()->clear_changes();
2382 vector<Command*> cmds;
2383 orig_tav->playlist()->rdiff (cmds);
2384 _editor->session()->add_commands (cmds);
2386 if (prev_tav && prev_tav != orig_tav) {
2387 prev_tav->playlist()->clear_changes();
2388 vector<Command*> cmds;
2389 prev_tav->playlist()->rdiff (cmds);
2390 _editor->session()->add_commands (cmds);
2393 // selection spanned multiple tracks - all will need adding to undo record
2395 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2396 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2398 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2399 (*pi)->clear_changes();
2400 vector<Command*> cmds;
2401 (*pi)->rdiff (cmds);
2402 _editor->session()->add_commands (cmds);
2406 // other modified playlists are added to undo by RegionMoveDrag::finished()
2407 RegionMoveDrag::finished (event, movement_occurred);
2408 _editor->commit_reversible_command();
2412 RegionRippleDrag::aborted (bool movement_occurred)
2414 RegionMoveDrag::aborted (movement_occurred);
2419 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2421 _view (dynamic_cast<MidiTimeAxisView*> (v))
2423 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2429 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2433 _editor->begin_reversible_command (_("create region"));
2434 _region = add_midi_region (_view, false);
2435 _view->playlist()->freeze ();
2439 samplepos_t const f = adjusted_current_sample (event);
2440 if (f <= grab_sample()) {
2441 _region->set_initial_position (f);
2444 /* Don't use a zero-length region, and subtract 1 sample from the snapped length
2445 so that if this region is duplicated, its duplicate starts on
2446 a snap point rather than 1 sample after a snap point. Otherwise things get
2447 a bit confusing as if a region starts 1 sample after a snap point, one cannot
2448 place snapped notes at the start of the region.
2450 if (f != grab_sample()) {
2451 samplecnt_t const len = (samplecnt_t) fabs ((double)(f - grab_sample () - 1));
2452 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2459 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2461 if (!movement_occurred) {
2462 add_midi_region (_view, true);
2464 _view->playlist()->thaw ();
2465 _editor->commit_reversible_command();
2470 RegionCreateDrag::aborted (bool)
2473 _view->playlist()->thaw ();
2479 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2484 , _was_selected (false)
2487 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2491 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2493 Gdk::Cursor* cursor;
2494 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2496 float x_fraction = cnote->mouse_x_fraction ();
2498 if (x_fraction > 0.0 && x_fraction < 0.25) {
2499 cursor = _editor->cursors()->left_side_trim;
2502 cursor = _editor->cursors()->right_side_trim;
2506 Drag::start_grab (event, cursor);
2508 region = &cnote->region_view();
2511 temp = region->snap_to_pixel (cnote->x0 (), true);
2512 _snap_delta = temp - cnote->x0 ();
2516 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2521 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2522 if (ms.size() > 1) {
2523 /* has to be relative, may make no sense otherwise */
2527 if (!(_was_selected = cnote->selected())) {
2529 /* tertiary-click means extend selection - we'll do that on button release,
2530 so don't add it here, because otherwise we make it hard to figure
2531 out the "extend-to" range.
2534 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2537 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2540 region->note_selected (cnote, true);
2542 _editor->get_selection().clear_points();
2543 region->unique_select (cnote);
2550 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2552 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2554 _editor->begin_reversible_command (_("resize notes"));
2556 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2557 MidiRegionSelection::iterator next;
2560 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2562 mrv->begin_resizing (at_front);
2568 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2569 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2571 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2575 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2577 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2578 if (_editor->snap_mode () != SnapOff) {
2582 if (_editor->snap_mode () == SnapOff) {
2584 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2585 if (apply_snap_delta) {
2591 if (apply_snap_delta) {
2595 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2601 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2603 if (!movement_occurred) {
2604 /* no motion - select note */
2605 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2606 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2607 _editor->current_mouse_mode() == Editing::MouseDraw) {
2609 bool changed = false;
2611 if (_was_selected) {
2612 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2614 region->note_deselected (cnote);
2617 _editor->get_selection().clear_points();
2618 region->unique_select (cnote);
2622 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2623 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2625 if (!extend && !add && region->selection_size() > 1) {
2626 _editor->get_selection().clear_points();
2627 region->unique_select (cnote);
2629 } else if (extend) {
2630 region->note_selected (cnote, true, true);
2633 /* it was added during button press */
2639 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2640 _editor->commit_reversible_selection_op();
2647 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2648 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2649 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2651 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2654 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2656 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2657 if (_editor->snap_mode () != SnapOff) {
2661 if (_editor->snap_mode () == SnapOff) {
2663 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2664 if (apply_snap_delta) {
2670 if (apply_snap_delta) {
2674 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2678 _editor->commit_reversible_command ();
2682 NoteResizeDrag::aborted (bool)
2684 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2685 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2686 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2688 mrv->abort_resizing ();
2693 AVDraggingView::AVDraggingView (RegionView* v)
2696 initial_position = v->region()->position ();
2699 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2702 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2705 TrackViewList empty;
2707 _editor->get_regions_after(rs, (samplepos_t) 0, empty);
2708 std::list<RegionView*> views = rs.by_layer();
2711 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2712 RegionView* rv = (*i);
2713 if (!rv->region()->video_locked()) {
2716 if (rv->region()->locked()) {
2719 _views.push_back (AVDraggingView (rv));
2724 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2726 Drag::start_grab (event);
2727 if (_editor->session() == 0) {
2731 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2737 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2741 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2742 _max_backwards_drag = (
2743 ARDOUR_UI::instance()->video_timeline->get_duration()
2744 + ARDOUR_UI::instance()->video_timeline->get_offset()
2745 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2748 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2749 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2750 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv (i->initial_position);
2753 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2756 Timecode::Time timecode;
2757 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2758 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);
2759 show_verbose_cursor_text (buf);
2763 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2765 if (_editor->session() == 0) {
2768 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2772 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2776 samplecnt_t dt = adjusted_current_sample (event) - raw_grab_sample() + _pointer_sample_offset;
2777 dt = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2779 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2780 dt = - _max_backwards_drag;
2783 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2784 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2786 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2787 RegionView* rv = i->view;
2788 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2791 rv->region()->clear_changes ();
2792 rv->region()->suspend_property_changes();
2794 rv->region()->set_position(i->initial_position + dt);
2795 rv->region_changed(ARDOUR::Properties::position);
2798 const samplepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2799 Timecode::Time timecode;
2800 Timecode::Time timediff;
2802 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2803 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2804 snprintf (buf, sizeof (buf),
2805 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2806 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2807 , _("Video Start:"),
2808 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2810 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2812 show_verbose_cursor_text (buf);
2816 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2818 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2825 if (!movement_occurred || ! _editor->session()) {
2829 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2831 _editor->begin_reversible_command (_("Move Video"));
2833 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2834 ARDOUR_UI::instance()->video_timeline->save_undo();
2835 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2836 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2838 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2839 i->view->drag_end();
2840 i->view->region()->resume_property_changes ();
2842 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2845 _editor->session()->maybe_update_session_range(
2846 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::sampleoffset_t) 0),
2847 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::sampleoffset_t) 0)
2851 _editor->commit_reversible_command ();
2855 VideoTimeLineDrag::aborted (bool)
2857 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2860 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2861 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2863 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2864 i->view->region()->resume_property_changes ();
2865 i->view->region()->set_position(i->initial_position);
2869 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2870 : RegionDrag (e, i, p, v)
2871 , _operation (StartTrim)
2872 , _preserve_fade_anchor (preserve_fade_anchor)
2873 , _jump_position_when_done (false)
2875 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2879 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2881 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2883 samplepos_t const region_start = _primary->region()->position();
2884 samplepos_t const region_end = _primary->region()->last_sample();
2885 samplecnt_t const region_length = _primary->region()->length();
2887 samplepos_t const pf = adjusted_current_sample (event);
2888 setup_snap_delta (MusicSample(region_start, 0));
2890 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2891 /* Move the contents of the region around without changing the region bounds */
2892 _operation = ContentsTrim;
2893 Drag::start_grab (event, _editor->cursors()->trimmer);
2895 /* These will get overridden for a point trim.*/
2896 if (pf < (region_start + region_length/2)) {
2897 /* closer to front */
2898 _operation = StartTrim;
2899 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2900 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2902 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2906 _operation = EndTrim;
2907 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2908 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2910 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2914 /* jump trim disabled for now
2915 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2916 _jump_position_when_done = true;
2920 switch (_operation) {
2922 show_verbose_cursor_time (region_start);
2925 show_verbose_cursor_duration (region_start, region_end);
2928 show_verbose_cursor_time (pf);
2932 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2933 i->view->region()->suspend_property_changes ();
2938 TrimDrag::motion (GdkEvent* event, bool first_move)
2940 RegionView* rv = _primary;
2942 TimeAxisView* tvp = &_primary->get_time_axis_view ();
2943 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2944 sampleoffset_t sample_delta = 0;
2946 MusicSample adj_sample = adjusted_sample (_drags->current_pointer_sample () + snap_delta (event->button.state), event, true);
2947 samplecnt_t dt = adj_sample.sample - raw_grab_sample () + _pointer_sample_offset - snap_delta (event->button.state);
2953 switch (_operation) {
2955 trim_type = "Region start trim";
2958 trim_type = "Region end trim";
2961 trim_type = "Region content trim";
2968 _editor->begin_reversible_command (trim_type);
2970 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2971 RegionView* rv = i->view;
2972 rv->region()->playlist()->clear_owned_changes ();
2974 if (_operation == StartTrim) {
2975 rv->trim_front_starting ();
2978 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2981 arv->temporarily_hide_envelope ();
2985 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2986 insert_result = _editor->motion_frozen_playlists.insert (pl);
2988 if (insert_result.second) {
2992 MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (rv);
2993 /* a MRV start trim may change the source length. ensure we cover all playlists here */
2994 if (mrv && _operation == StartTrim) {
2995 vector<boost::shared_ptr<Playlist> > all_playlists;
2996 _editor->session()->playlists()->get (all_playlists);
2997 for (vector<boost::shared_ptr<Playlist> >::iterator x = all_playlists.begin(); x != all_playlists.end(); ++x) {
2999 if ((*x)->uses_source (rv->region()->source(0))) {
3000 insert_result = _editor->motion_frozen_playlists.insert (*x);
3001 if (insert_result.second) {
3002 (*x)->clear_owned_changes ();
3012 bool non_overlap_trim = false;
3014 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
3015 non_overlap_trim = true;
3018 /* contstrain trim to fade length */
3019 if (_preserve_fade_anchor) {
3020 switch (_operation) {
3022 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3023 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3025 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3026 if (ar->locked()) continue;
3027 samplecnt_t len = ar->fade_in()->back()->when;
3028 if (len < dt) dt = min(dt, len);
3032 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3033 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3035 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3036 if (ar->locked()) continue;
3037 samplecnt_t len = ar->fade_out()->back()->when;
3038 if (len < -dt) dt = max(dt, -len);
3046 switch (_operation) {
3048 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3049 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3050 , adj_sample.division);
3052 if (changed && _preserve_fade_anchor) {
3053 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3055 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3056 samplecnt_t len = ar->fade_in()->back()->when;
3057 samplecnt_t diff = ar->first_sample() - i->initial_position;
3058 samplepos_t new_length = len - diff;
3059 i->anchored_fade_length = min (ar->length(), new_length);
3060 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3061 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3068 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3069 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, adj_sample.division);
3070 if (changed && _preserve_fade_anchor) {
3071 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3073 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3074 samplecnt_t len = ar->fade_out()->back()->when;
3075 samplecnt_t diff = 1 + ar->last_sample() - i->initial_end;
3076 samplepos_t new_length = len + diff;
3077 i->anchored_fade_length = min (ar->length(), new_length);
3078 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3079 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3087 sample_delta = (last_pointer_sample() - adjusted_current_sample(event));
3089 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3090 i->view->move_contents (sample_delta);
3096 switch (_operation) {
3098 show_verbose_cursor_time (rv->region()->position());
3101 show_verbose_cursor_duration (rv->region()->position(), rv->region()->last_sample());
3104 // show_verbose_cursor_time (sample_delta);
3110 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3112 if (movement_occurred) {
3113 motion (event, false);
3115 if (_operation == StartTrim) {
3116 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3118 /* This must happen before the region's StatefulDiffCommand is created, as it may
3119 `correct' (ahem) the region's _start from being negative to being zero. It
3120 needs to be zero in the undo record.
3122 i->view->trim_front_ending ();
3124 if (_preserve_fade_anchor) {
3125 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3127 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3128 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3129 ar->set_fade_in_length(i->anchored_fade_length);
3130 ar->set_fade_in_active(true);
3133 if (_jump_position_when_done) {
3134 i->view->region()->set_position (i->initial_position);
3137 } else if (_operation == EndTrim) {
3138 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3139 if (_preserve_fade_anchor) {
3140 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3142 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3143 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3144 ar->set_fade_out_length(i->anchored_fade_length);
3145 ar->set_fade_out_active(true);
3148 if (_jump_position_when_done) {
3149 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3154 if (!_editor->selection->selected (_primary)) {
3155 _primary->thaw_after_trim ();
3157 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3158 i->view->thaw_after_trim ();
3159 i->view->enable_display (true);
3163 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3164 /* Trimming one region may affect others on the playlist, so we need
3165 to get undo Commands from the whole playlist rather than just the
3166 region. Use motion_frozen_playlists (a set) to make sure we don't
3167 diff a given playlist more than once.
3170 vector<Command*> cmds;
3172 _editor->session()->add_commands (cmds);
3176 _editor->motion_frozen_playlists.clear ();
3177 _editor->commit_reversible_command();
3180 /* no mouse movement */
3181 if (adjusted_current_sample (event) != adjusted_sample (_drags->current_pointer_sample(), event, false).sample) {
3182 _editor->point_trim (event, adjusted_current_sample (event));
3186 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3187 i->view->region()->resume_property_changes ();
3192 TrimDrag::aborted (bool movement_occurred)
3194 /* Our motion method is changing model state, so use the Undo system
3195 to cancel. Perhaps not ideal, as this will leave an Undo point
3196 behind which may be slightly odd from the user's point of view.
3200 memset (&ev, 0, sizeof (GdkEvent));
3201 finished (&ev, true);
3203 if (movement_occurred) {
3204 _editor->session()->undo (1);
3207 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3208 i->view->region()->resume_property_changes ();
3213 TrimDrag::setup_pointer_sample_offset ()
3215 list<DraggingView>::iterator i = _views.begin ();
3216 while (i != _views.end() && i->view != _primary) {
3220 if (i == _views.end()) {
3224 switch (_operation) {
3226 _pointer_sample_offset = raw_grab_sample() - i->initial_position;
3229 _pointer_sample_offset = raw_grab_sample() - i->initial_end;
3236 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3239 , _old_grid_type (e->grid_type())
3240 , _old_snap_mode (e->snap_mode())
3243 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3244 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3246 _real_section = &_marker->meter();
3251 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3253 Drag::start_grab (event, cursor);
3254 show_verbose_cursor_time (adjusted_current_sample(event));
3258 MeterMarkerDrag::setup_pointer_sample_offset ()
3260 _pointer_sample_offset = raw_grab_sample() - _marker->meter().sample();
3264 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3267 // create a dummy marker to catch events, then hide it.
3270 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3272 _marker = new MeterMarker (
3274 *_editor->meter_group,
3275 UIConfiguration::instance().color ("meter marker"),
3277 *new MeterSection (_marker->meter())
3280 /* use the new marker for the grab */
3281 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3284 TempoMap& map (_editor->session()->tempo_map());
3285 /* get current state */
3286 before_state = &map.get_state();
3289 _editor->begin_reversible_command (_("move meter mark"));
3291 _editor->begin_reversible_command (_("copy meter mark"));
3293 Timecode::BBT_Time bbt = _real_section->bbt();
3295 /* we can't add a meter where one currently exists */
3296 if (_real_section->sample() < adjusted_current_sample (event, false)) {
3301 const samplepos_t sample = map.sample_at_bbt (bbt);
3302 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3303 , bbt, sample, _real_section->position_lock_style());
3304 if (!_real_section) {
3310 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3311 if (_real_section->position_lock_style() != AudioTime) {
3312 _editor->set_grid_to (GridTypeBar);
3313 _editor->set_snap_mode (SnapMagnetic);
3317 samplepos_t pf = adjusted_current_sample (event);
3319 if (_real_section->position_lock_style() == AudioTime && _editor->grid_musical()) {
3320 /* never snap to music for audio locked */
3321 pf = adjusted_current_sample (event, false);
3324 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3326 /* fake marker meeds to stay under the mouse, unlike the real one. */
3327 _marker->set_position (adjusted_current_sample (event, false));
3329 show_verbose_cursor_time (_real_section->sample());
3330 _editor->set_snapped_cursor_position(_real_section->sample());
3334 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3336 if (!movement_occurred) {
3337 if (was_double_click()) {
3338 _editor->edit_meter_marker (*_marker);
3343 /* reinstate old snap setting */
3344 _editor->set_grid_to (_old_grid_type);
3345 _editor->set_snap_mode (_old_snap_mode);
3347 TempoMap& map (_editor->session()->tempo_map());
3349 XMLNode &after = map.get_state();
3350 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3351 _editor->commit_reversible_command ();
3353 // delete the dummy marker we used for visual representation while moving.
3354 // a new visual marker will show up automatically.
3359 MeterMarkerDrag::aborted (bool moved)
3361 _marker->set_position (_marker->meter().sample ());
3363 /* reinstate old snap setting */
3364 _editor->set_grid_to (_old_grid_type);
3365 _editor->set_snap_mode (_old_snap_mode);
3367 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3368 // delete the dummy marker we used for visual representation while moving.
3369 // a new visual marker will show up automatically.
3374 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3377 , _grab_bpm (120.0, 4.0)
3381 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3383 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3384 _real_section = &_marker->tempo();
3385 _movable = !_real_section->initial();
3386 _grab_bpm = Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), _real_section->end_note_types_per_minute());
3387 _grab_qn = _real_section->pulse() * 4.0;
3392 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3394 Drag::start_grab (event, cursor);
3395 if (!_real_section->active()) {
3396 show_verbose_cursor_text (_("inactive"));
3398 show_verbose_cursor_time (adjusted_current_sample (event));
3403 TempoMarkerDrag::setup_pointer_sample_offset ()
3405 _pointer_sample_offset = raw_grab_sample() - _real_section->sample();
3409 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3411 if (!_real_section->active()) {
3414 TempoMap& map (_editor->session()->tempo_map());
3418 // mvc drag - create a dummy marker to catch events, hide it.
3421 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3423 TempoSection section (_marker->tempo());
3425 _marker = new TempoMarker (
3427 *_editor->tempo_group,
3428 UIConfiguration::instance().color ("tempo marker"),
3430 *new TempoSection (_marker->tempo())
3433 /* use the new marker for the grab */
3434 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3437 /* get current state */
3438 _before_state = &map.get_state();
3441 _editor->begin_reversible_command (_("move tempo mark"));
3444 const Tempo tempo (_marker->tempo());
3445 const samplepos_t sample = adjusted_current_sample (event) + 1;
3447 _editor->begin_reversible_command (_("copy tempo mark"));
3449 if (_real_section->position_lock_style() == MusicTime) {
3450 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3451 _real_section = map.add_tempo (tempo, map.exact_qn_at_sample (sample, divisions), 0, MusicTime);
3453 _real_section = map.add_tempo (tempo, 0.0, sample, AudioTime);
3456 if (!_real_section) {
3463 if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) {
3464 double new_bpm = max (1.5, _grab_bpm.end_note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3466 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), new_bpm));
3467 strs << "end:" << fixed << setprecision(3) << new_bpm;
3468 show_verbose_cursor_text (strs.str());
3470 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3471 /* use vertical movement to alter tempo .. should be log */
3472 double new_bpm = max (1.5, _grab_bpm.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3474 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type(), _real_section->end_note_types_per_minute()));
3475 strs << "start:" << fixed << setprecision(3) << new_bpm;
3476 show_verbose_cursor_text (strs.str());
3478 } else if (_movable && !_real_section->locked_to_meter()) {
3481 if (_editor->grid_musical()) {
3482 /* we can't snap to a grid that we are about to move.
3483 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3485 pf = adjusted_current_sample (event, false);
3487 pf = adjusted_current_sample (event);
3490 /* snap to beat is 1, snap to bar is -1 (sorry) */
3491 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3493 map.gui_set_tempo_position (_real_section, pf, sub_num);
3495 show_verbose_cursor_time (_real_section->sample());
3496 _editor->set_snapped_cursor_position(_real_section->sample());
3498 _marker->set_position (adjusted_current_sample (event, false));
3502 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3504 if (!_real_section->active()) {
3507 if (!movement_occurred) {
3508 if (was_double_click()) {
3509 _editor->edit_tempo_marker (*_marker);
3514 TempoMap& map (_editor->session()->tempo_map());
3516 XMLNode &after = map.get_state();
3517 _editor->session()->add_command (new MementoCommand<TempoMap>(map, _before_state, &after));
3518 _editor->commit_reversible_command ();
3520 // delete the dummy marker we used for visual representation while moving.
3521 // a new visual marker will show up automatically.
3526 TempoMarkerDrag::aborted (bool moved)
3528 _marker->set_position (_marker->tempo().sample());
3530 TempoMap& map (_editor->session()->tempo_map());
3531 map.set_state (*_before_state, Stateful::current_state_version);
3532 // delete the dummy (hidden) marker we used for events while moving.
3537 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3542 , _drag_valid (true)
3544 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3549 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3551 Drag::start_grab (event, cursor);
3552 TempoMap& map (_editor->session()->tempo_map());
3553 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3555 if (adjusted_current_sample (event, false) <= _tempo->sample()) {
3556 _drag_valid = false;
3560 _editor->tempo_curve_selected (_tempo, true);
3563 if (_tempo->clamped()) {
3564 TempoSection* prev = map.previous_tempo_section (_tempo);
3566 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3570 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3571 show_verbose_cursor_text (sstr.str());
3575 BBTRulerDrag::setup_pointer_sample_offset ()
3577 TempoMap& map (_editor->session()->tempo_map());
3578 /* get current state */
3579 _before_state = &map.get_state();
3581 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3582 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3585 if (divisions > 0) {
3586 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3588 /* while it makes some sense for the user to determine the division to 'grab',
3589 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3590 and the result over steep tempo curves. Use sixteenths.
3592 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3595 _grab_qn = map.quarter_note_at_beat (beat);
3597 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3602 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3609 _editor->begin_reversible_command (_("stretch tempo"));
3612 TempoMap& map (_editor->session()->tempo_map());
3615 if (_editor->grid_musical()) {
3616 pf = adjusted_current_sample (event, false);
3618 pf = adjusted_current_sample (event);
3621 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3622 /* adjust previous tempo to match pointer sample */
3623 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.sample_at_quarter_note (_grab_qn), pf, _grab_qn, map.quarter_note_at_sample (pf));
3627 if (_tempo->clamped()) {
3628 TempoSection* prev = map.previous_tempo_section (_tempo);
3630 _editor->tempo_curve_selected (prev, true);
3631 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3634 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3635 show_verbose_cursor_text (sstr.str());
3639 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3641 if (!movement_occurred) {
3645 TempoMap& map (_editor->session()->tempo_map());
3647 _editor->tempo_curve_selected (_tempo, false);
3648 if (_tempo->clamped()) {
3649 TempoSection* prev_tempo = map.previous_tempo_section (_tempo);
3651 _editor->tempo_curve_selected (prev_tempo, false);
3655 if (!movement_occurred || !_drag_valid) {
3659 XMLNode &after = map.get_state();
3660 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3661 _editor->commit_reversible_command ();
3666 BBTRulerDrag::aborted (bool moved)
3669 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3673 TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i)
3679 , _drag_valid (true)
3682 DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n");
3687 TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3689 Drag::start_grab (event, cursor);
3690 TempoMap& map (_editor->session()->tempo_map());
3691 /* get current state */
3692 _before_state = &map.get_state();
3693 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3695 if (_tempo->locked_to_meter()) {
3696 _drag_valid = false;
3700 _next_tempo = map.next_tempo_section (_tempo);
3702 if (!map.next_tempo_section (_next_tempo)) {
3703 _drag_valid = false;
3704 finished (event, false);
3708 _editor->tempo_curve_selected (_tempo, true);
3709 _editor->tempo_curve_selected (_next_tempo, true);
3712 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3713 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3714 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3715 show_verbose_cursor_text (sstr.str());
3717 _drag_valid = false;
3720 _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type());
3724 TempoTwistDrag::setup_pointer_sample_offset ()
3726 TempoMap& map (_editor->session()->tempo_map());
3727 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3728 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3731 if (divisions > 0) {
3732 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3734 /* while it makes some sense for the user to determine the division to 'grab',
3735 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3736 and the result over steep tempo curves. Use sixteenths.
3738 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3741 _grab_qn = map.quarter_note_at_beat (beat);
3743 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3748 TempoTwistDrag::motion (GdkEvent* event, bool first_move)
3751 if (!_next_tempo || !_drag_valid) {
3755 TempoMap& map (_editor->session()->tempo_map());
3758 _editor->begin_reversible_command (_("twist tempo"));
3763 if (_editor->grid_musical()) {
3764 pf = adjusted_current_sample (event, false);
3766 pf = adjusted_current_sample (event);
3769 /* adjust this and the next tempi to match pointer sample */
3770 double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3771 _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.sample_at_quarter_note (_grab_qn), pf);
3774 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3775 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3776 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3777 show_verbose_cursor_text (sstr.str());
3781 TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred)
3783 if (!movement_occurred || !_drag_valid) {
3787 _editor->tempo_curve_selected (_tempo, false);
3788 _editor->tempo_curve_selected (_next_tempo, false);
3790 TempoMap& map (_editor->session()->tempo_map());
3791 XMLNode &after = map.get_state();
3792 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3793 _editor->commit_reversible_command ();
3797 TempoTwistDrag::aborted (bool moved)
3800 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3804 TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i)
3809 , _drag_valid (true)
3811 DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n");
3812 TempoMarker* marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3813 _tempo = &marker->tempo();
3814 _grab_qn = _tempo->pulse() * 4.0;
3818 TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3820 Drag::start_grab (event, cursor);
3821 TempoMap& tmap (_editor->session()->tempo_map());
3823 /* get current state */
3824 _before_state = &tmap.get_state();
3826 if (_tempo->locked_to_meter()) {
3827 _drag_valid = false;
3833 TempoSection* prev = 0;
3834 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3835 _editor->tempo_curve_selected (tmap.previous_tempo_section (_tempo), true);
3836 sstr << "end: " << fixed << setprecision(3) << tmap.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3839 if (_tempo->clamped()) {
3840 _editor->tempo_curve_selected (_tempo, true);
3841 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3844 show_verbose_cursor_text (sstr.str());
3848 TempoEndDrag::setup_pointer_sample_offset ()
3850 TempoMap& map (_editor->session()->tempo_map());
3852 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3857 TempoEndDrag::motion (GdkEvent* event, bool first_move)
3863 TempoMap& map (_editor->session()->tempo_map());
3866 _editor->begin_reversible_command (_("stretch end tempo"));
3869 samplepos_t const pf = adjusted_current_sample (event, false);
3870 map.gui_stretch_tempo_end (&map.tempo_section_at_sample (_tempo->sample() - 1), map.sample_at_quarter_note (_grab_qn), pf);
3873 sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3875 if (_tempo->clamped()) {
3876 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3879 show_verbose_cursor_text (sstr.str());
3883 TempoEndDrag::finished (GdkEvent* event, bool movement_occurred)
3885 if (!movement_occurred || !_drag_valid) {
3889 TempoMap& tmap (_editor->session()->tempo_map());
3891 XMLNode &after = tmap.get_state();
3892 _editor->session()->add_command(new MementoCommand<TempoMap>(tmap, _before_state, &after));
3893 _editor->commit_reversible_command ();
3895 TempoSection* prev = 0;
3896 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3897 _editor->tempo_curve_selected (prev, false);
3900 if (_tempo->clamped()) {
3901 _editor->tempo_curve_selected (_tempo, false);
3907 TempoEndDrag::aborted (bool moved)
3910 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3914 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3915 : Drag (e, &c.track_canvas_item(), false)
3920 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3923 /** Do all the things we do when dragging the playhead to make it look as though
3924 * we have located, without actually doing the locate (because that would cause
3925 * the diskstream buffers to be refilled, which is too slow).
3928 CursorDrag::fake_locate (samplepos_t t)
3930 if (_editor->session () == 0) {
3934 _editor->playhead_cursor->set_position (t);
3936 Session* s = _editor->session ();
3937 if (s->timecode_transmission_suspended ()) {
3938 samplepos_t const f = _editor->playhead_cursor->current_sample ();
3939 /* This is asynchronous so it will be sent "now"
3941 s->send_mmc_locate (f);
3942 /* These are synchronous and will be sent during the next
3945 s->queue_full_time_code ();
3946 s->queue_song_position_pointer ();
3949 show_verbose_cursor_time (t);
3950 _editor->UpdateAllTransportClocks (t);
3954 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3956 Drag::start_grab (event, c);
3957 setup_snap_delta (MusicSample (_editor->playhead_cursor->current_sample(), 0));
3959 _grab_zoom = _editor->samples_per_pixel;
3961 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
3963 _editor->snap_to_with_modifier (where, event);
3964 _editor->_dragging_playhead = true;
3965 _editor->_control_scroll_target = where.sample;
3967 Session* s = _editor->session ();
3969 /* grab the track canvas item as well */
3971 _cursor.track_canvas_item().grab();
3974 if (_was_rolling && _stop) {
3978 if (s->is_auditioning()) {
3979 s->cancel_audition ();
3983 if (AudioEngine::instance()->running()) {
3985 /* do this only if we're the engine is connected
3986 * because otherwise this request will never be
3987 * serviced and we'll busy wait forever. likewise,
3988 * notice if we are disconnected while waiting for the
3989 * request to be serviced.
3992 s->request_suspend_timecode_transmission ();
3993 while (AudioEngine::instance()->running() && !s->timecode_transmission_suspended ()) {
3994 /* twiddle our thumbs */
3999 fake_locate (where.sample - snap_delta (event->button.state));
4005 CursorDrag::motion (GdkEvent* event, bool)
4007 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4009 _editor->snap_to_with_modifier (where, event);
4011 if (where.sample != last_pointer_sample()) {
4012 fake_locate (where.sample - snap_delta (event->button.state));
4015 //maybe do zooming, too, if the option is enabled
4016 if (UIConfiguration::instance ().get_use_time_rulers_to_zoom_with_vertical_drag () ) {
4018 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
4019 //we use screen coordinates for this, not canvas-based grab_x
4020 double mx = event->button.x;
4021 double dx = fabs(mx - _last_mx);
4022 double my = event->button.y;
4023 double dy = fabs(my - _last_my);
4026 //do zooming in windowed "steps" so it feels more reversible ?
4027 const int stepsize = 2; //stepsize ==1 means "trigger on every pixel of movement"
4028 int y_delta = grab_y() - current_pointer_y();
4029 y_delta = y_delta / stepsize;
4031 //if all requirements are met, do the actual zoom
4032 const double scale = 1.2;
4033 if ( (dy>dx) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
4034 if ( _last_y_delta > y_delta ) {
4035 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
4037 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
4039 _last_y_delta = y_delta;
4050 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
4052 _editor->_dragging_playhead = false;
4054 _cursor.track_canvas_item().ungrab();
4056 if (!movement_occurred && _stop) {
4060 motion (event, false);
4062 Session* s = _editor->session ();
4064 s->request_locate (_editor->playhead_cursor->current_sample (), _was_rolling);
4065 _editor->_pending_locate_request = true;
4066 s->request_resume_timecode_transmission ();
4071 CursorDrag::aborted (bool)
4073 _cursor.track_canvas_item().ungrab();
4075 if (_editor->_dragging_playhead) {
4076 _editor->session()->request_resume_timecode_transmission ();
4077 _editor->_dragging_playhead = false;
4080 _editor->playhead_cursor->set_position (adjusted_sample (grab_sample (), 0, false).sample);
4083 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4084 : RegionDrag (e, i, p, v)
4086 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
4090 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4092 Drag::start_grab (event, cursor);
4094 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4095 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4096 setup_snap_delta (MusicSample (r->position(), 0));
4098 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
4102 FadeInDrag::setup_pointer_sample_offset ()
4104 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4105 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4106 _pointer_sample_offset = raw_grab_sample() - ((samplecnt_t) r->fade_in()->back()->when + r->position());
4110 FadeInDrag::motion (GdkEvent* event, bool)
4112 samplecnt_t fade_length;
4114 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4115 _editor->snap_to_with_modifier (pos, event);
4117 pos.sample -= snap_delta (event->button.state);
4119 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4121 if (pos.sample < (region->position() + 64)) {
4122 fade_length = 64; // this should be a minimum defined somewhere
4123 } else if (pos.sample > region->position() + region->length() - region->fade_out()->back()->when) {
4124 fade_length = region->length() - region->fade_out()->back()->when - 1;
4126 fade_length = pos.sample - region->position();
4129 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4131 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4137 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
4140 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
4144 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
4146 if (!movement_occurred) {
4150 samplecnt_t fade_length;
4151 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4153 _editor->snap_to_with_modifier (pos, event);
4154 pos.sample -= snap_delta (event->button.state);
4156 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4158 if (pos.sample < (region->position() + 64)) {
4159 fade_length = 64; // this should be a minimum defined somewhere
4160 } else if (pos.sample >= region->position() + region->length() - region->fade_out()->back()->when) {
4161 fade_length = region->length() - region->fade_out()->back()->when - 1;
4163 fade_length = pos.sample - region->position();
4166 bool in_command = false;
4168 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4170 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4176 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
4177 XMLNode &before = alist->get_state();
4179 tmp->audio_region()->set_fade_in_length (fade_length);
4180 tmp->audio_region()->set_fade_in_active (true);
4183 _editor->begin_reversible_command (_("change fade in length"));
4186 XMLNode &after = alist->get_state();
4187 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4191 _editor->commit_reversible_command ();
4196 FadeInDrag::aborted (bool)
4198 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4199 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4205 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
4209 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4210 : RegionDrag (e, i, p, v)
4212 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
4216 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4218 Drag::start_grab (event, cursor);
4220 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4221 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4222 setup_snap_delta (MusicSample (r->last_sample(), 0));
4224 show_verbose_cursor_duration (r->last_sample() - r->fade_out()->back()->when, r->last_sample());
4228 FadeOutDrag::setup_pointer_sample_offset ()
4230 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4231 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4232 _pointer_sample_offset = raw_grab_sample() - (r->length() - (samplecnt_t) r->fade_out()->back()->when + r->position());
4236 FadeOutDrag::motion (GdkEvent* event, bool)
4238 samplecnt_t fade_length;
4239 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4241 _editor->snap_to_with_modifier (pos, event);
4242 pos.sample -= snap_delta (event->button.state);
4244 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4246 if (pos.sample > (region->last_sample() - 64)) {
4247 fade_length = 64; // this should really be a minimum fade defined somewhere
4248 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4249 fade_length = region->length() - region->fade_in()->back()->when - 1;
4251 fade_length = region->last_sample() - pos.sample;
4254 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4256 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4262 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
4265 show_verbose_cursor_duration (region->last_sample() - fade_length, region->last_sample());
4269 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
4271 if (!movement_occurred) {
4275 samplecnt_t fade_length;
4276 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4278 _editor->snap_to_with_modifier (pos, event);
4279 pos.sample -= snap_delta (event->button.state);
4281 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4283 if (pos.sample > (region->last_sample() - 64)) {
4284 fade_length = 64; // this should really be a minimum fade defined somewhere
4285 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4286 fade_length = region->length() - region->fade_in()->back()->when - 1;
4288 fade_length = region->last_sample() - pos.sample;
4291 bool in_command = false;
4293 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4295 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4301 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
4302 XMLNode &before = alist->get_state();
4304 tmp->audio_region()->set_fade_out_length (fade_length);
4305 tmp->audio_region()->set_fade_out_active (true);
4308 _editor->begin_reversible_command (_("change fade out length"));
4311 XMLNode &after = alist->get_state();
4312 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4316 _editor->commit_reversible_command ();
4321 FadeOutDrag::aborted (bool)
4323 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4324 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4330 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
4334 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
4336 , _selection_changed (false)
4338 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
4339 Gtk::Window* toplevel = _editor->current_toplevel();
4340 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
4344 _points.push_back (ArdourCanvas::Duple (0, 0));
4346 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4349 MarkerDrag::~MarkerDrag ()
4351 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4356 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4358 location = new Location (*l);
4359 markers.push_back (m);
4364 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4366 Drag::start_grab (event, cursor);
4370 Location *location = _editor->find_location_from_marker (_marker, is_start);
4371 _editor->_dragging_edit_point = true;
4373 update_item (location);
4375 // _drag_line->show();
4376 // _line->raise_to_top();
4379 show_verbose_cursor_time (location->start());
4381 show_verbose_cursor_time (location->end());
4383 setup_snap_delta (MusicSample (is_start ? location->start() : location->end(), 0));
4385 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4388 case Selection::Toggle:
4389 /* we toggle on the button release */
4391 case Selection::Set:
4392 if (!_editor->selection->selected (_marker)) {
4393 _editor->selection->set (_marker);
4394 _selection_changed = true;
4397 case Selection::Extend:
4399 Locations::LocationList ll;
4400 list<ArdourMarker*> to_add;
4402 _editor->selection->markers.range (s, e);
4403 s = min (_marker->position(), s);
4404 e = max (_marker->position(), e);
4407 if (e < max_samplepos) {
4410 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4411 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4412 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4415 to_add.push_back (lm->start);
4418 to_add.push_back (lm->end);
4422 if (!to_add.empty()) {
4423 _editor->selection->add (to_add);
4424 _selection_changed = true;
4428 case Selection::Add:
4429 _editor->selection->add (_marker);
4430 _selection_changed = true;
4435 /* Set up copies for us to manipulate during the drag
4438 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4440 Location* l = _editor->find_location_from_marker (*i, is_start);
4447 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4449 /* range: check that the other end of the range isn't
4452 CopiedLocationInfo::iterator x;
4453 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4454 if (*(*x).location == *l) {
4458 if (x == _copied_locations.end()) {
4459 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4461 (*x).markers.push_back (*i);
4462 (*x).move_both = true;
4470 MarkerDrag::setup_pointer_sample_offset ()
4473 Location *location = _editor->find_location_from_marker (_marker, is_start);
4474 _pointer_sample_offset = raw_grab_sample() - (is_start ? location->start() : location->end());
4478 MarkerDrag::motion (GdkEvent* event, bool)
4480 samplecnt_t f_delta = 0;
4482 bool move_both = false;
4483 Location *real_location;
4484 Location *copy_location = 0;
4485 samplecnt_t const sd = snap_delta (event->button.state);
4487 samplecnt_t const newframe = adjusted_sample (_drags->current_pointer_sample () + sd, event, true).sample - sd;
4488 samplepos_t next = newframe;
4490 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4494 CopiedLocationInfo::iterator x;
4496 /* find the marker we're dragging, and compute the delta */
4498 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4500 copy_location = (*x).location;
4502 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4504 /* this marker is represented by this
4505 * CopiedLocationMarkerInfo
4508 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4513 if (real_location->is_mark()) {
4514 f_delta = newframe - copy_location->start();
4518 switch (_marker->type()) {
4519 case ArdourMarker::SessionStart:
4520 case ArdourMarker::RangeStart:
4521 case ArdourMarker::LoopStart:
4522 case ArdourMarker::PunchIn:
4523 f_delta = newframe - copy_location->start();
4526 case ArdourMarker::SessionEnd:
4527 case ArdourMarker::RangeEnd:
4528 case ArdourMarker::LoopEnd:
4529 case ArdourMarker::PunchOut:
4530 f_delta = newframe - copy_location->end();
4533 /* what kind of marker is this ? */
4542 if (x == _copied_locations.end()) {
4543 /* hmm, impossible - we didn't find the dragged marker */
4547 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4549 /* now move them all */
4551 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4553 copy_location = x->location;
4555 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4559 if (real_location->locked()) {
4563 if (copy_location->is_mark()) {
4566 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4570 samplepos_t new_start = copy_location->start() + f_delta;
4571 samplepos_t new_end = copy_location->end() + f_delta;
4573 if (is_start) { // start-of-range marker
4575 if (move_both || (*x).move_both) {
4576 copy_location->set_start (new_start, false, true, divisions);
4577 copy_location->set_end (new_end, false, true, divisions);
4578 } else if (new_start < copy_location->end()) {
4579 copy_location->set_start (new_start, false, true, divisions);
4580 } else if (newframe > 0) {
4581 //_editor->snap_to (next, RoundUpAlways, true);
4582 copy_location->set_end (next, false, true, divisions);
4583 copy_location->set_start (newframe, false, true, divisions);
4586 } else { // end marker
4588 if (move_both || (*x).move_both) {
4589 copy_location->set_end (new_end, divisions);
4590 copy_location->set_start (new_start, false, true, divisions);
4591 } else if (new_end > copy_location->start()) {
4592 copy_location->set_end (new_end, false, true, divisions);
4593 } else if (newframe > 0) {
4594 //_editor->snap_to (next, RoundDownAlways, true);
4595 copy_location->set_start (next, false, true, divisions);
4596 copy_location->set_end (newframe, false, true, divisions);
4601 update_item (copy_location);
4603 /* now lookup the actual GUI items used to display this
4604 * location and move them to wherever the copy of the location
4605 * is now. This means that the logic in ARDOUR::Location is
4606 * still enforced, even though we are not (yet) modifying
4607 * the real Location itself.
4610 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4613 lm->set_position (copy_location->start(), copy_location->end());
4618 assert (!_copied_locations.empty());
4620 show_verbose_cursor_time (newframe);
4621 _editor->set_snapped_cursor_position(newframe);
4625 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4627 if (!movement_occurred) {
4629 if (was_double_click()) {
4630 _editor->rename_marker (_marker);
4634 /* just a click, do nothing but finish
4635 off the selection process
4638 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4640 case Selection::Set:
4641 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4642 _editor->selection->set (_marker);
4643 _selection_changed = true;
4647 case Selection::Toggle:
4648 /* we toggle on the button release, click only */
4649 _editor->selection->toggle (_marker);
4650 _selection_changed = true;
4654 case Selection::Extend:
4655 case Selection::Add:
4659 if (_selection_changed) {
4660 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4661 _editor->commit_reversible_selection_op();
4667 _editor->_dragging_edit_point = false;
4669 XMLNode &before = _editor->session()->locations()->get_state();
4670 bool in_command = false;
4672 MarkerSelection::iterator i;
4673 CopiedLocationInfo::iterator x;
4674 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4677 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4678 x != _copied_locations.end() && i != _editor->selection->markers.end();
4681 Location * location = _editor->find_location_from_marker (*i, is_start);
4685 if (location->locked()) {
4689 _editor->begin_reversible_command ( _("move marker") );
4692 if (location->is_mark()) {
4693 location->set_start (((*x).location)->start(), false, true, divisions);
4695 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4698 if (location->is_session_range()) {
4699 _editor->session()->set_session_range_is_free (false);
4705 XMLNode &after = _editor->session()->locations()->get_state();
4706 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4707 _editor->commit_reversible_command ();
4712 MarkerDrag::aborted (bool movement_occurred)
4714 if (!movement_occurred) {
4718 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4720 /* move all markers to their original location */
4723 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4726 Location * location = _editor->find_location_from_marker (*m, is_start);
4729 (*m)->set_position (is_start ? location->start() : location->end());
4736 MarkerDrag::update_item (Location*)
4741 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4743 , _fixed_grab_x (0.0)
4744 , _fixed_grab_y (0.0)
4745 , _cumulative_x_drag (0.0)
4746 , _cumulative_y_drag (0.0)
4750 if (_zero_gain_fraction < 0.0) {
4751 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4754 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4756 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4762 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4764 Drag::start_grab (event, _editor->cursors()->fader);
4766 // start the grab at the center of the control point so
4767 // the point doesn't 'jump' to the mouse after the first drag
4768 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4769 _fixed_grab_y = _point->get_y();
4771 setup_snap_delta (MusicSample (_editor->pixel_to_sample (_fixed_grab_x), 0));
4773 float const fraction = 1 - (_point->get_y() / _point->line().height());
4774 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4776 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4778 if (!_point->can_slide ()) {
4779 _x_constrained = true;
4784 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4786 double dx = _drags->current_pointer_x() - last_pointer_x();
4787 double dy = current_pointer_y() - last_pointer_y();
4788 bool need_snap = true;
4790 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4796 /* coordinate in pixels relative to the start of the region (for region-based automation)
4797 or track (for track-based automation) */
4798 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4799 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4801 // calculate zero crossing point. back off by .01 to stay on the
4802 // positive side of zero
4803 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4805 if (_x_constrained) {
4808 if (_y_constrained) {
4812 _cumulative_x_drag = cx - _fixed_grab_x;
4813 _cumulative_y_drag = cy - _fixed_grab_y;
4817 cy = min ((double) _point->line().height(), cy);
4819 // make sure we hit zero when passing through
4820 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4824 MusicSample cx_mf (_editor->pixel_to_sample (cx) + snap_delta (event->button.state), 0);
4826 if (!_x_constrained && need_snap) {
4827 _editor->snap_to_with_modifier (cx_mf, event);
4830 cx_mf.sample -= snap_delta (event->button.state);
4831 cx_mf.sample = min (cx_mf.sample, _point->line().maximum_time() + _point->line().offset());
4833 float const fraction = 1.0 - (cy / _point->line().height());
4836 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4837 _editor->begin_reversible_command (_("automation event move"));
4838 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4840 pair<float, float> result;
4841 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_mf.sample), fraction, false, _pushing, _final_index);
4842 show_verbose_cursor_text (_point->line().get_verbose_cursor_relative_string (result.first, result.second));
4846 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4848 if (!movement_occurred) {
4851 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4852 _editor->reset_point_selection ();
4856 _point->line().end_drag (_pushing, _final_index);
4857 _editor->commit_reversible_command ();
4862 ControlPointDrag::aborted (bool)
4864 _point->line().reset ();
4868 ControlPointDrag::active (Editing::MouseMode m)
4870 if (m == Editing::MouseDraw) {
4871 /* always active in mouse draw */
4875 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4876 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4879 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4882 , _fixed_grab_x (0.0)
4883 , _fixed_grab_y (0.0)
4884 , _cumulative_y_drag (0)
4888 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4892 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4894 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4897 _item = &_line->grab_item ();
4899 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4900 origin, and ditto for y.
4903 double mx = event->button.x;
4904 double my = event->button.y;
4906 _line->grab_item().canvas_to_item (mx, my);
4908 samplecnt_t const sample_within_region = (samplecnt_t) floor (mx * _editor->samples_per_pixel);
4910 if (!_line->control_points_adjacent (sample_within_region, _before, _after)) {
4911 /* no adjacent points */
4915 Drag::start_grab (event, _editor->cursors()->fader);
4917 /* store grab start in item sample */
4918 double const bx = _line->nth (_before)->get_x();
4919 double const ax = _line->nth (_after)->get_x();
4920 double const click_ratio = (ax - mx) / (ax - bx);
4922 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4927 double fraction = 1.0 - (cy / _line->height());
4929 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4933 LineDrag::motion (GdkEvent* event, bool first_move)
4935 double dy = current_pointer_y() - last_pointer_y();
4937 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4941 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4943 _cumulative_y_drag = cy - _fixed_grab_y;
4946 cy = min ((double) _line->height(), cy);
4948 double const fraction = 1.0 - (cy / _line->height());
4952 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4954 _editor->begin_reversible_command (_("automation range move"));
4955 _line->start_drag_line (_before, _after, initial_fraction);
4958 /* we are ignoring x position for this drag, so we can just pass in anything */
4959 pair<float, float> result;
4961 result = _line->drag_motion (0, fraction, true, false, ignored);
4962 show_verbose_cursor_text (_line->get_verbose_cursor_relative_string (result.first, result.second));
4966 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4968 if (movement_occurred) {
4969 motion (event, false);
4970 _line->end_drag (false, 0);
4971 _editor->commit_reversible_command ();
4973 /* add a new control point on the line */
4975 AutomationTimeAxisView* atv;
4977 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4978 samplepos_t where = grab_sample ();
4981 double cy = _fixed_grab_y;
4983 _line->grab_item().item_to_canvas (cx, cy);
4985 atv->add_automation_event (event, where, cy, false);
4986 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4987 AudioRegionView* arv;
4989 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4990 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4997 LineDrag::aborted (bool)
5002 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
5006 _region_view_grab_x (0.0),
5007 _cumulative_x_drag (0),
5011 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
5015 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
5017 Drag::start_grab (event);
5019 _line = reinterpret_cast<Line*> (_item);
5022 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
5024 double cx = event->button.x;
5025 double cy = event->button.y;
5027 _item->parent()->canvas_to_item (cx, cy);
5029 /* store grab start in parent sample */
5030 _region_view_grab_x = cx;
5032 _before = *(float*) _item->get_data ("position");
5034 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5036 _max_x = _editor->sample_to_pixel(_arv->get_duration());
5040 FeatureLineDrag::motion (GdkEvent*, bool)
5042 double dx = _drags->current_pointer_x() - last_pointer_x();
5044 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
5046 _cumulative_x_drag += dx;
5048 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
5057 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
5059 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
5061 float *pos = new float;
5064 _line->set_data ("position", pos);
5070 FeatureLineDrag::finished (GdkEvent*, bool)
5072 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5073 _arv->update_transient(_before, _before);
5077 FeatureLineDrag::aborted (bool)
5082 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5084 , _vertical_only (false)
5086 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
5090 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5092 Drag::start_grab (event);
5093 show_verbose_cursor_time (adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
5097 RubberbandSelectDrag::motion (GdkEvent* event, bool)
5103 samplepos_t const pf = adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
5104 MusicSample grab (grab_sample (), 0);
5106 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5107 _editor->snap_to_with_modifier (grab, event, RoundNearest, SnapToGrid_Scaled);
5109 grab.sample = raw_grab_sample ();
5112 /* base start and end on initial click position */
5114 if (pf < grab.sample) {
5119 start = grab.sample;
5122 if (current_pointer_y() < grab_y()) {
5123 y1 = current_pointer_y();
5126 y2 = current_pointer_y();
5130 if (start != end || y1 != y2) {
5132 double x1 = _editor->sample_to_pixel (start);
5133 double x2 = _editor->sample_to_pixel (end);
5134 const double min_dimension = 2.0;
5136 if (_vertical_only) {
5137 /* fixed 10 pixel width */
5141 x2 = min (x1 - min_dimension, x2);
5143 x2 = max (x1 + min_dimension, x2);
5148 y2 = min (y1 - min_dimension, y2);
5150 y2 = max (y1 + min_dimension, y2);
5153 /* translate rect into item space and set */
5155 ArdourCanvas::Rect r (x1, y1, x2, y2);
5157 /* this drag is a _trackview_only == true drag, so the y1 and
5158 * y2 (computed using current_pointer_y() and grab_y()) will be
5159 * relative to the top of the trackview group). The
5160 * rubberband rect has the same parent/scroll offset as the
5161 * the trackview group, so we can use the "r" rect directly
5162 * to set the shape of the rubberband.
5165 _editor->rubberband_rect->set (r);
5166 _editor->rubberband_rect->show();
5167 _editor->rubberband_rect->raise_to_top();
5169 show_verbose_cursor_time (pf);
5171 do_select_things (event, true);
5176 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
5180 samplepos_t grab = grab_sample ();
5181 samplepos_t lpf = last_pointer_sample ();
5183 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5184 grab = raw_grab_sample ();
5185 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
5199 if (current_pointer_y() < grab_y()) {
5200 y1 = current_pointer_y();
5203 y2 = current_pointer_y();
5207 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
5211 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
5213 if (movement_occurred) {
5215 motion (event, false);
5216 do_select_things (event, false);
5222 bool do_deselect = true;
5223 MidiTimeAxisView* mtv;
5225 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
5227 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
5228 /* nothing selected */
5229 add_midi_region (mtv, true);
5230 do_deselect = false;
5234 /* do not deselect if Primary or Tertiary (toggle-select or
5235 * extend-select are pressed.
5238 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
5239 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
5246 _editor->rubberband_rect->hide();
5250 RubberbandSelectDrag::aborted (bool)
5252 _editor->rubberband_rect->hide ();
5255 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
5256 : RegionDrag (e, i, p, v)
5258 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
5262 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5264 Drag::start_grab (event, cursor);
5266 _editor->get_selection().add (_primary);
5268 MusicSample where (_primary->region()->position(), 0);
5269 setup_snap_delta (where);
5271 show_verbose_cursor_duration (where.sample, adjusted_current_sample (event), 0);
5275 TimeFXDrag::motion (GdkEvent* event, bool)
5277 RegionView* rv = _primary;
5278 StreamView* cv = rv->get_time_axis_view().view ();
5279 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
5280 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
5281 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
5282 MusicSample pf (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
5284 _editor->snap_to_with_modifier (pf, event);
5285 pf.sample -= snap_delta (event->button.state);
5287 if (pf.sample > rv->region()->position()) {
5288 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf.sample, layers, layer);
5291 show_verbose_cursor_duration (_primary->region()->position(), pf.sample, 0);
5295 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
5297 /* this may have been a single click, no drag. We still want the dialog
5298 to show up in that case, so that the user can manually edit the
5299 parameters for the timestretch.
5302 float fraction = 1.0;
5304 if (movement_occurred) {
5306 motion (event, false);
5308 _primary->get_time_axis_view().hide_timestretch ();
5310 samplepos_t adjusted_sample_pos = adjusted_current_sample (event);
5312 if (adjusted_sample_pos < _primary->region()->position()) {
5313 /* backwards drag of the left edge - not usable */
5317 samplecnt_t newlen = adjusted_sample_pos - _primary->region()->position();
5319 fraction = (double) newlen / (double) _primary->region()->length();
5321 #ifndef USE_RUBBERBAND
5322 // Soundtouch uses fraction / 100 instead of normal (/ 1)
5323 if (_primary->region()->data_type() == DataType::AUDIO) {
5324 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
5329 if (!_editor->get_selection().regions.empty()) {
5330 /* primary will already be included in the selection, and edit
5331 group shared editing will propagate selection across
5332 equivalent regions, so just use the current region
5336 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
5337 error << _("An error occurred while executing time stretch operation") << endmsg;
5343 TimeFXDrag::aborted (bool)
5345 _primary->get_time_axis_view().hide_timestretch ();
5348 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5351 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5355 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5357 Drag::start_grab (event);
5361 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5363 _editor->scrub (adjusted_current_sample (0, false), _drags->current_pointer_x ());
5367 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5369 if (movement_occurred && _editor->session()) {
5370 /* make sure we stop */
5371 _editor->session()->request_transport_speed (0.0);
5376 ScrubDrag::aborted (bool)
5381 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5385 , _track_selection_at_start (e)
5386 , _time_selection_at_start (!_editor->get_selection().time.empty())
5388 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5390 if (_time_selection_at_start) {
5391 start_at_start = _editor->get_selection().time.start();
5392 end_at_start = _editor->get_selection().time.end_sample();
5397 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5399 if (_editor->session() == 0) {
5403 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5405 switch (_operation) {
5406 case CreateSelection:
5407 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5412 cursor = _editor->cursors()->selector;
5413 Drag::start_grab (event, cursor);
5416 case SelectionStartTrim:
5417 if (_editor->clicked_axisview) {
5418 _editor->clicked_axisview->order_selection_trims (_item, true);
5420 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5423 case SelectionEndTrim:
5424 if (_editor->clicked_axisview) {
5425 _editor->clicked_axisview->order_selection_trims (_item, false);
5427 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5431 Drag::start_grab (event, cursor);
5434 case SelectionExtend:
5435 Drag::start_grab (event, cursor);
5439 if (_operation == SelectionMove) {
5440 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5442 show_verbose_cursor_time (adjusted_current_sample (event));
5447 SelectionDrag::setup_pointer_sample_offset ()
5449 switch (_operation) {
5450 case CreateSelection:
5451 _pointer_sample_offset = 0;
5454 case SelectionStartTrim:
5456 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].start;
5459 case SelectionEndTrim:
5460 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].end;
5463 case SelectionExtend:
5469 SelectionDrag::motion (GdkEvent* event, bool first_move)
5471 samplepos_t start = 0;
5472 samplepos_t end = 0;
5473 samplecnt_t length = 0;
5474 samplecnt_t distance = 0;
5475 MusicSample start_mf (0, 0);
5476 samplepos_t const pending_position = adjusted_current_sample (event);
5478 if (_operation != CreateSelection && pending_position == last_pointer_sample()) {
5483 _track_selection_at_start = _editor->selection->tracks;
5486 switch (_operation) {
5487 case CreateSelection:
5489 MusicSample grab (grab_sample (), 0);
5491 grab.sample = adjusted_current_sample (event, false);
5492 if (grab.sample < pending_position) {
5493 _editor->snap_to (grab, RoundDownMaybe);
5495 _editor->snap_to (grab, RoundUpMaybe);
5499 if (pending_position < grab.sample) {
5500 start = pending_position;
5503 end = pending_position;
5504 start = grab.sample;
5507 /* first drag: Either add to the selection
5508 or create a new selection
5515 /* adding to the selection */
5516 _editor->set_selected_track_as_side_effect (Selection::Add);
5517 _editor->clicked_selection = _editor->selection->add (start, end);
5524 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5525 _editor->set_selected_track_as_side_effect (Selection::Set);
5528 _editor->clicked_selection = _editor->selection->set (start, end);
5532 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5533 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5534 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5536 _editor->selection->add (atest);
5540 /* select all tracks within the rectangle that we've marked out so far */
5541 TrackViewList new_selection;
5542 TrackViewList& all_tracks (_editor->track_views);
5544 ArdourCanvas::Coord const top = grab_y();
5545 ArdourCanvas::Coord const bottom = current_pointer_y();
5547 if (top >= 0 && bottom >= 0) {
5549 //first, find the tracks that are covered in the y range selection
5550 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5551 if ((*i)->covered_by_y_range (top, bottom)) {
5552 new_selection.push_back (*i);
5556 //now compare our list with the current selection, and add as necessary
5557 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5558 TrackViewList tracks_to_add;
5559 TrackViewList tracks_to_remove;
5560 vector<RouteGroup*> selected_route_groups;
5563 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5564 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5565 tracks_to_remove.push_back (*i);
5567 RouteGroup* rg = (*i)->route_group();
5568 if (rg && rg->is_active() && rg->is_select()) {
5569 selected_route_groups.push_back (rg);
5575 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5576 if (!_editor->selection->tracks.contains (*i)) {
5577 tracks_to_add.push_back (*i);
5578 RouteGroup* rg = (*i)->route_group();
5580 if (rg && rg->is_active() && rg->is_select()) {
5581 selected_route_groups.push_back (rg);
5586 _editor->selection->add (tracks_to_add);
5588 if (!tracks_to_remove.empty()) {
5590 /* check all these to-be-removed tracks against the
5591 * possibility that they are selected by being
5592 * in the same group as an approved track.
5595 for (TrackViewList::iterator i = tracks_to_remove.begin(); i != tracks_to_remove.end(); ) {
5596 RouteGroup* rg = (*i)->route_group();
5598 if (rg && find (selected_route_groups.begin(), selected_route_groups.end(), rg) != selected_route_groups.end()) {
5599 i = tracks_to_remove.erase (i);
5605 /* remove whatever is left */
5607 _editor->selection->remove (tracks_to_remove);
5613 case SelectionStartTrim:
5615 end = _editor->selection->time[_editor->clicked_selection].end;
5617 if (pending_position > end) {
5620 start = pending_position;
5624 case SelectionEndTrim:
5626 start = _editor->selection->time[_editor->clicked_selection].start;
5628 if (pending_position < start) {
5631 end = pending_position;
5638 start = _editor->selection->time[_editor->clicked_selection].start;
5639 end = _editor->selection->time[_editor->clicked_selection].end;
5641 length = end - start;
5642 distance = pending_position - start;
5643 start = pending_position;
5645 start_mf.sample = start;
5646 _editor->snap_to (start_mf);
5648 end = start_mf.sample + length;
5652 case SelectionExtend:
5657 switch (_operation) {
5659 if (_time_selection_at_start) {
5660 _editor->selection->move_time (distance);
5664 _editor->selection->replace (_editor->clicked_selection, start, end);
5668 if (_operation == SelectionMove) {
5669 show_verbose_cursor_time(start);
5671 show_verbose_cursor_time(pending_position);
5676 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5678 Session* s = _editor->session();
5680 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5681 if (movement_occurred) {
5682 motion (event, false);
5683 /* XXX this is not object-oriented programming at all. ick */
5684 if (_editor->selection->time.consolidate()) {
5685 _editor->selection->TimeChanged ();
5688 /* XXX what if its a music time selection? */
5691 //if Follow Edits is on, maybe try to follow the range selection ... also consider range-audition mode
5692 if ( !s->config.get_external_sync() && s->transport_rolling() ) {
5693 if ( s->solo_selection_active() ) {
5694 _editor->play_solo_selection(true); //play the newly selected range, and move solos to match
5695 } else if ( UIConfiguration::instance().get_follow_edits() && s->get_play_range() ) { //already rolling a selected range
5696 s->request_play_range (&_editor->selection->time, true); //play the newly selected range
5698 } else if ( !s->transport_rolling() && UIConfiguration::instance().get_follow_edits() ) {
5699 s->request_locate (_editor->get_selection().time.start());
5702 if (_editor->get_selection().time.length() != 0) {
5703 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_sample());
5705 s->clear_range_selection ();
5710 /* just a click, no pointer movement.
5713 if (was_double_click()) {
5714 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5715 _editor->temporal_zoom_selection (Both);
5720 if (_operation == SelectionExtend) {
5721 if (_time_selection_at_start) {
5722 samplepos_t pos = adjusted_current_sample (event, false);
5723 samplepos_t start = min (pos, start_at_start);
5724 samplepos_t end = max (pos, end_at_start);
5725 _editor->selection->set (start, end);
5728 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5729 if (_editor->clicked_selection) {
5730 _editor->selection->remove (_editor->clicked_selection);
5733 if (!_editor->clicked_selection) {
5734 _editor->selection->clear_time();
5739 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5740 _editor->selection->set (_editor->clicked_axisview);
5743 if (s && s->get_play_range () && s->transport_rolling()) {
5744 s->request_stop (false, false);
5749 _editor->stop_canvas_autoscroll ();
5750 _editor->clicked_selection = 0;
5751 _editor->commit_reversible_selection_op ();
5755 SelectionDrag::aborted (bool)
5760 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5761 : Drag (e, i, false),
5765 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5767 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5768 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5769 physical_screen_height (_editor->current_toplevel()->get_window())));
5770 _drag_rect->hide ();
5772 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5773 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5776 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5778 /* normal canvas items will be cleaned up when their parent group is deleted. But
5779 this item is created as the child of a long-lived parent group, and so we
5780 need to explicitly delete it.
5786 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5788 if (_editor->session() == 0) {
5792 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5794 if (!_editor->temp_location) {
5795 _editor->temp_location = new Location (*_editor->session());
5798 switch (_operation) {
5799 case CreateSkipMarker:
5800 case CreateRangeMarker:
5801 case CreateTransportMarker:
5802 case CreateCDMarker:
5804 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5809 cursor = _editor->cursors()->selector;
5813 Drag::start_grab (event, cursor);
5815 show_verbose_cursor_time (adjusted_current_sample (event));
5819 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5821 samplepos_t start = 0;
5822 samplepos_t end = 0;
5823 ArdourCanvas::Rectangle *crect;
5825 switch (_operation) {
5826 case CreateSkipMarker:
5827 crect = _editor->range_bar_drag_rect;
5829 case CreateRangeMarker:
5830 crect = _editor->range_bar_drag_rect;
5832 case CreateTransportMarker:
5833 crect = _editor->transport_bar_drag_rect;
5835 case CreateCDMarker:
5836 crect = _editor->cd_marker_bar_drag_rect;
5839 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5844 samplepos_t const pf = adjusted_current_sample (event);
5846 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5847 MusicSample grab (grab_sample (), 0);
5848 _editor->snap_to (grab);
5850 if (pf < grab_sample()) {
5855 start = grab.sample;
5858 /* first drag: Either add to the selection
5859 or create a new selection.
5864 _editor->temp_location->set (start, end);
5868 update_item (_editor->temp_location);
5870 //_drag_rect->raise_to_top();
5876 _editor->temp_location->set (start, end);
5878 double x1 = _editor->sample_to_pixel (start);
5879 double x2 = _editor->sample_to_pixel (end);
5883 update_item (_editor->temp_location);
5886 show_verbose_cursor_time (pf);
5891 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5893 Location * newloc = 0;
5897 if (movement_occurred) {
5898 motion (event, false);
5901 switch (_operation) {
5902 case CreateSkipMarker:
5903 case CreateRangeMarker:
5904 case CreateCDMarker:
5906 XMLNode &before = _editor->session()->locations()->get_state();
5907 if (_operation == CreateSkipMarker) {
5908 _editor->begin_reversible_command (_("new skip marker"));
5909 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5910 flags = Location::IsRangeMarker | Location::IsSkip;
5911 _editor->range_bar_drag_rect->hide();
5912 } else if (_operation == CreateCDMarker) {
5913 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5914 _editor->begin_reversible_command (_("new CD marker"));
5915 flags = Location::IsRangeMarker | Location::IsCDMarker;
5916 _editor->cd_marker_bar_drag_rect->hide();
5918 _editor->begin_reversible_command (_("new skip marker"));
5919 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5920 flags = Location::IsRangeMarker;
5921 _editor->range_bar_drag_rect->hide();
5923 newloc = new Location (
5924 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5925 , _editor->get_grid_music_divisions (event->button.state));
5927 _editor->session()->locations()->add (newloc, true);
5928 XMLNode &after = _editor->session()->locations()->get_state();
5929 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5930 _editor->commit_reversible_command ();
5934 case CreateTransportMarker:
5935 // popup menu to pick loop or punch
5936 _editor->new_transport_marker_context_menu (&event->button, _item);
5942 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5944 if (_operation == CreateTransportMarker) {
5946 /* didn't drag, so just locate */
5948 _editor->session()->request_locate (grab_sample(), _editor->session()->transport_rolling());
5950 } else if (_operation == CreateCDMarker) {
5952 /* didn't drag, but mark is already created so do
5955 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5960 _editor->session()->locations()->marks_either_side (grab_sample(), start, end);
5962 if (end == max_samplepos) {
5963 end = _editor->session()->current_end_sample ();
5966 if (start == max_samplepos) {
5967 start = _editor->session()->current_start_sample ();
5970 switch (_editor->mouse_mode) {
5972 /* find the two markers on either side and then make the selection from it */
5973 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5977 /* find the two markers on either side of the click and make the range out of it */
5978 _editor->selection->set (start, end);
5987 _editor->stop_canvas_autoscroll ();
5991 RangeMarkerBarDrag::aborted (bool movement_occurred)
5993 if (movement_occurred) {
5994 _drag_rect->hide ();
5999 RangeMarkerBarDrag::update_item (Location* location)
6001 double const x1 = _editor->sample_to_pixel (location->start());
6002 double const x2 = _editor->sample_to_pixel (location->end());
6004 _drag_rect->set_x0 (x1);
6005 _drag_rect->set_x1 (x2);
6008 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
6010 , _cumulative_dx (0)
6011 , _cumulative_dy (0)
6013 , _was_selected (false)
6016 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
6018 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
6020 _region = &_primary->region_view ();
6021 _note_height = _region->midi_stream_view()->note_height ();
6025 NoteDrag::setup_pointer_sample_offset ()
6027 _pointer_sample_offset = raw_grab_sample()
6028 - _editor->session()->tempo_map().sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6032 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
6034 Drag::start_grab (event);
6036 if (ArdourKeyboard::indicates_copy (event->button.state)) {
6042 setup_snap_delta (MusicSample (_region->source_beats_to_absolute_samples (_primary->note()->time ()), 0));
6044 if (!(_was_selected = _primary->selected())) {
6046 /* tertiary-click means extend selection - we'll do that on button release,
6047 so don't add it here, because otherwise we make it hard to figure
6048 out the "extend-to" range.
6051 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
6054 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
6057 _region->note_selected (_primary, true);
6059 _editor->get_selection().clear_points();
6060 _region->unique_select (_primary);
6066 /** @return Current total drag x change in quarter notes */
6068 NoteDrag::total_dx (GdkEvent * event) const
6070 if (_x_constrained) {
6074 TempoMap& map (_editor->session()->tempo_map());
6077 sampleoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
6079 /* primary note time */
6080 sampleoffset_t const n = map.sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6082 /* primary note time in quarter notes */
6083 double const n_qn = _region->session_relative_qn (_primary->note()->time().to_double());
6085 /* new time of the primary note in session samples */
6086 sampleoffset_t st = n + dx + snap_delta (event->button.state);
6088 /* possibly snap and return corresponding delta in quarter notes */
6089 MusicSample snap (st, 0);
6090 _editor->snap_to_with_modifier (snap, event, RoundNearest, SnapToGrid_Unscaled);
6091 double ret = map.exact_qn_at_sample (snap.sample, snap.division) - n_qn - snap_delta_music (event->button.state);
6093 /* prevent the earliest note being dragged earlier than the region's start position */
6094 if (_earliest + ret < _region->midi_region()->start_beats()) {
6095 ret -= (_earliest + ret) - _region->midi_region()->start_beats();
6101 /** @return Current total drag y change in note number */
6103 NoteDrag::total_dy () const
6105 if (_y_constrained) {
6109 double const y = _region->midi_view()->y_position ();
6110 /* new current note */
6111 uint8_t n = _region->y_to_note (current_pointer_y () - y);
6113 MidiStreamView* msv = _region->midi_stream_view ();
6114 n = max (msv->lowest_note(), n);
6115 n = min (msv->highest_note(), n);
6116 /* and work out delta */
6117 return n - _region->y_to_note (grab_y() - y);
6121 NoteDrag::motion (GdkEvent * event, bool first_move)
6124 _earliest = _region->earliest_in_selection().to_double();
6126 /* make copies of all the selected notes */
6127 _primary = _region->copy_selection (_primary);
6131 /* Total change in x and y since the start of the drag */
6132 double const dx_qn = total_dx (event);
6133 int8_t const dy = total_dy ();
6135 /* Now work out what we have to do to the note canvas items to set this new drag delta */
6136 double const tdx = _x_constrained ? 0 : dx_qn - _cumulative_dx;
6137 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
6140 _cumulative_dx = dx_qn;
6141 _cumulative_dy += tdy;
6143 int8_t note_delta = total_dy();
6147 _region->move_copies (dx_qn, tdy, note_delta);
6149 _region->move_selection (dx_qn, tdy, note_delta);
6152 /* the new note value may be the same as the old one, but we
6153 * don't know what that means because the selection may have
6154 * involved more than one note and we might be doing something
6155 * odd with them. so show the note value anyway, always.
6158 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
6160 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
6162 _editor->set_snapped_cursor_position( _region->source_beats_to_absolute_samples(_primary->note()->time()) );
6168 NoteDrag::finished (GdkEvent* ev, bool moved)
6171 /* no motion - select note */
6173 if (_editor->current_mouse_mode() == Editing::MouseContent ||
6174 _editor->current_mouse_mode() == Editing::MouseDraw) {
6176 bool changed = false;
6178 if (_was_selected) {
6179 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6181 _region->note_deselected (_primary);
6184 _editor->get_selection().clear_points();
6185 _region->unique_select (_primary);
6189 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
6190 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6192 if (!extend && !add && _region->selection_size() > 1) {
6193 _editor->get_selection().clear_points();
6194 _region->unique_select (_primary);
6196 } else if (extend) {
6197 _region->note_selected (_primary, true, true);
6200 /* it was added during button press */
6207 _editor->begin_reversible_selection_op(X_("Select Note Release"));
6208 _editor->commit_reversible_selection_op();
6212 _region->note_dropped (_primary, total_dx (ev), total_dy(), _copy);
6217 NoteDrag::aborted (bool)
6222 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
6223 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
6224 : Drag (editor, atv->base_item ())
6226 , _y_origin (atv->y_position())
6227 , _y_height (atv->effective_height()) // or atv->lines()->front()->height() ?!
6228 , _nothing_to_drag (false)
6230 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6231 setup (atv->lines ());
6234 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
6235 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, list<RegionView*> const & v, list<AudioRange> const & r, double y_origin, double y_height)
6236 : Drag (editor, v.front()->get_canvas_group ())
6238 , _y_origin (y_origin)
6239 , _y_height (y_height)
6240 , _nothing_to_drag (false)
6243 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6245 list<boost::shared_ptr<AutomationLine> > lines;
6247 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
6248 if (AudioRegionView* audio_view = dynamic_cast<AudioRegionView*>(*i)) {
6249 lines.push_back (audio_view->get_gain_line ());
6250 } else if (AutomationRegionView* automation_view = dynamic_cast<AutomationRegionView*>(*i)) {
6251 lines.push_back (automation_view->line ());
6254 error << _("Automation range drag created for invalid region type") << endmsg;
6260 /** @param lines AutomationLines to drag.
6261 * @param offset Offset from the session start to the points in the AutomationLines.
6264 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
6266 /* find the lines that overlap the ranges being dragged */
6267 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
6268 while (i != lines.end ()) {
6269 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
6272 pair<samplepos_t, samplepos_t> r = (*i)->get_point_x_range ();
6274 //need a special detection for automation lanes (not region gain line)
6275 //TODO: if we implement automation regions, this check can probably be removed
6276 AudioRegionGainLine *argl = dynamic_cast<AudioRegionGainLine*> ((*i).get());
6278 //in automation lanes, the EFFECTIVE range should be considered 0->max_samplepos (even if there is no line)
6280 r.second = max_samplepos;
6283 /* check this range against all the AudioRanges that we are using */
6284 list<AudioRange>::const_iterator k = _ranges.begin ();
6285 while (k != _ranges.end()) {
6286 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
6292 /* add it to our list if it overlaps at all */
6293 if (k != _ranges.end()) {
6298 _lines.push_back (n);
6304 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
6308 AutomationRangeDrag::y_fraction (double global_y) const
6310 return 1.0 - ((global_y - _y_origin) / _y_height);
6314 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
6316 const double v = list->eval(x);
6317 return _integral ? rint(v) : v;
6321 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6323 Drag::start_grab (event, cursor);
6325 /* Get line states before we start changing things */
6326 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6327 i->state = &i->line->get_state ();
6330 if (_ranges.empty()) {
6332 /* No selected time ranges: drag all points */
6333 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6334 uint32_t const N = i->line->npoints ();
6335 for (uint32_t j = 0; j < N; ++j) {
6336 i->points.push_back (i->line->nth (j));
6342 if (_nothing_to_drag) {
6348 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
6350 if (_nothing_to_drag && !first_move) {
6355 _editor->begin_reversible_command (_("automation range move"));
6357 if (!_ranges.empty()) {
6359 /* add guard points */
6360 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
6362 samplecnt_t const half = (i->start + i->end) / 2;
6364 for (list<Line>::iterator j = _lines.begin(); j != _lines.end(); ++j) {
6365 if (j->range.first > i->start || j->range.second < i->start) {
6369 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6371 /* j is the line that this audio range starts in; fade into it;
6372 * 64 samples length plucked out of thin air.
6375 samplepos_t a = i->start + 64;
6380 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
6381 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
6383 XMLNode &before = the_list->get_state();
6384 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6385 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6387 if (add_p || add_q) {
6388 _editor->session()->add_command (
6389 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6393 /* same thing for the end */
6394 for (list<Line>::iterator j = _lines.begin(); j != _lines.end(); ++j) {
6396 if (j->range.first > i->end || j->range.second < i->end) {
6400 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6402 /* j is the line that this audio range starts in; fade out of it;
6403 * 64 samples length plucked out of thin air.
6406 samplepos_t b = i->end - 64;
6411 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6412 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6414 XMLNode &before = the_list->get_state();
6415 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6416 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6418 if (add_p || add_q) {
6419 _editor->session()->add_command (
6420 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6425 _nothing_to_drag = true;
6427 /* Find all the points that should be dragged and put them in the relevant
6428 * points lists in the Line structs.
6430 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6432 uint32_t const N = i->line->npoints ();
6433 for (uint32_t j = 0; j < N; ++j) {
6435 /* here's a control point on this line */
6436 ControlPoint* p = i->line->nth (j);
6437 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6439 /* see if it's inside a range */
6440 list<AudioRange>::const_iterator k = _ranges.begin ();
6441 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6445 if (k != _ranges.end()) {
6446 /* dragging this point */
6447 _nothing_to_drag = false;
6448 i->points.push_back (p);
6454 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6455 i->line->start_drag_multiple (i->points, y_fraction (current_pointer_y()), i->state);
6459 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6460 float const f = y_fraction (current_pointer_y());
6461 /* we are ignoring x position for this drag, so we can just pass in anything */
6462 pair<float, float> result;
6464 result = l->line->drag_motion (0, f, true, false, ignored);
6465 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (result.first, result.second));
6470 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6472 if (_nothing_to_drag || !motion_occurred) {
6476 motion (event, false);
6477 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6478 i->line->end_drag (false, 0);
6481 _editor->commit_reversible_command ();
6485 AutomationRangeDrag::aborted (bool)
6487 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6492 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6494 , initial_time_axis_view (itav)
6496 /* note that time_axis_view may be null if the regionview was created
6497 * as part of a copy operation.
6499 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6500 layer = v->region()->layer ();
6501 initial_y = v->get_canvas_group()->position().y;
6502 initial_playlist = v->region()->playlist ();
6503 initial_position = v->region()->position ();
6504 initial_end = v->region()->position () + v->region()->length ();
6507 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6508 : Drag (e, i->canvas_item ())
6511 , _cumulative_dx (0)
6513 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6514 _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time()),
6519 PatchChangeDrag::motion (GdkEvent* ev, bool)
6521 samplepos_t f = adjusted_current_sample (ev);
6522 boost::shared_ptr<Region> r = _region_view->region ();
6523 f = max (f, r->position ());
6524 f = min (f, r->last_sample ());
6526 samplecnt_t const dxf = f - grab_sample(); // permitted dx in samples
6527 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6528 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6529 _cumulative_dx = dxu;
6533 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6535 if (!movement_occurred) {
6536 if (was_double_click()) {
6537 _region_view->edit_patch_change (_patch_change);
6542 boost::shared_ptr<Region> r (_region_view->region ());
6543 samplepos_t f = adjusted_current_sample (ev);
6544 f = max (f, r->position ());
6545 f = min (f, r->last_sample ());
6547 _region_view->move_patch_change (
6549 _region_view->region_samples_to_region_beats (f - (r->position() - r->start()))
6554 PatchChangeDrag::aborted (bool)
6556 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6560 PatchChangeDrag::setup_pointer_sample_offset ()
6562 boost::shared_ptr<Region> region = _region_view->region ();
6563 _pointer_sample_offset = raw_grab_sample() - _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time());
6566 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6567 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6574 MidiRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6576 _region_view->update_drag_selection (
6578 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6582 MidiRubberbandSelectDrag::deselect_things ()
6587 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6588 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6591 _vertical_only = true;
6595 MidiVerticalSelectDrag::select_things (int button_state, samplepos_t /*x1*/, samplepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6597 double const y = _region_view->midi_view()->y_position ();
6599 y1 = max (0.0, y1 - y);
6600 y2 = max (0.0, y2 - y);
6602 _region_view->update_vertical_drag_selection (
6605 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6610 MidiVerticalSelectDrag::deselect_things ()
6615 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6616 : RubberbandSelectDrag (e, i)
6622 EditorRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool drag_in_progress)
6624 if (drag_in_progress) {
6625 /* We just want to select things at the end of the drag, not during it */
6629 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6631 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6633 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6635 _editor->commit_reversible_selection_op ();
6639 EditorRubberbandSelectDrag::deselect_things ()
6641 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6643 _editor->selection->clear_tracks();
6644 _editor->selection->clear_regions();
6645 _editor->selection->clear_points ();
6646 _editor->selection->clear_lines ();
6647 _editor->selection->clear_midi_notes ();
6649 _editor->commit_reversible_selection_op();
6652 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6657 _note[0] = _note[1] = 0;
6660 NoteCreateDrag::~NoteCreateDrag ()
6666 NoteCreateDrag::grid_samples (samplepos_t t) const
6669 const Temporal::Beats grid_beats = _region_view->get_grid_beats (t);
6670 const Temporal::Beats t_beats = _region_view->region_samples_to_region_beats (t);
6672 return _region_view->region_beats_to_region_samples (t_beats + grid_beats)
6673 - _region_view->region_beats_to_region_samples (t_beats);
6677 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6679 Drag::start_grab (event, cursor);
6681 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6682 TempoMap& map (_editor->session()->tempo_map());
6684 const samplepos_t pf = _drags->current_pointer_sample ();
6685 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6687 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6689 double eqaf = map.exact_qn_at_sample (pf, divisions);
6691 if (divisions != 0) {
6693 const double qaf = map.quarter_note_at_sample (pf);
6695 /* Hack so that we always snap to the note that we are over, instead of snapping
6696 to the next one if we're more than halfway through the one we're over.
6699 const double rem = eqaf - qaf;
6701 eqaf -= grid_beats.to_double();
6705 _note[0] = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6706 /* minimum initial length is grid beats */
6707 _note[1] = map.sample_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6709 double const x0 = _editor->sample_to_pixel (_note[0]);
6710 double const x1 = _editor->sample_to_pixel (_note[1]);
6711 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6713 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6714 _drag_rect->set_outline_all ();
6715 _drag_rect->set_outline_color (0xffffff99);
6716 _drag_rect->set_fill_color (0xffffff66);
6720 NoteCreateDrag::motion (GdkEvent* event, bool)
6722 TempoMap& map (_editor->session()->tempo_map());
6723 const samplepos_t pf = _drags->current_pointer_sample ();
6724 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6725 double eqaf = map.exact_qn_at_sample (pf, divisions);
6727 if (divisions != 0) {
6729 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6731 const double qaf = map.quarter_note_at_sample (pf);
6732 /* Hack so that we always snap to the note that we are over, instead of snapping
6733 to the next one if we're more than halfway through the one we're over.
6736 const double rem = eqaf - qaf;
6738 eqaf -= grid_beats.to_double();
6741 eqaf += grid_beats.to_double();
6743 _note[1] = max ((samplepos_t)0, map.sample_at_quarter_note (eqaf) - _region_view->region()->position ());
6745 double const x0 = _editor->sample_to_pixel (_note[0]);
6746 double const x1 = _editor->sample_to_pixel (_note[1]);
6747 _drag_rect->set_x0 (std::min(x0, x1));
6748 _drag_rect->set_x1 (std::max(x0, x1));
6752 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6754 /* we create a note even if there was no movement */
6755 samplepos_t const start = min (_note[0], _note[1]);
6756 samplepos_t const start_sess_rel = start + _region_view->region()->position();
6757 samplecnt_t length = max (_editor->pixel_to_sample (1.0), (samplecnt_t) fabs ((double)(_note[0] - _note[1])));
6758 samplecnt_t const g = grid_samples (start_sess_rel);
6760 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6764 TempoMap& map (_editor->session()->tempo_map());
6765 const double qn_length = map.quarter_notes_between_samples (start_sess_rel, start_sess_rel + length);
6766 Temporal::Beats qn_length_beats = max (Temporal::Beats::ticks(1), Temporal::Beats (qn_length));
6768 _editor->begin_reversible_command (_("Create Note"));
6769 _region_view->clear_editor_note_selection();
6770 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6771 _editor->commit_reversible_command ();
6775 NoteCreateDrag::y_to_region (double y) const
6778 _region_view->get_canvas_group()->canvas_to_item (x, y);
6783 NoteCreateDrag::aborted (bool)
6788 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6796 HitCreateDrag::~HitCreateDrag ()
6801 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6803 Drag::start_grab (event, cursor);
6805 TempoMap& map (_editor->session()->tempo_map());
6807 _y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6809 const samplepos_t pf = _drags->current_pointer_sample ();
6810 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6812 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6814 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6816 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6820 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6821 Temporal::Beats length = _region_view->get_grid_beats (pf);
6823 _editor->begin_reversible_command (_("Create Hit"));
6824 _region_view->clear_editor_note_selection();
6825 _region_view->create_note_at (start, _y, length, event->button.state, false);
6831 HitCreateDrag::motion (GdkEvent* event, bool)
6833 TempoMap& map (_editor->session()->tempo_map());
6835 const samplepos_t pf = _drags->current_pointer_sample ();
6836 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6838 if (divisions == 0) {
6842 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6843 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position ();
6845 if (_last_pos == start) {
6849 Temporal::Beats length = _region_view->get_grid_beats (pf);
6851 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6853 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6857 _region_view->create_note_at (start, _y, length, event->button.state, false);
6863 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6865 _editor->commit_reversible_command ();
6870 HitCreateDrag::y_to_region (double y) const
6873 _region_view->get_canvas_group()->canvas_to_item (x, y);
6878 HitCreateDrag::aborted (bool)
6883 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6888 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6892 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6894 Drag::start_grab (event, cursor);
6898 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6904 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6907 distance = _drags->current_pointer_x() - grab_x();
6908 len = ar->fade_in()->back()->when;
6910 distance = grab_x() - _drags->current_pointer_x();
6911 len = ar->fade_out()->back()->when;
6914 /* how long should it be ? */
6916 new_length = len + _editor->pixel_to_sample (distance);
6918 /* now check with the region that this is legal */
6920 new_length = ar->verify_xfade_bounds (new_length, start);
6923 arv->reset_fade_in_shape_width (ar, new_length);
6925 arv->reset_fade_out_shape_width (ar, new_length);
6930 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6936 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6939 distance = _drags->current_pointer_x() - grab_x();
6940 len = ar->fade_in()->back()->when;
6942 distance = grab_x() - _drags->current_pointer_x();
6943 len = ar->fade_out()->back()->when;
6946 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6948 _editor->begin_reversible_command ("xfade trim");
6949 ar->playlist()->clear_owned_changes ();
6952 ar->set_fade_in_length (new_length);
6954 ar->set_fade_out_length (new_length);
6957 /* Adjusting the xfade may affect other regions in the playlist, so we need
6958 to get undo Commands from the whole playlist rather than just the
6962 vector<Command*> cmds;
6963 ar->playlist()->rdiff (cmds);
6964 _editor->session()->add_commands (cmds);
6965 _editor->commit_reversible_command ();
6970 CrossfadeEdgeDrag::aborted (bool)
6973 // arv->redraw_start_xfade ();
6975 // arv->redraw_end_xfade ();
6979 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, samplepos_t pos)
6980 : Drag (e, item, true)
6984 RegionCutDrag::~RegionCutDrag ()
6989 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6991 Drag::start_grab (event, c);
6992 motion (event, false);
6996 RegionCutDrag::motion (GdkEvent* event, bool)
7001 RegionCutDrag::finished (GdkEvent* event, bool)
7003 _editor->get_track_canvas()->canvas()->re_enter();
7006 MusicSample pos (_drags->current_pointer_sample(), 0);
7007 _editor->snap_to_with_modifier (pos, event);
7009 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos.sample);
7015 _editor->split_regions_at (pos, rs);
7019 RegionCutDrag::aborted (bool)