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"
78 #include "video_timeline.h"
81 using namespace ARDOUR;
84 using namespace Gtkmm2ext;
85 using namespace Editing;
86 using namespace ArdourCanvas;
88 using Gtkmm2ext::Keyboard;
90 double ControlPointDrag::_zero_gain_fraction = -1.0;
92 DragManager::DragManager (Editor* e)
95 , _current_pointer_x (0.0)
96 , _current_pointer_y (0.0)
97 , _current_pointer_sample (0)
98 , _old_follow_playhead (false)
102 DragManager::~DragManager ()
107 /** Call abort for each active drag */
109 DragManager::abort ()
113 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
118 if (!_drags.empty ()) {
119 _editor->set_follow_playhead (_old_follow_playhead, false);
123 _editor->abort_reversible_command();
129 DragManager::add (Drag* d)
131 d->set_manager (this);
132 _drags.push_back (d);
136 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
138 d->set_manager (this);
139 _drags.push_back (d);
144 DragManager::preview_video () const {
145 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
146 if ((*i)->preview_video ()) {
154 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
156 /* Prevent follow playhead during the drag to be nice to the user */
157 _old_follow_playhead = _editor->follow_playhead ();
158 _editor->set_follow_playhead (false);
160 _current_pointer_sample = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
162 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
163 (*i)->start_grab (e, c);
167 /** Call end_grab for each active drag.
168 * @return true if any drag reported movement having occurred.
171 DragManager::end_grab (GdkEvent* e)
176 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
177 bool const t = (*i)->end_grab (e);
188 _editor->set_follow_playhead (_old_follow_playhead, false);
194 DragManager::mark_double_click ()
196 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
197 (*i)->set_double_click (true);
202 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
206 /* calling this implies that we expect the event to have canvas
209 * Can we guarantee that this is true?
212 _current_pointer_sample = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
214 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
215 bool const t = (*i)->motion_handler (e, from_autoscroll);
216 /* run all handlers; return true if at least one of them
217 returns true (indicating that the event has been handled).
229 DragManager::have_item (ArdourCanvas::Item* i) const
231 list<Drag*>::const_iterator j = _drags.begin ();
232 while (j != _drags.end() && (*j)->item () != i) {
236 return j != _drags.end ();
239 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
243 , _pointer_sample_offset (0)
244 , _video_sample_offset (0)
245 , _preview_video (false)
246 , _x_constrained (false)
247 , _y_constrained (false)
248 , _was_rolling (false)
249 , _trackview_only (trackview_only)
250 , _move_threshold_passed (false)
251 , _starting_point_passed (false)
252 , _initially_vertical (false)
253 , _was_double_click (false)
256 , _last_pointer_x (0.0)
257 , _last_pointer_y (0.0)
258 , _raw_grab_sample (0)
260 , _last_pointer_sample (0)
262 , _snap_delta_music (0.0)
263 , _constraint_pressed (false)
269 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
275 _cursor_ctx = CursorContext::create (*_editor, cursor);
277 _cursor_ctx->change (cursor);
284 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
287 /* we set up x/y dragging constraints on first move */
288 _constraint_pressed = ArdourKeyboard::indicates_constraint (event->button.state);
290 _raw_grab_sample = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
292 setup_pointer_sample_offset ();
293 setup_video_sample_offset ();
294 if (! UIConfiguration::instance ().get_preview_video_frame_on_drag ()) {
295 _preview_video = false;
297 _grab_sample = adjusted_sample (_raw_grab_sample, event).sample;
298 _last_pointer_sample = _grab_sample;
299 _last_pointer_x = _grab_x;
301 if (_trackview_only) {
302 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
305 _last_pointer_y = _grab_y;
309 if (!_editor->cursors()->is_invalid (cursor)) {
310 /* CAIROCANVAS need a variant here that passes *cursor */
311 _cursor_ctx = CursorContext::create (*_editor, cursor);
314 if (_editor->session() && _editor->session()->transport_rolling()) {
317 _was_rolling = false;
320 // if ( UIConfiguration::instance().get_snap_to_region_start() || UIConfiguration::instance().get_snap_to_region_end() || UIConfiguration::instance().get_snap_to_region_sync() ) {
321 // _editor->build_region_boundary_cache ();
325 /** Call to end a drag `successfully'. Ungrabs item and calls
326 * subclass' finished() method.
328 * @param event GDK event, or 0.
329 * @return true if some movement occurred, otherwise false.
332 Drag::end_grab (GdkEvent* event)
334 _editor->stop_canvas_autoscroll ();
338 finished (event, _move_threshold_passed);
340 _editor->verbose_cursor()->hide ();
343 return _move_threshold_passed;
347 Drag::adjusted_sample (samplepos_t f, GdkEvent const * event, bool snap) const
349 MusicSample pos (0, 0);
351 if (f > _pointer_sample_offset) {
352 pos.sample = f - _pointer_sample_offset;
356 _editor->snap_to_with_modifier (pos, event);
363 Drag::adjusted_current_sample (GdkEvent const * event, bool snap) const
365 return adjusted_sample (_drags->current_pointer_sample (), event, snap).sample;
369 Drag::snap_delta (guint state) const
371 if (ArdourKeyboard::indicates_snap_delta (state)) {
378 Drag::snap_delta_music (guint state) const
380 if (ArdourKeyboard::indicates_snap_delta (state)) {
381 return _snap_delta_music;
388 Drag::current_pointer_x() const
390 return _drags->current_pointer_x ();
394 Drag::current_pointer_y () const
396 if (!_trackview_only) {
397 return _drags->current_pointer_y ();
400 return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
404 Drag::setup_snap_delta (MusicSample pos)
406 TempoMap& map (_editor->session()->tempo_map());
407 MusicSample snap (pos);
408 _editor->snap_to (snap, ARDOUR::RoundNearest, ARDOUR::SnapToAny_Visual, true);
409 _snap_delta = snap.sample - pos.sample;
411 _snap_delta_music = 0.0;
413 if (_snap_delta != 0) {
414 _snap_delta_music = map.exact_qn_at_sample (snap.sample, snap.division) - map.exact_qn_at_sample (pos.sample, pos.division);
419 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
421 /* check to see if we have moved in any way that matters since the last motion event */
422 if (_move_threshold_passed &&
423 (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
424 (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
428 pair<samplecnt_t, int> const threshold = move_threshold ();
430 bool const old_move_threshold_passed = _move_threshold_passed;
432 if (!_move_threshold_passed) {
434 bool const xp = (::llabs (_drags->current_pointer_sample () - _raw_grab_sample) >= threshold.first);
435 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
437 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
440 if (active (_editor->mouse_mode) && _move_threshold_passed) {
442 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
444 if (old_move_threshold_passed != _move_threshold_passed) {
448 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
449 _initially_vertical = true;
451 _initially_vertical = false;
453 /** check constraints for this drag.
454 * Note that the current convention is to use "contains" for
455 * key modifiers during motion and "equals" when initiating a drag.
456 * In this case we haven't moved yet, so "equals" applies here.
458 if (Config->get_edit_mode() != Lock) {
459 if (event->motion.state & Gdk::BUTTON2_MASK) {
460 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
461 if (_constraint_pressed) {
462 _x_constrained = false;
463 _y_constrained = true;
465 _x_constrained = true;
466 _y_constrained = false;
468 } else if (_constraint_pressed) {
469 // if dragging normally, the motion is constrained to the first direction of movement.
470 if (_initially_vertical) {
471 _x_constrained = true;
472 _y_constrained = false;
474 _x_constrained = false;
475 _y_constrained = true;
479 if (event->button.state & Gdk::BUTTON2_MASK) {
480 _x_constrained = false;
482 _x_constrained = true;
484 _y_constrained = false;
488 if (!from_autoscroll) {
489 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
492 if (!_editor->autoscroll_active() || from_autoscroll) {
495 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
497 motion (event, first_move && !_starting_point_passed);
499 if (first_move && !_starting_point_passed) {
500 _starting_point_passed = true;
503 _last_pointer_x = _drags->current_pointer_x ();
504 _last_pointer_y = current_pointer_y ();
505 _last_pointer_sample = adjusted_current_sample (event, false);
515 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
523 aborted (_move_threshold_passed);
525 _editor->stop_canvas_autoscroll ();
526 _editor->verbose_cursor()->hide ();
530 Drag::show_verbose_cursor_time (samplepos_t sample)
532 _editor->verbose_cursor()->set_time (sample);
533 _editor->verbose_cursor()->show ();
537 Drag::show_verbose_cursor_duration (samplepos_t start, samplepos_t end, double /*xoffset*/)
539 _editor->verbose_cursor()->set_duration (start, end);
540 _editor->verbose_cursor()->show ();
544 Drag::show_verbose_cursor_text (string const & text)
546 _editor->verbose_cursor()->set (text);
547 _editor->verbose_cursor()->show ();
551 Drag::show_view_preview (samplepos_t sample)
553 if (_preview_video) {
554 ARDOUR_UI::instance()->video_timeline->manual_seek_video_monitor (sample);
558 boost::shared_ptr<Region>
559 Drag::add_midi_region (MidiTimeAxisView* view, bool commit)
561 if (_editor->session()) {
562 const TempoMap& map (_editor->session()->tempo_map());
563 samplecnt_t pos = grab_sample();
564 /* not that the frame rate used here can be affected by pull up/down which
567 samplecnt_t len = map.sample_at_beat (max (0.0, map.beat_at_sample (pos)) + 1.0) - pos;
568 return view->add_region (grab_sample(), len, commit);
571 return boost::shared_ptr<Region>();
574 struct TimeAxisViewStripableSorter {
575 bool operator() (TimeAxisView* tav_a, TimeAxisView* tav_b) {
576 boost::shared_ptr<ARDOUR::Stripable> const& a = tav_a->stripable ();
577 boost::shared_ptr<ARDOUR::Stripable> const& b = tav_b->stripable ();
578 return ARDOUR::Stripable::Sorter () (a, b);
582 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
587 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
589 /* Make a list of tracks to refer to during the drag; we include hidden tracks,
590 as some of the regions we are dragging may be on such tracks.
593 TrackViewList track_views = _editor->track_views;
594 track_views.sort (TimeAxisViewStripableSorter ());
596 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
597 _time_axis_views.push_back (*i);
599 TimeAxisView::Children children_list = (*i)->get_child_list ();
600 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
601 _time_axis_views.push_back (j->get());
605 /* the list of views can be empty at this point if this is a region list-insert drag
608 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
609 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
612 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
616 RegionDrag::region_going_away (RegionView* v)
618 list<DraggingView>::iterator i = _views.begin ();
619 while (i != _views.end() && i->view != v) {
623 if (i != _views.end()) {
628 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
629 * or -1 if it is not found.
632 RegionDrag::find_time_axis_view (TimeAxisView* t) const
635 int const N = _time_axis_views.size ();
636 while (i < N && _time_axis_views[i] != t) {
640 if (_time_axis_views[i] != t) {
648 RegionDrag::setup_video_sample_offset ()
650 if (_views.empty ()) {
651 _preview_video = true;
654 samplepos_t first_sync = _views.begin()->view->region()->sync_position ();
655 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
656 first_sync = std::min (first_sync, i->view->region()->sync_position ());
658 _video_sample_offset = first_sync + _pointer_sample_offset - raw_grab_sample ();
659 _preview_video = true;
662 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
663 : RegionDrag (e, i, p, v)
665 , _ignore_video_lock (false)
666 , _last_position (0, 0)
668 , _last_pointer_time_axis_view (0)
669 , _last_pointer_layer (0)
674 DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
678 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
680 Drag::start_grab (event, cursor);
681 setup_snap_delta (_last_position);
683 show_verbose_cursor_time (_last_position.sample);
684 show_view_preview (_last_position.sample + _video_sample_offset);
686 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
688 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
689 assert(_last_pointer_time_axis_view >= 0);
690 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
693 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
694 _ignore_video_lock = true;
698 /* cross track dragging seems broken here. disabled for now. */
699 _y_constrained = true;
704 RegionMotionDrag::compute_x_delta (GdkEvent const * event, MusicSample* pending_region_position)
706 /* compute the amount of pointer motion in samples, and where
707 the region would be if we moved it by that much.
709 if (_x_constrained) {
710 *pending_region_position = _last_position;
714 *pending_region_position = adjusted_sample (_drags->current_pointer_sample (), event, false);
716 samplecnt_t sync_offset;
719 sync_offset = _primary->region()->sync_offset (sync_dir);
721 /* we don't handle a sync point that lies before zero.
723 if (sync_dir >= 0 || (sync_dir < 0 && pending_region_position->sample >= sync_offset)) {
725 samplecnt_t const sd = snap_delta (event->button.state);
726 MusicSample sync_snap (pending_region_position->sample + (sync_dir * sync_offset) + sd, 0);
727 _editor->snap_to_with_modifier (sync_snap, event);
728 if (sync_offset == 0 && sd == 0) {
729 *pending_region_position = sync_snap;
731 pending_region_position->set (_primary->region()->adjust_to_sync (sync_snap.sample) - sd, 0);
734 *pending_region_position = _last_position;
737 if (pending_region_position->sample > max_samplepos - _primary->region()->length()) {
738 *pending_region_position = _last_position;
743 bool const x_move_allowed = !_x_constrained;
745 if ((pending_region_position->sample != _last_position.sample) && x_move_allowed) {
747 /* x movement since last time (in pixels) */
748 dx = _editor->sample_to_pixel_unrounded (pending_region_position->sample - _last_position.sample);
750 /* total x movement */
751 samplecnt_t total_dx = _editor->pixel_to_sample (_total_x_delta + dx);
753 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
754 sampleoffset_t const off = i->view->region()->position() + total_dx;
756 dx = dx - _editor->sample_to_pixel_unrounded (off);
757 *pending_region_position = MusicSample (pending_region_position->sample - off, 0);
763 _editor->set_snapped_cursor_position(pending_region_position->sample);
769 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
775 const int tavsize = _time_axis_views.size();
776 const int dt = delta > 0 ? +1 : -1;
778 int target = start + delta - skip;
780 assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
781 assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
783 while (current >= 0 && current != target) {
785 if (current < 0 && dt < 0) {
788 if (current >= tavsize && dt > 0) {
791 if (current < 0 || current >= tavsize) {
795 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
796 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
800 if (distance_only && current == start + delta) {
808 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
810 if (_y_constrained) {
814 const int tavsize = _time_axis_views.size();
815 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
816 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
817 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
819 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
820 /* already in the drop zone */
821 if (delta_track >= 0) {
822 /* downward motion - OK if others are still not in the dropzone */
831 } else if (n >= tavsize) {
832 /* downward motion into drop zone. That's fine. */
836 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
837 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
838 /* not a track, or the wrong type */
842 double const l = i->layer + delta_layer;
844 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
845 mode to allow the user to place a region below another on layer 0.
847 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
848 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
849 If it has, the layers will be munged later anyway, so it's ok.
855 /* all regions being dragged are ok with this change */
859 struct DraggingViewSorter {
860 bool operator() (const DraggingView& a, const DraggingView& b) {
861 return a.time_axis_view < b.time_axis_view;
866 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
868 double delta_layer = 0;
869 int delta_time_axis_view = 0;
870 int current_pointer_time_axis_view = -1;
872 assert (!_views.empty ());
874 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
876 /* Find the TimeAxisView that the pointer is now over */
877 const double cur_y = current_pointer_y ();
878 pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
879 TimeAxisView* tv = r.first;
881 if (!tv && cur_y < 0) {
882 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
886 /* find drop-zone y-position */
887 Coord last_track_bottom_edge;
888 last_track_bottom_edge = 0;
889 for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
890 if (!(*t)->hidden()) {
891 last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
896 if (tv && tv->view()) {
897 /* the mouse is over a track */
898 double layer = r.second;
900 if (first_move && tv->view()->layer_display() == Stacked) {
901 tv->view()->set_layer_display (Expanded);
904 /* Here's the current pointer position in terms of time axis view and layer */
905 current_pointer_time_axis_view = find_time_axis_view (tv);
906 assert(current_pointer_time_axis_view >= 0);
908 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
910 /* Work out the change in y */
912 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
913 if (!rtv || !rtv->is_track()) {
914 /* ignore non-tracks early on. we can't move any regions on them */
915 } else if (_last_pointer_time_axis_view < 0) {
916 /* Was in the drop-zone, now over a track.
917 * Hence it must be an upward move (from the bottom)
919 * track_index is still -1, so delta must be set to
920 * move up the correct number of tracks from the bottom.
922 * This is necessary because steps may be skipped if
923 * the bottom-most track is not a valid target and/or
924 * if there are hidden tracks at the bottom.
925 * Hence the initial offset (_ddropzone) as well as the
926 * last valid pointer position (_pdropzone) need to be
927 * taken into account.
929 delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
931 delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
934 /* TODO needs adjustment per DraggingView,
936 * e.g. select one region on the top-layer of a track
937 * and one region which is at the bottom-layer of another track
940 * Indicated drop-zones and layering is wrong.
941 * and may infer additional layers on the target-track
942 * (depending how many layers the original track had).
944 * Or select two regions (different layers) on a same track,
945 * move across a non-layer track.. -> layering info is lost.
946 * on drop either of the regions may be on top.
948 * Proposed solution: screw it :) well,
949 * don't use delta_layer, use an absolute value
950 * 1) remember DraggingView's layer as float 0..1 [current layer / all layers of source]
951 * 2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
952 * 3) iterate over all DraggingView, find the one that is over the track with most layers
953 * 4) proportionally scale layer to layers available on target
955 delta_layer = current_pointer_layer - _last_pointer_layer;
958 /* for automation lanes, there is a TimeAxisView but no ->view()
959 * if (!tv) -> dropzone
961 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
962 /* Moving into the drop-zone.. */
963 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
964 /* delta_time_axis_view may not be sufficient to move into the DZ
965 * the mouse may enter it, but it may not be a valid move due to
968 * -> remember the delta needed to move into the dropzone
970 _ddropzone = delta_time_axis_view;
971 /* ..but subtract hidden tracks (or routes) at the bottom.
972 * we silently move mover them
974 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
975 - _time_axis_views.size();
977 else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
978 /* move around inside the zone.
979 * This allows to move further down until all regions are in the zone.
981 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
982 assert(ptr_y >= last_track_bottom_edge);
983 assert(_ddropzone > 0);
985 /* calculate mouse position in 'tracks' below last track. */
986 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
987 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
989 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
991 delta_time_axis_view = dzpos - _pdropzone;
992 } else if (dzpos < _pdropzone && _ndropzone > 0) {
993 // move up inside the DZ
994 delta_time_axis_view = dzpos - _pdropzone;
998 /* Work out the change in x */
999 TempoMap& tmap = _editor->session()->tempo_map();
1000 MusicSample pending_region_position (0, 0);
1001 double const x_delta = compute_x_delta (event, &pending_region_position);
1003 double const last_pos_qn = tmap.exact_qn_at_sample (_last_position.sample, _last_position.division);
1004 double const qn_delta = tmap.exact_qn_at_sample (pending_region_position.sample, pending_region_position.division) - last_pos_qn;
1006 _last_position = pending_region_position;
1008 /* calculate hidden tracks in current y-axis delta */
1010 if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
1011 /* The mouse is more than one track below the dropzone.
1012 * distance calculation is not needed (and would not work, either
1013 * because the dropzone is "packed").
1015 * Except when [partially] moving regions out of dropzone in a large step.
1016 * (the mouse may or may not remain in the DZ)
1017 * Hidden tracks at the bottom of the TAV need to be skipped.
1019 * This also handles the case if the mouse entered the DZ
1020 * in a large step (exessive delta), either due to fast-movement,
1021 * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
1023 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
1024 const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
1026 delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
1027 -_time_axis_views.size() - dt;
1030 else if (_last_pointer_time_axis_view < 0) {
1031 /* Moving out of the zone. Check for hidden tracks at the bottom. */
1032 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
1033 -_time_axis_views.size() - delta_time_axis_view;
1035 /* calculate hidden tracks that are skipped by the pointer movement */
1036 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
1037 - _last_pointer_time_axis_view
1038 - delta_time_axis_view;
1041 /* Verify change in y */
1042 if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
1043 /* this y movement is not allowed, so do no y movement this time */
1044 delta_time_axis_view = 0;
1049 if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1050 /* haven't reached next snap point, and we're not switching
1051 trackviews nor layers. nothing to do.
1056 typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1057 PlaylistDropzoneMap playlist_dropzone_map;
1058 _ndropzone = 0; // number of elements currently in the dropzone
1061 /* sort views by time_axis.
1062 * This retains track order in the dropzone, regardless
1063 * of actual selection order
1065 _views.sort (DraggingViewSorter());
1067 /* count number of distinct tracks of all regions
1068 * being dragged, used for dropzone.
1070 int prev_track = -1;
1071 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1072 if (i->time_axis_view != prev_track) {
1073 prev_track = i->time_axis_view;
1079 _views.back().time_axis_view -
1080 _views.front().time_axis_view;
1082 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1083 - _views.back().time_axis_view;
1085 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1089 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1091 RegionView* rv = i->view;
1096 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1103 /* reparent the regionview into a group above all
1107 ArdourCanvas::Item* rvg = rv->get_canvas_group();
1108 Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1109 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1110 rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1111 /* move the item so that it continues to appear at the
1112 same location now that its parent has changed.
1114 rvg->move (rv_canvas_offset - dmg_canvas_offset);
1117 /* If we have moved tracks, we'll fudge the layer delta so that the
1118 region gets moved back onto layer 0 on its new track; this avoids
1119 confusion when dragging regions from non-zero layers onto different
1122 double this_delta_layer = delta_layer;
1123 if (delta_time_axis_view != 0) {
1124 this_delta_layer = - i->layer;
1127 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1129 int track_index = i->time_axis_view + this_delta_time_axis_view;
1130 assert(track_index >= 0);
1132 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1133 /* Track is in the Dropzone */
1135 i->time_axis_view = track_index;
1136 assert(i->time_axis_view >= (int) _time_axis_views.size());
1139 double yposition = 0;
1140 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1141 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1144 /* store index of each new playlist as a negative count, starting at -1 */
1146 if (pdz == playlist_dropzone_map.end()) {
1147 /* compute where this new track (which doesn't exist yet) will live
1150 yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1152 /* How high is this region view ? */
1154 boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1155 ArdourCanvas::Rect bbox;
1158 bbox = obbox.get ();
1161 last_track_bottom_edge += bbox.height();
1163 playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1166 yposition = pdz->second;
1169 /* values are zero or negative, hence the use of min() */
1170 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1173 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1175 mrv->apply_note_range (60, 71, true);
1179 /* The TimeAxisView that this region is now over */
1180 TimeAxisView* current_tv = _time_axis_views[track_index];
1182 /* Ensure it is moved from stacked -> expanded if appropriate */
1183 if (current_tv->view()->layer_display() == Stacked) {
1184 current_tv->view()->set_layer_display (Expanded);
1187 /* We're only allowed to go -ve in layer on Expanded views */
1188 if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1189 this_delta_layer = - i->layer;
1193 rv->set_height (current_tv->view()->child_height ());
1195 /* Update show/hidden status as the region view may have come from a hidden track,
1196 or have moved to one.
1198 if (current_tv->hidden ()) {
1199 rv->get_canvas_group()->hide ();
1201 rv->get_canvas_group()->show ();
1204 /* Update the DraggingView */
1205 i->time_axis_view = track_index;
1206 i->layer += this_delta_layer;
1209 _editor->mouse_brush_insert_region (rv, pending_region_position.sample);
1213 /* Get the y coordinate of the top of the track that this region is now over */
1214 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1216 /* And adjust for the layer that it should be on */
1217 StreamView* cv = current_tv->view ();
1218 switch (cv->layer_display ()) {
1222 track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1225 track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1229 /* need to get the parent of the regionview
1230 * canvas group and get its position in
1231 * equivalent coordinate space as the trackview
1232 * we are now dragging over.
1235 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1239 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1241 MidiStreamView* msv;
1242 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1243 mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1248 /* Now move the region view */
1249 if (rv->region()->position_lock_style() == MusicTime) {
1250 double const last_qn = tmap.quarter_note_at_sample (rv->get_position());
1251 samplepos_t const x_pos_music = tmap.sample_at_quarter_note (last_qn + qn_delta);
1253 rv->set_position (x_pos_music, 0);
1254 rv->move (0, y_delta);
1256 rv->move (x_delta, y_delta);
1259 } /* foreach region */
1261 _total_x_delta += x_delta;
1263 if (x_delta != 0 && !_brushing) {
1264 show_verbose_cursor_time (_last_position.sample);
1265 show_view_preview (_last_position.sample + _video_sample_offset);
1268 /* keep track of pointer movement */
1270 /* the pointer is currently over a time axis view */
1272 if (_last_pointer_time_axis_view < 0) {
1273 /* last motion event was not over a time axis view
1274 * or last y-movement out of the dropzone was not valid
1277 if (delta_time_axis_view < 0) {
1278 /* in the drop zone, moving up */
1280 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1281 * We do not use negative _last_pointer_time_axis_view because
1282 * the dropzone is "packed" (the actual track offset is ignored)
1284 * As opposed to the actual number
1285 * of elements in the dropzone (_ndropzone)
1286 * _pdropzone is not constrained. This is necessary
1287 * to allow moving multiple regions with y-distance
1290 * There can be 0 elements in the dropzone,
1291 * even though the drag-pointer is inside the DZ.
1294 * [ Audio-track, Midi-track, Audio-track, DZ ]
1295 * move regions from both audio tracks at the same time into the
1296 * DZ by grabbing the region in the bottom track.
1298 assert(current_pointer_time_axis_view >= 0);
1299 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1303 /* only move out of the zone if the movement is OK */
1304 if (_pdropzone == 0 && delta_time_axis_view != 0) {
1305 assert(delta_time_axis_view < 0);
1306 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1307 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1308 * the current position can be calculated as follows:
1310 // a well placed oofus attack can still throw this off.
1311 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1312 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1315 /* last motion event was also over a time axis view */
1316 _last_pointer_time_axis_view += delta_time_axis_view;
1317 assert(_last_pointer_time_axis_view >= 0);
1322 /* the pointer is not over a time axis view */
1323 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1324 _pdropzone += delta_time_axis_view - delta_skip;
1325 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1328 _last_pointer_layer += delta_layer;
1332 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1334 if (_copy && first_move) {
1335 if (_x_constrained && !_brushing) {
1336 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1337 } else if (!_brushing) {
1338 _editor->begin_reversible_command (Operations::region_copy);
1339 } else if (_brushing) {
1340 _editor->begin_reversible_command (Operations::drag_region_brush);
1342 /* duplicate the regionview(s) and region(s) */
1344 list<DraggingView> new_regionviews;
1346 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1348 RegionView* rv = i->view;
1349 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1350 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1352 const boost::shared_ptr<const Region> original = rv->region();
1353 boost::shared_ptr<Region> region_copy;
1355 region_copy = RegionFactory::create (original, true);
1357 /* need to set this so that the drop zone code can work. This doesn't
1358 actually put the region into the playlist, but just sets a weak pointer
1361 region_copy->set_playlist (original->playlist());
1365 boost::shared_ptr<AudioRegion> audioregion_copy
1366 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1368 nrv = new AudioRegionView (*arv, audioregion_copy);
1370 boost::shared_ptr<MidiRegion> midiregion_copy
1371 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1372 nrv = new MidiRegionView (*mrv, midiregion_copy);
1377 nrv->get_canvas_group()->show ();
1378 new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1380 /* swap _primary to the copy */
1382 if (rv == _primary) {
1386 /* ..and deselect the one we copied */
1388 rv->set_selected (false);
1391 if (!new_regionviews.empty()) {
1393 /* reflect the fact that we are dragging the copies */
1395 _views = new_regionviews;
1397 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1400 } else if (!_copy && first_move) {
1401 if (_x_constrained && !_brushing) {
1402 _editor->begin_reversible_command (_("fixed time region drag"));
1403 } else if (!_brushing) {
1404 _editor->begin_reversible_command (Operations::region_drag);
1405 } else if (_brushing) {
1406 _editor->begin_reversible_command (Operations::drag_region_brush);
1409 RegionMotionDrag::motion (event, first_move);
1413 RegionMotionDrag::finished (GdkEvent *, bool)
1415 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1416 if (!(*i)->view()) {
1420 if ((*i)->view()->layer_display() == Expanded) {
1421 (*i)->view()->set_layer_display (Stacked);
1427 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1429 RegionMotionDrag::finished (ev, movement_occurred);
1431 if (!movement_occurred) {
1435 if (was_double_click() && !_views.empty()) {
1436 DraggingView dv = _views.front();
1437 _editor->edit_region (dv.view);
1443 assert (!_views.empty ());
1445 /* We might have hidden region views so that they weren't visible during the drag
1446 (when they have been reparented). Now everything can be shown again, as region
1447 views are back in their track parent groups.
1449 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1450 i->view->get_canvas_group()->show ();
1453 bool const changed_position = (_last_position.sample != _primary->region()->position());
1454 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1478 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1480 if (!ARDOUR_UI_UTILS::engine_is_running ()) {
1484 /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1487 TimeAxisView* tav = 0;
1489 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1490 list<boost::shared_ptr<AudioTrack> > audio_tracks;
1491 uint32_t output_chan = region->n_channels();
1492 if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1493 output_chan = _editor->session()->master_out()->n_inputs().n_audio();
1495 audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1496 tav =_editor->time_axis_view_from_stripable (audio_tracks.front());
1498 ChanCount one_midi_port (DataType::MIDI, 1);
1499 list<boost::shared_ptr<MidiTrack> > midi_tracks;
1500 midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1501 Config->get_strict_io () || Profile->get_mixbus (),
1502 boost::shared_ptr<ARDOUR::PluginInfo>(),
1503 (ARDOUR::Plugin::PresetRecord*) 0,
1504 (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1505 tav = _editor->time_axis_view_from_stripable (midi_tracks.front());
1509 tav->set_height (original->current_height());
1512 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1515 return dynamic_cast<RouteTimeAxisView*> (tav);
1519 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, MusicSample last_position, int32_t const ev_state)
1521 RegionSelection new_views;
1522 PlaylistSet modified_playlists;
1523 RouteTimeAxisView* new_time_axis_view = 0;
1524 samplecnt_t const drag_delta = _primary->region()->position() - _last_position.sample;
1526 TempoMap& tmap (_editor->session()->tempo_map());
1527 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1528 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1531 /* all changes were made during motion event handlers */
1533 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1537 _editor->commit_reversible_command ();
1541 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1542 PlaylistMapping playlist_mapping;
1544 /* insert the regions into their new playlists */
1545 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1547 RouteTimeAxisView* dest_rtv = 0;
1549 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1554 MusicSample where (0, 0);
1555 double quarter_note;
1557 if (changed_position && !_x_constrained) {
1558 where.set (i->view->region()->position() - drag_delta, 0);
1559 quarter_note = i->view->region()->quarter_note() - qn_delta;
1561 /* region has not moved - divisor will not affect musical pos */
1562 where.set (i->view->region()->position(), 0);
1563 quarter_note = i->view->region()->quarter_note();
1566 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1567 /* dragged to drop zone */
1569 PlaylistMapping::iterator pm;
1571 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1572 /* first region from this original playlist: create a new track */
1573 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1574 if(!new_time_axis_view) {
1578 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1579 dest_rtv = new_time_axis_view;
1581 /* we already created a new track for regions from this playlist, use it */
1582 dest_rtv = pm->second;
1585 /* destination time axis view is the one we dragged to */
1586 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1589 if (dest_rtv != 0) {
1590 RegionView* new_view;
1591 if (i->view == _primary && !_x_constrained) {
1592 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, last_position, last_pos_qn,
1593 modified_playlists, true);
1595 if (i->view->region()->position_lock_style() == AudioTime) {
1596 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1597 modified_playlists);
1599 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where, quarter_note,
1600 modified_playlists, true);
1604 if (new_view != 0) {
1605 new_views.push_back (new_view);
1609 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1610 since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1613 list<DraggingView>::const_iterator next = i;
1619 /* If we've created new regions either by copying or moving
1620 to a new track, we want to replace the old selection with the new ones
1623 if (new_views.size() > 0) {
1624 _editor->selection->set (new_views);
1627 /* write commands for the accumulated diffs for all our modified playlists */
1628 add_stateful_diff_commands_for_playlists (modified_playlists);
1630 _editor->commit_reversible_command ();
1634 RegionMoveDrag::finished_no_copy (
1635 bool const changed_position,
1636 bool const changed_tracks,
1637 MusicSample last_position,
1638 int32_t const ev_state
1641 RegionSelection new_views;
1642 PlaylistSet modified_playlists;
1643 PlaylistSet frozen_playlists;
1644 set<RouteTimeAxisView*> views_to_update;
1645 RouteTimeAxisView* new_time_axis_view = 0;
1646 samplecnt_t const drag_delta = _primary->region()->position() - last_position.sample;
1648 typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1649 PlaylistMapping playlist_mapping;
1651 TempoMap& tmap (_editor->session()->tempo_map());
1652 const double last_pos_qn = tmap.exact_qn_at_sample (last_position.sample, last_position.division);
1653 const double qn_delta = _primary->region()->quarter_note() - last_pos_qn;
1655 std::set<boost::shared_ptr<const Region> > uniq;
1656 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1658 RegionView* rv = i->view;
1659 RouteTimeAxisView* dest_rtv = 0;
1661 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1666 if (uniq.find (rv->region()) != uniq.end()) {
1667 /* prevent duplicate moves when selecting regions from shared playlists */
1671 uniq.insert(rv->region());
1673 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1674 /* dragged to drop zone */
1676 PlaylistMapping::iterator pm;
1678 if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1679 /* first region from this original playlist: create a new track */
1680 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1681 if(!new_time_axis_view) { // New track creation failed
1685 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1686 dest_rtv = new_time_axis_view;
1688 /* we already created a new track for regions from this playlist, use it */
1689 dest_rtv = pm->second;
1693 /* destination time axis view is the one we dragged to */
1694 dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1699 double const dest_layer = i->layer;
1701 views_to_update.insert (dest_rtv);
1703 MusicSample where (0, 0);
1704 double quarter_note;
1706 if (changed_position && !_x_constrained) {
1707 where.set (rv->region()->position() - drag_delta, 0);
1708 quarter_note = i->view->region()->quarter_note() - qn_delta;
1710 where.set (rv->region()->position(), 0);
1711 quarter_note = i->view->region()->quarter_note();
1714 if (changed_tracks) {
1716 /* insert into new playlist */
1717 RegionView* new_view;
1718 if (rv == _primary && !_x_constrained) {
1719 new_view = insert_region_into_playlist (
1720 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, last_position, last_pos_qn,
1721 modified_playlists, true
1724 if (rv->region()->position_lock_style() == AudioTime) {
1726 new_view = insert_region_into_playlist (
1727 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1731 new_view = insert_region_into_playlist (
1732 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, quarter_note,
1733 modified_playlists, true
1738 if (new_view == 0) {
1743 new_views.push_back (new_view);
1745 /* remove from old playlist */
1747 /* the region that used to be in the old playlist is not
1748 moved to the new one - we use a copy of it. as a result,
1749 any existing editor for the region should no longer be
1752 rv->hide_region_editor();
1755 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1759 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1761 /* this movement may result in a crossfade being modified, or a layering change,
1762 so we need to get undo data from the playlist as well as the region.
1765 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1767 playlist->clear_changes ();
1770 rv->region()->clear_changes ();
1773 motion on the same track. plonk the previously reparented region
1774 back to its original canvas group (its streamview).
1775 No need to do anything for copies as they are fake regions which will be deleted.
1778 rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1779 rv->get_canvas_group()->set_y_position (i->initial_y);
1782 /* just change the model */
1783 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1784 playlist->set_layer (rv->region(), dest_layer);
1787 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1789 r = frozen_playlists.insert (playlist);
1792 playlist->freeze ();
1794 if (rv == _primary) {
1795 rv->region()->set_position (where.sample, last_position.division);
1797 if (rv->region()->position_lock_style() == AudioTime) {
1798 /* move by sample offset */
1799 rv->region()->set_position (where.sample, 0);
1801 /* move by music offset */
1802 rv->region()->set_position_music (rv->region()->quarter_note() - qn_delta);
1805 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1808 if (changed_tracks) {
1810 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1811 was selected in all of them, then removing it from a playlist will have removed all
1812 trace of it from _views (i.e. there were N regions selected, we removed 1,
1813 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1814 corresponding regionview, and _views is now empty).
1816 This could have invalidated any and all iterators into _views.
1818 The heuristic we use here is: if the region selection is empty, break out of the loop
1819 here. if the region selection is not empty, then restart the loop because we know that
1820 we must have removed at least the region(view) we've just been working on as well as any
1821 that we processed on previous iterations.
1823 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1824 we can just iterate.
1828 if (_views.empty()) {
1839 /* If we've created new regions either by copying or moving
1840 to a new track, we want to replace the old selection with the new ones
1843 if (new_views.size() > 0) {
1844 _editor->selection->set (new_views);
1847 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1851 /* write commands for the accumulated diffs for all our modified playlists */
1852 add_stateful_diff_commands_for_playlists (modified_playlists);
1853 /* applies to _brushing */
1854 _editor->commit_reversible_command ();
1856 /* We have futzed with the layering of canvas items on our streamviews.
1857 If any region changed layer, this will have resulted in the stream
1858 views being asked to set up their region views, and all will be well.
1859 If not, we might now have badly-ordered region views. Ask the StreamViews
1860 involved to sort themselves out, just in case.
1863 for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1864 (*i)->view()->playlist_layered ((*i)->track ());
1868 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1869 * @param region Region to remove.
1870 * @param playlist playlist To remove from.
1871 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1872 * that clear_changes () is only called once per playlist.
1875 RegionMoveDrag::remove_region_from_playlist (
1876 boost::shared_ptr<Region> region,
1877 boost::shared_ptr<Playlist> playlist,
1878 PlaylistSet& modified_playlists
1881 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1884 playlist->clear_changes ();
1887 playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1891 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1892 * clearing the playlist's diff history first if necessary.
1893 * @param region Region to insert.
1894 * @param dest_rtv Destination RouteTimeAxisView.
1895 * @param dest_layer Destination layer.
1896 * @param where Destination position.
1897 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1898 * that clear_changes () is only called once per playlist.
1899 * @return New RegionView, or 0 if no insert was performed.
1902 RegionMoveDrag::insert_region_into_playlist (
1903 boost::shared_ptr<Region> region,
1904 RouteTimeAxisView* dest_rtv,
1907 double quarter_note,
1908 PlaylistSet& modified_playlists,
1912 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1913 if (!dest_playlist) {
1917 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1918 _new_region_view = 0;
1919 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1921 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1922 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1924 dest_playlist->clear_changes ();
1927 dest_playlist->add_region (region, where.sample, 1.0, false, where.division, quarter_note, true);
1929 dest_playlist->add_region (region, where.sample, 1.0, false, where.division);
1932 if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1933 dest_playlist->set_layer (region, dest_layer);
1938 assert (_new_region_view);
1940 return _new_region_view;
1944 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1946 _new_region_view = rv;
1950 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1952 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1953 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1955 _editor->session()->add_command (c);
1964 RegionMoveDrag::aborted (bool movement_occurred)
1968 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1969 list<DraggingView>::const_iterator next = i;
1978 RegionMotionDrag::aborted (movement_occurred);
1983 RegionMotionDrag::aborted (bool)
1985 for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1987 StreamView* sview = (*i)->view();
1990 if (sview->layer_display() == Expanded) {
1991 sview->set_layer_display (Stacked);
1996 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1997 RegionView* rv = i->view;
1998 TimeAxisView* tv = &(rv->get_time_axis_view ());
1999 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2001 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
2002 rv->get_canvas_group()->set_y_position (0);
2004 rv->move (-_total_x_delta, 0);
2005 rv->set_height (rtv->view()->child_height ());
2009 /** @param b true to brush, otherwise false.
2010 * @param c true to make copies of the regions being moved, otherwise false.
2012 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
2013 : RegionMotionDrag (e, i, p, v, b)
2015 , _new_region_view (0)
2017 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
2019 _last_position = MusicSample (_primary->region()->position(), 0);
2023 RegionMoveDrag::setup_pointer_sample_offset ()
2025 _pointer_sample_offset = raw_grab_sample() - _last_position.sample;
2028 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, samplepos_t pos)
2029 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
2031 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
2033 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
2034 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
2036 _primary = v->view()->create_region_view (r, false, false);
2038 _primary->get_canvas_group()->show ();
2039 _primary->set_position (pos, 0);
2040 _views.push_back (DraggingView (_primary, this, v));
2042 _last_position = MusicSample (pos, 0);
2044 _item = _primary->get_canvas_group ();
2048 RegionInsertDrag::finished (GdkEvent * event, bool)
2050 int pos = _views.front().time_axis_view;
2051 assert(pos >= 0 && pos < (int)_time_axis_views.size());
2053 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
2055 _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
2056 _primary->get_canvas_group()->set_y_position (0);
2058 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
2060 _editor->begin_reversible_command (Operations::insert_region);
2061 playlist->clear_changes ();
2062 _editor->snap_to_with_modifier (_last_position, event);
2064 playlist->add_region (_primary->region (), _last_position.sample, 1.0, false, _last_position.division);
2066 // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2067 if (Config->get_edit_mode() == Ripple) {
2068 playlist->ripple (_last_position.sample, _primary->region()->length(), _primary->region());
2071 _editor->session()->add_command (new StatefulDiffCommand (playlist));
2072 _editor->commit_reversible_command ();
2080 RegionInsertDrag::aborted (bool)
2087 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2088 : RegionMoveDrag (e, i, p, v, false, false)
2090 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2093 struct RegionSelectionByPosition {
2094 bool operator() (RegionView*a, RegionView* b) {
2095 return a->region()->position () < b->region()->position();
2100 RegionSpliceDrag::motion (GdkEvent* event, bool)
2102 /* Which trackview is this ? */
2104 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2105 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2107 /* The region motion is only processed if the pointer is over
2111 if (!tv || !tv->is_track()) {
2112 /* To make sure we hide the verbose canvas cursor when the mouse is
2113 not held over an audio track.
2115 _editor->verbose_cursor()->hide ();
2118 _editor->verbose_cursor()->show ();
2123 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2129 RegionSelection copy;
2130 _editor->selection->regions.by_position(copy);
2132 samplepos_t const pf = adjusted_current_sample (event);
2134 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2136 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2142 boost::shared_ptr<Playlist> playlist;
2144 if ((playlist = atv->playlist()) == 0) {
2148 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2153 if (pf < (*i)->region()->last_sample() + 1) {
2157 if (pf > (*i)->region()->first_sample()) {
2163 playlist->shuffle ((*i)->region(), dir);
2168 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2170 RegionMoveDrag::finished (event, movement_occurred);
2174 RegionSpliceDrag::aborted (bool)
2184 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, samplepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2187 boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<samplepos_t>(where, max_samplepos));
2189 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2190 RegionSelection to_ripple;
2191 for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2192 if ((*i)->position() >= where) {
2193 to_ripple.push_back (rtv->view()->find_view(*i));
2197 for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2198 if (!exclude.contains (*i)) {
2199 // the selection has already been added to _views
2201 if (drag_in_progress) {
2202 // do the same things that RegionMotionDrag::motion does when
2203 // first_move is true, for the region views that we're adding
2204 // to _views this time
2207 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2208 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2209 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2210 rvg->reparent (_editor->_drag_motion_group);
2212 // we only need to move in the y direction
2213 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2218 _views.push_back (DraggingView (*i, this, tav));
2224 RegionRippleDrag::remove_unselected_from_views(samplecnt_t amount, bool move_regions)
2227 for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2228 // we added all the regions after the selection
2230 std::list<DraggingView>::iterator to_erase = i++;
2231 if (!_editor->selection->regions.contains (to_erase->view)) {
2232 // restore the non-selected regions to their original playlist & positions,
2233 // and then ripple them back by the length of the regions that were dragged away
2234 // do the same things as RegionMotionDrag::aborted
2236 RegionView *rv = to_erase->view;
2237 TimeAxisView* tv = &(rv->get_time_axis_view ());
2238 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2241 // plonk them back onto their own track
2242 rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2243 rv->get_canvas_group()->set_y_position (0);
2247 // move the underlying region to match the view
2248 rv->region()->set_position (rv->region()->position() + amount);
2250 // restore the view to match the underlying region's original position
2251 rv->move(-amount, 0); // second parameter is y delta - seems 0 is OK
2254 rv->set_height (rtv->view()->child_height ());
2255 _views.erase (to_erase);
2261 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2263 if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2265 return allow_moves_across_tracks;
2273 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2274 : RegionMoveDrag (e, i, p, v, false, false)
2276 DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2277 // compute length of selection
2278 RegionSelection selected_regions = _editor->selection->regions;
2279 selection_length = selected_regions.end_sample() - selected_regions.start();
2281 // we'll only allow dragging to another track in ripple mode if all the regions
2282 // being dragged start off on the same track
2283 allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2286 exclude = new RegionList;
2287 for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2288 exclude->push_back((*i)->region());
2291 // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2292 RegionSelection copy;
2293 selected_regions.by_position(copy); // get selected regions sorted by position into copy
2295 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2296 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2298 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2299 // find ripple start point on each applicable playlist
2300 RegionView *first_selected_on_this_track = NULL;
2301 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2302 if ((*i)->region()->playlist() == (*pi)) {
2303 // region is on this playlist - it's the first, because they're sorted
2304 first_selected_on_this_track = *i;
2308 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2309 add_all_after_to_views (
2310 &first_selected_on_this_track->get_time_axis_view(),
2311 first_selected_on_this_track->region()->position(),
2312 selected_regions, false);
2315 if (allow_moves_across_tracks) {
2316 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2324 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2326 /* Which trackview is this ? */
2328 pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2329 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2331 /* The region motion is only processed if the pointer is over
2335 if (!tv || !tv->is_track()) {
2336 /* To make sure we hide the verbose canvas cursor when the mouse is
2337 not held over an audiotrack.
2339 _editor->verbose_cursor()->hide ();
2343 samplepos_t where = adjusted_current_sample (event);
2344 assert (where >= 0);
2345 MusicSample after (0, 0);
2346 double delta = compute_x_delta (event, &after);
2348 samplecnt_t amount = _editor->pixel_to_sample (delta);
2350 if (allow_moves_across_tracks) {
2351 // all the originally selected regions were on the same track
2353 samplecnt_t adjust = 0;
2354 if (prev_tav && tv != prev_tav) {
2355 // dragged onto a different track
2356 // remove the unselected regions from _views, restore them to their original positions
2357 // and add the regions after the drop point on the new playlist to _views instead.
2358 // undo the effect of rippling the previous playlist, and include the effect of removing
2359 // the dragged region(s) from this track
2361 remove_unselected_from_views (prev_amount, false);
2362 // ripple previous playlist according to the regions that have been removed onto the new playlist
2363 prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2366 // move just the selected regions
2367 RegionMoveDrag::motion(event, first_move);
2369 // ensure that the ripple operation on the new playlist inserts selection_length time
2370 adjust = selection_length;
2371 // ripple the new current playlist
2372 tv->playlist()->ripple (where, amount+adjust, exclude);
2374 // add regions after point where drag entered this track to subsequent ripples
2375 add_all_after_to_views (tv, where, _editor->selection->regions, true);
2378 // motion on same track
2379 RegionMoveDrag::motion(event, first_move);
2383 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2384 prev_position = where;
2386 // selection encompasses multiple tracks - just drag
2387 // cross-track drags are forbidden
2388 RegionMoveDrag::motion(event, first_move);
2391 if (!_x_constrained) {
2392 prev_amount += amount;
2395 _last_position = after;
2399 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2401 if (!movement_occurred) {
2405 if (was_double_click() && !_views.empty()) {
2406 DraggingView dv = _views.front();
2407 _editor->edit_region (dv.view);
2413 _editor->begin_reversible_command(_("Ripple drag"));
2415 // remove the regions being rippled from the dragging view, updating them to
2416 // their new positions
2417 remove_unselected_from_views (prev_amount, true);
2419 if (allow_moves_across_tracks) {
2421 // if regions were dragged across tracks, we've rippled any later
2422 // regions on the track the regions were dragged off, so we need
2423 // to add the original track to the undo record
2424 orig_tav->playlist()->clear_changes();
2425 vector<Command*> cmds;
2426 orig_tav->playlist()->rdiff (cmds);
2427 _editor->session()->add_commands (cmds);
2429 if (prev_tav && prev_tav != orig_tav) {
2430 prev_tav->playlist()->clear_changes();
2431 vector<Command*> cmds;
2432 prev_tav->playlist()->rdiff (cmds);
2433 _editor->session()->add_commands (cmds);
2436 // selection spanned multiple tracks - all will need adding to undo record
2438 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2439 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2441 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2442 (*pi)->clear_changes();
2443 vector<Command*> cmds;
2444 (*pi)->rdiff (cmds);
2445 _editor->session()->add_commands (cmds);
2449 // other modified playlists are added to undo by RegionMoveDrag::finished()
2450 RegionMoveDrag::finished (event, movement_occurred);
2451 _editor->commit_reversible_command();
2455 RegionRippleDrag::aborted (bool movement_occurred)
2457 RegionMoveDrag::aborted (movement_occurred);
2462 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2464 _view (dynamic_cast<MidiTimeAxisView*> (v))
2466 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2472 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2476 _editor->begin_reversible_command (_("create region"));
2477 _region = add_midi_region (_view, false);
2478 _view->playlist()->freeze ();
2482 samplepos_t const f = adjusted_current_sample (event);
2483 if (f <= grab_sample()) {
2484 _region->set_initial_position (f);
2487 /* Don't use a zero-length region, and subtract 1 sample from the snapped length
2488 so that if this region is duplicated, its duplicate starts on
2489 a snap point rather than 1 sample after a snap point. Otherwise things get
2490 a bit confusing as if a region starts 1 sample after a snap point, one cannot
2491 place snapped notes at the start of the region.
2493 if (f != grab_sample()) {
2494 samplecnt_t const len = (samplecnt_t) fabs ((double)(f - grab_sample () - 1));
2495 _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2502 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2504 if (!movement_occurred) {
2505 add_midi_region (_view, true);
2507 _view->playlist()->thaw ();
2508 _editor->commit_reversible_command();
2513 RegionCreateDrag::aborted (bool)
2516 _view->playlist()->thaw ();
2522 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2527 , _was_selected (false)
2530 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2534 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2536 Gdk::Cursor* cursor;
2537 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2539 float x_fraction = cnote->mouse_x_fraction ();
2541 if (x_fraction > 0.0 && x_fraction < 0.25) {
2542 cursor = _editor->cursors()->left_side_trim;
2545 cursor = _editor->cursors()->right_side_trim;
2549 Drag::start_grab (event, cursor);
2551 region = &cnote->region_view();
2554 temp = region->snap_to_pixel (cnote->x0 (), true);
2555 _snap_delta = temp - cnote->x0 ();
2559 if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2564 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2565 if (ms.size() > 1) {
2566 /* has to be relative, may make no sense otherwise */
2570 if (!(_was_selected = cnote->selected())) {
2572 /* tertiary-click means extend selection - we'll do that on button release,
2573 so don't add it here, because otherwise we make it hard to figure
2574 out the "extend-to" range.
2577 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2580 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2583 region->note_selected (cnote, true);
2585 _editor->get_selection().clear_points();
2586 region->unique_select (cnote);
2593 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2595 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2597 _editor->begin_reversible_command (_("resize notes"));
2599 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2600 MidiRegionSelection::iterator next;
2603 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2605 mrv->begin_resizing (at_front);
2611 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2612 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2614 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2618 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2620 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2621 if (_editor->snap_mode () != SnapOff) {
2625 if (_editor->snap_mode () == SnapOff) {
2627 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2628 if (apply_snap_delta) {
2634 if (apply_snap_delta) {
2638 mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2644 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2646 if (!movement_occurred) {
2647 /* no motion - select note */
2648 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2649 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2650 _editor->current_mouse_mode() == Editing::MouseDraw) {
2652 bool changed = false;
2654 if (_was_selected) {
2655 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2657 region->note_deselected (cnote);
2660 _editor->get_selection().clear_points();
2661 region->unique_select (cnote);
2665 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2666 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2668 if (!extend && !add && region->selection_size() > 1) {
2669 _editor->get_selection().clear_points();
2670 region->unique_select (cnote);
2672 } else if (extend) {
2673 region->note_selected (cnote, true, true);
2676 /* it was added during button press */
2682 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2683 _editor->commit_reversible_selection_op();
2690 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2691 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2692 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2694 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2697 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2699 if (ArdourKeyboard::indicates_snap (event->button.state)) {
2700 if (_editor->snap_mode () != SnapOff) {
2704 if (_editor->snap_mode () == SnapOff) {
2706 /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2707 if (apply_snap_delta) {
2713 if (apply_snap_delta) {
2717 mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2721 _editor->commit_reversible_command ();
2725 NoteResizeDrag::aborted (bool)
2727 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2728 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2729 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2731 mrv->abort_resizing ();
2736 AVDraggingView::AVDraggingView (RegionView* v)
2739 initial_position = v->region()->position ();
2742 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2745 DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2748 TrackViewList empty;
2750 _editor->get_regions_after(rs, (samplepos_t) 0, empty);
2751 std::list<RegionView*> views = rs.by_layer();
2754 for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2755 RegionView* rv = (*i);
2756 if (!rv->region()->video_locked()) {
2759 if (rv->region()->locked()) {
2762 _views.push_back (AVDraggingView (rv));
2767 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2769 Drag::start_grab (event);
2770 if (_editor->session() == 0) {
2774 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2780 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2784 _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2785 _max_backwards_drag = (
2786 ARDOUR_UI::instance()->video_timeline->get_duration()
2787 + ARDOUR_UI::instance()->video_timeline->get_offset()
2788 - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2791 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2792 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2793 _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv (i->initial_position);
2796 DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2799 Timecode::Time timecode;
2800 _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2801 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);
2802 show_verbose_cursor_text (buf);
2806 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2808 if (_editor->session() == 0) {
2811 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2815 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2819 samplecnt_t dt = adjusted_current_sample (event) - raw_grab_sample() + _pointer_sample_offset;
2820 dt = ARDOUR_UI::instance()->video_timeline->quantify_samples_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2822 if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2823 dt = - _max_backwards_drag;
2826 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2827 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2829 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2830 RegionView* rv = i->view;
2831 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2834 rv->region()->clear_changes ();
2835 rv->region()->suspend_property_changes();
2837 rv->region()->set_position(i->initial_position + dt);
2838 rv->region_changed(ARDOUR::Properties::position);
2841 const samplepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2842 Timecode::Time timecode;
2843 Timecode::Time timediff;
2845 _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2846 _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2847 snprintf (buf, sizeof (buf),
2848 "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2849 "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2850 , _("Video Start:"),
2851 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2853 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2855 show_verbose_cursor_text (buf);
2859 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2861 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2868 if (!movement_occurred || ! _editor->session()) {
2872 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2874 _editor->begin_reversible_command (_("Move Video"));
2876 XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2877 ARDOUR_UI::instance()->video_timeline->save_undo();
2878 XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2879 _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2881 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2882 i->view->drag_end();
2883 i->view->region()->resume_property_changes ();
2885 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2888 _editor->session()->maybe_update_session_range(
2889 std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::sampleoffset_t) 0),
2890 std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::sampleoffset_t) 0)
2894 _editor->commit_reversible_command ();
2898 VideoTimeLineDrag::aborted (bool)
2900 if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2903 ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2904 ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2906 for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2907 i->view->region()->resume_property_changes ();
2908 i->view->region()->set_position(i->initial_position);
2912 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2913 : RegionDrag (e, i, p, v)
2914 , _operation (StartTrim)
2915 , _preserve_fade_anchor (preserve_fade_anchor)
2916 , _jump_position_when_done (false)
2918 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2922 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2924 samplepos_t const region_start = _primary->region()->position();
2925 samplepos_t const region_end = _primary->region()->last_sample();
2926 samplecnt_t const region_length = _primary->region()->length();
2928 samplepos_t const pf = adjusted_current_sample (event);
2929 setup_snap_delta (MusicSample(region_start, 0));
2931 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2932 /* Move the contents of the region around without changing the region bounds */
2933 _operation = ContentsTrim;
2934 _preview_video = false;
2935 Drag::start_grab (event, _editor->cursors()->trimmer);
2937 /* These will get overridden for a point trim.*/
2938 if (pf < (region_start + region_length/2)) {
2939 /* closer to front */
2940 _operation = StartTrim;
2941 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2942 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2944 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2948 _operation = EndTrim;
2949 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2950 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2952 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2956 /* jump trim disabled for now
2957 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2958 _jump_position_when_done = true;
2962 switch (_operation) {
2964 show_verbose_cursor_time (region_start);
2967 show_verbose_cursor_duration (region_start, region_end);
2970 show_verbose_cursor_time (pf);
2973 show_view_preview (_operation == StartTrim ? region_start : region_end);
2975 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2976 i->view->region()->suspend_property_changes ();
2981 TrimDrag::motion (GdkEvent* event, bool first_move)
2983 RegionView* rv = _primary;
2985 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2986 sampleoffset_t sample_delta = 0;
2988 MusicSample adj_sample = adjusted_sample (_drags->current_pointer_sample () + snap_delta (event->button.state), event, true);
2989 samplecnt_t dt = adj_sample.sample - raw_grab_sample () + _pointer_sample_offset - snap_delta (event->button.state);
2995 switch (_operation) {
2997 trim_type = "Region start trim";
3000 trim_type = "Region end trim";
3003 trim_type = "Region content trim";
3010 _editor->begin_reversible_command (trim_type);
3012 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3013 RegionView* rv = i->view;
3014 rv->region()->playlist()->clear_owned_changes ();
3016 if (_operation == StartTrim) {
3017 rv->trim_front_starting ();
3020 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
3023 arv->temporarily_hide_envelope ();
3027 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
3028 insert_result = _editor->motion_frozen_playlists.insert (pl);
3030 if (insert_result.second) {
3034 MidiRegionView* const mrv = dynamic_cast<MidiRegionView*> (rv);
3035 /* a MRV start trim may change the source length. ensure we cover all playlists here */
3036 if (mrv && _operation == StartTrim) {
3037 vector<boost::shared_ptr<Playlist> > all_playlists;
3038 _editor->session()->playlists()->get (all_playlists);
3039 for (vector<boost::shared_ptr<Playlist> >::iterator x = all_playlists.begin(); x != all_playlists.end(); ++x) {
3041 if ((*x)->uses_source (rv->region()->source(0))) {
3042 insert_result = _editor->motion_frozen_playlists.insert (*x);
3043 if (insert_result.second) {
3044 (*x)->clear_owned_changes ();
3054 bool non_overlap_trim = false;
3056 if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
3057 non_overlap_trim = true;
3060 /* contstrain trim to fade length */
3061 if (_preserve_fade_anchor) {
3062 switch (_operation) {
3064 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3065 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3067 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3068 if (ar->locked()) continue;
3069 samplecnt_t len = ar->fade_in()->back()->when;
3070 if (len < dt) dt = min(dt, len);
3074 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3075 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3077 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3078 if (ar->locked()) continue;
3079 samplecnt_t len = ar->fade_out()->back()->when;
3080 if (len < -dt) dt = max(dt, -len);
3088 switch (_operation) {
3090 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3091 bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3092 , adj_sample.division);
3094 if (changed && _preserve_fade_anchor) {
3095 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3097 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3098 samplecnt_t len = ar->fade_in()->back()->when;
3099 samplecnt_t diff = ar->first_sample() - i->initial_position;
3100 samplepos_t new_length = len - diff;
3101 i->anchored_fade_length = min (ar->length(), new_length);
3102 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true /*START*/ );
3103 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3110 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3111 bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, adj_sample.division);
3112 if (changed && _preserve_fade_anchor) {
3113 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3115 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3116 samplecnt_t len = ar->fade_out()->back()->when;
3117 samplecnt_t diff = 1 + ar->last_sample() - i->initial_end;
3118 samplepos_t new_length = len + diff;
3119 i->anchored_fade_length = min (ar->length(), new_length);
3120 //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false /*END*/ );
3121 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3129 sample_delta = (last_pointer_sample() - adjusted_current_sample(event));
3131 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3132 i->view->move_contents (sample_delta);
3138 switch (_operation) {
3140 show_verbose_cursor_time (rv->region()->position());
3143 show_verbose_cursor_duration (rv->region()->position(), rv->region()->last_sample());
3146 // show_verbose_cursor_time (sample_delta);
3149 show_view_preview ((_operation == StartTrim ? rv->region()->position() : rv->region()->last_sample()));
3153 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3155 if (movement_occurred) {
3156 motion (event, false);
3158 if (_operation == StartTrim) {
3159 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3161 /* This must happen before the region's StatefulDiffCommand is created, as it may
3162 `correct' (ahem) the region's _start from being negative to being zero. It
3163 needs to be zero in the undo record.
3165 i->view->trim_front_ending ();
3167 if (_preserve_fade_anchor) {
3168 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3170 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3171 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3172 ar->set_fade_in_length(i->anchored_fade_length);
3173 ar->set_fade_in_active(true);
3176 if (_jump_position_when_done) {
3177 i->view->region()->set_position (i->initial_position);
3180 } else if (_operation == EndTrim) {
3181 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3182 if (_preserve_fade_anchor) {
3183 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3185 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3186 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3187 ar->set_fade_out_length(i->anchored_fade_length);
3188 ar->set_fade_out_active(true);
3191 if (_jump_position_when_done) {
3192 i->view->region()->set_position (i->initial_end - i->view->region()->length());
3197 if (!_editor->selection->selected (_primary)) {
3198 _primary->thaw_after_trim ();
3200 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3201 i->view->thaw_after_trim ();
3202 i->view->enable_display (true);
3206 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3207 /* Trimming one region may affect others on the playlist, so we need
3208 to get undo Commands from the whole playlist rather than just the
3209 region. Use motion_frozen_playlists (a set) to make sure we don't
3210 diff a given playlist more than once.
3213 vector<Command*> cmds;
3215 _editor->session()->add_commands (cmds);
3219 _editor->motion_frozen_playlists.clear ();
3220 _editor->commit_reversible_command();
3223 /* no mouse movement */
3224 if (adjusted_current_sample (event) != adjusted_sample (_drags->current_pointer_sample(), event, false).sample) {
3225 _editor->point_trim (event, adjusted_current_sample (event));
3229 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3230 i->view->region()->resume_property_changes ();
3235 TrimDrag::aborted (bool movement_occurred)
3237 /* Our motion method is changing model state, so use the Undo system
3238 to cancel. Perhaps not ideal, as this will leave an Undo point
3239 behind which may be slightly odd from the user's point of view.
3243 memset (&ev, 0, sizeof (GdkEvent));
3244 finished (&ev, true);
3246 if (movement_occurred) {
3247 _editor->session()->undo (1);
3250 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3251 i->view->region()->resume_property_changes ();
3256 TrimDrag::setup_pointer_sample_offset ()
3258 list<DraggingView>::iterator i = _views.begin ();
3259 while (i != _views.end() && i->view != _primary) {
3263 if (i == _views.end()) {
3267 switch (_operation) {
3269 _pointer_sample_offset = raw_grab_sample() - i->initial_position;
3272 _pointer_sample_offset = raw_grab_sample() - i->initial_end;
3279 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3282 , _old_grid_type (e->grid_type())
3283 , _old_snap_mode (e->snap_mode())
3286 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3287 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3289 _real_section = &_marker->meter();
3294 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3296 Drag::start_grab (event, cursor);
3297 show_verbose_cursor_time (adjusted_current_sample(event));
3301 MeterMarkerDrag::setup_pointer_sample_offset ()
3303 _pointer_sample_offset = raw_grab_sample() - _marker->meter().sample();
3307 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3310 // create a dummy marker to catch events, then hide it.
3313 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3315 _marker = new MeterMarker (
3317 *_editor->meter_group,
3318 UIConfiguration::instance().color ("meter marker"),
3320 *new MeterSection (_marker->meter())
3323 /* use the new marker for the grab */
3324 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3327 TempoMap& map (_editor->session()->tempo_map());
3328 /* get current state */
3329 before_state = &map.get_state();
3332 _editor->begin_reversible_command (_("move meter mark"));
3334 _editor->begin_reversible_command (_("copy meter mark"));
3336 Timecode::BBT_Time bbt = _real_section->bbt();
3338 /* we can't add a meter where one currently exists */
3339 if (_real_section->sample() < adjusted_current_sample (event, false)) {
3344 const samplepos_t sample = map.sample_at_bbt (bbt);
3345 _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3346 , bbt, sample, _real_section->position_lock_style());
3347 if (!_real_section) {
3353 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3354 if (_real_section->position_lock_style() != AudioTime) {
3355 _editor->set_grid_to (GridTypeBar);
3356 _editor->set_snap_mode (SnapMagnetic);
3360 samplepos_t pf = adjusted_current_sample (event);
3362 if (_real_section->position_lock_style() == AudioTime && _editor->grid_musical()) {
3363 /* never snap to music for audio locked */
3364 pf = adjusted_current_sample (event, false);
3367 _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3369 /* fake marker meeds to stay under the mouse, unlike the real one. */
3370 _marker->set_position (adjusted_current_sample (event, false));
3372 show_verbose_cursor_time (_real_section->sample());
3373 _editor->set_snapped_cursor_position(_real_section->sample());
3377 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3379 if (!movement_occurred) {
3380 if (was_double_click()) {
3381 _editor->edit_meter_marker (*_marker);
3386 /* reinstate old snap setting */
3387 _editor->set_grid_to (_old_grid_type);
3388 _editor->set_snap_mode (_old_snap_mode);
3390 TempoMap& map (_editor->session()->tempo_map());
3392 XMLNode &after = map.get_state();
3393 _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3394 _editor->commit_reversible_command ();
3396 // delete the dummy marker we used for visual representation while moving.
3397 // a new visual marker will show up automatically.
3402 MeterMarkerDrag::aborted (bool moved)
3404 _marker->set_position (_marker->meter().sample ());
3406 /* reinstate old snap setting */
3407 _editor->set_grid_to (_old_grid_type);
3408 _editor->set_snap_mode (_old_snap_mode);
3410 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3411 // delete the dummy marker we used for visual representation while moving.
3412 // a new visual marker will show up automatically.
3417 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3420 , _grab_bpm (120.0, 4.0)
3424 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3426 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3427 _real_section = &_marker->tempo();
3428 _movable = !_real_section->initial();
3429 _grab_bpm = Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), _real_section->end_note_types_per_minute());
3430 _grab_qn = _real_section->pulse() * 4.0;
3435 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3437 Drag::start_grab (event, cursor);
3438 if (!_real_section->active()) {
3439 show_verbose_cursor_text (_("inactive"));
3441 show_verbose_cursor_time (adjusted_current_sample (event));
3446 TempoMarkerDrag::setup_pointer_sample_offset ()
3448 _pointer_sample_offset = raw_grab_sample() - _real_section->sample();
3452 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3454 if (!_real_section->active()) {
3457 TempoMap& map (_editor->session()->tempo_map());
3461 // mvc drag - create a dummy marker to catch events, hide it.
3464 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3466 TempoSection section (_marker->tempo());
3468 _marker = new TempoMarker (
3470 *_editor->tempo_group,
3471 UIConfiguration::instance().color ("tempo marker"),
3473 *new TempoSection (_marker->tempo())
3476 /* use the new marker for the grab */
3477 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3480 /* get current state */
3481 _before_state = &map.get_state();
3484 _editor->begin_reversible_command (_("move tempo mark"));
3487 const Tempo tempo (_marker->tempo());
3488 const samplepos_t sample = adjusted_current_sample (event) + 1;
3490 _editor->begin_reversible_command (_("copy tempo mark"));
3492 if (_real_section->position_lock_style() == MusicTime) {
3493 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3494 _real_section = map.add_tempo (tempo, map.exact_qn_at_sample (sample, divisions), 0, MusicTime);
3496 _real_section = map.add_tempo (tempo, 0.0, sample, AudioTime);
3499 if (!_real_section) {
3506 if (ArdourKeyboard::indicates_constraint (event->button.state) && ArdourKeyboard::indicates_copy (event->button.state)) {
3507 double new_bpm = max (1.5, _grab_bpm.end_note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3509 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (_real_section->note_types_per_minute(), _real_section->note_type(), new_bpm));
3510 strs << "end:" << fixed << setprecision(3) << new_bpm;
3511 show_verbose_cursor_text (strs.str());
3513 } else if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3514 /* use vertical movement to alter tempo .. should be log */
3515 double new_bpm = max (1.5, _grab_bpm.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3517 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type(), _real_section->end_note_types_per_minute()));
3518 strs << "start:" << fixed << setprecision(3) << new_bpm;
3519 show_verbose_cursor_text (strs.str());
3521 } else if (_movable && !_real_section->locked_to_meter()) {
3524 if (_editor->grid_musical()) {
3525 /* we can't snap to a grid that we are about to move.
3526 * gui_move_tempo() will sort out snap using the supplied beat divisions.
3528 pf = adjusted_current_sample (event, false);
3530 pf = adjusted_current_sample (event);
3533 /* snap to beat is 1, snap to bar is -1 (sorry) */
3534 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3536 map.gui_set_tempo_position (_real_section, pf, sub_num);
3538 show_verbose_cursor_time (_real_section->sample());
3539 _editor->set_snapped_cursor_position(_real_section->sample());
3541 _marker->set_position (adjusted_current_sample (event, false));
3545 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3547 if (!_real_section->active()) {
3550 if (!movement_occurred) {
3551 if (was_double_click()) {
3552 _editor->edit_tempo_marker (*_marker);
3557 TempoMap& map (_editor->session()->tempo_map());
3559 XMLNode &after = map.get_state();
3560 _editor->session()->add_command (new MementoCommand<TempoMap>(map, _before_state, &after));
3561 _editor->commit_reversible_command ();
3563 // delete the dummy marker we used for visual representation while moving.
3564 // a new visual marker will show up automatically.
3569 TempoMarkerDrag::aborted (bool moved)
3571 _marker->set_position (_marker->tempo().sample());
3573 TempoMap& map (_editor->session()->tempo_map());
3574 map.set_state (*_before_state, Stateful::current_state_version);
3575 // delete the dummy (hidden) marker we used for events while moving.
3580 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3585 , _drag_valid (true)
3587 DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3592 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3594 Drag::start_grab (event, cursor);
3595 TempoMap& map (_editor->session()->tempo_map());
3596 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3598 if (adjusted_current_sample (event, false) <= _tempo->sample()) {
3599 _drag_valid = false;
3603 _editor->tempo_curve_selected (_tempo, true);
3606 if (_tempo->clamped()) {
3607 TempoSection* prev = map.previous_tempo_section (_tempo);
3609 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3613 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3614 show_verbose_cursor_text (sstr.str());
3618 BBTRulerDrag::setup_pointer_sample_offset ()
3620 TempoMap& map (_editor->session()->tempo_map());
3621 /* get current state */
3622 _before_state = &map.get_state();
3624 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3625 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3628 if (divisions > 0) {
3629 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3631 /* while it makes some sense for the user to determine the division to 'grab',
3632 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3633 and the result over steep tempo curves. Use sixteenths.
3635 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3638 _grab_qn = map.quarter_note_at_beat (beat);
3640 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3645 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3652 _editor->begin_reversible_command (_("stretch tempo"));
3655 TempoMap& map (_editor->session()->tempo_map());
3658 if (_editor->grid_musical()) {
3659 pf = adjusted_current_sample (event, false);
3661 pf = adjusted_current_sample (event);
3664 if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3665 /* adjust previous tempo to match pointer sample */
3666 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.sample_at_quarter_note (_grab_qn), pf, _grab_qn, map.quarter_note_at_sample (pf));
3670 if (_tempo->clamped()) {
3671 TempoSection* prev = map.previous_tempo_section (_tempo);
3673 _editor->tempo_curve_selected (prev, true);
3674 sstr << "end: " << fixed << setprecision(3) << prev->end_note_types_per_minute() << "\n";
3677 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3678 show_verbose_cursor_text (sstr.str());
3682 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3684 if (!movement_occurred) {
3688 TempoMap& map (_editor->session()->tempo_map());
3690 _editor->tempo_curve_selected (_tempo, false);
3691 if (_tempo->clamped()) {
3692 TempoSection* prev_tempo = map.previous_tempo_section (_tempo);
3694 _editor->tempo_curve_selected (prev_tempo, false);
3698 if (!movement_occurred || !_drag_valid) {
3702 XMLNode &after = map.get_state();
3703 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3704 _editor->commit_reversible_command ();
3709 BBTRulerDrag::aborted (bool moved)
3712 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3716 TempoTwistDrag::TempoTwistDrag (Editor* e, ArdourCanvas::Item* i)
3722 , _drag_valid (true)
3725 DEBUG_TRACE (DEBUG::Drags, "New TempoTwistDrag\n");
3730 TempoTwistDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3732 Drag::start_grab (event, cursor);
3733 TempoMap& map (_editor->session()->tempo_map());
3734 /* get current state */
3735 _before_state = &map.get_state();
3736 _tempo = const_cast<TempoSection*> (&map.tempo_section_at_sample (raw_grab_sample()));
3738 if (_tempo->locked_to_meter()) {
3739 _drag_valid = false;
3743 _next_tempo = map.next_tempo_section (_tempo);
3745 if (!map.next_tempo_section (_next_tempo)) {
3746 _drag_valid = false;
3747 finished (event, false);
3751 _editor->tempo_curve_selected (_tempo, true);
3752 _editor->tempo_curve_selected (_next_tempo, true);
3755 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3756 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3757 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3758 show_verbose_cursor_text (sstr.str());
3760 _drag_valid = false;
3763 _grab_tempo = Tempo (_tempo->note_types_per_minute(), _tempo->note_type());
3767 TempoTwistDrag::setup_pointer_sample_offset ()
3769 TempoMap& map (_editor->session()->tempo_map());
3770 const double beat_at_sample = max (0.0, map.beat_at_sample (raw_grab_sample()));
3771 const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3774 if (divisions > 0) {
3775 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * divisions)) / divisions);
3777 /* while it makes some sense for the user to determine the division to 'grab',
3778 grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3779 and the result over steep tempo curves. Use sixteenths.
3781 beat = floor (beat_at_sample) + (floor (((beat_at_sample - floor (beat_at_sample)) * 4)) / 4);
3784 _grab_qn = map.quarter_note_at_beat (beat);
3786 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3791 TempoTwistDrag::motion (GdkEvent* event, bool first_move)
3794 if (!_next_tempo || !_drag_valid) {
3798 TempoMap& map (_editor->session()->tempo_map());
3801 _editor->begin_reversible_command (_("twist tempo"));
3806 if (_editor->grid_musical()) {
3807 pf = adjusted_current_sample (event, false);
3809 pf = adjusted_current_sample (event);
3812 /* adjust this and the next tempi to match pointer sample */
3813 double new_bpm = max (1.5, _grab_tempo.note_types_per_minute() + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3814 _editor->session()->tempo_map().gui_twist_tempi (_tempo, new_bpm, map.sample_at_quarter_note (_grab_qn), pf);
3817 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute() << "\n";
3818 sstr << "end: " << fixed << setprecision(3) << _tempo->end_note_types_per_minute() << "\n";
3819 sstr << "start: " << fixed << setprecision(3) << _next_tempo->note_types_per_minute();
3820 show_verbose_cursor_text (sstr.str());
3824 TempoTwistDrag::finished (GdkEvent* event, bool movement_occurred)
3826 if (!movement_occurred || !_drag_valid) {
3830 _editor->tempo_curve_selected (_tempo, false);
3831 _editor->tempo_curve_selected (_next_tempo, false);
3833 TempoMap& map (_editor->session()->tempo_map());
3834 XMLNode &after = map.get_state();
3835 _editor->session()->add_command(new MementoCommand<TempoMap>(map, _before_state, &after));
3836 _editor->commit_reversible_command ();
3840 TempoTwistDrag::aborted (bool moved)
3843 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3847 TempoEndDrag::TempoEndDrag (Editor* e, ArdourCanvas::Item* i)
3852 , _drag_valid (true)
3854 DEBUG_TRACE (DEBUG::Drags, "New TempoEndDrag\n");
3855 TempoMarker* marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3856 _tempo = &marker->tempo();
3857 _grab_qn = _tempo->pulse() * 4.0;
3861 TempoEndDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3863 Drag::start_grab (event, cursor);
3864 TempoMap& tmap (_editor->session()->tempo_map());
3866 /* get current state */
3867 _before_state = &tmap.get_state();
3869 if (_tempo->locked_to_meter()) {
3870 _drag_valid = false;
3876 TempoSection* prev = 0;
3877 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3878 _editor->tempo_curve_selected (tmap.previous_tempo_section (_tempo), true);
3879 sstr << "end: " << fixed << setprecision(3) << tmap.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3882 if (_tempo->clamped()) {
3883 _editor->tempo_curve_selected (_tempo, true);
3884 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3887 show_verbose_cursor_text (sstr.str());
3891 TempoEndDrag::setup_pointer_sample_offset ()
3893 TempoMap& map (_editor->session()->tempo_map());
3895 _pointer_sample_offset = raw_grab_sample() - map.sample_at_quarter_note (_grab_qn);
3900 TempoEndDrag::motion (GdkEvent* event, bool first_move)
3906 TempoMap& map (_editor->session()->tempo_map());
3909 _editor->begin_reversible_command (_("stretch end tempo"));
3912 samplepos_t const pf = adjusted_current_sample (event, false);
3913 map.gui_stretch_tempo_end (&map.tempo_section_at_sample (_tempo->sample() - 1), map.sample_at_quarter_note (_grab_qn), pf);
3916 sstr << "end: " << fixed << setprecision(3) << map.tempo_section_at_sample (_tempo->sample() - 1).end_note_types_per_minute() << "\n";
3918 if (_tempo->clamped()) {
3919 sstr << "start: " << fixed << setprecision(3) << _tempo->note_types_per_minute();
3922 show_verbose_cursor_text (sstr.str());
3926 TempoEndDrag::finished (GdkEvent* event, bool movement_occurred)
3928 if (!movement_occurred || !_drag_valid) {
3932 TempoMap& tmap (_editor->session()->tempo_map());
3934 XMLNode &after = tmap.get_state();
3935 _editor->session()->add_command(new MementoCommand<TempoMap>(tmap, _before_state, &after));
3936 _editor->commit_reversible_command ();
3938 TempoSection* prev = 0;
3939 if ((prev = tmap.previous_tempo_section (_tempo)) != 0) {
3940 _editor->tempo_curve_selected (prev, false);
3943 if (_tempo->clamped()) {
3944 _editor->tempo_curve_selected (_tempo, false);
3950 TempoEndDrag::aborted (bool moved)
3953 _editor->session()->tempo_map().set_state (*_before_state, Stateful::current_state_version);
3957 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3958 : Drag (e, &c.track_canvas_item(), false)
3963 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3966 /** Do all the things we do when dragging the playhead to make it look as though
3967 * we have located, without actually doing the locate (because that would cause
3968 * the diskstream buffers to be refilled, which is too slow).
3971 CursorDrag::fake_locate (samplepos_t t)
3973 if (_editor->session () == 0) {
3977 _editor->playhead_cursor->set_position (t);
3979 Session* s = _editor->session ();
3980 if (s->timecode_transmission_suspended ()) {
3981 samplepos_t const f = _editor->playhead_cursor->current_sample ();
3982 /* This is asynchronous so it will be sent "now"
3984 s->send_mmc_locate (f);
3985 /* These are synchronous and will be sent during the next
3988 s->queue_full_time_code ();
3989 s->queue_song_position_pointer ();
3992 show_verbose_cursor_time (t);
3993 _editor->UpdateAllTransportClocks (t);
3997 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3999 Drag::start_grab (event, c);
4000 setup_snap_delta (MusicSample (_editor->playhead_cursor->current_sample(), 0));
4002 _grab_zoom = _editor->samples_per_pixel;
4004 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4006 _editor->snap_to_with_modifier (where, event);
4007 _editor->_dragging_playhead = true;
4008 _editor->_control_scroll_target = where.sample;
4010 Session* s = _editor->session ();
4012 /* grab the track canvas item as well */
4014 _cursor.track_canvas_item().grab();
4017 if (_was_rolling && _stop) {
4021 if (s->is_auditioning()) {
4022 s->cancel_audition ();
4026 if (AudioEngine::instance()->running()) {
4028 /* do this only if we're the engine is connected
4029 * because otherwise this request will never be
4030 * serviced and we'll busy wait forever. likewise,
4031 * notice if we are disconnected while waiting for the
4032 * request to be serviced.
4035 s->request_suspend_timecode_transmission ();
4036 while (AudioEngine::instance()->running() && !s->timecode_transmission_suspended ()) {
4037 /* twiddle our thumbs */
4042 fake_locate (where.sample - snap_delta (event->button.state));
4048 CursorDrag::motion (GdkEvent* event, bool)
4050 MusicSample where (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4052 _editor->snap_to_with_modifier (where, event);
4054 if (where.sample != last_pointer_sample()) {
4055 fake_locate (where.sample - snap_delta (event->button.state));
4058 //maybe do zooming, too, if the option is enabled
4059 if (UIConfiguration::instance ().get_use_time_rulers_to_zoom_with_vertical_drag () ) {
4061 //To avoid accidental zooming, the mouse must move exactly vertical, not diagonal, to trigger a zoom step
4062 //we use screen coordinates for this, not canvas-based grab_x
4063 double mx = event->button.x;
4064 double dx = fabs(mx - _last_mx);
4065 double my = event->button.y;
4066 double dy = fabs(my - _last_my);
4069 //do zooming in windowed "steps" so it feels more reversible ?
4070 const int stepsize = 2; //stepsize ==1 means "trigger on every pixel of movement"
4071 int y_delta = grab_y() - current_pointer_y();
4072 y_delta = y_delta / stepsize;
4074 //if all requirements are met, do the actual zoom
4075 const double scale = 1.2;
4076 if ( (dy>dx) && (_last_dx ==0) && (y_delta != _last_y_delta) ) {
4077 if ( _last_y_delta > y_delta ) {
4078 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
4080 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
4082 _last_y_delta = y_delta;
4093 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
4095 _editor->_dragging_playhead = false;
4097 _cursor.track_canvas_item().ungrab();
4099 if (!movement_occurred && _stop) {
4103 motion (event, false);
4105 Session* s = _editor->session ();
4107 s->request_locate (_editor->playhead_cursor->current_sample (), _was_rolling);
4108 _editor->_pending_locate_request = true;
4109 s->request_resume_timecode_transmission ();
4114 CursorDrag::aborted (bool)
4116 _cursor.track_canvas_item().ungrab();
4118 if (_editor->_dragging_playhead) {
4119 _editor->session()->request_resume_timecode_transmission ();
4120 _editor->_dragging_playhead = false;
4123 _editor->playhead_cursor->set_position (adjusted_sample (grab_sample (), 0, false).sample);
4126 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4127 : RegionDrag (e, i, p, v)
4129 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
4133 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4135 Drag::start_grab (event, cursor);
4137 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4138 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4139 setup_snap_delta (MusicSample (r->position(), 0));
4141 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
4142 show_view_preview (r->position() + r->fade_in()->back()->when);
4146 FadeInDrag::setup_pointer_sample_offset ()
4148 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4149 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
4150 _pointer_sample_offset = raw_grab_sample() - ((samplecnt_t) r->fade_in()->back()->when + r->position());
4154 FadeInDrag::motion (GdkEvent* event, bool)
4156 samplecnt_t fade_length;
4158 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4159 _editor->snap_to_with_modifier (pos, event);
4161 pos.sample -= snap_delta (event->button.state);
4163 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4165 if (pos.sample < (region->position() + 64)) {
4166 fade_length = 64; // this should be a minimum defined somewhere
4167 } else if (pos.sample > region->position() + region->length() - region->fade_out()->back()->when) {
4168 fade_length = region->length() - region->fade_out()->back()->when - 1;
4170 fade_length = pos.sample - region->position();
4173 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4175 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4181 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
4184 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
4185 show_view_preview (region->position() + fade_length);
4189 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
4191 if (!movement_occurred) {
4195 samplecnt_t fade_length;
4196 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4198 _editor->snap_to_with_modifier (pos, event);
4199 pos.sample -= snap_delta (event->button.state);
4201 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4203 if (pos.sample < (region->position() + 64)) {
4204 fade_length = 64; // this should be a minimum defined somewhere
4205 } else if (pos.sample >= region->position() + region->length() - region->fade_out()->back()->when) {
4206 fade_length = region->length() - region->fade_out()->back()->when - 1;
4208 fade_length = pos.sample - region->position();
4211 bool in_command = false;
4213 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4215 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4221 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
4222 XMLNode &before = alist->get_state();
4224 tmp->audio_region()->set_fade_in_length (fade_length);
4225 tmp->audio_region()->set_fade_in_active (true);
4228 _editor->begin_reversible_command (_("change fade in length"));
4231 XMLNode &after = alist->get_state();
4232 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4236 _editor->commit_reversible_command ();
4241 FadeInDrag::aborted (bool)
4243 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4244 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4250 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
4254 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
4255 : RegionDrag (e, i, p, v)
4257 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
4261 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4263 Drag::start_grab (event, cursor);
4265 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4266 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4267 setup_snap_delta (MusicSample (r->last_sample(), 0));
4269 show_verbose_cursor_duration (r->last_sample() - r->fade_out()->back()->when, r->last_sample());
4270 show_view_preview (r->fade_out()->back()->when);
4274 FadeOutDrag::setup_pointer_sample_offset ()
4276 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
4277 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
4278 _pointer_sample_offset = raw_grab_sample() - (r->length() - (samplecnt_t) r->fade_out()->back()->when + r->position());
4282 FadeOutDrag::motion (GdkEvent* event, bool)
4284 samplecnt_t fade_length;
4285 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4287 _editor->snap_to_with_modifier (pos, event);
4288 pos.sample -= snap_delta (event->button.state);
4290 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4292 if (pos.sample > (region->last_sample() - 64)) {
4293 fade_length = 64; // this should really be a minimum fade defined somewhere
4294 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4295 fade_length = region->length() - region->fade_in()->back()->when - 1;
4297 fade_length = region->last_sample() - pos.sample;
4300 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4302 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4308 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
4311 show_verbose_cursor_duration (region->last_sample() - fade_length, region->last_sample());
4312 show_view_preview (region->last_sample() - fade_length);
4316 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
4318 if (!movement_occurred) {
4322 samplecnt_t fade_length;
4323 MusicSample pos (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
4325 _editor->snap_to_with_modifier (pos, event);
4326 pos.sample -= snap_delta (event->button.state);
4328 boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
4330 if (pos.sample > (region->last_sample() - 64)) {
4331 fade_length = 64; // this should really be a minimum fade defined somewhere
4332 } else if (pos.sample <= region->position() + region->fade_in()->back()->when) {
4333 fade_length = region->length() - region->fade_in()->back()->when - 1;
4335 fade_length = region->last_sample() - pos.sample;
4338 bool in_command = false;
4340 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4342 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4348 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
4349 XMLNode &before = alist->get_state();
4351 tmp->audio_region()->set_fade_out_length (fade_length);
4352 tmp->audio_region()->set_fade_out_active (true);
4355 _editor->begin_reversible_command (_("change fade out length"));
4358 XMLNode &after = alist->get_state();
4359 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
4363 _editor->commit_reversible_command ();
4368 FadeOutDrag::aborted (bool)
4370 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
4371 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
4377 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
4381 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
4383 , _selection_changed (false)
4385 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
4386 Gtk::Window* toplevel = _editor->current_toplevel();
4387 _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
4391 _points.push_back (ArdourCanvas::Duple (0, 0));
4393 _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4396 MarkerDrag::~MarkerDrag ()
4398 for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4403 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4405 location = new Location (*l);
4406 markers.push_back (m);
4411 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4413 Drag::start_grab (event, cursor);
4417 Location *location = _editor->find_location_from_marker (_marker, is_start);
4418 _editor->_dragging_edit_point = true;
4420 update_item (location);
4422 // _drag_line->show();
4423 // _line->raise_to_top();
4426 show_verbose_cursor_time (location->start());
4428 show_verbose_cursor_time (location->end());
4430 show_view_preview ((is_start ? location->start() : location->end()) + _video_sample_offset);
4431 setup_snap_delta (MusicSample (is_start ? location->start() : location->end(), 0));
4433 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4436 case Selection::Toggle:
4437 /* we toggle on the button release */
4439 case Selection::Set:
4440 if (!_editor->selection->selected (_marker)) {
4441 _editor->selection->set (_marker);
4442 _selection_changed = true;
4445 case Selection::Extend:
4447 Locations::LocationList ll;
4448 list<ArdourMarker*> to_add;
4450 _editor->selection->markers.range (s, e);
4451 s = min (_marker->position(), s);
4452 e = max (_marker->position(), e);
4455 if (e < max_samplepos) {
4458 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4459 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4460 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4463 to_add.push_back (lm->start);
4466 to_add.push_back (lm->end);
4470 if (!to_add.empty()) {
4471 _editor->selection->add (to_add);
4472 _selection_changed = true;
4476 case Selection::Add:
4477 _editor->selection->add (_marker);
4478 _selection_changed = true;
4483 /* Set up copies for us to manipulate during the drag
4486 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4488 Location* l = _editor->find_location_from_marker (*i, is_start);
4495 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4497 /* range: check that the other end of the range isn't
4500 CopiedLocationInfo::iterator x;
4501 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4502 if (*(*x).location == *l) {
4506 if (x == _copied_locations.end()) {
4507 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4509 (*x).markers.push_back (*i);
4510 (*x).move_both = true;
4518 MarkerDrag::setup_pointer_sample_offset ()
4521 Location *location = _editor->find_location_from_marker (_marker, is_start);
4522 _pointer_sample_offset = raw_grab_sample() - (is_start ? location->start() : location->end());
4526 MarkerDrag::setup_video_sample_offset ()
4528 _video_sample_offset = 0;
4529 _preview_video = true;
4533 MarkerDrag::motion (GdkEvent* event, bool)
4535 samplecnt_t f_delta = 0;
4537 bool move_both = false;
4538 Location *real_location;
4539 Location *copy_location = 0;
4540 samplecnt_t const sd = snap_delta (event->button.state);
4542 samplecnt_t const newframe = adjusted_sample (_drags->current_pointer_sample () + sd, event, true).sample - sd;
4543 samplepos_t next = newframe;
4545 if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4549 CopiedLocationInfo::iterator x;
4551 /* find the marker we're dragging, and compute the delta */
4553 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4555 copy_location = (*x).location;
4557 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4559 /* this marker is represented by this
4560 * CopiedLocationMarkerInfo
4563 if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4568 if (real_location->is_mark()) {
4569 f_delta = newframe - copy_location->start();
4573 switch (_marker->type()) {
4574 case ArdourMarker::SessionStart:
4575 case ArdourMarker::RangeStart:
4576 case ArdourMarker::LoopStart:
4577 case ArdourMarker::PunchIn:
4578 f_delta = newframe - copy_location->start();
4581 case ArdourMarker::SessionEnd:
4582 case ArdourMarker::RangeEnd:
4583 case ArdourMarker::LoopEnd:
4584 case ArdourMarker::PunchOut:
4585 f_delta = newframe - copy_location->end();
4588 /* what kind of marker is this ? */
4597 if (x == _copied_locations.end()) {
4598 /* hmm, impossible - we didn't find the dragged marker */
4602 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4604 /* now move them all */
4606 for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4608 copy_location = x->location;
4610 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4614 if (real_location->locked()) {
4618 if (copy_location->is_mark()) {
4621 copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4625 samplepos_t new_start = copy_location->start() + f_delta;
4626 samplepos_t new_end = copy_location->end() + f_delta;
4628 if (is_start) { // start-of-range marker
4630 if (move_both || (*x).move_both) {
4631 copy_location->set_start (new_start, false, true, divisions);
4632 copy_location->set_end (new_end, false, true, divisions);
4633 } else if (new_start < copy_location->end()) {
4634 copy_location->set_start (new_start, false, true, divisions);
4635 } else if (newframe > 0) {
4636 //_editor->snap_to (next, RoundUpAlways, true);
4637 copy_location->set_end (next, false, true, divisions);
4638 copy_location->set_start (newframe, false, true, divisions);
4641 } else { // end marker
4643 if (move_both || (*x).move_both) {
4644 copy_location->set_end (new_end, divisions);
4645 copy_location->set_start (new_start, false, true, divisions);
4646 } else if (new_end > copy_location->start()) {
4647 copy_location->set_end (new_end, false, true, divisions);
4648 } else if (newframe > 0) {
4649 //_editor->snap_to (next, RoundDownAlways, true);
4650 copy_location->set_start (next, false, true, divisions);
4651 copy_location->set_end (newframe, false, true, divisions);
4656 update_item (copy_location);
4658 /* now lookup the actual GUI items used to display this
4659 * location and move them to wherever the copy of the location
4660 * is now. This means that the logic in ARDOUR::Location is
4661 * still enforced, even though we are not (yet) modifying
4662 * the real Location itself.
4665 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4668 lm->set_position (copy_location->start(), copy_location->end());
4673 assert (!_copied_locations.empty());
4675 show_verbose_cursor_time (newframe);
4676 show_view_preview (newframe + _video_sample_offset);
4677 _editor->set_snapped_cursor_position(newframe);
4681 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4683 if (!movement_occurred) {
4685 if (was_double_click()) {
4686 _editor->rename_marker (_marker);
4690 /* just a click, do nothing but finish
4691 off the selection process
4694 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4696 case Selection::Set:
4697 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4698 _editor->selection->set (_marker);
4699 _selection_changed = true;
4703 case Selection::Toggle:
4704 /* we toggle on the button release, click only */
4705 _editor->selection->toggle (_marker);
4706 _selection_changed = true;
4710 case Selection::Extend:
4711 case Selection::Add:
4715 if (_selection_changed) {
4716 _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4717 _editor->commit_reversible_selection_op();
4723 _editor->_dragging_edit_point = false;
4725 XMLNode &before = _editor->session()->locations()->get_state();
4726 bool in_command = false;
4728 MarkerSelection::iterator i;
4729 CopiedLocationInfo::iterator x;
4730 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4733 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4734 x != _copied_locations.end() && i != _editor->selection->markers.end();
4737 Location * location = _editor->find_location_from_marker (*i, is_start);
4741 if (location->locked()) {
4745 _editor->begin_reversible_command ( _("move marker") );
4748 if (location->is_mark()) {
4749 location->set_start (((*x).location)->start(), false, true, divisions);
4751 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4754 if (location->is_session_range()) {
4755 _editor->session()->set_session_range_is_free (false);
4761 XMLNode &after = _editor->session()->locations()->get_state();
4762 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4763 _editor->commit_reversible_command ();
4768 MarkerDrag::aborted (bool movement_occurred)
4770 if (!movement_occurred) {
4774 for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4776 /* move all markers to their original location */
4779 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4782 Location * location = _editor->find_location_from_marker (*m, is_start);
4785 (*m)->set_position (is_start ? location->start() : location->end());
4792 MarkerDrag::update_item (Location*)
4797 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4799 , _fixed_grab_x (0.0)
4800 , _fixed_grab_y (0.0)
4801 , _cumulative_x_drag (0.0)
4802 , _cumulative_y_drag (0.0)
4806 if (_zero_gain_fraction < 0.0) {
4807 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4810 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4812 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4818 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4820 Drag::start_grab (event, _editor->cursors()->fader);
4822 // start the grab at the center of the control point so
4823 // the point doesn't 'jump' to the mouse after the first drag
4824 _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4825 _fixed_grab_y = _point->get_y();
4827 setup_snap_delta (MusicSample (_editor->pixel_to_sample (_fixed_grab_x), 0));
4829 float const fraction = 1 - (_point->get_y() / _point->line().height());
4830 show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4832 _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4834 if (!_point->can_slide ()) {
4835 _x_constrained = true;
4840 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4842 double dx = _drags->current_pointer_x() - last_pointer_x();
4843 double dy = current_pointer_y() - last_pointer_y();
4844 bool need_snap = true;
4846 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4852 /* coordinate in pixels relative to the start of the region (for region-based automation)
4853 or track (for track-based automation) */
4854 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4855 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4857 // calculate zero crossing point. back off by .01 to stay on the
4858 // positive side of zero
4859 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4861 if (_x_constrained) {
4864 if (_y_constrained) {
4868 _cumulative_x_drag = cx - _fixed_grab_x;
4869 _cumulative_y_drag = cy - _fixed_grab_y;
4873 cy = min ((double) _point->line().height(), cy);
4875 // make sure we hit zero when passing through
4876 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4880 MusicSample cx_mf (_editor->pixel_to_sample (cx) + snap_delta (event->button.state), 0);
4882 if (!_x_constrained && need_snap) {
4883 _editor->snap_to_with_modifier (cx_mf, event);
4886 cx_mf.sample -= snap_delta (event->button.state);
4887 cx_mf.sample = min (cx_mf.sample, _point->line().maximum_time() + _point->line().offset());
4889 float const fraction = 1.0 - (cy / _point->line().height());
4892 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4893 _editor->begin_reversible_command (_("automation event move"));
4894 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4896 pair<float, float> result;
4897 result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_mf.sample), fraction, false, _pushing, _final_index);
4898 show_verbose_cursor_text (_point->line().get_verbose_cursor_relative_string (result.first, result.second));
4902 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4904 if (!movement_occurred) {
4907 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4908 _editor->reset_point_selection ();
4912 _point->line().end_drag (_pushing, _final_index);
4913 _editor->commit_reversible_command ();
4918 ControlPointDrag::aborted (bool)
4920 _point->line().reset ();
4924 ControlPointDrag::active (Editing::MouseMode m)
4926 if (m == Editing::MouseDraw) {
4927 /* always active in mouse draw */
4931 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4932 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4935 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4938 , _fixed_grab_x (0.0)
4939 , _fixed_grab_y (0.0)
4940 , _cumulative_y_drag (0)
4944 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4948 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4950 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4953 _item = &_line->grab_item ();
4955 /* need to get x coordinate in terms of parent (TimeAxisItemView)
4956 origin, and ditto for y.
4959 double mx = event->button.x;
4960 double my = event->button.y;
4962 _line->grab_item().canvas_to_item (mx, my);
4964 samplecnt_t const sample_within_region = (samplecnt_t) floor (mx * _editor->samples_per_pixel);
4966 if (!_line->control_points_adjacent (sample_within_region, _before, _after)) {
4967 /* no adjacent points */
4971 Drag::start_grab (event, _editor->cursors()->fader);
4973 /* store grab start in item sample */
4974 double const bx = _line->nth (_before)->get_x();
4975 double const ax = _line->nth (_after)->get_x();
4976 double const click_ratio = (ax - mx) / (ax - bx);
4978 double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4983 double fraction = 1.0 - (cy / _line->height());
4985 show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4989 LineDrag::motion (GdkEvent* event, bool first_move)
4991 double dy = current_pointer_y() - last_pointer_y();
4993 if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4997 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4999 _cumulative_y_drag = cy - _fixed_grab_y;
5002 cy = min ((double) _line->height(), cy);
5004 double const fraction = 1.0 - (cy / _line->height());
5008 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
5010 _editor->begin_reversible_command (_("automation range move"));
5011 _line->start_drag_line (_before, _after, initial_fraction);
5014 /* we are ignoring x position for this drag, so we can just pass in anything */
5015 pair<float, float> result;
5017 result = _line->drag_motion (0, fraction, true, false, ignored);
5018 show_verbose_cursor_text (_line->get_verbose_cursor_relative_string (result.first, result.second));
5022 LineDrag::finished (GdkEvent* event, bool movement_occurred)
5024 if (movement_occurred) {
5025 motion (event, false);
5026 _line->end_drag (false, 0);
5027 _editor->commit_reversible_command ();
5029 /* add a new control point on the line */
5031 AutomationTimeAxisView* atv;
5033 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
5034 samplepos_t where = grab_sample ();
5037 double cy = _fixed_grab_y;
5039 _line->grab_item().item_to_canvas (cx, cy);
5041 atv->add_automation_event (event, where, cy, false);
5042 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
5043 AudioRegionView* arv;
5045 if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
5046 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
5053 LineDrag::aborted (bool)
5058 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
5062 _region_view_grab_x (0.0),
5063 _cumulative_x_drag (0),
5067 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
5071 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
5073 Drag::start_grab (event);
5075 _line = reinterpret_cast<Line*> (_item);
5078 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
5080 double cx = event->button.x;
5081 double cy = event->button.y;
5083 _item->parent()->canvas_to_item (cx, cy);
5085 /* store grab start in parent sample */
5086 _region_view_grab_x = cx;
5088 _before = *(float*) _item->get_data ("position");
5090 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5092 _max_x = _editor->sample_to_pixel(_arv->get_duration());
5096 FeatureLineDrag::motion (GdkEvent*, bool)
5098 double dx = _drags->current_pointer_x() - last_pointer_x();
5100 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
5102 _cumulative_x_drag += dx;
5104 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
5113 boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
5115 _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
5117 float *pos = new float;
5120 _line->set_data ("position", pos);
5126 FeatureLineDrag::finished (GdkEvent*, bool)
5128 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
5129 _arv->update_transient(_before, _before);
5133 FeatureLineDrag::aborted (bool)
5138 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
5140 , _vertical_only (false)
5142 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
5146 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5148 Drag::start_grab (event);
5149 show_verbose_cursor_time (adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
5153 RubberbandSelectDrag::motion (GdkEvent* event, bool)
5159 samplepos_t const pf = adjusted_current_sample (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
5160 MusicSample grab (grab_sample (), 0);
5162 if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5163 _editor->snap_to_with_modifier (grab, event, RoundNearest, SnapToGrid_Scaled);
5165 grab.sample = raw_grab_sample ();
5168 /* base start and end on initial click position */
5170 if (pf < grab.sample) {
5175 start = grab.sample;
5178 if (current_pointer_y() < grab_y()) {
5179 y1 = current_pointer_y();
5182 y2 = current_pointer_y();
5186 if (start != end || y1 != y2) {
5188 double x1 = _editor->sample_to_pixel (start);
5189 double x2 = _editor->sample_to_pixel (end);
5190 const double min_dimension = 2.0;
5192 if (_vertical_only) {
5193 /* fixed 10 pixel width */
5197 x2 = min (x1 - min_dimension, x2);
5199 x2 = max (x1 + min_dimension, x2);
5204 y2 = min (y1 - min_dimension, y2);
5206 y2 = max (y1 + min_dimension, y2);
5209 /* translate rect into item space and set */
5211 ArdourCanvas::Rect r (x1, y1, x2, y2);
5213 /* this drag is a _trackview_only == true drag, so the y1 and
5214 * y2 (computed using current_pointer_y() and grab_y()) will be
5215 * relative to the top of the trackview group). The
5216 * rubberband rect has the same parent/scroll offset as the
5217 * the trackview group, so we can use the "r" rect directly
5218 * to set the shape of the rubberband.
5221 _editor->rubberband_rect->set (r);
5222 _editor->rubberband_rect->show();
5223 _editor->rubberband_rect->raise_to_top();
5225 show_verbose_cursor_time (pf);
5227 do_select_things (event, true);
5232 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
5236 samplepos_t grab = grab_sample ();
5237 samplepos_t lpf = last_pointer_sample ();
5239 if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
5240 grab = raw_grab_sample ();
5241 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
5255 if (current_pointer_y() < grab_y()) {
5256 y1 = current_pointer_y();
5259 y2 = current_pointer_y();
5263 select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
5267 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
5269 if (movement_occurred) {
5271 motion (event, false);
5272 do_select_things (event, false);
5278 bool do_deselect = true;
5279 MidiTimeAxisView* mtv;
5281 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
5283 if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
5284 /* nothing selected */
5285 add_midi_region (mtv, true);
5286 do_deselect = false;
5290 /* do not deselect if Primary or Tertiary (toggle-select or
5291 * extend-select are pressed.
5294 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
5295 !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
5302 _editor->rubberband_rect->hide();
5306 RubberbandSelectDrag::aborted (bool)
5308 _editor->rubberband_rect->hide ();
5311 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
5312 : RegionDrag (e, i, p, v)
5314 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
5315 _preview_video = false;
5319 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5321 Drag::start_grab (event, cursor);
5323 _editor->get_selection().add (_primary);
5325 MusicSample where (_primary->region()->position(), 0);
5326 setup_snap_delta (where);
5328 show_verbose_cursor_duration (where.sample, adjusted_current_sample (event), 0);
5332 TimeFXDrag::motion (GdkEvent* event, bool)
5334 RegionView* rv = _primary;
5335 StreamView* cv = rv->get_time_axis_view().view ();
5336 pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
5337 int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
5338 int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
5339 MusicSample pf (_editor->canvas_event_sample (event) + snap_delta (event->button.state), 0);
5341 _editor->snap_to_with_modifier (pf, event);
5342 pf.sample -= snap_delta (event->button.state);
5344 if (pf.sample > rv->region()->position()) {
5345 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf.sample, layers, layer);
5348 show_verbose_cursor_duration (_primary->region()->position(), pf.sample, 0);
5352 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
5354 /* this may have been a single click, no drag. We still want the dialog
5355 to show up in that case, so that the user can manually edit the
5356 parameters for the timestretch.
5359 float fraction = 1.0;
5361 if (movement_occurred) {
5363 motion (event, false);
5365 _primary->get_time_axis_view().hide_timestretch ();
5367 samplepos_t adjusted_sample_pos = adjusted_current_sample (event);
5369 if (adjusted_sample_pos < _primary->region()->position()) {
5370 /* backwards drag of the left edge - not usable */
5374 samplecnt_t newlen = adjusted_sample_pos - _primary->region()->position();
5376 fraction = (double) newlen / (double) _primary->region()->length();
5378 #ifndef USE_RUBBERBAND
5379 // Soundtouch uses fraction / 100 instead of normal (/ 1)
5380 if (_primary->region()->data_type() == DataType::AUDIO) {
5381 fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
5386 if (!_editor->get_selection().regions.empty()) {
5387 /* primary will already be included in the selection, and edit
5388 group shared editing will propagate selection across
5389 equivalent regions, so just use the current region
5393 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
5394 error << _("An error occurred while executing time stretch operation") << endmsg;
5400 TimeFXDrag::aborted (bool)
5402 _primary->get_time_axis_view().hide_timestretch ();
5405 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5408 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5412 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5414 Drag::start_grab (event);
5418 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5420 _editor->scrub (adjusted_current_sample (0, false), _drags->current_pointer_x ());
5424 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5426 if (movement_occurred && _editor->session()) {
5427 /* make sure we stop */
5428 _editor->session()->request_transport_speed (0.0);
5433 ScrubDrag::aborted (bool)
5438 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5442 , _track_selection_at_start (e)
5443 , _time_selection_at_start (!_editor->get_selection().time.empty())
5445 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5447 if (_time_selection_at_start) {
5448 start_at_start = _editor->get_selection().time.start();
5449 end_at_start = _editor->get_selection().time.end_sample();
5454 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5456 if (_editor->session() == 0) {
5460 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5462 switch (_operation) {
5463 case CreateSelection:
5464 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5469 cursor = _editor->cursors()->selector;
5470 Drag::start_grab (event, cursor);
5473 case SelectionStartTrim:
5474 if (_editor->clicked_axisview) {
5475 _editor->clicked_axisview->order_selection_trims (_item, true);
5477 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5480 case SelectionEndTrim:
5481 if (_editor->clicked_axisview) {
5482 _editor->clicked_axisview->order_selection_trims (_item, false);
5484 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5488 Drag::start_grab (event, cursor);
5491 case SelectionExtend:
5492 Drag::start_grab (event, cursor);
5496 if (_operation == SelectionMove) {
5497 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5499 show_verbose_cursor_time (adjusted_current_sample (event));
5504 SelectionDrag::setup_pointer_sample_offset ()
5506 switch (_operation) {
5507 case CreateSelection:
5508 _pointer_sample_offset = 0;
5511 case SelectionStartTrim:
5513 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].start;
5516 case SelectionEndTrim:
5517 _pointer_sample_offset = raw_grab_sample() - _editor->selection->time[_editor->clicked_selection].end;
5520 case SelectionExtend:
5526 SelectionDrag::motion (GdkEvent* event, bool first_move)
5528 samplepos_t start = 0;
5529 samplepos_t end = 0;
5530 samplecnt_t length = 0;
5531 samplecnt_t distance = 0;
5532 MusicSample start_mf (0, 0);
5533 samplepos_t const pending_position = adjusted_current_sample (event);
5535 if (_operation != CreateSelection && pending_position == last_pointer_sample()) {
5540 _track_selection_at_start = _editor->selection->tracks;
5543 switch (_operation) {
5544 case CreateSelection:
5546 MusicSample grab (grab_sample (), 0);
5548 grab.sample = adjusted_current_sample (event, false);
5549 if (grab.sample < pending_position) {
5550 _editor->snap_to (grab, RoundDownMaybe);
5552 _editor->snap_to (grab, RoundUpMaybe);
5556 if (pending_position < grab.sample) {
5557 start = pending_position;
5560 end = pending_position;
5561 start = grab.sample;
5564 /* first drag: Either add to the selection
5565 or create a new selection
5572 /* adding to the selection */
5573 _editor->set_selected_track_as_side_effect (Selection::Add);
5574 _editor->clicked_selection = _editor->selection->add (start, end);
5581 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5582 _editor->set_selected_track_as_side_effect (Selection::Set);
5585 _editor->clicked_selection = _editor->selection->set (start, end);
5589 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5590 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5591 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5593 _editor->selection->add (atest);
5597 /* select all tracks within the rectangle that we've marked out so far */
5598 TrackViewList new_selection;
5599 TrackViewList& all_tracks (_editor->track_views);
5601 ArdourCanvas::Coord const top = grab_y();
5602 ArdourCanvas::Coord const bottom = current_pointer_y();
5604 if (top >= 0 && bottom >= 0) {
5606 //first, find the tracks that are covered in the y range selection
5607 for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5608 if ((*i)->covered_by_y_range (top, bottom)) {
5609 new_selection.push_back (*i);
5613 //now compare our list with the current selection, and add as necessary
5614 //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5615 TrackViewList tracks_to_add;
5616 TrackViewList tracks_to_remove;
5617 vector<RouteGroup*> selected_route_groups;
5620 for (TrackViewList::const_iterator i = _editor->selection->tracks.begin(); i != _editor->selection->tracks.end(); ++i) {
5621 if (!new_selection.contains (*i) && !_track_selection_at_start.contains (*i)) {
5622 tracks_to_remove.push_back (*i);
5624 RouteGroup* rg = (*i)->route_group();
5625 if (rg && rg->is_active() && rg->is_select()) {
5626 selected_route_groups.push_back (rg);
5632 for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) {
5633 if (!_editor->selection->tracks.contains (*i)) {
5634 tracks_to_add.push_back (*i);
5635 RouteGroup* rg = (*i)->route_group();
5637 if (rg && rg->is_active() && rg->is_select()) {
5638 selected_route_groups.push_back (rg);
5643 _editor->selection->add (tracks_to_add);
5645 if (!tracks_to_remove.empty()) {
5647 /* check all these to-be-removed tracks against the
5648 * possibility that they are selected by being
5649 * in the same group as an approved track.
5652 for (TrackViewList::iterator i = tracks_to_remove.begin(); i != tracks_to_remove.end(); ) {
5653 RouteGroup* rg = (*i)->route_group();
5655 if (rg && find (selected_route_groups.begin(), selected_route_groups.end(), rg) != selected_route_groups.end()) {
5656 i = tracks_to_remove.erase (i);
5662 /* remove whatever is left */
5664 _editor->selection->remove (tracks_to_remove);
5670 case SelectionStartTrim:
5672 end = _editor->selection->time[_editor->clicked_selection].end;
5674 if (pending_position > end) {
5677 start = pending_position;
5681 case SelectionEndTrim:
5683 start = _editor->selection->time[_editor->clicked_selection].start;
5685 if (pending_position < start) {
5688 end = pending_position;
5695 start = _editor->selection->time[_editor->clicked_selection].start;
5696 end = _editor->selection->time[_editor->clicked_selection].end;
5698 length = end - start;
5699 distance = pending_position - start;
5700 start = pending_position;
5702 start_mf.sample = start;
5703 _editor->snap_to (start_mf);
5705 end = start_mf.sample + length;
5709 case SelectionExtend:
5714 switch (_operation) {
5716 if (_time_selection_at_start) {
5717 _editor->selection->move_time (distance);
5721 _editor->selection->replace (_editor->clicked_selection, start, end);
5725 if (_operation == SelectionMove) {
5726 show_verbose_cursor_time(start);
5728 show_verbose_cursor_time(pending_position);
5733 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5735 Session* s = _editor->session();
5737 _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5738 if (movement_occurred) {
5739 motion (event, false);
5740 /* XXX this is not object-oriented programming at all. ick */
5741 if (_editor->selection->time.consolidate()) {
5742 _editor->selection->TimeChanged ();
5745 /* XXX what if its a music time selection? */
5748 //if Follow Edits is on, maybe try to follow the range selection ... also consider range-audition mode
5749 if ( !s->config.get_external_sync() && s->transport_rolling() ) {
5750 if ( s->solo_selection_active() ) {
5751 _editor->play_solo_selection(true); //play the newly selected range, and move solos to match
5752 } else if ( UIConfiguration::instance().get_follow_edits() && s->get_play_range() ) { //already rolling a selected range
5753 s->request_play_range (&_editor->selection->time, true); //play the newly selected range
5755 } else if ( !s->transport_rolling() && UIConfiguration::instance().get_follow_edits() ) {
5756 s->request_locate (_editor->get_selection().time.start());
5759 if (_editor->get_selection().time.length() != 0) {
5760 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_sample());
5762 s->clear_range_selection ();
5767 /* just a click, no pointer movement.
5770 if (was_double_click()) {
5771 if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5772 _editor->temporal_zoom_selection (Both);
5777 if (_operation == SelectionExtend) {
5778 if (_time_selection_at_start) {
5779 samplepos_t pos = adjusted_current_sample (event, false);
5780 samplepos_t start = min (pos, start_at_start);
5781 samplepos_t end = max (pos, end_at_start);
5782 _editor->selection->set (start, end);
5785 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5786 if (_editor->clicked_selection) {
5787 _editor->selection->remove (_editor->clicked_selection);
5790 if (!_editor->clicked_selection) {
5791 _editor->selection->clear_time();
5796 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5797 _editor->selection->set (_editor->clicked_axisview);
5800 if (s && s->get_play_range () && s->transport_rolling()) {
5801 s->request_stop (false, false);
5806 _editor->stop_canvas_autoscroll ();
5807 _editor->clicked_selection = 0;
5808 _editor->commit_reversible_selection_op ();
5812 SelectionDrag::aborted (bool)
5817 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5818 : Drag (e, i, false),
5822 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5824 _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5825 ArdourCanvas::Rect (0.0, 0.0, 0.0,
5826 physical_screen_height (_editor->current_toplevel()->get_window())));
5827 _drag_rect->hide ();
5829 _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5830 _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5833 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5835 /* normal canvas items will be cleaned up when their parent group is deleted. But
5836 this item is created as the child of a long-lived parent group, and so we
5837 need to explicitly delete it.
5843 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5845 if (_editor->session() == 0) {
5849 Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5851 if (!_editor->temp_location) {
5852 _editor->temp_location = new Location (*_editor->session());
5855 switch (_operation) {
5856 case CreateSkipMarker:
5857 case CreateRangeMarker:
5858 case CreateTransportMarker:
5859 case CreateCDMarker:
5861 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5866 cursor = _editor->cursors()->selector;
5870 Drag::start_grab (event, cursor);
5872 show_verbose_cursor_time (adjusted_current_sample (event));
5876 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5878 samplepos_t start = 0;
5879 samplepos_t end = 0;
5880 ArdourCanvas::Rectangle *crect;
5882 switch (_operation) {
5883 case CreateSkipMarker:
5884 crect = _editor->range_bar_drag_rect;
5886 case CreateRangeMarker:
5887 crect = _editor->range_bar_drag_rect;
5889 case CreateTransportMarker:
5890 crect = _editor->transport_bar_drag_rect;
5892 case CreateCDMarker:
5893 crect = _editor->cd_marker_bar_drag_rect;
5896 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5901 samplepos_t const pf = adjusted_current_sample (event);
5903 if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5904 MusicSample grab (grab_sample (), 0);
5905 _editor->snap_to (grab);
5907 if (pf < grab_sample()) {
5912 start = grab.sample;
5915 /* first drag: Either add to the selection
5916 or create a new selection.
5921 _editor->temp_location->set (start, end);
5925 update_item (_editor->temp_location);
5927 //_drag_rect->raise_to_top();
5933 _editor->temp_location->set (start, end);
5935 double x1 = _editor->sample_to_pixel (start);
5936 double x2 = _editor->sample_to_pixel (end);
5940 update_item (_editor->temp_location);
5943 show_verbose_cursor_time (pf);
5948 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5950 Location * newloc = 0;
5954 if (movement_occurred) {
5955 motion (event, false);
5958 switch (_operation) {
5959 case CreateSkipMarker:
5960 case CreateRangeMarker:
5961 case CreateCDMarker:
5963 XMLNode &before = _editor->session()->locations()->get_state();
5964 if (_operation == CreateSkipMarker) {
5965 _editor->begin_reversible_command (_("new skip marker"));
5966 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5967 flags = Location::IsRangeMarker | Location::IsSkip;
5968 _editor->range_bar_drag_rect->hide();
5969 } else if (_operation == CreateCDMarker) {
5970 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5971 _editor->begin_reversible_command (_("new CD marker"));
5972 flags = Location::IsRangeMarker | Location::IsCDMarker;
5973 _editor->cd_marker_bar_drag_rect->hide();
5975 _editor->begin_reversible_command (_("new skip marker"));
5976 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5977 flags = Location::IsRangeMarker;
5978 _editor->range_bar_drag_rect->hide();
5980 newloc = new Location (
5981 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5982 , _editor->get_grid_music_divisions (event->button.state));
5984 _editor->session()->locations()->add (newloc, true);
5985 XMLNode &after = _editor->session()->locations()->get_state();
5986 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5987 _editor->commit_reversible_command ();
5991 case CreateTransportMarker:
5992 // popup menu to pick loop or punch
5993 _editor->new_transport_marker_context_menu (&event->button, _item);
5999 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
6001 if (_operation == CreateTransportMarker) {
6003 /* didn't drag, so just locate */
6005 _editor->session()->request_locate (grab_sample(), _editor->session()->transport_rolling());
6007 } else if (_operation == CreateCDMarker) {
6009 /* didn't drag, but mark is already created so do
6012 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
6017 _editor->session()->locations()->marks_either_side (grab_sample(), start, end);
6019 if (end == max_samplepos) {
6020 end = _editor->session()->current_end_sample ();
6023 if (start == max_samplepos) {
6024 start = _editor->session()->current_start_sample ();
6027 switch (_editor->mouse_mode) {
6029 /* find the two markers on either side and then make the selection from it */
6030 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
6034 /* find the two markers on either side of the click and make the range out of it */
6035 _editor->selection->set (start, end);
6044 _editor->stop_canvas_autoscroll ();
6048 RangeMarkerBarDrag::aborted (bool movement_occurred)
6050 if (movement_occurred) {
6051 _drag_rect->hide ();
6056 RangeMarkerBarDrag::update_item (Location* location)
6058 double const x1 = _editor->sample_to_pixel (location->start());
6059 double const x2 = _editor->sample_to_pixel (location->end());
6061 _drag_rect->set_x0 (x1);
6062 _drag_rect->set_x1 (x2);
6065 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
6067 , _cumulative_dx (0)
6068 , _cumulative_dy (0)
6070 , _was_selected (false)
6073 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
6075 _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
6077 _region = &_primary->region_view ();
6078 _note_height = _region->midi_stream_view()->note_height ();
6082 NoteDrag::setup_pointer_sample_offset ()
6084 _pointer_sample_offset = raw_grab_sample()
6085 - _editor->session()->tempo_map().sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6089 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
6091 Drag::start_grab (event);
6093 if (ArdourKeyboard::indicates_copy (event->button.state)) {
6099 setup_snap_delta (MusicSample (_region->source_beats_to_absolute_samples (_primary->note()->time ()), 0));
6101 if (!(_was_selected = _primary->selected())) {
6103 /* tertiary-click means extend selection - we'll do that on button release,
6104 so don't add it here, because otherwise we make it hard to figure
6105 out the "extend-to" range.
6108 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
6111 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
6114 _region->note_selected (_primary, true);
6116 _editor->get_selection().clear_points();
6117 _region->unique_select (_primary);
6123 /** @return Current total drag x change in quarter notes */
6125 NoteDrag::total_dx (GdkEvent * event) const
6127 if (_x_constrained) {
6131 TempoMap& map (_editor->session()->tempo_map());
6134 sampleoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
6136 /* primary note time */
6137 sampleoffset_t const n = map.sample_at_quarter_note (_region->session_relative_qn (_primary->note()->time().to_double()));
6139 /* primary note time in quarter notes */
6140 double const n_qn = _region->session_relative_qn (_primary->note()->time().to_double());
6142 /* new time of the primary note in session samples */
6143 sampleoffset_t st = n + dx + snap_delta (event->button.state);
6145 /* possibly snap and return corresponding delta in quarter notes */
6146 MusicSample snap (st, 0);
6147 _editor->snap_to_with_modifier (snap, event, RoundNearest, SnapToGrid_Unscaled);
6148 double ret = map.exact_qn_at_sample (snap.sample, snap.division) - n_qn - snap_delta_music (event->button.state);
6150 /* prevent the earliest note being dragged earlier than the region's start position */
6151 if (_earliest + ret < _region->midi_region()->start_beats()) {
6152 ret -= (_earliest + ret) - _region->midi_region()->start_beats();
6158 /** @return Current total drag y change in note number */
6160 NoteDrag::total_dy () const
6162 if (_y_constrained) {
6166 double const y = _region->midi_view()->y_position ();
6167 /* new current note */
6168 uint8_t n = _region->y_to_note (current_pointer_y () - y);
6170 MidiStreamView* msv = _region->midi_stream_view ();
6171 n = max (msv->lowest_note(), n);
6172 n = min (msv->highest_note(), n);
6173 /* and work out delta */
6174 return n - _region->y_to_note (grab_y() - y);
6178 NoteDrag::motion (GdkEvent * event, bool first_move)
6181 _earliest = _region->earliest_in_selection().to_double();
6183 /* make copies of all the selected notes */
6184 _primary = _region->copy_selection (_primary);
6188 /* Total change in x and y since the start of the drag */
6189 double const dx_qn = total_dx (event);
6190 int8_t const dy = total_dy ();
6192 /* Now work out what we have to do to the note canvas items to set this new drag delta */
6193 double const tdx = _x_constrained ? 0 : dx_qn - _cumulative_dx;
6194 double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
6197 _cumulative_dx = dx_qn;
6198 _cumulative_dy += tdy;
6200 int8_t note_delta = total_dy();
6204 _region->move_copies (dx_qn, tdy, note_delta);
6206 _region->move_selection (dx_qn, tdy, note_delta);
6209 /* the new note value may be the same as the old one, but we
6210 * don't know what that means because the selection may have
6211 * involved more than one note and we might be doing something
6212 * odd with them. so show the note value anyway, always.
6215 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
6217 _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
6219 _editor->set_snapped_cursor_position( _region->source_beats_to_absolute_samples(_primary->note()->time()) );
6225 NoteDrag::finished (GdkEvent* ev, bool moved)
6228 /* no motion - select note */
6230 if (_editor->current_mouse_mode() == Editing::MouseContent ||
6231 _editor->current_mouse_mode() == Editing::MouseDraw) {
6233 bool changed = false;
6235 if (_was_selected) {
6236 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6238 _region->note_deselected (_primary);
6241 _editor->get_selection().clear_points();
6242 _region->unique_select (_primary);
6246 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
6247 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
6249 if (!extend && !add && _region->selection_size() > 1) {
6250 _editor->get_selection().clear_points();
6251 _region->unique_select (_primary);
6253 } else if (extend) {
6254 _region->note_selected (_primary, true, true);
6257 /* it was added during button press */
6264 _editor->begin_reversible_selection_op(X_("Select Note Release"));
6265 _editor->commit_reversible_selection_op();
6269 _region->note_dropped (_primary, total_dx (ev), total_dy(), _copy);
6274 NoteDrag::aborted (bool)
6279 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
6280 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
6281 : Drag (editor, atv->base_item ())
6283 , _y_origin (atv->y_position())
6284 , _y_height (atv->effective_height()) // or atv->lines()->front()->height() ?!
6285 , _nothing_to_drag (false)
6287 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6288 setup (atv->lines ());
6291 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
6292 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, list<RegionView*> const & v, list<AudioRange> const & r, double y_origin, double y_height)
6293 : Drag (editor, v.front()->get_canvas_group ())
6295 , _y_origin (y_origin)
6296 , _y_height (y_height)
6297 , _nothing_to_drag (false)
6300 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
6302 list<boost::shared_ptr<AutomationLine> > lines;
6304 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
6305 if (AudioRegionView* audio_view = dynamic_cast<AudioRegionView*>(*i)) {
6306 lines.push_back (audio_view->get_gain_line ());
6307 } else if (AutomationRegionView* automation_view = dynamic_cast<AutomationRegionView*>(*i)) {
6308 lines.push_back (automation_view->line ());
6311 error << _("Automation range drag created for invalid region type") << endmsg;
6317 /** @param lines AutomationLines to drag.
6318 * @param offset Offset from the session start to the points in the AutomationLines.
6321 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
6323 /* find the lines that overlap the ranges being dragged */
6324 list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
6325 while (i != lines.end ()) {
6326 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
6329 pair<samplepos_t, samplepos_t> r = (*i)->get_point_x_range ();
6331 //need a special detection for automation lanes (not region gain line)
6332 //TODO: if we implement automation regions, this check can probably be removed
6333 AudioRegionGainLine *argl = dynamic_cast<AudioRegionGainLine*> ((*i).get());
6335 //in automation lanes, the EFFECTIVE range should be considered 0->max_samplepos (even if there is no line)
6337 r.second = max_samplepos;
6340 /* check this range against all the AudioRanges that we are using */
6341 list<AudioRange>::const_iterator k = _ranges.begin ();
6342 while (k != _ranges.end()) {
6343 if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
6349 /* add it to our list if it overlaps at all */
6350 if (k != _ranges.end()) {
6355 _lines.push_back (n);
6361 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
6365 AutomationRangeDrag::y_fraction (double global_y) const
6367 return 1.0 - ((global_y - _y_origin) / _y_height);
6371 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
6373 const double v = list->eval(x);
6374 return _integral ? rint(v) : v;
6378 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6380 Drag::start_grab (event, cursor);
6382 /* Get line states before we start changing things */
6383 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6384 i->state = &i->line->get_state ();
6387 if (_ranges.empty()) {
6389 /* No selected time ranges: drag all points */
6390 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6391 uint32_t const N = i->line->npoints ();
6392 for (uint32_t j = 0; j < N; ++j) {
6393 i->points.push_back (i->line->nth (j));
6399 if (_nothing_to_drag) {
6405 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
6407 if (_nothing_to_drag && !first_move) {
6412 _editor->begin_reversible_command (_("automation range move"));
6414 if (!_ranges.empty()) {
6416 /* add guard points */
6417 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
6419 samplecnt_t const half = (i->start + i->end) / 2;
6421 for (list<Line>::iterator j = _lines.begin(); j != _lines.end(); ++j) {
6422 if (j->range.first > i->start || j->range.second < i->start) {
6426 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6428 /* j is the line that this audio range starts in; fade into it;
6429 * 64 samples length plucked out of thin air.
6432 samplepos_t a = i->start + 64;
6437 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
6438 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
6440 XMLNode &before = the_list->get_state();
6441 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6442 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6444 if (add_p || add_q) {
6445 _editor->session()->add_command (
6446 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6450 /* same thing for the end */
6451 for (list<Line>::iterator j = _lines.begin(); j != _lines.end(); ++j) {
6453 if (j->range.first > i->end || j->range.second < i->end) {
6457 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
6459 /* j is the line that this audio range starts in; fade out of it;
6460 * 64 samples length plucked out of thin air.
6463 samplepos_t b = i->end - 64;
6468 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
6469 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
6471 XMLNode &before = the_list->get_state();
6472 bool const add_p = the_list->editor_add (p, value (the_list, p), false);
6473 bool const add_q = the_list->editor_add (q, value (the_list, q), false);
6475 if (add_p || add_q) {
6476 _editor->session()->add_command (
6477 new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6482 _nothing_to_drag = true;
6484 /* Find all the points that should be dragged and put them in the relevant
6485 * points lists in the Line structs.
6487 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6489 uint32_t const N = i->line->npoints ();
6490 for (uint32_t j = 0; j < N; ++j) {
6492 /* here's a control point on this line */
6493 ControlPoint* p = i->line->nth (j);
6494 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6496 /* see if it's inside a range */
6497 list<AudioRange>::const_iterator k = _ranges.begin ();
6498 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6502 if (k != _ranges.end()) {
6503 /* dragging this point */
6504 _nothing_to_drag = false;
6505 i->points.push_back (p);
6511 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6512 i->line->start_drag_multiple (i->points, y_fraction (current_pointer_y()), i->state);
6516 for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6517 float const f = y_fraction (current_pointer_y());
6518 /* we are ignoring x position for this drag, so we can just pass in anything */
6519 pair<float, float> result;
6521 result = l->line->drag_motion (0, f, true, false, ignored);
6522 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (result.first, result.second));
6527 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6529 if (_nothing_to_drag || !motion_occurred) {
6533 motion (event, false);
6534 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6535 i->line->end_drag (false, 0);
6538 _editor->commit_reversible_command ();
6542 AutomationRangeDrag::aborted (bool)
6544 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6549 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6551 , initial_time_axis_view (itav)
6553 /* note that time_axis_view may be null if the regionview was created
6554 * as part of a copy operation.
6556 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6557 layer = v->region()->layer ();
6558 initial_y = v->get_canvas_group()->position().y;
6559 initial_playlist = v->region()->playlist ();
6560 initial_position = v->region()->position ();
6561 initial_end = v->region()->position () + v->region()->length ();
6564 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6565 : Drag (e, i->canvas_item ())
6568 , _cumulative_dx (0)
6570 DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6571 _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time()),
6576 PatchChangeDrag::motion (GdkEvent* ev, bool)
6578 samplepos_t f = adjusted_current_sample (ev);
6579 boost::shared_ptr<Region> r = _region_view->region ();
6580 f = max (f, r->position ());
6581 f = min (f, r->last_sample ());
6583 samplecnt_t const dxf = f - grab_sample(); // permitted dx in samples
6584 double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6585 _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6586 _cumulative_dx = dxu;
6590 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6592 if (!movement_occurred) {
6593 if (was_double_click()) {
6594 _region_view->edit_patch_change (_patch_change);
6599 boost::shared_ptr<Region> r (_region_view->region ());
6600 samplepos_t f = adjusted_current_sample (ev);
6601 f = max (f, r->position ());
6602 f = min (f, r->last_sample ());
6604 _region_view->move_patch_change (
6606 _region_view->region_samples_to_region_beats (f - (r->position() - r->start()))
6611 PatchChangeDrag::aborted (bool)
6613 _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6617 PatchChangeDrag::setup_pointer_sample_offset ()
6619 boost::shared_ptr<Region> region = _region_view->region ();
6620 _pointer_sample_offset = raw_grab_sample() - _region_view->source_beats_to_absolute_samples (_patch_change->patch()->time());
6623 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6624 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6631 MidiRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6633 _region_view->update_drag_selection (
6635 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6639 MidiRubberbandSelectDrag::deselect_things ()
6644 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6645 : RubberbandSelectDrag (e, rv->get_canvas_group ())
6648 _vertical_only = true;
6652 MidiVerticalSelectDrag::select_things (int button_state, samplepos_t /*x1*/, samplepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6654 double const y = _region_view->midi_view()->y_position ();
6656 y1 = max (0.0, y1 - y);
6657 y2 = max (0.0, y2 - y);
6659 _region_view->update_vertical_drag_selection (
6662 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6667 MidiVerticalSelectDrag::deselect_things ()
6672 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6673 : RubberbandSelectDrag (e, i)
6679 EditorRubberbandSelectDrag::select_things (int button_state, samplepos_t x1, samplepos_t x2, double y1, double y2, bool drag_in_progress)
6681 if (drag_in_progress) {
6682 /* We just want to select things at the end of the drag, not during it */
6686 Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6688 _editor->begin_reversible_selection_op (X_("rubberband selection"));
6690 _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6692 _editor->commit_reversible_selection_op ();
6696 EditorRubberbandSelectDrag::deselect_things ()
6698 _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6700 _editor->selection->clear_tracks();
6701 _editor->selection->clear_regions();
6702 _editor->selection->clear_points ();
6703 _editor->selection->clear_lines ();
6704 _editor->selection->clear_midi_notes ();
6706 _editor->commit_reversible_selection_op();
6709 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6714 _note[0] = _note[1] = 0;
6717 NoteCreateDrag::~NoteCreateDrag ()
6723 NoteCreateDrag::grid_samples (samplepos_t t) const
6726 const Temporal::Beats grid_beats = _region_view->get_grid_beats (t);
6727 const Temporal::Beats t_beats = _region_view->region_samples_to_region_beats (t);
6729 return _region_view->region_beats_to_region_samples (t_beats + grid_beats)
6730 - _region_view->region_beats_to_region_samples (t_beats);
6734 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6736 Drag::start_grab (event, cursor);
6738 _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6739 TempoMap& map (_editor->session()->tempo_map());
6741 const samplepos_t pf = _drags->current_pointer_sample ();
6742 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6744 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6746 double eqaf = map.exact_qn_at_sample (pf, divisions);
6748 if (divisions != 0) {
6750 const double qaf = map.quarter_note_at_sample (pf);
6752 /* Hack so that we always snap to the note that we are over, instead of snapping
6753 to the next one if we're more than halfway through the one we're over.
6756 const double rem = eqaf - qaf;
6758 eqaf -= grid_beats.to_double();
6762 _note[0] = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6763 /* minimum initial length is grid beats */
6764 _note[1] = map.sample_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6766 double const x0 = _editor->sample_to_pixel (_note[0]);
6767 double const x1 = _editor->sample_to_pixel (_note[1]);
6768 double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6770 _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6771 _drag_rect->set_outline_all ();
6772 _drag_rect->set_outline_color (0xffffff99);
6773 _drag_rect->set_fill_color (0xffffff66);
6777 NoteCreateDrag::motion (GdkEvent* event, bool)
6779 TempoMap& map (_editor->session()->tempo_map());
6780 const samplepos_t pf = _drags->current_pointer_sample ();
6781 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6782 double eqaf = map.exact_qn_at_sample (pf, divisions);
6784 if (divisions != 0) {
6786 const Temporal::Beats grid_beats = _region_view->get_grid_beats (pf);
6788 const double qaf = map.quarter_note_at_sample (pf);
6789 /* Hack so that we always snap to the note that we are over, instead of snapping
6790 to the next one if we're more than halfway through the one we're over.
6793 const double rem = eqaf - qaf;
6795 eqaf -= grid_beats.to_double();
6798 eqaf += grid_beats.to_double();
6800 _note[1] = max ((samplepos_t)0, map.sample_at_quarter_note (eqaf) - _region_view->region()->position ());
6802 double const x0 = _editor->sample_to_pixel (_note[0]);
6803 double const x1 = _editor->sample_to_pixel (_note[1]);
6804 _drag_rect->set_x0 (std::min(x0, x1));
6805 _drag_rect->set_x1 (std::max(x0, x1));
6809 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6811 /* we create a note even if there was no movement */
6812 samplepos_t const start = min (_note[0], _note[1]);
6813 samplepos_t const start_sess_rel = start + _region_view->region()->position();
6814 samplecnt_t length = max (_editor->pixel_to_sample (1.0), (samplecnt_t) fabs ((double)(_note[0] - _note[1])));
6815 samplecnt_t const g = grid_samples (start_sess_rel);
6817 if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6821 TempoMap& map (_editor->session()->tempo_map());
6822 const double qn_length = map.quarter_notes_between_samples (start_sess_rel, start_sess_rel + length);
6823 Temporal::Beats qn_length_beats = max (Temporal::Beats::ticks(1), Temporal::Beats (qn_length));
6825 _editor->begin_reversible_command (_("Create Note"));
6826 _region_view->clear_editor_note_selection();
6827 _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6828 _editor->commit_reversible_command ();
6832 NoteCreateDrag::y_to_region (double y) const
6835 _region_view->get_canvas_group()->canvas_to_item (x, y);
6840 NoteCreateDrag::aborted (bool)
6845 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6853 HitCreateDrag::~HitCreateDrag ()
6858 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6860 Drag::start_grab (event, cursor);
6862 TempoMap& map (_editor->session()->tempo_map());
6864 _y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6866 const samplepos_t pf = _drags->current_pointer_sample ();
6867 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6869 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6871 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6873 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6877 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position();
6878 Temporal::Beats length = Temporal::Beats(1.0 / 32.0); /* 1/32 beat = 1/128 note */
6880 _editor->begin_reversible_command (_("Create Hit"));
6881 _region_view->clear_editor_note_selection();
6882 _region_view->create_note_at (start, _y, length, event->button.state, false);
6888 HitCreateDrag::motion (GdkEvent* event, bool)
6890 TempoMap& map (_editor->session()->tempo_map());
6892 const samplepos_t pf = _drags->current_pointer_sample ();
6893 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6895 if (divisions == 0) {
6899 const double eqaf = map.exact_qn_at_sample (pf, divisions);
6900 const samplepos_t start = map.sample_at_quarter_note (eqaf) - _region_view->region()->position ();
6902 if (_last_pos == start) {
6906 Temporal::Beats length = _region_view->get_grid_beats (pf);
6908 boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6910 if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6914 _region_view->create_note_at (start, _y, length, event->button.state, false);
6920 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6922 _editor->commit_reversible_command ();
6927 HitCreateDrag::y_to_region (double y) const
6930 _region_view->get_canvas_group()->canvas_to_item (x, y);
6935 HitCreateDrag::aborted (bool)
6940 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6945 std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl;
6949 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6951 Drag::start_grab (event, cursor);
6955 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6961 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6964 distance = _drags->current_pointer_x() - grab_x();
6965 len = ar->fade_in()->back()->when;
6967 distance = grab_x() - _drags->current_pointer_x();
6968 len = ar->fade_out()->back()->when;
6971 /* how long should it be ? */
6973 new_length = len + _editor->pixel_to_sample (distance);
6975 /* now check with the region that this is legal */
6977 new_length = ar->verify_xfade_bounds (new_length, start);
6980 arv->reset_fade_in_shape_width (ar, new_length);
6982 arv->reset_fade_out_shape_width (ar, new_length);
6987 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6993 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6996 distance = _drags->current_pointer_x() - grab_x();
6997 len = ar->fade_in()->back()->when;
6999 distance = grab_x() - _drags->current_pointer_x();
7000 len = ar->fade_out()->back()->when;
7003 new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
7005 _editor->begin_reversible_command ("xfade trim");
7006 ar->playlist()->clear_owned_changes ();
7009 ar->set_fade_in_length (new_length);
7011 ar->set_fade_out_length (new_length);
7014 /* Adjusting the xfade may affect other regions in the playlist, so we need
7015 to get undo Commands from the whole playlist rather than just the
7019 vector<Command*> cmds;
7020 ar->playlist()->rdiff (cmds);
7021 _editor->session()->add_commands (cmds);
7022 _editor->commit_reversible_command ();
7027 CrossfadeEdgeDrag::aborted (bool)
7030 // arv->redraw_start_xfade ();
7032 // arv->redraw_end_xfade ();
7036 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, samplepos_t pos)
7037 : Drag (e, item, true)
7041 RegionCutDrag::~RegionCutDrag ()
7046 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
7048 Drag::start_grab (event, c);
7049 motion (event, false);
7053 RegionCutDrag::motion (GdkEvent* event, bool)
7058 RegionCutDrag::finished (GdkEvent* event, bool)
7060 _editor->get_track_canvas()->canvas()->re_enter();
7063 MusicSample pos (_drags->current_pointer_sample(), 0);
7064 _editor->snap_to_with_modifier (pos, event);
7066 RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos.sample);
7072 _editor->split_regions_at (pos, rs);
7076 RegionCutDrag::aborted (bool)