switch cartesian/spherical function names and make them use length. still a tweak...
[ardour2.git] / gtk2_ardour / editor_drag.cc
blob37f15b014222fe471ecdbfde8443b0fa39fe76fe
1 /*
2 Copyright (C) 2009 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
24 #include <stdint.h>
26 #include "pbd/memento_command.h"
27 #include "pbd/basename.h"
28 #include "pbd/stateful_diff_command.h"
30 #include "gtkmm2ext/utils.h"
32 #include "ardour/session.h"
33 #include "ardour/dB.h"
34 #include "ardour/region_factory.h"
35 #include "ardour/operations.h"
37 #include "editor.h"
38 #include "i18n.h"
39 #include "keyboard.h"
40 #include "audio_region_view.h"
41 #include "midi_region_view.h"
42 #include "ardour_ui.h"
43 #include "gui_thread.h"
44 #include "control_point.h"
45 #include "utils.h"
46 #include "region_gain_line.h"
47 #include "editor_drag.h"
48 #include "audio_time_axis.h"
49 #include "midi_time_axis.h"
50 #include "canvas-note.h"
51 #include "selection.h"
52 #include "midi_selection.h"
53 #include "automation_time_axis.h"
54 #include "debug.h"
55 #include "editor_cursors.h"
56 #include "mouse_cursors.h"
58 using namespace std;
59 using namespace ARDOUR;
60 using namespace PBD;
61 using namespace Gtk;
62 using namespace Gtkmm2ext;
63 using namespace Editing;
64 using namespace ArdourCanvas;
66 using Gtkmm2ext::Keyboard;
68 double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
70 DragManager::DragManager (Editor* e)
71 : _editor (e)
72 , _ending (false)
73 , _current_pointer_frame (0)
78 DragManager::~DragManager ()
80 abort ();
83 /** Call abort for each active drag */
84 void
85 DragManager::abort ()
87 _ending = true;
89 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
90 (*i)->abort ();
91 delete *i;
94 _drags.clear ();
96 _editor->set_follow_playhead (_old_follow_playhead, false);
98 _ending = false;
101 void
102 DragManager::add (Drag* d)
104 d->set_manager (this);
105 _drags.push_back (d);
108 void
109 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
111 d->set_manager (this);
112 _drags.push_back (d);
113 start_grab (e, c);
116 void
117 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
119 /* Prevent follow playhead during the drag to be nice to the user */
120 _old_follow_playhead = _editor->follow_playhead ();
121 _editor->set_follow_playhead (false);
123 _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
125 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
126 (*i)->start_grab (e, c);
130 /** Call end_grab for each active drag.
131 * @return true if any drag reported movement having occurred.
133 bool
134 DragManager::end_grab (GdkEvent* e)
136 _ending = true;
138 bool r = false;
139 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
140 bool const t = (*i)->end_grab (e);
141 if (t) {
142 r = true;
144 delete *i;
147 _drags.clear ();
149 _ending = false;
151 _editor->set_follow_playhead (_old_follow_playhead, false);
153 return r;
156 bool
157 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
159 bool r = false;
161 _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
163 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
164 bool const t = (*i)->motion_handler (e, from_autoscroll);
165 if (t) {
166 r = true;
171 return r;
174 bool
175 DragManager::have_item (ArdourCanvas::Item* i) const
177 list<Drag*>::const_iterator j = _drags.begin ();
178 while (j != _drags.end() && (*j)->item () != i) {
179 ++j;
182 return j != _drags.end ();
185 Drag::Drag (Editor* e, ArdourCanvas::Item* i)
186 : _editor (e)
187 , _item (i)
188 , _pointer_frame_offset (0)
189 , _move_threshold_passed (false)
190 , _raw_grab_frame (0)
191 , _grab_frame (0)
192 , _last_pointer_frame (0)
197 void
198 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time)
200 _item->ungrab (0);
201 _item = new_item;
203 if (cursor == 0) {
204 cursor = _editor->which_grabber_cursor ();
207 _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
210 void
211 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
213 if (cursor == 0) {
214 cursor = _editor->which_grabber_cursor ();
217 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
219 if (Keyboard::is_button2_event (&event->button)) {
220 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
221 _y_constrained = true;
222 _x_constrained = false;
223 } else {
224 _y_constrained = false;
225 _x_constrained = true;
227 } else {
228 _x_constrained = false;
229 _y_constrained = false;
232 _raw_grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
233 setup_pointer_frame_offset ();
234 _grab_frame = adjusted_frame (_raw_grab_frame, event);
235 _last_pointer_frame = _grab_frame;
236 _last_pointer_x = _grab_x;
237 _last_pointer_y = _grab_y;
239 _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
240 *cursor,
241 event->button.time);
243 if (_editor->session() && _editor->session()->transport_rolling()) {
244 _was_rolling = true;
245 } else {
246 _was_rolling = false;
249 switch (_editor->snap_type()) {
250 case SnapToRegionStart:
251 case SnapToRegionEnd:
252 case SnapToRegionSync:
253 case SnapToRegionBoundary:
254 _editor->build_region_boundary_cache ();
255 break;
256 default:
257 break;
261 /** Call to end a drag `successfully'. Ungrabs item and calls
262 * subclass' finished() method.
264 * @param event GDK event, or 0.
265 * @return true if some movement occurred, otherwise false.
267 bool
268 Drag::end_grab (GdkEvent* event)
270 _editor->stop_canvas_autoscroll ();
272 _item->ungrab (event ? event->button.time : 0);
274 finished (event, _move_threshold_passed);
276 _editor->hide_verbose_canvas_cursor();
278 return _move_threshold_passed;
281 framepos_t
282 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
284 framepos_t pos = 0;
286 if (f > _pointer_frame_offset) {
287 pos = f - _pointer_frame_offset;
290 if (snap) {
291 _editor->snap_to_with_modifier (pos, event);
294 return pos;
297 framepos_t
298 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
300 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
303 bool
304 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
306 /* check to see if we have moved in any way that matters since the last motion event */
307 if (_move_threshold_passed &&
308 (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
309 (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
310 return false;
313 pair<framecnt_t, int> const threshold = move_threshold ();
315 bool const old_move_threshold_passed = _move_threshold_passed;
317 if (!from_autoscroll && !_move_threshold_passed) {
319 bool const xp = (::llabs (_drags->current_pointer_frame () - _grab_frame) >= threshold.first);
320 bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);
322 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
325 if (active (_editor->mouse_mode) && _move_threshold_passed) {
327 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
328 if (!from_autoscroll) {
329 _editor->maybe_autoscroll (true, allow_vertical_autoscroll ());
332 motion (event, _move_threshold_passed != old_move_threshold_passed);
334 _last_pointer_x = _drags->current_pointer_x ();
335 _last_pointer_y = _drags->current_pointer_y ();
336 _last_pointer_frame = adjusted_current_frame (event);
338 return true;
341 return false;
344 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
345 void
346 Drag::abort ()
348 if (_item) {
349 _item->ungrab (0);
352 aborted (_move_threshold_passed);
354 _editor->stop_canvas_autoscroll ();
355 _editor->hide_verbose_canvas_cursor ();
358 struct EditorOrderTimeAxisViewSorter {
359 bool operator() (TimeAxisView* a, TimeAxisView* b) {
360 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
361 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
362 assert (ra && rb);
363 return ra->route()->order_key (N_ ("editor")) < rb->route()->order_key (N_ ("editor"));
367 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
368 : Drag (e, i),
369 _primary (p)
371 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
373 /* Make a list of non-hidden tracks to refer to during the drag */
375 TrackViewList track_views = _editor->track_views;
376 track_views.sort (EditorOrderTimeAxisViewSorter ());
378 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
379 if (!(*i)->hidden()) {
381 _time_axis_views.push_back (*i);
383 TimeAxisView::Children children_list = (*i)->get_child_list ();
384 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
385 _time_axis_views.push_back (j->get());
390 /* the list of views can be empty at this point if this is a region list-insert drag
393 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
394 _views.push_back (DraggingView (*i, this));
397 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
400 void
401 RegionDrag::region_going_away (RegionView* v)
403 list<DraggingView>::iterator i = _views.begin ();
404 while (i != _views.end() && i->view != v) {
405 ++i;
408 if (i != _views.end()) {
409 _views.erase (i);
413 /** Given a non-hidden TimeAxisView, return the index of it into the _time_axis_views vector */
415 RegionDrag::find_time_axis_view (TimeAxisView* t) const
417 int i = 0;
418 int const N = _time_axis_views.size ();
419 while (i < N && _time_axis_views[i] != t) {
420 ++i;
423 if (i == N) {
424 return -1;
427 return i;
430 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
431 : RegionDrag (e, i, p, v),
432 _brushing (b),
433 _total_x_delta (0)
439 void
440 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
442 Drag::start_grab (event, cursor);
444 _editor->show_verbose_time_cursor (_last_frame_position, 10);
446 pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
447 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
448 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
451 double
452 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
454 /* compute the amount of pointer motion in frames, and where
455 the region would be if we moved it by that much.
457 *pending_region_position = adjusted_current_frame (event);
459 framepos_t sync_frame;
460 framecnt_t sync_offset;
461 int32_t sync_dir;
463 sync_offset = _primary->region()->sync_offset (sync_dir);
465 /* we don't handle a sync point that lies before zero.
467 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
469 sync_frame = *pending_region_position + (sync_dir*sync_offset);
471 _editor->snap_to_with_modifier (sync_frame, event);
473 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
475 } else {
476 *pending_region_position = _last_frame_position;
479 if (*pending_region_position > max_framepos - _primary->region()->length()) {
480 *pending_region_position = _last_frame_position;
483 double dx = 0;
485 /* in locked edit mode, reverse the usual meaning of _x_constrained */
486 bool const x_move_allowed = Config->get_edit_mode() == Lock ? _x_constrained : !_x_constrained;
488 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
490 /* x movement since last time */
491 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
493 /* total x movement */
494 framecnt_t total_dx = *pending_region_position;
495 if (regions_came_from_canvas()) {
496 total_dx = total_dx - grab_frame () + _pointer_frame_offset;
499 /* check that no regions have gone off the start of the session */
500 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
501 if ((i->view->region()->position() + total_dx) < 0) {
502 dx = 0;
503 *pending_region_position = _last_frame_position;
504 break;
508 _last_frame_position = *pending_region_position;
511 return dx;
514 bool
515 RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) const
517 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
518 int const n = i->time_axis_view + delta_track;
519 if (n < 0 || n >= int (_time_axis_views.size())) {
520 /* off the top or bottom track */
521 return false;
524 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
525 if (to == 0 || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
526 /* not a track, or the wrong type */
527 return false;
530 int const l = i->layer + delta_layer;
531 if (delta_track == 0 && (l < 0 || l >= int (to->view()->layers()))) {
532 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
533 If it has, the layers will be munged later anyway, so it's ok.
535 return false;
539 /* all regions being dragged are ok with this change */
540 return true;
543 void
544 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
546 assert (!_views.empty ());
548 /* Find the TimeAxisView that the pointer is now over */
549 pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
551 /* Bail early if we're not over a track */
552 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv.first);
553 if (!rtv || !rtv->is_track()) {
554 _editor->hide_verbose_canvas_cursor ();
555 return;
558 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
560 /* Here's the current pointer position in terms of time axis view and layer */
561 int const current_pointer_time_axis_view = find_time_axis_view (tv.first);
562 layer_t const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
564 /* Work out the change in x */
565 framepos_t pending_region_position;
566 double const x_delta = compute_x_delta (event, &pending_region_position);
568 /* Work out the change in y */
569 int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
570 int delta_layer = current_pointer_layer - _last_pointer_layer;
572 if (!y_movement_allowed (delta_time_axis_view, delta_layer)) {
573 /* this y movement is not allowed, so do no y movement this time */
574 delta_time_axis_view = 0;
575 delta_layer = 0;
578 if (x_delta == 0 && delta_time_axis_view == 0 && delta_layer == 0 && !first_move) {
579 /* haven't reached next snap point, and we're not switching
580 trackviews nor layers. nothing to do.
582 return;
585 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
587 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
589 RegionView* rv = i->view;
591 if (rv->region()->locked()) {
592 continue;
595 if (first_move) {
597 /* here we are calculating the y distance from the
598 top of the first track view to the top of the region
599 area of the track view that we're working on */
601 /* this x value is just a dummy value so that we have something
602 to pass to i2w () */
604 double ix1 = 0;
606 /* distance from the top of this track view to the region area
607 of our track view is always 1 */
609 double iy1 = 1;
611 /* convert to world coordinates, ie distance from the top of
612 the ruler section */
614 rv->get_canvas_frame()->i2w (ix1, iy1);
616 /* compensate for the ruler section and the vertical scrollbar position */
617 iy1 += _editor->get_trackview_group_vertical_offset ();
619 // hide any dependent views
621 rv->get_time_axis_view().hide_dependent_views (*rv);
624 reparent to a non scrolling group so that we can keep the
625 region selection above all time axis views.
626 reparenting means we have to move the rv as the two
627 parent groups have different coordinates.
630 rv->get_canvas_group()->property_y() = iy1 - 1;
631 rv->get_canvas_group()->reparent (*(_editor->_region_motion_group));
633 rv->fake_set_opaque (true);
636 /* Work out the change in y position of this region view */
638 double y_delta = 0;
640 /* If we have moved tracks, we'll fudge the layer delta so that the
641 region gets moved back onto layer 0 on its new track; this avoids
642 confusion when dragging regions from non-zero layers onto different
643 tracks.
645 int this_delta_layer = delta_layer;
646 if (delta_time_axis_view != 0) {
647 this_delta_layer = - i->layer;
650 /* Move this region to layer 0 on its old track */
651 StreamView* lv = _time_axis_views[i->time_axis_view]->view ();
652 if (lv->layer_display() == Stacked) {
653 y_delta -= (lv->layers() - i->layer - 1) * lv->child_height ();
656 /* Now move it to its right layer on the current track */
657 StreamView* cv = _time_axis_views[i->time_axis_view + delta_time_axis_view]->view ();
658 if (cv->layer_display() == Stacked) {
659 y_delta += (cv->layers() - (i->layer + this_delta_layer) - 1) * cv->child_height ();
662 /* Move tracks */
663 if (delta_time_axis_view > 0) {
664 for (int j = 0; j < delta_time_axis_view; ++j) {
665 y_delta += _time_axis_views[i->time_axis_view + j]->current_height ();
667 } else {
668 /* start by subtracting the height of the track above where we are now */
669 for (int j = 1; j <= -delta_time_axis_view; ++j) {
670 y_delta -= _time_axis_views[i->time_axis_view - j]->current_height ();
674 /* Set height */
675 rv->set_height (_time_axis_views[i->time_axis_view + delta_time_axis_view]->view()->child_height ());
677 /* Update the DraggingView */
678 i->time_axis_view += delta_time_axis_view;
679 i->layer += this_delta_layer;
681 if (_brushing) {
682 _editor->mouse_brush_insert_region (rv, pending_region_position);
683 } else {
684 rv->move (x_delta, y_delta);
687 } /* foreach region */
689 _total_x_delta += x_delta;
691 if (first_move) {
692 _editor->cursor_group->raise_to_top();
695 if (x_delta != 0 && !_brushing) {
696 _editor->show_verbose_time_cursor (_last_frame_position, 10);
699 _last_pointer_time_axis_view += delta_time_axis_view;
700 _last_pointer_layer += delta_layer;
703 void
704 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
706 if (_copy && first_move) {
708 /* duplicate the regionview(s) and region(s) */
710 list<DraggingView> new_regionviews;
712 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
714 RegionView* rv = i->view;
715 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
716 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
718 const boost::shared_ptr<const Region> original = rv->region();
719 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
720 region_copy->set_position (original->position(), this);
722 RegionView* nrv;
723 if (arv) {
724 boost::shared_ptr<AudioRegion> audioregion_copy
725 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
727 nrv = new AudioRegionView (*arv, audioregion_copy);
728 } else if (mrv) {
729 boost::shared_ptr<MidiRegion> midiregion_copy
730 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
731 nrv = new MidiRegionView (*mrv, midiregion_copy);
732 } else {
733 continue;
736 nrv->get_canvas_group()->show ();
737 new_regionviews.push_back (DraggingView (nrv, this));
739 /* swap _primary to the copy */
741 if (rv == _primary) {
742 _primary = nrv;
745 /* ..and deselect the one we copied */
747 rv->set_selected (false);
750 if (!new_regionviews.empty()) {
752 /* reflect the fact that we are dragging the copies */
754 _views = new_regionviews;
756 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
759 sync the canvas to what we think is its current state
760 without it, the canvas seems to
761 "forget" to update properly after the upcoming reparent()
762 ..only if the mouse is in rapid motion at the time of the grab.
763 something to do with regionview creation taking so long?
765 _editor->update_canvas_now();
769 RegionMotionDrag::motion (event, first_move);
772 void
773 RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
775 if (!movement_occurred) {
776 /* just a click */
777 return;
780 /* reverse this here so that we have the correct logic to finalize
781 the drag.
784 if (Config->get_edit_mode() == Lock) {
785 _x_constrained = !_x_constrained;
788 assert (!_views.empty ());
790 bool const changed_position = (_last_frame_position != _primary->region()->position());
791 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
792 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
794 _editor->update_canvas_now ();
796 if (_copy) {
798 finished_copy (
799 changed_position,
800 changed_tracks,
801 drag_delta
804 } else {
806 finished_no_copy (
807 changed_position,
808 changed_tracks,
809 drag_delta
815 void
816 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
818 RegionSelection new_views;
819 PlaylistSet modified_playlists;
820 list<RegionView*> views_to_delete;
822 if (_brushing) {
823 /* all changes were made during motion event handlers */
825 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
826 delete i->view;
829 _editor->commit_reversible_command ();
830 return;
833 if (_x_constrained) {
834 _editor->begin_reversible_command (_("fixed time region copy"));
835 } else {
836 _editor->begin_reversible_command (_("region copy"));
839 /* insert the regions into their new playlists */
840 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
842 if (i->view->region()->locked()) {
843 continue;
846 framepos_t where;
848 if (changed_position && !_x_constrained) {
849 where = i->view->region()->position() - drag_delta;
850 } else {
851 where = i->view->region()->position();
854 RegionView* new_view = insert_region_into_playlist (
855 i->view->region(), dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]), i->layer, where, modified_playlists
858 if (new_view == 0) {
859 continue;
862 new_views.push_back (new_view);
864 /* we don't need the copied RegionView any more */
865 views_to_delete.push_back (i->view);
868 /* Delete views that are no longer needed; we can't do this directly in the iteration over _views
869 because when views are deleted they are automagically removed from _views, which messes
870 up the iteration.
872 for (list<RegionView*>::iterator i = views_to_delete.begin(); i != views_to_delete.end(); ++i) {
873 delete *i;
876 /* If we've created new regions either by copying or moving
877 to a new track, we want to replace the old selection with the new ones
880 if (new_views.size() > 0) {
881 _editor->selection->set (new_views);
884 /* write commands for the accumulated diffs for all our modified playlists */
885 add_stateful_diff_commands_for_playlists (modified_playlists);
887 _editor->commit_reversible_command ();
890 void
891 RegionMoveDrag::finished_no_copy (
892 bool const changed_position,
893 bool const changed_tracks,
894 framecnt_t const drag_delta
897 RegionSelection new_views;
898 PlaylistSet modified_playlists;
899 PlaylistSet frozen_playlists;
901 if (_brushing) {
902 /* all changes were made during motion event handlers */
903 _editor->commit_reversible_command ();
904 return;
907 if (_x_constrained) {
908 _editor->begin_reversible_command (_("fixed time region drag"));
909 } else {
910 _editor->begin_reversible_command (Operations::region_drag);
913 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
915 RegionView* rv = i->view;
917 RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
918 layer_t const dest_layer = i->layer;
920 if (rv->region()->locked()) {
921 ++i;
922 continue;
925 framepos_t where;
927 if (changed_position && !_x_constrained) {
928 where = rv->region()->position() - drag_delta;
929 } else {
930 where = rv->region()->position();
933 if (changed_tracks) {
935 /* insert into new playlist */
937 RegionView* new_view = insert_region_into_playlist (
938 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
941 if (new_view == 0) {
942 ++i;
943 continue;
946 new_views.push_back (new_view);
948 /* remove from old playlist */
950 /* the region that used to be in the old playlist is not
951 moved to the new one - we use a copy of it. as a result,
952 any existing editor for the region should no longer be
953 visible.
955 rv->hide_region_editor();
956 rv->fake_set_opaque (false);
958 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
960 } else {
962 rv->region()->clear_changes ();
965 motion on the same track. plonk the previously reparented region
966 back to its original canvas group (its streamview).
967 No need to do anything for copies as they are fake regions which will be deleted.
970 rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
971 rv->get_canvas_group()->property_y() = i->initial_y;
972 rv->get_time_axis_view().reveal_dependent_views (*rv);
974 /* just change the model */
976 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
978 if (dest_rtv->view()->layer_display() == Stacked) {
979 rv->region()->set_layer (dest_layer);
980 rv->region()->set_pending_explicit_relayer (true);
983 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
985 pair<PlaylistSet::iterator, bool> r = frozen_playlists.insert (playlist);
987 if (r.second) {
988 playlist->freeze ();
991 /* this movement may result in a crossfade being modified, so we need to get undo
992 data from the playlist as well as the region.
995 r = modified_playlists.insert (playlist);
996 if (r.second) {
997 playlist->clear_changes ();
1000 rv->region()->set_position (where, (void*) this);
1002 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1005 if (changed_tracks) {
1007 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1008 was selected in all of them, then removing it from a playlist will have removed all
1009 trace of it from _views (i.e. there were N regions selected, we removed 1,
1010 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1011 corresponding regionview, and _views is now empty).
1013 This could have invalidated any and all iterators into _views.
1015 The heuristic we use here is: if the region selection is empty, break out of the loop
1016 here. if the region selection is not empty, then restart the loop because we know that
1017 we must have removed at least the region(view) we've just been working on as well as any
1018 that we processed on previous iterations.
1020 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1021 we can just iterate.
1025 if (_views.empty()) {
1026 break;
1027 } else {
1028 i = _views.begin();
1031 } else {
1032 ++i;
1036 /* If we've created new regions either by copying or moving
1037 to a new track, we want to replace the old selection with the new ones
1040 if (new_views.size() > 0) {
1041 _editor->selection->set (new_views);
1044 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1045 (*p)->thaw();
1048 /* write commands for the accumulated diffs for all our modified playlists */
1049 add_stateful_diff_commands_for_playlists (modified_playlists);
1051 _editor->commit_reversible_command ();
1054 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1055 * @param region Region to remove.
1056 * @param playlist playlist To remove from.
1057 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1058 * that clear_changes () is only called once per playlist.
1060 void
1061 RegionMoveDrag::remove_region_from_playlist (
1062 boost::shared_ptr<Region> region,
1063 boost::shared_ptr<Playlist> playlist,
1064 PlaylistSet& modified_playlists
1067 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1069 if (r.second) {
1070 playlist->clear_changes ();
1073 playlist->remove_region (region);
1077 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1078 * clearing the playlist's diff history first if necessary.
1079 * @param region Region to insert.
1080 * @param dest_rtv Destination RouteTimeAxisView.
1081 * @param dest_layer Destination layer.
1082 * @param where Destination position.
1083 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1084 * that clear_changes () is only called once per playlist.
1085 * @return New RegionView, or 0 if no insert was performed.
1087 RegionView *
1088 RegionMoveDrag::insert_region_into_playlist (
1089 boost::shared_ptr<Region> region,
1090 RouteTimeAxisView* dest_rtv,
1091 layer_t dest_layer,
1092 framecnt_t where,
1093 PlaylistSet& modified_playlists
1096 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1097 if (!dest_playlist) {
1098 return 0;
1101 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1102 _new_region_view = 0;
1103 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1105 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1106 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1107 if (r.second) {
1108 dest_playlist->clear_changes ();
1111 dest_playlist->add_region (region, where);
1113 if (dest_rtv->view()->layer_display() == Stacked) {
1114 region->set_layer (dest_layer);
1115 region->set_pending_explicit_relayer (true);
1118 c.disconnect ();
1120 assert (_new_region_view);
1122 return _new_region_view;
1125 void
1126 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1128 _new_region_view = rv;
1131 void
1132 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1134 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1135 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1136 if (!c->empty()) {
1137 _editor->session()->add_command (c);
1138 } else {
1139 delete c;
1145 void
1146 RegionMoveDrag::aborted (bool movement_occurred)
1148 if (_copy) {
1150 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1151 delete i->view;
1154 _views.clear ();
1156 } else {
1157 RegionMotionDrag::aborted (movement_occurred);
1161 void
1162 RegionMotionDrag::aborted (bool)
1164 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1165 RegionView* rv = i->view;
1166 TimeAxisView* tv = &(rv->get_time_axis_view ());
1167 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1168 assert (rtv);
1169 rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
1170 rv->get_canvas_group()->property_y() = 0;
1171 rv->get_time_axis_view().reveal_dependent_views (*rv);
1172 rv->fake_set_opaque (false);
1173 rv->move (-_total_x_delta, 0);
1174 rv->set_height (rtv->view()->child_height ());
1177 _editor->update_canvas_now ();
1180 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1181 : RegionMotionDrag (e, i, p, v, b),
1182 _copy (c)
1184 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1186 double speed = 1;
1187 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1188 if (rtv && rtv->is_track()) {
1189 speed = rtv->track()->speed ();
1192 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1195 void
1196 RegionMoveDrag::setup_pointer_frame_offset ()
1198 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1201 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1202 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1204 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1206 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1207 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1209 _primary = v->view()->create_region_view (r, false, false);
1211 _primary->get_canvas_group()->show ();
1212 _primary->set_position (pos, 0);
1213 _views.push_back (DraggingView (_primary, this));
1215 _last_frame_position = pos;
1217 _item = _primary->get_canvas_group ();
1220 void
1221 RegionInsertDrag::finished (GdkEvent *, bool)
1223 _editor->update_canvas_now ();
1225 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[_views.front().time_axis_view]);
1227 _primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
1228 _primary->get_canvas_group()->property_y() = 0;
1230 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1232 _editor->begin_reversible_command (Operations::insert_region);
1233 playlist->clear_changes ();
1234 playlist->add_region (_primary->region (), _last_frame_position);
1235 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1236 _editor->commit_reversible_command ();
1238 delete _primary;
1239 _primary = 0;
1240 _views.clear ();
1243 void
1244 RegionInsertDrag::aborted (bool)
1246 delete _primary;
1247 _primary = 0;
1248 _views.clear ();
1251 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1252 : RegionMoveDrag (e, i, p, v, false, false)
1254 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1257 struct RegionSelectionByPosition {
1258 bool operator() (RegionView*a, RegionView* b) {
1259 return a->region()->position () < b->region()->position();
1263 void
1264 RegionSpliceDrag::motion (GdkEvent* event, bool)
1266 /* Which trackview is this ? */
1268 pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
1269 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1270 layer_t layer = tvp.second;
1272 if (tv && tv->layer_display() == Overlaid) {
1273 layer = 0;
1276 /* The region motion is only processed if the pointer is over
1277 an audio track.
1280 if (!tv || !tv->is_track()) {
1281 /* To make sure we hide the verbose canvas cursor when the mouse is
1282 not held over and audiotrack.
1284 _editor->hide_verbose_canvas_cursor ();
1285 return;
1288 int dir;
1290 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1291 dir = 1;
1292 } else {
1293 dir = -1;
1296 RegionSelection copy (_editor->selection->regions);
1298 RegionSelectionByPosition cmp;
1299 copy.sort (cmp);
1301 framepos_t const pf = adjusted_current_frame (event);
1303 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1305 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1307 if (!atv) {
1308 continue;
1311 boost::shared_ptr<Playlist> playlist;
1313 if ((playlist = atv->playlist()) == 0) {
1314 continue;
1317 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1318 continue;
1321 if (dir > 0) {
1322 if (pf < (*i)->region()->last_frame() + 1) {
1323 continue;
1325 } else {
1326 if (pf > (*i)->region()->first_frame()) {
1327 continue;
1332 playlist->shuffle ((*i)->region(), dir);
1336 void
1337 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1339 RegionMoveDrag::finished (event, movement_occurred);
1342 void
1343 RegionSpliceDrag::aborted (bool)
1345 /* XXX: TODO */
1348 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
1349 : Drag (e, i),
1350 _view (dynamic_cast<MidiTimeAxisView*> (v))
1352 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
1354 assert (_view);
1357 void
1358 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
1360 if (first_move) {
1361 add_region();
1362 } else {
1363 if (_region) {
1364 framepos_t const f = adjusted_current_frame (event);
1365 if (f < grab_frame()) {
1366 _region->set_position (f, this);
1369 /* again, don't use a zero-length region (see above) */
1370 framecnt_t const len = abs (f - grab_frame ());
1371 _region->set_length (len < 1 ? 1 : len, this);
1376 void
1377 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
1379 if (!movement_occurred) {
1380 add_region ();
1383 if (_region) {
1384 _editor->commit_reversible_command ();
1388 void
1389 RegionCreateDrag::add_region ()
1391 if (_editor->session()) {
1392 const TempoMap& map (_editor->session()->tempo_map());
1393 framecnt_t pos = grab_frame();
1394 const Meter& m = map.meter_at (pos);
1395 /* not that the frame rate used here can be affected by pull up/down which
1396 might be wrong.
1398 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
1399 _region = _view->add_region (grab_frame(), len, false);
1403 void
1404 RegionCreateDrag::aborted (bool)
1406 /* XXX */
1409 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
1410 : Drag (e, i)
1411 , region (0)
1413 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
1416 void
1417 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
1419 Gdk::Cursor* cursor;
1420 ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item);
1421 float x_fraction = cnote->mouse_x_fraction ();
1423 if (x_fraction > 0.0 && x_fraction < 0.25) {
1424 cursor = _editor->cursors()->left_side_trim;
1425 } else {
1426 cursor = _editor->cursors()->right_side_trim;
1429 Drag::start_grab (event, cursor);
1431 region = &cnote->region_view();
1433 double const region_start = region->get_position_pixels();
1434 double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;
1436 if (grab_x() <= middle_point) {
1437 cursor = _editor->cursors()->left_side_trim;
1438 at_front = true;
1439 } else {
1440 cursor = _editor->cursors()->right_side_trim;
1441 at_front = false;
1444 _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, *cursor, event->motion.time);
1446 if (event->motion.state & Keyboard::PrimaryModifier) {
1447 relative = false;
1448 } else {
1449 relative = true;
1452 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1454 if (ms.size() > 1) {
1455 /* has to be relative, may make no sense otherwise */
1456 relative = true;
1459 /* select this note; if it is already selected, preserve the existing selection,
1460 otherwise make this note the only one selected.
1462 region->note_selected (cnote, cnote->selected ());
1464 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
1465 MidiRegionSelection::iterator next;
1466 next = r;
1467 ++next;
1468 (*r)->begin_resizing (at_front);
1469 r = next;
1473 void
1474 NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
1476 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1477 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1478 (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1482 void
1483 NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
1485 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1486 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1487 (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1491 void
1492 NoteResizeDrag::aborted (bool)
1494 /* XXX: TODO */
1497 RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
1498 : Drag (e, i)
1500 DEBUG_TRACE (DEBUG::Drags, "New RegionGainDrag\n");
1503 void
1504 RegionGainDrag::motion (GdkEvent* /*event*/, bool)
1509 void
1510 RegionGainDrag::finished (GdkEvent *, bool)
1515 void
1516 RegionGainDrag::aborted (bool)
1518 /* XXX: TODO */
1521 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1522 : RegionDrag (e, i, p, v)
1524 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
1527 void
1528 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
1530 double speed = 1.0;
1531 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1532 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1534 if (tv && tv->is_track()) {
1535 speed = tv->track()->speed();
1538 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
1539 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
1540 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
1542 framepos_t const pf = adjusted_current_frame (event);
1544 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1545 /* Move the contents of the region around without changing the region bounds */
1546 _operation = ContentsTrim;
1547 Drag::start_grab (event, _editor->cursors()->trimmer);
1548 } else {
1549 /* These will get overridden for a point trim.*/
1550 if (pf < (region_start + region_length/2)) {
1551 /* closer to front */
1552 _operation = StartTrim;
1553 Drag::start_grab (event, _editor->cursors()->left_side_trim);
1554 } else {
1555 /* closer to end */
1556 _operation = EndTrim;
1557 Drag::start_grab (event, _editor->cursors()->right_side_trim);
1561 switch (_operation) {
1562 case StartTrim:
1563 _editor->show_verbose_time_cursor (region_start, 10);
1564 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1565 i->view->trim_front_starting ();
1567 break;
1568 case EndTrim:
1569 _editor->show_verbose_time_cursor (region_end, 10);
1570 break;
1571 case ContentsTrim:
1572 _editor->show_verbose_time_cursor (pf, 10);
1573 break;
1576 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1577 i->view->region()->suspend_property_changes ();
1581 void
1582 TrimDrag::motion (GdkEvent* event, bool first_move)
1584 RegionView* rv = _primary;
1586 double speed = 1.0;
1587 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1588 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1589 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
1591 if (tv && tv->is_track()) {
1592 speed = tv->track()->speed();
1595 framecnt_t const dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
1597 if (first_move) {
1599 string trim_type;
1601 switch (_operation) {
1602 case StartTrim:
1603 trim_type = "Region start trim";
1604 break;
1605 case EndTrim:
1606 trim_type = "Region end trim";
1607 break;
1608 case ContentsTrim:
1609 trim_type = "Region content trim";
1610 break;
1613 _editor->begin_reversible_command (trim_type);
1615 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1616 RegionView* rv = i->view;
1617 rv->fake_set_opaque (false);
1618 rv->enable_display (false);
1619 rv->region()->playlist()->clear_owned_changes ();
1621 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
1623 if (arv) {
1624 arv->temporarily_hide_envelope ();
1627 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
1628 insert_result = _editor->motion_frozen_playlists.insert (pl);
1630 if (insert_result.second) {
1631 pl->freeze();
1636 bool non_overlap_trim = false;
1638 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1639 non_overlap_trim = true;
1642 switch (_operation) {
1643 case StartTrim:
1644 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1645 i->view->trim_front (i->initial_position + dt, non_overlap_trim);
1647 break;
1649 case EndTrim:
1650 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1651 i->view->trim_end (i->initial_end + dt, non_overlap_trim);
1653 break;
1655 case ContentsTrim:
1657 bool swap_direction = false;
1659 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1660 swap_direction = true;
1663 framecnt_t frame_delta = 0;
1665 bool left_direction = false;
1666 if (last_pointer_frame() > adjusted_current_frame(event)) {
1667 left_direction = true;
1670 if (left_direction) {
1671 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
1672 } else {
1673 frame_delta = (adjusted_current_frame(event) - last_pointer_frame());
1676 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1677 i->view->trim_contents (frame_delta, left_direction, swap_direction);
1680 break;
1683 switch (_operation) {
1684 case StartTrim:
1685 _editor->show_verbose_time_cursor ((framepos_t) (rv->region()->position() / speed), 10);
1686 break;
1687 case EndTrim:
1688 _editor->show_verbose_time_cursor ((framepos_t) (rv->region()->last_frame() / speed), 10);
1689 break;
1690 case ContentsTrim:
1691 _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
1692 break;
1697 void
1698 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
1700 if (movement_occurred) {
1701 motion (event, false);
1703 /* This must happen before the region's StatefulDiffCommand is created, as it may
1704 `correct' (ahem) the region's _start from being negative to being zero. It
1705 needs to be zero in the undo record.
1707 if (_operation == StartTrim) {
1708 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1709 i->view->trim_front_ending ();
1713 if (!_editor->selection->selected (_primary)) {
1714 _primary->thaw_after_trim ();
1715 } else {
1717 set<boost::shared_ptr<Playlist> > diffed_playlists;
1719 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1720 i->view->thaw_after_trim ();
1721 i->view->enable_display (true);
1722 i->view->fake_set_opaque (true);
1724 /* Trimming one region may affect others on the playlist, so we need
1725 to get undo Commands from the whole playlist rather than just the
1726 region. Use diffed_playlists to make sure we don't diff a given
1727 playlist more than once.
1729 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
1730 if (diffed_playlists.find (p) == diffed_playlists.end()) {
1731 vector<Command*> cmds;
1732 p->rdiff (cmds);
1733 _editor->session()->add_commands (cmds);
1734 diffed_playlists.insert (p);
1738 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
1739 (*p)->thaw ();
1742 _editor->motion_frozen_playlists.clear ();
1743 _editor->commit_reversible_command();
1745 } else {
1746 /* no mouse movement */
1747 _editor->point_trim (event, adjusted_current_frame (event));
1750 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1751 if (_operation == StartTrim) {
1752 i->view->trim_front_ending ();
1755 i->view->region()->resume_property_changes ();
1759 void
1760 TrimDrag::aborted (bool movement_occurred)
1762 /* Our motion method is changing model state, so use the Undo system
1763 to cancel. Perhaps not ideal, as this will leave an Undo point
1764 behind which may be slightly odd from the user's point of view.
1767 finished (0, true);
1769 if (movement_occurred) {
1770 _editor->undo ();
1773 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1774 i->view->region()->resume_property_changes ();
1778 void
1779 TrimDrag::setup_pointer_frame_offset ()
1781 list<DraggingView>::iterator i = _views.begin ();
1782 while (i != _views.end() && i->view != _primary) {
1783 ++i;
1786 if (i == _views.end()) {
1787 return;
1790 switch (_operation) {
1791 case StartTrim:
1792 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
1793 break;
1794 case EndTrim:
1795 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
1796 break;
1797 case ContentsTrim:
1798 break;
1802 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1803 : Drag (e, i),
1804 _copy (c)
1806 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
1808 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
1809 assert (_marker);
1812 void
1813 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1815 if (_copy) {
1816 // create a dummy marker for visual representation of moving the copy.
1817 // The actual copying is not done before we reach the finish callback.
1818 char name[64];
1819 snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
1821 MeterMarker* new_marker = new MeterMarker (
1822 *_editor,
1823 *_editor->meter_group,
1824 ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
1825 name,
1826 *new MeterSection (_marker->meter())
1829 _item = &new_marker->the_item ();
1830 _marker = new_marker;
1832 } else {
1834 MetricSection& section (_marker->meter());
1836 if (!section.movable()) {
1837 return;
1842 Drag::start_grab (event, cursor);
1844 _editor->show_verbose_time_cursor (adjusted_current_frame(event), 10);
1847 void
1848 MeterMarkerDrag::setup_pointer_frame_offset ()
1850 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
1853 void
1854 MeterMarkerDrag::motion (GdkEvent* event, bool)
1856 framepos_t const pf = adjusted_current_frame (event);
1858 _marker->set_position (pf);
1860 _editor->show_verbose_time_cursor (pf, 10);
1863 void
1864 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
1866 if (!movement_occurred) {
1867 return;
1870 motion (event, false);
1872 Timecode::BBT_Time when;
1874 TempoMap& map (_editor->session()->tempo_map());
1875 map.bbt_time (last_pointer_frame(), when);
1877 if (_copy == true) {
1878 _editor->begin_reversible_command (_("copy meter mark"));
1879 XMLNode &before = map.get_state();
1880 map.add_meter (_marker->meter(), when);
1881 XMLNode &after = map.get_state();
1882 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1883 _editor->commit_reversible_command ();
1885 // delete the dummy marker we used for visual representation of copying.
1886 // a new visual marker will show up automatically.
1887 delete _marker;
1888 } else {
1889 _editor->begin_reversible_command (_("move meter mark"));
1890 XMLNode &before = map.get_state();
1891 map.move_meter (_marker->meter(), when);
1892 XMLNode &after = map.get_state();
1893 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1894 _editor->commit_reversible_command ();
1898 void
1899 MeterMarkerDrag::aborted (bool)
1901 _marker->set_position (_marker->meter().frame ());
1904 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1905 : Drag (e, i),
1906 _copy (c)
1908 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
1910 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
1911 assert (_marker);
1914 void
1915 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1917 if (_copy) {
1919 // create a dummy marker for visual representation of moving the copy.
1920 // The actual copying is not done before we reach the finish callback.
1921 char name[64];
1922 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
1924 TempoMarker* new_marker = new TempoMarker (
1925 *_editor,
1926 *_editor->tempo_group,
1927 ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
1928 name,
1929 *new TempoSection (_marker->tempo())
1932 _item = &new_marker->the_item ();
1933 _marker = new_marker;
1937 Drag::start_grab (event, cursor);
1939 _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
1942 void
1943 TempoMarkerDrag::setup_pointer_frame_offset ()
1945 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
1948 void
1949 TempoMarkerDrag::motion (GdkEvent* event, bool)
1951 framepos_t const pf = adjusted_current_frame (event);
1952 _marker->set_position (pf);
1953 _editor->show_verbose_time_cursor (pf, 10);
1956 void
1957 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
1959 if (!movement_occurred) {
1960 return;
1963 motion (event, false);
1965 Timecode::BBT_Time when;
1967 TempoMap& map (_editor->session()->tempo_map());
1968 map.bbt_time (last_pointer_frame(), when);
1970 if (_copy == true) {
1971 _editor->begin_reversible_command (_("copy tempo mark"));
1972 XMLNode &before = map.get_state();
1973 map.add_tempo (_marker->tempo(), when);
1974 XMLNode &after = map.get_state();
1975 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
1976 _editor->commit_reversible_command ();
1978 // delete the dummy marker we used for visual representation of copying.
1979 // a new visual marker will show up automatically.
1980 delete _marker;
1981 } else {
1982 _editor->begin_reversible_command (_("move tempo mark"));
1983 XMLNode &before = map.get_state();
1984 map.move_tempo (_marker->tempo(), when);
1985 XMLNode &after = map.get_state();
1986 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
1987 _editor->commit_reversible_command ();
1991 void
1992 TempoMarkerDrag::aborted (bool)
1994 _marker->set_position (_marker->tempo().frame());
1997 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
1998 : Drag (e, i),
1999 _stop (s)
2001 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
2004 /** Do all the things we do when dragging the playhead to make it look as though
2005 * we have located, without actually doing the locate (because that would cause
2006 * the diskstream buffers to be refilled, which is too slow).
2008 void
2009 CursorDrag::fake_locate (framepos_t t)
2011 _editor->playhead_cursor->set_position (t);
2013 Session* s = _editor->session ();
2014 if (s->timecode_transmission_suspended ()) {
2015 framepos_t const f = _editor->playhead_cursor->current_frame;
2016 s->send_mmc_locate (f);
2017 s->send_full_time_code (f);
2020 _editor->show_verbose_time_cursor (t, 10);
2021 _editor->UpdateAllTransportClocks (t);
2024 void
2025 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
2027 Drag::start_grab (event, c);
2029 framepos_t where = _editor->event_frame (event, 0, 0);
2030 _editor->snap_to_with_modifier (where, event);
2032 _editor->_dragging_playhead = true;
2034 Session* s = _editor->session ();
2036 if (s) {
2037 if (_was_rolling && _stop) {
2038 s->request_stop ();
2041 if (s->is_auditioning()) {
2042 s->cancel_audition ();
2045 s->request_suspend_timecode_transmission ();
2046 while (!s->timecode_transmission_suspended ()) {
2047 /* twiddle our thumbs */
2051 fake_locate (where);
2054 void
2055 CursorDrag::motion (GdkEvent* event, bool)
2057 framepos_t const adjusted_frame = adjusted_current_frame (event);
2059 if (adjusted_frame == last_pointer_frame()) {
2060 return;
2063 fake_locate (adjusted_frame);
2065 #ifdef GTKOSX
2066 _editor->update_canvas_now ();
2067 #endif
2070 void
2071 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
2073 _editor->_dragging_playhead = false;
2075 if (!movement_occurred && _stop) {
2076 return;
2079 motion (event, false);
2081 Session* s = _editor->session ();
2082 if (s) {
2083 s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
2084 _editor->_pending_locate_request = true;
2085 s->request_resume_timecode_transmission ();
2089 void
2090 CursorDrag::aborted (bool)
2092 if (_editor->_dragging_playhead) {
2093 _editor->session()->request_resume_timecode_transmission ();
2094 _editor->_dragging_playhead = false;
2097 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
2100 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2101 : RegionDrag (e, i, p, v)
2103 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
2106 void
2107 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2109 Drag::start_grab (event, cursor);
2111 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2112 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2114 _editor->show_verbose_duration_cursor (r->position(), r->position() + r->fade_in()->back()->when, 10);
2116 arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
2119 void
2120 FadeInDrag::setup_pointer_frame_offset ()
2122 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2123 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2124 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
2127 void
2128 FadeInDrag::motion (GdkEvent* event, bool)
2130 framecnt_t fade_length;
2132 framepos_t const pos = adjusted_current_frame (event);
2134 boost::shared_ptr<Region> region = _primary->region ();
2136 if (pos < (region->position() + 64)) {
2137 fade_length = 64; // this should be a minimum defined somewhere
2138 } else if (pos > region->last_frame()) {
2139 fade_length = region->length();
2140 } else {
2141 fade_length = pos - region->position();
2144 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2146 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2148 if (!tmp) {
2149 continue;
2152 tmp->reset_fade_in_shape_width (fade_length);
2153 tmp->show_fade_line((framecnt_t) fade_length);
2156 _editor->show_verbose_duration_cursor (region->position(), region->position() + fade_length, 10);
2159 void
2160 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
2162 if (!movement_occurred) {
2163 return;
2166 framecnt_t fade_length;
2168 framepos_t const pos = adjusted_current_frame (event);
2170 boost::shared_ptr<Region> region = _primary->region ();
2172 if (pos < (region->position() + 64)) {
2173 fade_length = 64; // this should be a minimum defined somewhere
2174 } else if (pos > region->last_frame()) {
2175 fade_length = region->length();
2176 } else {
2177 fade_length = pos - region->position();
2180 _editor->begin_reversible_command (_("change fade in length"));
2182 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2184 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2186 if (!tmp) {
2187 continue;
2190 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
2191 XMLNode &before = alist->get_state();
2193 tmp->audio_region()->set_fade_in_length (fade_length);
2194 tmp->audio_region()->set_fade_in_active (true);
2195 tmp->hide_fade_line();
2197 XMLNode &after = alist->get_state();
2198 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2201 _editor->commit_reversible_command ();
2204 void
2205 FadeInDrag::aborted (bool)
2207 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2208 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2210 if (!tmp) {
2211 continue;
2214 tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
2215 tmp->hide_fade_line();
2219 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2220 : RegionDrag (e, i, p, v)
2222 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
2225 void
2226 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2228 Drag::start_grab (event, cursor);
2230 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2231 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2233 _editor->show_verbose_duration_cursor (r->last_frame() - r->fade_out()->back()->when, r->last_frame(), 10);
2235 arv->show_fade_line(r->length() - r->fade_out()->back()->when);
2238 void
2239 FadeOutDrag::setup_pointer_frame_offset ()
2241 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2242 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2243 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
2246 void
2247 FadeOutDrag::motion (GdkEvent* event, bool)
2249 framecnt_t fade_length;
2251 framepos_t const pos = adjusted_current_frame (event);
2253 boost::shared_ptr<Region> region = _primary->region ();
2255 if (pos > (region->last_frame() - 64)) {
2256 fade_length = 64; // this should really be a minimum fade defined somewhere
2258 else if (pos < region->position()) {
2259 fade_length = region->length();
2261 else {
2262 fade_length = region->last_frame() - pos;
2265 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2267 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2269 if (!tmp) {
2270 continue;
2273 tmp->reset_fade_out_shape_width (fade_length);
2274 tmp->show_fade_line(region->length() - fade_length);
2277 _editor->show_verbose_duration_cursor (region->last_frame() - fade_length, region->last_frame(), 10);
2280 void
2281 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
2283 if (!movement_occurred) {
2284 return;
2287 framecnt_t fade_length;
2289 framepos_t const pos = adjusted_current_frame (event);
2291 boost::shared_ptr<Region> region = _primary->region ();
2293 if (pos > (region->last_frame() - 64)) {
2294 fade_length = 64; // this should really be a minimum fade defined somewhere
2296 else if (pos < region->position()) {
2297 fade_length = region->length();
2299 else {
2300 fade_length = region->last_frame() - pos;
2303 _editor->begin_reversible_command (_("change fade out length"));
2305 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2307 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2309 if (!tmp) {
2310 continue;
2313 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
2314 XMLNode &before = alist->get_state();
2316 tmp->audio_region()->set_fade_out_length (fade_length);
2317 tmp->audio_region()->set_fade_out_active (true);
2318 tmp->hide_fade_line();
2320 XMLNode &after = alist->get_state();
2321 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2324 _editor->commit_reversible_command ();
2327 void
2328 FadeOutDrag::aborted (bool)
2330 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2331 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2333 if (!tmp) {
2334 continue;
2337 tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
2338 tmp->hide_fade_line();
2342 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
2343 : Drag (e, i)
2345 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
2347 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
2348 assert (_marker);
2350 _points.push_back (Gnome::Art::Point (0, 0));
2351 _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
2354 MarkerDrag::~MarkerDrag ()
2356 for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
2357 delete *i;
2361 void
2362 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2364 Drag::start_grab (event, cursor);
2366 bool is_start;
2368 Location *location = _editor->find_location_from_marker (_marker, is_start);
2369 _editor->_dragging_edit_point = true;
2371 update_item (location);
2373 // _drag_line->show();
2374 // _line->raise_to_top();
2376 if (is_start) {
2377 _editor->show_verbose_time_cursor (location->start(), 10);
2378 } else {
2379 _editor->show_verbose_time_cursor (location->end(), 10);
2382 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2384 switch (op) {
2385 case Selection::Toggle:
2386 _editor->selection->toggle (_marker);
2387 break;
2388 case Selection::Set:
2389 if (!_editor->selection->selected (_marker)) {
2390 _editor->selection->set (_marker);
2392 break;
2393 case Selection::Extend:
2395 Locations::LocationList ll;
2396 list<Marker*> to_add;
2397 framepos_t s, e;
2398 _editor->selection->markers.range (s, e);
2399 s = min (_marker->position(), s);
2400 e = max (_marker->position(), e);
2401 s = min (s, e);
2402 e = max (s, e);
2403 if (e < max_framepos) {
2404 ++e;
2406 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
2407 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
2408 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
2409 if (lm) {
2410 if (lm->start) {
2411 to_add.push_back (lm->start);
2413 if (lm->end) {
2414 to_add.push_back (lm->end);
2418 if (!to_add.empty()) {
2419 _editor->selection->add (to_add);
2421 break;
2423 case Selection::Add:
2424 _editor->selection->add (_marker);
2425 break;
2428 /* Set up copies for us to manipulate during the drag */
2430 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
2431 Location* l = _editor->find_location_from_marker (*i, is_start);
2432 _copied_locations.push_back (new Location (*l));
2436 void
2437 MarkerDrag::setup_pointer_frame_offset ()
2439 bool is_start;
2440 Location *location = _editor->find_location_from_marker (_marker, is_start);
2441 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
2444 void
2445 MarkerDrag::motion (GdkEvent* event, bool)
2447 framecnt_t f_delta = 0;
2448 bool is_start;
2449 bool move_both = false;
2450 Marker* marker;
2451 Location *real_location;
2452 Location *copy_location = 0;
2454 framepos_t const newframe = adjusted_current_frame (event);
2456 framepos_t next = newframe;
2458 if (newframe == last_pointer_frame()) {
2459 return;
2462 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2463 move_both = true;
2466 MarkerSelection::iterator i;
2467 list<Location*>::iterator x;
2469 /* find the marker we're dragging, and compute the delta */
2471 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2472 x != _copied_locations.end() && i != _editor->selection->markers.end();
2473 ++i, ++x) {
2475 copy_location = *x;
2476 marker = *i;
2478 if (marker == _marker) {
2480 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2481 /* que pasa ?? */
2482 return;
2485 if (real_location->is_mark()) {
2486 f_delta = newframe - copy_location->start();
2487 } else {
2490 switch (marker->type()) {
2491 case Marker::SessionStart:
2492 case Marker::RangeStart:
2493 case Marker::LoopStart:
2494 case Marker::PunchIn:
2495 f_delta = newframe - copy_location->start();
2496 break;
2498 case Marker::SessionEnd:
2499 case Marker::RangeEnd:
2500 case Marker::LoopEnd:
2501 case Marker::PunchOut:
2502 f_delta = newframe - copy_location->end();
2503 break;
2504 default:
2505 /* what kind of marker is this ? */
2506 return;
2509 break;
2513 if (i == _editor->selection->markers.end()) {
2514 /* hmm, impossible - we didn't find the dragged marker */
2515 return;
2518 /* now move them all */
2520 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2521 x != _copied_locations.end() && i != _editor->selection->markers.end();
2522 ++i, ++x) {
2524 copy_location = *x;
2525 marker = *i;
2527 /* call this to find out if its the start or end */
2529 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2530 continue;
2533 if (real_location->locked()) {
2534 continue;
2537 if (copy_location->is_mark()) {
2539 /* now move it */
2541 copy_location->set_start (copy_location->start() + f_delta);
2543 } else {
2545 framepos_t new_start = copy_location->start() + f_delta;
2546 framepos_t new_end = copy_location->end() + f_delta;
2548 if (is_start) { // start-of-range marker
2550 if (move_both) {
2551 copy_location->set_start (new_start);
2552 copy_location->set_end (new_end);
2553 } else if (new_start < copy_location->end()) {
2554 copy_location->set_start (new_start);
2555 } else if (newframe > 0) {
2556 _editor->snap_to (next, 1, true);
2557 copy_location->set_end (next);
2558 copy_location->set_start (newframe);
2561 } else { // end marker
2563 if (move_both) {
2564 copy_location->set_end (new_end);
2565 copy_location->set_start (new_start);
2566 } else if (new_end > copy_location->start()) {
2567 copy_location->set_end (new_end);
2568 } else if (newframe > 0) {
2569 _editor->snap_to (next, -1, true);
2570 copy_location->set_start (next);
2571 copy_location->set_end (newframe);
2576 update_item (copy_location);
2578 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
2580 if (lm) {
2581 lm->set_position (copy_location->start(), copy_location->end());
2585 assert (!_copied_locations.empty());
2587 _editor->show_verbose_time_cursor (newframe, 10);
2589 #ifdef GTKOSX
2590 _editor->update_canvas_now ();
2591 #endif
2594 void
2595 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2597 if (!movement_occurred) {
2599 /* just a click, do nothing but finish
2600 off the selection process
2603 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2605 switch (op) {
2606 case Selection::Set:
2607 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
2608 _editor->selection->set (_marker);
2610 break;
2612 case Selection::Toggle:
2613 case Selection::Extend:
2614 case Selection::Add:
2615 break;
2618 return;
2621 _editor->_dragging_edit_point = false;
2623 _editor->begin_reversible_command ( _("move marker") );
2624 XMLNode &before = _editor->session()->locations()->get_state();
2626 MarkerSelection::iterator i;
2627 list<Location*>::iterator x;
2628 bool is_start;
2630 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2631 x != _copied_locations.end() && i != _editor->selection->markers.end();
2632 ++i, ++x) {
2634 Location * location = _editor->find_location_from_marker (*i, is_start);
2636 if (location) {
2638 if (location->locked()) {
2639 return;
2642 if (location->is_mark()) {
2643 location->set_start ((*x)->start());
2644 } else {
2645 location->set ((*x)->start(), (*x)->end());
2650 XMLNode &after = _editor->session()->locations()->get_state();
2651 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
2652 _editor->commit_reversible_command ();
2655 void
2656 MarkerDrag::aborted (bool)
2658 /* XXX: TODO */
2661 void
2662 MarkerDrag::update_item (Location* location)
2664 /* noop */
2667 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
2668 : Drag (e, i),
2669 _cumulative_x_drag (0),
2670 _cumulative_y_drag (0)
2672 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
2674 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
2675 assert (_point);
2679 void
2680 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2682 Drag::start_grab (event, _editor->cursors()->fader);
2684 // start the grab at the center of the control point so
2685 // the point doesn't 'jump' to the mouse after the first drag
2686 _fixed_grab_x = _point->get_x();
2687 _fixed_grab_y = _point->get_y();
2689 float const fraction = 1 - (_point->get_y() / _point->line().height());
2691 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
2693 _editor->set_verbose_canvas_cursor (_point->line().get_verbose_cursor_string (fraction),
2694 event->button.x + 10, event->button.y + 10);
2696 _editor->show_verbose_canvas_cursor ();
2699 void
2700 ControlPointDrag::motion (GdkEvent* event, bool)
2702 double dx = _drags->current_pointer_x() - last_pointer_x();
2703 double dy = _drags->current_pointer_y() - last_pointer_y();
2705 if (event->button.state & Keyboard::SecondaryModifier) {
2706 dx *= 0.1;
2707 dy *= 0.1;
2710 /* coordinate in pixels relative to the start of the region (for region-based automation)
2711 or track (for track-based automation) */
2712 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
2713 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2715 // calculate zero crossing point. back off by .01 to stay on the
2716 // positive side of zero
2717 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
2719 // make sure we hit zero when passing through
2720 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
2721 cy = zero_gain_y;
2724 if (_x_constrained) {
2725 cx = _fixed_grab_x;
2727 if (_y_constrained) {
2728 cy = _fixed_grab_y;
2731 _cumulative_x_drag = cx - _fixed_grab_x;
2732 _cumulative_y_drag = cy - _fixed_grab_y;
2734 cx = max (0.0, cx);
2735 cy = max (0.0, cy);
2736 cy = min ((double) _point->line().height(), cy);
2738 framepos_t cx_frames = _editor->unit_to_frame (cx);
2740 if (!_x_constrained) {
2741 _editor->snap_to_with_modifier (cx_frames, event);
2744 cx_frames = min (cx_frames, _point->line().maximum_time());
2746 float const fraction = 1.0 - (cy / _point->line().height());
2748 bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
2750 _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
2752 _editor->set_verbose_canvas_cursor_text (_point->line().get_verbose_cursor_string (fraction));
2755 void
2756 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
2758 if (!movement_occurred) {
2760 /* just a click */
2762 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2763 _editor->reset_point_selection ();
2766 } else {
2767 motion (event, false);
2770 _point->line().end_drag ();
2771 _editor->session()->commit_reversible_command ();
2774 void
2775 ControlPointDrag::aborted (bool)
2777 _point->line().reset ();
2780 bool
2781 ControlPointDrag::active (Editing::MouseMode m)
2783 if (m == Editing::MouseGain) {
2784 /* always active in mouse gain */
2785 return true;
2788 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
2789 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
2792 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
2793 : Drag (e, i),
2794 _line (0),
2795 _cumulative_y_drag (0)
2797 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
2800 void
2801 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2803 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
2804 assert (_line);
2806 _item = &_line->grab_item ();
2808 /* need to get x coordinate in terms of parent (TimeAxisItemView)
2809 origin, and ditto for y.
2812 double cx = event->button.x;
2813 double cy = event->button.y;
2815 _line->parent_group().w2i (cx, cy);
2817 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit);
2819 uint32_t before;
2820 uint32_t after;
2822 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
2823 /* no adjacent points */
2824 return;
2827 Drag::start_grab (event, _editor->cursors()->fader);
2829 /* store grab start in parent frame */
2831 _fixed_grab_x = cx;
2832 _fixed_grab_y = cy;
2834 double fraction = 1.0 - (cy / _line->height());
2836 _line->start_drag_line (before, after, fraction);
2838 _editor->set_verbose_canvas_cursor (_line->get_verbose_cursor_string (fraction),
2839 event->button.x + 10, event->button.y + 10);
2841 _editor->show_verbose_canvas_cursor ();
2844 void
2845 LineDrag::motion (GdkEvent* event, bool)
2847 double dy = _drags->current_pointer_y() - last_pointer_y();
2849 if (event->button.state & Keyboard::SecondaryModifier) {
2850 dy *= 0.1;
2853 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2855 _cumulative_y_drag = cy - _fixed_grab_y;
2857 cy = max (0.0, cy);
2858 cy = min ((double) _line->height(), cy);
2860 double const fraction = 1.0 - (cy / _line->height());
2862 bool push;
2864 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
2865 push = false;
2866 } else {
2867 push = true;
2870 /* we are ignoring x position for this drag, so we can just pass in anything */
2871 _line->drag_motion (0, fraction, true, push);
2873 _editor->set_verbose_canvas_cursor_text (_line->get_verbose_cursor_string (fraction));
2876 void
2877 LineDrag::finished (GdkEvent* event, bool)
2879 motion (event, false);
2880 _line->end_drag ();
2881 _editor->session()->commit_reversible_command ();
2884 void
2885 LineDrag::aborted (bool)
2887 _line->reset ();
2890 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
2891 : Drag (e, i),
2892 _line (0),
2893 _cumulative_x_drag (0)
2895 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
2898 void
2899 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2901 Drag::start_grab (event);
2903 _line = reinterpret_cast<Line*> (_item);
2904 assert (_line);
2906 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
2908 double cx = event->button.x;
2909 double cy = event->button.y;
2911 _item->property_parent().get_value()->w2i(cx, cy);
2913 /* store grab start in parent frame */
2914 _region_view_grab_x = cx;
2916 _before = *(float*) _item->get_data ("position");
2918 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
2920 _max_x = _editor->frame_to_pixel(_arv->get_duration());
2923 void
2924 FeatureLineDrag::motion (GdkEvent*, bool)
2926 double dx = _drags->current_pointer_x() - last_pointer_x();
2928 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
2930 _cumulative_x_drag += dx;
2932 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
2934 if (cx > _max_x){
2935 cx = _max_x;
2937 else if(cx < 0){
2938 cx = 0;
2941 ArdourCanvas::Points points;
2943 double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
2945 _line->get_bounds(x1, y2, x2, y2);
2947 points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
2948 points.push_back(Gnome::Art::Point(cx, y2 - y1));
2950 _line->property_points() = points;
2952 float *pos = new float;
2953 *pos = cx;
2955 _line->set_data ("position", pos);
2957 _before = cx;
2960 void
2961 FeatureLineDrag::finished (GdkEvent*, bool)
2963 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
2964 _arv->update_transient(_before, _before);
2967 void
2968 FeatureLineDrag::aborted (bool)
2970 //_line->reset ();
2973 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
2974 : Drag (e, i)
2976 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
2979 void
2980 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
2982 Drag::start_grab (event);
2983 _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
2986 void
2987 RubberbandSelectDrag::motion (GdkEvent* event, bool)
2989 framepos_t start;
2990 framepos_t end;
2991 double y1;
2992 double y2;
2994 framepos_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
2996 framepos_t grab = grab_frame ();
2997 if (Config->get_rubberbanding_snaps_to_grid ()) {
2998 _editor->snap_to_with_modifier (grab, event);
3001 /* base start and end on initial click position */
3003 if (pf < grab) {
3004 start = pf;
3005 end = grab;
3006 } else {
3007 end = pf;
3008 start = grab;
3011 if (_drags->current_pointer_y() < grab_y()) {
3012 y1 = _drags->current_pointer_y();
3013 y2 = grab_y();
3014 } else {
3015 y2 = _drags->current_pointer_y();
3016 y1 = grab_y();
3020 if (start != end || y1 != y2) {
3022 double x1 = _editor->frame_to_pixel (start);
3023 double x2 = _editor->frame_to_pixel (end);
3025 _editor->rubberband_rect->property_x1() = x1;
3026 _editor->rubberband_rect->property_y1() = y1;
3027 _editor->rubberband_rect->property_x2() = x2;
3028 _editor->rubberband_rect->property_y2() = y2;
3030 _editor->rubberband_rect->show();
3031 _editor->rubberband_rect->raise_to_top();
3033 _editor->show_verbose_time_cursor (pf, 10);
3037 void
3038 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
3040 if (movement_occurred) {
3042 motion (event, false);
3044 double y1,y2;
3045 if (_drags->current_pointer_y() < grab_y()) {
3046 y1 = _drags->current_pointer_y();
3047 y2 = grab_y();
3048 } else {
3049 y2 = _drags->current_pointer_y();
3050 y1 = grab_y();
3054 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3056 _editor->begin_reversible_command (_("rubberband selection"));
3058 if (grab_frame() < last_pointer_frame()) {
3059 _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
3060 } else {
3061 _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
3064 _editor->commit_reversible_command ();
3066 } else {
3067 if (!getenv("ARDOUR_SAE")) {
3068 _editor->selection->clear_tracks();
3070 _editor->selection->clear_regions();
3071 _editor->selection->clear_points ();
3072 _editor->selection->clear_lines ();
3075 _editor->rubberband_rect->hide();
3078 void
3079 RubberbandSelectDrag::aborted (bool)
3081 _editor->rubberband_rect->hide ();
3084 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
3085 : RegionDrag (e, i, p, v)
3087 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
3090 void
3091 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3093 Drag::start_grab (event, cursor);
3095 _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
3098 void
3099 TimeFXDrag::motion (GdkEvent* event, bool)
3101 RegionView* rv = _primary;
3103 framepos_t const pf = adjusted_current_frame (event);
3105 if (pf > rv->region()->position()) {
3106 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
3109 _editor->show_verbose_time_cursor (pf, 10);
3112 void
3113 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3115 _primary->get_time_axis_view().hide_timestretch ();
3117 if (!movement_occurred) {
3118 return;
3121 if (last_pointer_frame() < _primary->region()->position()) {
3122 /* backwards drag of the left edge - not usable */
3123 return;
3126 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
3128 float percentage = (double) newlen / (double) _primary->region()->length();
3130 #ifndef USE_RUBBERBAND
3131 // Soundtouch uses percentage / 100 instead of normal (/ 1)
3132 if (_primary->region()->data_type() == DataType::AUDIO) {
3133 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
3135 #endif
3137 _editor->begin_reversible_command (_("timestretch"));
3139 // XXX how do timeFX on multiple regions ?
3141 RegionSelection rs;
3142 rs.add (_primary);
3144 if (_editor->time_stretch (rs, percentage) == -1) {
3145 error << _("An error occurred while executing time stretch operation") << endmsg;
3149 void
3150 TimeFXDrag::aborted (bool)
3152 _primary->get_time_axis_view().hide_timestretch ();
3155 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
3156 : Drag (e, i)
3158 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
3161 void
3162 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3164 Drag::start_grab (event);
3167 void
3168 ScrubDrag::motion (GdkEvent* /*event*/, bool)
3170 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
3173 void
3174 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3176 if (movement_occurred && _editor->session()) {
3177 /* make sure we stop */
3178 _editor->session()->request_transport_speed (0.0);
3182 void
3183 ScrubDrag::aborted (bool)
3185 /* XXX: TODO */
3188 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3189 : Drag (e, i)
3190 , _operation (o)
3191 , _copy (false)
3192 , _original_pointer_time_axis (-1)
3193 , _last_pointer_time_axis (-1)
3195 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
3198 void
3199 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
3201 if (_editor->session() == 0) {
3202 return;
3205 Gdk::Cursor* cursor = 0;
3207 switch (_operation) {
3208 case CreateSelection:
3209 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3210 _copy = true;
3211 } else {
3212 _copy = false;
3214 cursor = _editor->cursors()->selector;
3215 Drag::start_grab (event, cursor);
3216 break;
3218 case SelectionStartTrim:
3219 if (_editor->clicked_axisview) {
3220 _editor->clicked_axisview->order_selection_trims (_item, true);
3222 Drag::start_grab (event, _editor->cursors()->left_side_trim);
3223 break;
3225 case SelectionEndTrim:
3226 if (_editor->clicked_axisview) {
3227 _editor->clicked_axisview->order_selection_trims (_item, false);
3229 Drag::start_grab (event, _editor->cursors()->right_side_trim);
3230 break;
3232 case SelectionMove:
3233 Drag::start_grab (event, cursor);
3234 break;
3237 if (_operation == SelectionMove) {
3238 _editor->show_verbose_time_cursor (_editor->selection->time[_editor->clicked_selection].start, 10);
3239 } else {
3240 _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
3243 _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
3246 void
3247 SelectionDrag::setup_pointer_frame_offset ()
3249 switch (_operation) {
3250 case CreateSelection:
3251 _pointer_frame_offset = 0;
3252 break;
3254 case SelectionStartTrim:
3255 case SelectionMove:
3256 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
3257 break;
3259 case SelectionEndTrim:
3260 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
3261 break;
3265 void
3266 SelectionDrag::motion (GdkEvent* event, bool first_move)
3268 framepos_t start = 0;
3269 framepos_t end = 0;
3270 framecnt_t length;
3272 pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
3273 if (pending_time_axis.first == 0) {
3274 return;
3277 framepos_t const pending_position = adjusted_current_frame (event);
3279 /* only alter selection if things have changed */
3281 if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
3282 return;
3285 switch (_operation) {
3286 case CreateSelection:
3288 framepos_t grab = grab_frame ();
3290 if (first_move) {
3291 _editor->snap_to (grab);
3294 if (pending_position < grab_frame()) {
3295 start = pending_position;
3296 end = grab;
3297 } else {
3298 end = pending_position;
3299 start = grab;
3302 /* first drag: Either add to the selection
3303 or create a new selection
3306 if (first_move) {
3308 if (_copy) {
3309 /* adding to the selection */
3310 _editor->set_selected_track_as_side_effect (Selection::Add);
3311 //_editor->selection->add (_editor->clicked_axisview);
3312 _editor->clicked_selection = _editor->selection->add (start, end);
3313 _copy = false;
3314 } else {
3315 /* new selection */
3317 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3318 //_editor->selection->set (_editor->clicked_axisview);
3319 _editor->set_selected_track_as_side_effect (Selection::Set);
3322 _editor->clicked_selection = _editor->selection->set (start, end);
3326 /* select the track that we're in */
3327 if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
3328 // _editor->set_selected_track_as_side_effect (Selection::Add);
3329 _editor->selection->add (pending_time_axis.first);
3330 _added_time_axes.push_back (pending_time_axis.first);
3333 /* deselect any tracks that this drag no longer includes, being careful to only deselect
3334 tracks that we selected in the first place.
3337 int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
3338 int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
3340 list<TimeAxisView*>::iterator i = _added_time_axes.begin();
3341 while (i != _added_time_axes.end()) {
3343 list<TimeAxisView*>::iterator tmp = i;
3344 ++tmp;
3346 if ((*i)->order() < min_order || (*i)->order() > max_order) {
3347 _editor->selection->remove (*i);
3348 _added_time_axes.remove (*i);
3351 i = tmp;
3355 break;
3357 case SelectionStartTrim:
3359 start = _editor->selection->time[_editor->clicked_selection].start;
3360 end = _editor->selection->time[_editor->clicked_selection].end;
3362 if (pending_position > end) {
3363 start = end;
3364 } else {
3365 start = pending_position;
3367 break;
3369 case SelectionEndTrim:
3371 start = _editor->selection->time[_editor->clicked_selection].start;
3372 end = _editor->selection->time[_editor->clicked_selection].end;
3374 if (pending_position < start) {
3375 end = start;
3376 } else {
3377 end = pending_position;
3380 break;
3382 case SelectionMove:
3384 start = _editor->selection->time[_editor->clicked_selection].start;
3385 end = _editor->selection->time[_editor->clicked_selection].end;
3387 length = end - start;
3389 start = pending_position;
3390 _editor->snap_to (start);
3392 end = start + length;
3394 break;
3397 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3398 _editor->start_canvas_autoscroll (1, 0);
3401 if (start != end) {
3402 _editor->selection->replace (_editor->clicked_selection, start, end);
3405 if (_operation == SelectionMove) {
3406 _editor->show_verbose_time_cursor(start, 10);
3407 } else {
3408 _editor->show_verbose_time_cursor(pending_position, 10);
3412 void
3413 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
3415 Session* s = _editor->session();
3417 if (movement_occurred) {
3418 motion (event, false);
3419 /* XXX this is not object-oriented programming at all. ick */
3420 if (_editor->selection->time.consolidate()) {
3421 _editor->selection->TimeChanged ();
3424 /* XXX what if its a music time selection? */
3425 if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
3426 s->request_play_range (&_editor->selection->time, true);
3430 } else {
3431 /* just a click, no pointer movement.*/
3433 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
3434 _editor->selection->clear_time();
3437 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3438 _editor->selection->set (_editor->clicked_axisview);
3441 if (s && s->get_play_range () && s->transport_rolling()) {
3442 s->request_stop (false, false);
3447 _editor->stop_canvas_autoscroll ();
3450 void
3451 SelectionDrag::aborted (bool)
3453 /* XXX: TODO */
3456 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3457 : Drag (e, i),
3458 _operation (o),
3459 _copy (false)
3461 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
3463 _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
3464 physical_screen_height (_editor->get_window()));
3465 _drag_rect->hide ();
3467 _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3468 _drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3471 void
3472 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3474 if (_editor->session() == 0) {
3475 return;
3478 Gdk::Cursor* cursor = 0;
3480 if (!_editor->temp_location) {
3481 _editor->temp_location = new Location (*_editor->session());
3484 switch (_operation) {
3485 case CreateRangeMarker:
3486 case CreateTransportMarker:
3487 case CreateCDMarker:
3489 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3490 _copy = true;
3491 } else {
3492 _copy = false;
3494 cursor = _editor->cursors()->selector;
3495 break;
3498 Drag::start_grab (event, cursor);
3500 _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
3503 void
3504 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
3506 framepos_t start = 0;
3507 framepos_t end = 0;
3508 ArdourCanvas::SimpleRect *crect;
3510 switch (_operation) {
3511 case CreateRangeMarker:
3512 crect = _editor->range_bar_drag_rect;
3513 break;
3514 case CreateTransportMarker:
3515 crect = _editor->transport_bar_drag_rect;
3516 break;
3517 case CreateCDMarker:
3518 crect = _editor->cd_marker_bar_drag_rect;
3519 break;
3520 default:
3521 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
3522 return;
3523 break;
3526 framepos_t const pf = adjusted_current_frame (event);
3528 if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
3529 framepos_t grab = grab_frame ();
3530 _editor->snap_to (grab);
3532 if (pf < grab_frame()) {
3533 start = pf;
3534 end = grab;
3535 } else {
3536 end = pf;
3537 start = grab;
3540 /* first drag: Either add to the selection
3541 or create a new selection.
3544 if (first_move) {
3546 _editor->temp_location->set (start, end);
3548 crect->show ();
3550 update_item (_editor->temp_location);
3551 _drag_rect->show();
3552 //_drag_rect->raise_to_top();
3557 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3558 _editor->start_canvas_autoscroll (1, 0);
3561 if (start != end) {
3562 _editor->temp_location->set (start, end);
3564 double x1 = _editor->frame_to_pixel (start);
3565 double x2 = _editor->frame_to_pixel (end);
3566 crect->property_x1() = x1;
3567 crect->property_x2() = x2;
3569 update_item (_editor->temp_location);
3572 _editor->show_verbose_time_cursor (pf, 10);
3576 void
3577 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
3579 Location * newloc = 0;
3580 string rangename;
3581 int flags;
3583 if (movement_occurred) {
3584 motion (event, false);
3585 _drag_rect->hide();
3587 switch (_operation) {
3588 case CreateRangeMarker:
3589 case CreateCDMarker:
3591 _editor->begin_reversible_command (_("new range marker"));
3592 XMLNode &before = _editor->session()->locations()->get_state();
3593 _editor->session()->locations()->next_available_name(rangename,"unnamed");
3594 if (_operation == CreateCDMarker) {
3595 flags = Location::IsRangeMarker | Location::IsCDMarker;
3596 _editor->cd_marker_bar_drag_rect->hide();
3598 else {
3599 flags = Location::IsRangeMarker;
3600 _editor->range_bar_drag_rect->hide();
3602 newloc = new Location (
3603 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
3606 _editor->session()->locations()->add (newloc, true);
3607 XMLNode &after = _editor->session()->locations()->get_state();
3608 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3609 _editor->commit_reversible_command ();
3610 break;
3613 case CreateTransportMarker:
3614 // popup menu to pick loop or punch
3615 _editor->new_transport_marker_context_menu (&event->button, _item);
3616 break;
3618 } else {
3619 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
3621 if (Keyboard::no_modifier_keys_pressed (&event->button) && _operation != CreateCDMarker) {
3623 framepos_t start;
3624 framepos_t end;
3626 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
3628 if (end == max_framepos) {
3629 end = _editor->session()->current_end_frame ();
3632 if (start == max_framepos) {
3633 start = _editor->session()->current_start_frame ();
3636 switch (_editor->mouse_mode) {
3637 case MouseObject:
3638 /* find the two markers on either side and then make the selection from it */
3639 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
3640 break;
3642 case MouseRange:
3643 /* find the two markers on either side of the click and make the range out of it */
3644 _editor->selection->set (start, end);
3645 break;
3647 default:
3648 break;
3653 _editor->stop_canvas_autoscroll ();
3656 void
3657 RangeMarkerBarDrag::aborted (bool)
3659 /* XXX: TODO */
3662 void
3663 RangeMarkerBarDrag::update_item (Location* location)
3665 double const x1 = _editor->frame_to_pixel (location->start());
3666 double const x2 = _editor->frame_to_pixel (location->end());
3668 _drag_rect->property_x1() = x1;
3669 _drag_rect->property_x2() = x2;
3672 MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i)
3673 : Drag (e, i)
3674 , _zoom_out (false)
3676 DEBUG_TRACE (DEBUG::Drags, "New MouseZoomDrag\n");
3679 void
3680 MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3682 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
3683 Drag::start_grab (event, _editor->cursors()->zoom_out);
3684 _zoom_out = true;
3685 } else {
3686 Drag::start_grab (event, _editor->cursors()->zoom_in);
3687 _zoom_out = false;
3690 _editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
3693 void
3694 MouseZoomDrag::motion (GdkEvent* event, bool first_move)
3696 framepos_t start;
3697 framepos_t end;
3699 framepos_t const pf = adjusted_current_frame (event);
3701 framepos_t grab = grab_frame ();
3702 _editor->snap_to_with_modifier (grab, event);
3704 /* base start and end on initial click position */
3705 if (pf < grab) {
3706 start = pf;
3707 end = grab;
3708 } else {
3709 end = pf;
3710 start = grab;
3713 if (start != end) {
3715 if (first_move) {
3716 _editor->zoom_rect->show();
3717 _editor->zoom_rect->raise_to_top();
3720 _editor->reposition_zoom_rect(start, end);
3722 _editor->show_verbose_time_cursor (pf, 10);
3726 void
3727 MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
3729 if (movement_occurred) {
3730 motion (event, false);
3732 if (grab_frame() < last_pointer_frame()) {
3733 _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
3734 } else {
3735 _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
3737 } else {
3738 if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
3739 _editor->tav_zoom_step (_zoom_out);
3740 } else {
3741 _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
3745 _editor->zoom_rect->hide();
3748 void
3749 MouseZoomDrag::aborted (bool)
3751 _editor->zoom_rect->hide ();
3754 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
3755 : Drag (e, i)
3756 , _cumulative_dx (0)
3757 , _cumulative_dy (0)
3759 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
3761 _primary = dynamic_cast<CanvasNoteEvent*> (_item);
3762 _region = &_primary->region_view ();
3763 _note_height = _region->midi_stream_view()->note_height ();
3766 void
3767 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3769 Drag::start_grab (event);
3771 if (!(_was_selected = _primary->selected())) {
3773 /* tertiary-click means extend selection - we'll do that on button release,
3774 so don't add it here, because otherwise we make it hard to figure
3775 out the "extend-to" range.
3778 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
3780 if (!extend) {
3781 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
3783 if (add) {
3784 _region->note_selected (_primary, true);
3785 } else {
3786 _region->unique_select (_primary);
3792 /** @return Current total drag x change in frames */
3793 frameoffset_t
3794 NoteDrag::total_dx () const
3796 /* dx in frames */
3797 frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
3799 /* primary note time */
3800 frameoffset_t const n = _region->beats_to_frames (_primary->note()->time ());
3802 /* new time of the primary note relative to the region position */
3803 frameoffset_t const st = n + dx;
3805 /* snap and return corresponding delta */
3806 return _region->snap_frame_to_frame (st) - n;
3809 /** @return Current total drag y change in notes */
3810 int8_t
3811 NoteDrag::total_dy () const
3813 /* this is `backwards' to make increasing note number go in the right direction */
3814 double const dy = _drags->current_pointer_y() - grab_y();
3816 /* dy in notes */
3817 int8_t ndy = 0;
3819 if (abs (dy) >= _note_height) {
3820 if (dy > 0) {
3821 ndy = (int8_t) ceil (dy / _note_height / 2.0);
3822 } else {
3823 ndy = (int8_t) floor (dy / _note_height / 2.0);
3827 /* more positive value = higher pitch and higher y-axis position on track,
3828 which is the inverse of the X-centric geometric universe
3831 return -ndy;
3834 void
3835 NoteDrag::motion (GdkEvent *, bool)
3837 /* Total change in x and y since the start of the drag */
3838 frameoffset_t const dx = total_dx ();
3839 int8_t const dy = -total_dy ();
3841 /* Now work out what we have to do to the note canvas items to set this new drag delta */
3842 double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx;
3843 double const tdy = dy * _note_height - _cumulative_dy;
3845 if (tdx || tdy) {
3846 _cumulative_dx += tdx;
3847 _cumulative_dy += tdy;
3849 int8_t note_delta = total_dy();
3851 _region->move_selection (tdx, tdy, note_delta);
3853 char buf[12];
3854 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (_primary->note()->note() + note_delta).c_str(),
3855 (int) floor (_primary->note()->note() + note_delta));
3857 _editor->show_verbose_canvas_cursor_with (buf);
3861 void
3862 NoteDrag::finished (GdkEvent* ev, bool moved)
3864 if (!moved) {
3865 if (_editor->current_mouse_mode() == Editing::MouseObject) {
3867 if (_was_selected) {
3868 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3869 if (add) {
3870 _region->note_deselected (_primary);
3872 } else {
3873 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
3874 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3876 if (!extend && !add && _region->selection_size() > 1) {
3877 _region->unique_select (_primary);
3878 } else if (extend) {
3879 _region->note_selected (_primary, true, true);
3880 } else {
3881 /* it was added during button press */
3885 } else {
3886 _region->note_dropped (_primary, total_dx(), total_dy());
3890 void
3891 NoteDrag::aborted (bool)
3893 /* XXX: TODO */
3896 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* item, list<AudioRange> const & r)
3897 : Drag (editor, item)
3898 , _ranges (r)
3899 , _nothing_to_drag (false)
3901 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
3903 _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
3904 assert (_atav);
3906 /* get all lines in the automation view */
3907 list<boost::shared_ptr<AutomationLine> > lines = _atav->lines ();
3909 /* find those that overlap the ranges being dragged */
3910 list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
3911 while (i != lines.end ()) {
3912 list<boost::shared_ptr<AutomationLine> >::iterator j = i;
3913 ++j;
3915 pair<framepos_t, framepos_t> const r = (*i)->get_point_x_range ();
3917 /* check this range against all the AudioRanges that we are using */
3918 list<AudioRange>::const_iterator k = _ranges.begin ();
3919 while (k != _ranges.end()) {
3920 if (k->coverage (r.first, r.second) != OverlapNone) {
3921 break;
3923 ++k;
3926 /* add it to our list if it overlaps at all */
3927 if (k != _ranges.end()) {
3928 Line n;
3929 n.line = *i;
3930 n.state = 0;
3931 n.range = r;
3932 _lines.push_back (n);
3935 i = j;
3938 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
3941 void
3942 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3944 Drag::start_grab (event, cursor);
3946 /* Get line states before we start changing things */
3947 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
3948 i->state = &i->line->get_state ();
3951 if (_ranges.empty()) {
3953 /* No selected time ranges: drag all points */
3954 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
3955 uint32_t const N = i->line->npoints ();
3956 for (uint32_t j = 0; j < N; ++j) {
3957 i->points.push_back (i->line->nth (j));
3961 } else {
3963 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
3965 framecnt_t const half = (i->start + i->end) / 2;
3967 /* find the line that this audio range starts in */
3968 list<Line>::iterator j = _lines.begin();
3969 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
3970 ++j;
3973 if (j != _lines.end()) {
3974 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
3976 /* j is the line that this audio range starts in; fade into it;
3977 64 samples length plucked out of thin air.
3980 framepos_t a = i->start + 64;
3981 if (a > half) {
3982 a = half;
3985 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
3986 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
3988 the_list->add (p, the_list->eval (p));
3989 j->line->add_always_in_view (p);
3990 the_list->add (q, the_list->eval (q));
3991 j->line->add_always_in_view (q);
3994 /* same thing for the end */
3996 j = _lines.begin();
3997 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
3998 ++j;
4001 if (j != _lines.end()) {
4002 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4004 /* j is the line that this audio range starts in; fade out of it;
4005 64 samples length plucked out of thin air.
4008 framepos_t b = i->end - 64;
4009 if (b < half) {
4010 b = half;
4013 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
4014 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
4016 the_list->add (p, the_list->eval (p));
4017 j->line->add_always_in_view (p);
4018 the_list->add (q, the_list->eval (q));
4019 j->line->add_always_in_view (q);
4023 _nothing_to_drag = true;
4025 /* Find all the points that should be dragged and put them in the relevant
4026 points lists in the Line structs.
4029 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4031 uint32_t const N = i->line->npoints ();
4032 for (uint32_t j = 0; j < N; ++j) {
4034 /* here's a control point on this line */
4035 ControlPoint* p = i->line->nth (j);
4036 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
4038 /* see if it's inside a range */
4039 list<AudioRange>::const_iterator k = _ranges.begin ();
4040 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
4041 ++k;
4044 if (k != _ranges.end()) {
4045 /* dragging this point */
4046 _nothing_to_drag = false;
4047 i->points.push_back (p);
4053 if (_nothing_to_drag) {
4054 return;
4057 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4058 i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state);
4062 void
4063 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
4065 if (_nothing_to_drag) {
4066 return;
4069 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4070 float const f = 1 - (_drags->current_pointer_y() / i->line->height());
4072 /* we are ignoring x position for this drag, so we can just pass in anything */
4073 i->line->drag_motion (0, f, true, false);
4077 void
4078 AutomationRangeDrag::finished (GdkEvent* event, bool)
4080 if (_nothing_to_drag) {
4081 return;
4084 motion (event, false);
4085 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4086 i->line->end_drag ();
4087 i->line->clear_always_in_view ();
4090 _editor->session()->commit_reversible_command ();
4093 void
4094 AutomationRangeDrag::aborted (bool)
4096 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4097 i->line->clear_always_in_view ();
4098 i->line->reset ();
4102 DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
4103 : view (v)
4105 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
4106 layer = v->region()->layer ();
4107 initial_y = v->get_canvas_group()->property_y ();
4108 initial_playlist = v->region()->playlist ();
4109 initial_position = v->region()->position ();
4110 initial_end = v->region()->position () + v->region()->length ();
4113 PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
4114 : Drag (e, i)
4115 , _region_view (r)
4116 , _patch_change (i)
4117 , _cumulative_dx (0)
4119 DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
4122 void
4123 PatchChangeDrag::motion (GdkEvent* ev, bool)
4125 framepos_t f = adjusted_current_frame (ev);
4126 boost::shared_ptr<Region> r = _region_view->region ();
4127 f = max (f, r->position ());
4128 f = min (f, r->last_frame ());
4130 framecnt_t const dxf = f - grab_frame();
4131 double const dxu = _editor->frame_to_unit (dxf);
4132 _patch_change->move (dxu - _cumulative_dx, 0);
4133 _cumulative_dx = dxu;
4136 void
4137 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
4139 if (!movement_occurred) {
4140 return;
4143 boost::shared_ptr<Region> r (_region_view->region ());
4145 framepos_t f = adjusted_current_frame (ev);
4146 f = max (f, r->position ());
4147 f = min (f, r->last_frame ());
4149 _region_view->move_patch_change (
4150 *_patch_change,
4151 _region_view->frames_to_beats (f - r->position() - r->start())
4155 void
4156 PatchChangeDrag::aborted (bool)
4158 _patch_change->move (-_cumulative_dx, 0);
4161 void
4162 PatchChangeDrag::setup_pointer_frame_offset ()
4164 boost::shared_ptr<Region> region = _region_view->region ();
4165 _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_patch_change->patch()->time()) - region->position() + region->start();