Fix crash on moving back beyond the region start in the step editor (#4113).
[ardour2.git] / gtk2_ardour / editor_drag.cc
blob3be4e78db30e81bf26dcf424022c006cd84afe2a
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"
57 #include "verbose_cursor.h"
59 using namespace std;
60 using namespace ARDOUR;
61 using namespace PBD;
62 using namespace Gtk;
63 using namespace Gtkmm2ext;
64 using namespace Editing;
65 using namespace ArdourCanvas;
67 using Gtkmm2ext::Keyboard;
69 double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));
71 DragManager::DragManager (Editor* e)
72 : _editor (e)
73 , _ending (false)
74 , _current_pointer_frame (0)
79 DragManager::~DragManager ()
81 abort ();
84 /** Call abort for each active drag */
85 void
86 DragManager::abort ()
88 _ending = true;
90 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
91 (*i)->abort ();
92 delete *i;
95 if (!_drags.empty ()) {
96 _editor->set_follow_playhead (_old_follow_playhead, false);
99 _drags.clear ();
101 _ending = false;
104 void
105 DragManager::add (Drag* d)
107 d->set_manager (this);
108 _drags.push_back (d);
111 void
112 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
114 d->set_manager (this);
115 _drags.push_back (d);
116 start_grab (e, c);
119 void
120 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
122 /* Prevent follow playhead during the drag to be nice to the user */
123 _old_follow_playhead = _editor->follow_playhead ();
124 _editor->set_follow_playhead (false);
126 _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
128 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
129 (*i)->start_grab (e, c);
133 /** Call end_grab for each active drag.
134 * @return true if any drag reported movement having occurred.
136 bool
137 DragManager::end_grab (GdkEvent* e)
139 _ending = true;
141 bool r = false;
142 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
143 bool const t = (*i)->end_grab (e);
144 if (t) {
145 r = true;
147 delete *i;
150 _drags.clear ();
152 _ending = false;
154 _editor->set_follow_playhead (_old_follow_playhead, false);
156 return r;
159 bool
160 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
162 bool r = false;
164 _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
166 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
167 bool const t = (*i)->motion_handler (e, from_autoscroll);
168 if (t) {
169 r = true;
174 return r;
177 bool
178 DragManager::have_item (ArdourCanvas::Item* i) const
180 list<Drag*>::const_iterator j = _drags.begin ();
181 while (j != _drags.end() && (*j)->item () != i) {
182 ++j;
185 return j != _drags.end ();
188 Drag::Drag (Editor* e, ArdourCanvas::Item* i)
189 : _editor (e)
190 , _item (i)
191 , _pointer_frame_offset (0)
192 , _move_threshold_passed (false)
193 , _raw_grab_frame (0)
194 , _grab_frame (0)
195 , _last_pointer_frame (0)
200 void
201 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time)
203 _item->ungrab (0);
204 _item = new_item;
206 if (cursor == 0) {
207 cursor = _editor->which_grabber_cursor ();
210 _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
213 void
214 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
216 if (cursor == 0) {
217 cursor = _editor->which_grabber_cursor ();
220 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
222 if (Keyboard::is_button2_event (&event->button)) {
223 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
224 _y_constrained = true;
225 _x_constrained = false;
226 } else {
227 _y_constrained = false;
228 _x_constrained = true;
230 } else {
231 _x_constrained = false;
232 _y_constrained = false;
235 _raw_grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
236 setup_pointer_frame_offset ();
237 _grab_frame = adjusted_frame (_raw_grab_frame, event);
238 _last_pointer_frame = _grab_frame;
239 _last_pointer_x = _grab_x;
240 _last_pointer_y = _grab_y;
242 _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
243 *cursor,
244 event->button.time);
246 if (_editor->session() && _editor->session()->transport_rolling()) {
247 _was_rolling = true;
248 } else {
249 _was_rolling = false;
252 switch (_editor->snap_type()) {
253 case SnapToRegionStart:
254 case SnapToRegionEnd:
255 case SnapToRegionSync:
256 case SnapToRegionBoundary:
257 _editor->build_region_boundary_cache ();
258 break;
259 default:
260 break;
264 /** Call to end a drag `successfully'. Ungrabs item and calls
265 * subclass' finished() method.
267 * @param event GDK event, or 0.
268 * @return true if some movement occurred, otherwise false.
270 bool
271 Drag::end_grab (GdkEvent* event)
273 _editor->stop_canvas_autoscroll ();
275 _item->ungrab (event ? event->button.time : 0);
277 finished (event, _move_threshold_passed);
279 _editor->verbose_cursor()->hide ();
281 return _move_threshold_passed;
284 framepos_t
285 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
287 framepos_t pos = 0;
289 if (f > _pointer_frame_offset) {
290 pos = f - _pointer_frame_offset;
293 if (snap) {
294 _editor->snap_to_with_modifier (pos, event);
297 return pos;
300 framepos_t
301 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
303 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
306 bool
307 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
309 /* check to see if we have moved in any way that matters since the last motion event */
310 if (_move_threshold_passed &&
311 (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
312 (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
313 return false;
316 pair<framecnt_t, int> const threshold = move_threshold ();
318 bool const old_move_threshold_passed = _move_threshold_passed;
320 if (!from_autoscroll && !_move_threshold_passed) {
322 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
323 bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);
325 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
328 if (active (_editor->mouse_mode) && _move_threshold_passed) {
330 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
331 if (!from_autoscroll) {
332 _editor->maybe_autoscroll (true, allow_vertical_autoscroll ());
335 motion (event, _move_threshold_passed != old_move_threshold_passed);
337 _last_pointer_x = _drags->current_pointer_x ();
338 _last_pointer_y = _drags->current_pointer_y ();
339 _last_pointer_frame = adjusted_current_frame (event);
341 return true;
344 return false;
347 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
348 void
349 Drag::abort ()
351 if (_item) {
352 _item->ungrab (0);
355 aborted (_move_threshold_passed);
357 _editor->stop_canvas_autoscroll ();
358 _editor->verbose_cursor()->hide ();
361 void
362 Drag::show_verbose_cursor_time (framepos_t frame)
364 _editor->verbose_cursor()->set_time (
365 frame,
366 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
367 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
370 _editor->verbose_cursor()->show ();
373 void
374 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double xoffset)
376 _editor->verbose_cursor()->show (xoffset);
378 _editor->verbose_cursor()->set_duration (
379 start, end,
380 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
381 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
385 void
386 Drag::show_verbose_cursor_text (string const & text)
388 _editor->verbose_cursor()->show ();
390 _editor->verbose_cursor()->set (
391 text,
392 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
393 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
398 struct EditorOrderTimeAxisViewSorter {
399 bool operator() (TimeAxisView* a, TimeAxisView* b) {
400 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
401 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
402 assert (ra && rb);
403 return ra->route()->order_key (N_ ("editor")) < rb->route()->order_key (N_ ("editor"));
407 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
408 : Drag (e, i),
409 _primary (p)
411 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
413 /* Make a list of non-hidden tracks to refer to during the drag */
415 TrackViewList track_views = _editor->track_views;
416 track_views.sort (EditorOrderTimeAxisViewSorter ());
418 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
419 if (!(*i)->hidden()) {
421 _time_axis_views.push_back (*i);
423 TimeAxisView::Children children_list = (*i)->get_child_list ();
424 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
425 _time_axis_views.push_back (j->get());
430 /* the list of views can be empty at this point if this is a region list-insert drag
433 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
434 _views.push_back (DraggingView (*i, this));
437 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
440 void
441 RegionDrag::region_going_away (RegionView* v)
443 list<DraggingView>::iterator i = _views.begin ();
444 while (i != _views.end() && i->view != v) {
445 ++i;
448 if (i != _views.end()) {
449 _views.erase (i);
453 /** Given a non-hidden TimeAxisView, return the index of it into the _time_axis_views vector */
455 RegionDrag::find_time_axis_view (TimeAxisView* t) const
457 int i = 0;
458 int const N = _time_axis_views.size ();
459 while (i < N && _time_axis_views[i] != t) {
460 ++i;
463 if (i == N) {
464 return -1;
467 return i;
470 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
471 : RegionDrag (e, i, p, v),
472 _brushing (b),
473 _total_x_delta (0)
479 void
480 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
482 Drag::start_grab (event, cursor);
484 show_verbose_cursor_time (_last_frame_position);
486 pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
487 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
488 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
491 double
492 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
494 /* compute the amount of pointer motion in frames, and where
495 the region would be if we moved it by that much.
497 *pending_region_position = adjusted_current_frame (event);
499 framepos_t sync_frame;
500 framecnt_t sync_offset;
501 int32_t sync_dir;
503 sync_offset = _primary->region()->sync_offset (sync_dir);
505 /* we don't handle a sync point that lies before zero.
507 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
509 sync_frame = *pending_region_position + (sync_dir*sync_offset);
511 _editor->snap_to_with_modifier (sync_frame, event);
513 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
515 } else {
516 *pending_region_position = _last_frame_position;
519 if (*pending_region_position > max_framepos - _primary->region()->length()) {
520 *pending_region_position = _last_frame_position;
523 double dx = 0;
525 /* in locked edit mode, reverse the usual meaning of _x_constrained */
526 bool const x_move_allowed = Config->get_edit_mode() == Lock ? _x_constrained : !_x_constrained;
528 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
530 /* x movement since last time (in pixels) */
531 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
533 /* total x movement */
534 framecnt_t total_dx = *pending_region_position;
535 if (regions_came_from_canvas()) {
536 total_dx = total_dx - grab_frame ();
539 /* check that no regions have gone off the start of the session */
540 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
541 if ((i->view->region()->position() + total_dx) < 0) {
542 dx = 0;
543 *pending_region_position = _last_frame_position;
544 break;
548 _last_frame_position = *pending_region_position;
551 return dx;
554 bool
555 RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) const
557 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
558 int const n = i->time_axis_view + delta_track;
559 if (n < 0 || n >= int (_time_axis_views.size())) {
560 /* off the top or bottom track */
561 return false;
564 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
565 if (to == 0 || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
566 /* not a track, or the wrong type */
567 return false;
570 int const l = i->layer + delta_layer;
571 if (delta_track == 0 && (l < 0 || l >= int (to->view()->layers()))) {
572 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
573 If it has, the layers will be munged later anyway, so it's ok.
575 return false;
579 /* all regions being dragged are ok with this change */
580 return true;
583 void
584 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
586 assert (!_views.empty ());
588 /* Find the TimeAxisView that the pointer is now over */
589 pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
591 /* Bail early if we're not over a track */
592 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv.first);
593 if (!rtv || !rtv->is_track()) {
594 _editor->verbose_cursor()->hide ();
595 return;
598 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
600 /* Here's the current pointer position in terms of time axis view and layer */
601 int const current_pointer_time_axis_view = find_time_axis_view (tv.first);
602 layer_t const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
604 /* Work out the change in x */
605 framepos_t pending_region_position;
606 double const x_delta = compute_x_delta (event, &pending_region_position);
608 /* Work out the change in y */
609 int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
610 int delta_layer = current_pointer_layer - _last_pointer_layer;
612 if (!y_movement_allowed (delta_time_axis_view, delta_layer)) {
613 /* this y movement is not allowed, so do no y movement this time */
614 delta_time_axis_view = 0;
615 delta_layer = 0;
618 if (x_delta == 0 && delta_time_axis_view == 0 && delta_layer == 0 && !first_move) {
619 /* haven't reached next snap point, and we're not switching
620 trackviews nor layers. nothing to do.
622 return;
625 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
627 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
629 RegionView* rv = i->view;
631 if (rv->region()->locked()) {
632 continue;
635 if (first_move) {
637 /* here we are calculating the y distance from the
638 top of the first track view to the top of the region
639 area of the track view that we're working on */
641 /* this x value is just a dummy value so that we have something
642 to pass to i2w () */
644 double ix1 = 0;
646 /* distance from the top of this track view to the region area
647 of our track view is always 1 */
649 double iy1 = 1;
651 /* convert to world coordinates, ie distance from the top of
652 the ruler section */
654 rv->get_canvas_frame()->i2w (ix1, iy1);
656 /* compensate for the ruler section and the vertical scrollbar position */
657 iy1 += _editor->get_trackview_group_vertical_offset ();
659 // hide any dependent views
661 rv->get_time_axis_view().hide_dependent_views (*rv);
664 reparent to a non scrolling group so that we can keep the
665 region selection above all time axis views.
666 reparenting means we have to move the rv as the two
667 parent groups have different coordinates.
670 rv->get_canvas_group()->property_y() = iy1 - 1;
671 rv->get_canvas_group()->reparent (*(_editor->_region_motion_group));
673 rv->fake_set_opaque (true);
676 /* Work out the change in y position of this region view */
678 double y_delta = 0;
680 /* If we have moved tracks, we'll fudge the layer delta so that the
681 region gets moved back onto layer 0 on its new track; this avoids
682 confusion when dragging regions from non-zero layers onto different
683 tracks.
685 int this_delta_layer = delta_layer;
686 if (delta_time_axis_view != 0) {
687 this_delta_layer = - i->layer;
690 /* Move this region to layer 0 on its old track */
691 StreamView* lv = _time_axis_views[i->time_axis_view]->view ();
692 if (lv->layer_display() == Stacked) {
693 y_delta -= (lv->layers() - i->layer - 1) * lv->child_height ();
696 /* Now move it to its right layer on the current track */
697 StreamView* cv = _time_axis_views[i->time_axis_view + delta_time_axis_view]->view ();
698 if (cv->layer_display() == Stacked) {
699 y_delta += (cv->layers() - (i->layer + this_delta_layer) - 1) * cv->child_height ();
702 /* Move tracks */
703 if (delta_time_axis_view > 0) {
704 for (int j = 0; j < delta_time_axis_view; ++j) {
705 y_delta += _time_axis_views[i->time_axis_view + j]->current_height ();
707 } else {
708 /* start by subtracting the height of the track above where we are now */
709 for (int j = 1; j <= -delta_time_axis_view; ++j) {
710 y_delta -= _time_axis_views[i->time_axis_view - j]->current_height ();
714 /* Set height */
715 rv->set_height (_time_axis_views[i->time_axis_view + delta_time_axis_view]->view()->child_height ());
717 /* Update the DraggingView */
718 i->time_axis_view += delta_time_axis_view;
719 i->layer += this_delta_layer;
721 if (_brushing) {
722 _editor->mouse_brush_insert_region (rv, pending_region_position);
723 } else {
724 rv->move (x_delta, y_delta);
727 } /* foreach region */
729 _total_x_delta += x_delta;
731 if (first_move) {
732 _editor->cursor_group->raise_to_top();
735 if (x_delta != 0 && !_brushing) {
736 show_verbose_cursor_time (_last_frame_position);
739 _last_pointer_time_axis_view += delta_time_axis_view;
740 _last_pointer_layer += delta_layer;
743 void
744 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
746 if (_copy && first_move) {
748 /* duplicate the regionview(s) and region(s) */
750 list<DraggingView> new_regionviews;
752 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
754 RegionView* rv = i->view;
755 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
756 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
758 const boost::shared_ptr<const Region> original = rv->region();
759 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
760 region_copy->set_position (original->position());
762 RegionView* nrv;
763 if (arv) {
764 boost::shared_ptr<AudioRegion> audioregion_copy
765 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
767 nrv = new AudioRegionView (*arv, audioregion_copy);
768 } else if (mrv) {
769 boost::shared_ptr<MidiRegion> midiregion_copy
770 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
771 nrv = new MidiRegionView (*mrv, midiregion_copy);
772 } else {
773 continue;
776 nrv->get_canvas_group()->show ();
777 new_regionviews.push_back (DraggingView (nrv, this));
779 /* swap _primary to the copy */
781 if (rv == _primary) {
782 _primary = nrv;
785 /* ..and deselect the one we copied */
787 rv->set_selected (false);
790 if (!new_regionviews.empty()) {
792 /* reflect the fact that we are dragging the copies */
794 _views = new_regionviews;
796 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
799 sync the canvas to what we think is its current state
800 without it, the canvas seems to
801 "forget" to update properly after the upcoming reparent()
802 ..only if the mouse is in rapid motion at the time of the grab.
803 something to do with regionview creation taking so long?
805 _editor->update_canvas_now();
809 RegionMotionDrag::motion (event, first_move);
812 void
813 RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
815 if (!movement_occurred) {
816 /* just a click */
817 return;
820 /* reverse this here so that we have the correct logic to finalize
821 the drag.
824 if (Config->get_edit_mode() == Lock) {
825 _x_constrained = !_x_constrained;
828 assert (!_views.empty ());
830 bool const changed_position = (_last_frame_position != _primary->region()->position());
831 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
832 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
834 _editor->update_canvas_now ();
836 if (_copy) {
838 finished_copy (
839 changed_position,
840 changed_tracks,
841 drag_delta
844 } else {
846 finished_no_copy (
847 changed_position,
848 changed_tracks,
849 drag_delta
855 void
856 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
858 RegionSelection new_views;
859 PlaylistSet modified_playlists;
860 list<RegionView*> views_to_delete;
862 if (_brushing) {
863 /* all changes were made during motion event handlers */
865 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
866 delete i->view;
869 _editor->commit_reversible_command ();
870 return;
873 if (_x_constrained) {
874 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
875 } else {
876 _editor->begin_reversible_command (Operations::region_copy);
879 /* insert the regions into their new playlists */
880 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
882 if (i->view->region()->locked()) {
883 continue;
886 framepos_t where;
888 if (changed_position && !_x_constrained) {
889 where = i->view->region()->position() - drag_delta;
890 } else {
891 where = i->view->region()->position();
894 RegionView* new_view = insert_region_into_playlist (
895 i->view->region(), dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]), i->layer, where, modified_playlists
898 if (new_view == 0) {
899 continue;
902 new_views.push_back (new_view);
904 /* we don't need the copied RegionView any more */
905 views_to_delete.push_back (i->view);
908 /* Delete views that are no longer needed; we can't do this directly in the iteration over _views
909 because when views are deleted they are automagically removed from _views, which messes
910 up the iteration.
912 for (list<RegionView*>::iterator i = views_to_delete.begin(); i != views_to_delete.end(); ++i) {
913 delete *i;
916 /* If we've created new regions either by copying or moving
917 to a new track, we want to replace the old selection with the new ones
920 if (new_views.size() > 0) {
921 _editor->selection->set (new_views);
924 /* write commands for the accumulated diffs for all our modified playlists */
925 add_stateful_diff_commands_for_playlists (modified_playlists);
927 _editor->commit_reversible_command ();
930 void
931 RegionMoveDrag::finished_no_copy (
932 bool const changed_position,
933 bool const changed_tracks,
934 framecnt_t const drag_delta
937 RegionSelection new_views;
938 PlaylistSet modified_playlists;
939 PlaylistSet frozen_playlists;
941 if (_brushing) {
942 /* all changes were made during motion event handlers */
943 _editor->commit_reversible_command ();
944 return;
947 if (_x_constrained) {
948 _editor->begin_reversible_command (_("fixed time region drag"));
949 } else {
950 _editor->begin_reversible_command (Operations::region_drag);
953 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
955 RegionView* rv = i->view;
957 RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
958 layer_t const dest_layer = i->layer;
960 if (rv->region()->locked()) {
961 ++i;
962 continue;
965 framepos_t where;
967 if (changed_position && !_x_constrained) {
968 where = rv->region()->position() - drag_delta;
969 } else {
970 where = rv->region()->position();
973 if (changed_tracks) {
975 /* insert into new playlist */
977 RegionView* new_view = insert_region_into_playlist (
978 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
981 if (new_view == 0) {
982 ++i;
983 continue;
986 new_views.push_back (new_view);
988 /* remove from old playlist */
990 /* the region that used to be in the old playlist is not
991 moved to the new one - we use a copy of it. as a result,
992 any existing editor for the region should no longer be
993 visible.
995 rv->hide_region_editor();
996 rv->fake_set_opaque (false);
998 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1000 } else {
1002 rv->region()->clear_changes ();
1005 motion on the same track. plonk the previously reparented region
1006 back to its original canvas group (its streamview).
1007 No need to do anything for copies as they are fake regions which will be deleted.
1010 rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
1011 rv->get_canvas_group()->property_y() = i->initial_y;
1012 rv->get_time_axis_view().reveal_dependent_views (*rv);
1014 /* just change the model */
1016 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1018 if (dest_rtv->view()->layer_display() == Stacked) {
1019 rv->region()->set_layer (dest_layer);
1020 rv->region()->set_pending_explicit_relayer (true);
1023 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1025 pair<PlaylistSet::iterator, bool> r = frozen_playlists.insert (playlist);
1027 if (r.second) {
1028 playlist->freeze ();
1031 /* this movement may result in a crossfade being modified, so we need to get undo
1032 data from the playlist as well as the region.
1035 r = modified_playlists.insert (playlist);
1036 if (r.second) {
1037 playlist->clear_changes ();
1040 rv->region()->set_position (where);
1042 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1045 if (changed_tracks) {
1047 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1048 was selected in all of them, then removing it from a playlist will have removed all
1049 trace of it from _views (i.e. there were N regions selected, we removed 1,
1050 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1051 corresponding regionview, and _views is now empty).
1053 This could have invalidated any and all iterators into _views.
1055 The heuristic we use here is: if the region selection is empty, break out of the loop
1056 here. if the region selection is not empty, then restart the loop because we know that
1057 we must have removed at least the region(view) we've just been working on as well as any
1058 that we processed on previous iterations.
1060 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1061 we can just iterate.
1065 if (_views.empty()) {
1066 break;
1067 } else {
1068 i = _views.begin();
1071 } else {
1072 ++i;
1076 /* If we've created new regions either by copying or moving
1077 to a new track, we want to replace the old selection with the new ones
1080 if (new_views.size() > 0) {
1081 _editor->selection->set (new_views);
1084 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1085 (*p)->thaw();
1088 /* write commands for the accumulated diffs for all our modified playlists */
1089 add_stateful_diff_commands_for_playlists (modified_playlists);
1091 _editor->commit_reversible_command ();
1094 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1095 * @param region Region to remove.
1096 * @param playlist playlist To remove from.
1097 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1098 * that clear_changes () is only called once per playlist.
1100 void
1101 RegionMoveDrag::remove_region_from_playlist (
1102 boost::shared_ptr<Region> region,
1103 boost::shared_ptr<Playlist> playlist,
1104 PlaylistSet& modified_playlists
1107 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1109 if (r.second) {
1110 playlist->clear_changes ();
1113 playlist->remove_region (region);
1117 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1118 * clearing the playlist's diff history first if necessary.
1119 * @param region Region to insert.
1120 * @param dest_rtv Destination RouteTimeAxisView.
1121 * @param dest_layer Destination layer.
1122 * @param where Destination position.
1123 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1124 * that clear_changes () is only called once per playlist.
1125 * @return New RegionView, or 0 if no insert was performed.
1127 RegionView *
1128 RegionMoveDrag::insert_region_into_playlist (
1129 boost::shared_ptr<Region> region,
1130 RouteTimeAxisView* dest_rtv,
1131 layer_t dest_layer,
1132 framecnt_t where,
1133 PlaylistSet& modified_playlists
1136 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1137 if (!dest_playlist) {
1138 return 0;
1141 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1142 _new_region_view = 0;
1143 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1145 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1146 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1147 if (r.second) {
1148 dest_playlist->clear_changes ();
1151 dest_playlist->add_region (region, where);
1153 if (dest_rtv->view()->layer_display() == Stacked) {
1154 region->set_layer (dest_layer);
1155 region->set_pending_explicit_relayer (true);
1158 c.disconnect ();
1160 assert (_new_region_view);
1162 return _new_region_view;
1165 void
1166 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1168 _new_region_view = rv;
1171 void
1172 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1174 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1175 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1176 if (!c->empty()) {
1177 _editor->session()->add_command (c);
1178 } else {
1179 delete c;
1185 void
1186 RegionMoveDrag::aborted (bool movement_occurred)
1188 if (_copy) {
1190 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1191 delete i->view;
1194 _views.clear ();
1196 } else {
1197 RegionMotionDrag::aborted (movement_occurred);
1201 void
1202 RegionMotionDrag::aborted (bool)
1204 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1205 RegionView* rv = i->view;
1206 TimeAxisView* tv = &(rv->get_time_axis_view ());
1207 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1208 assert (rtv);
1209 rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
1210 rv->get_canvas_group()->property_y() = 0;
1211 rv->get_time_axis_view().reveal_dependent_views (*rv);
1212 rv->fake_set_opaque (false);
1213 rv->move (-_total_x_delta, 0);
1214 rv->set_height (rtv->view()->child_height ());
1217 _editor->update_canvas_now ();
1220 /** @param b true to brush, otherwise false.
1221 * @param c true to make copies of the regions being moved, otherwise false.
1223 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1224 : RegionMotionDrag (e, i, p, v, b),
1225 _copy (c)
1227 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1229 double speed = 1;
1230 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1231 if (rtv && rtv->is_track()) {
1232 speed = rtv->track()->speed ();
1235 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1238 void
1239 RegionMoveDrag::setup_pointer_frame_offset ()
1241 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1244 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1245 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1247 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1249 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1250 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1252 _primary = v->view()->create_region_view (r, false, false);
1254 _primary->get_canvas_group()->show ();
1255 _primary->set_position (pos, 0);
1256 _views.push_back (DraggingView (_primary, this));
1258 _last_frame_position = pos;
1260 _item = _primary->get_canvas_group ();
1263 void
1264 RegionInsertDrag::finished (GdkEvent *, bool)
1266 _editor->update_canvas_now ();
1268 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[_views.front().time_axis_view]);
1270 _primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
1271 _primary->get_canvas_group()->property_y() = 0;
1273 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1275 _editor->begin_reversible_command (Operations::insert_region);
1276 playlist->clear_changes ();
1277 playlist->add_region (_primary->region (), _last_frame_position);
1278 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1279 _editor->commit_reversible_command ();
1281 delete _primary;
1282 _primary = 0;
1283 _views.clear ();
1286 void
1287 RegionInsertDrag::aborted (bool)
1289 delete _primary;
1290 _primary = 0;
1291 _views.clear ();
1294 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1295 : RegionMoveDrag (e, i, p, v, false, false)
1297 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1300 struct RegionSelectionByPosition {
1301 bool operator() (RegionView*a, RegionView* b) {
1302 return a->region()->position () < b->region()->position();
1306 void
1307 RegionSpliceDrag::motion (GdkEvent* event, bool)
1309 /* Which trackview is this ? */
1311 pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
1312 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1313 layer_t layer = tvp.second;
1315 if (tv && tv->layer_display() == Overlaid) {
1316 layer = 0;
1319 /* The region motion is only processed if the pointer is over
1320 an audio track.
1323 if (!tv || !tv->is_track()) {
1324 /* To make sure we hide the verbose canvas cursor when the mouse is
1325 not held over and audiotrack.
1327 _editor->verbose_cursor()->hide ();
1328 return;
1331 int dir;
1333 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1334 dir = 1;
1335 } else {
1336 dir = -1;
1339 RegionSelection copy (_editor->selection->regions);
1341 RegionSelectionByPosition cmp;
1342 copy.sort (cmp);
1344 framepos_t const pf = adjusted_current_frame (event);
1346 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1348 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1350 if (!atv) {
1351 continue;
1354 boost::shared_ptr<Playlist> playlist;
1356 if ((playlist = atv->playlist()) == 0) {
1357 continue;
1360 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1361 continue;
1364 if (dir > 0) {
1365 if (pf < (*i)->region()->last_frame() + 1) {
1366 continue;
1368 } else {
1369 if (pf > (*i)->region()->first_frame()) {
1370 continue;
1375 playlist->shuffle ((*i)->region(), dir);
1379 void
1380 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1382 RegionMoveDrag::finished (event, movement_occurred);
1385 void
1386 RegionSpliceDrag::aborted (bool)
1388 /* XXX: TODO */
1391 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
1392 : Drag (e, i),
1393 _view (dynamic_cast<MidiTimeAxisView*> (v))
1395 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
1397 assert (_view);
1400 void
1401 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
1403 if (first_move) {
1404 add_region();
1405 _view->playlist()->freeze ();
1406 } else {
1407 if (_region) {
1408 framepos_t const f = adjusted_current_frame (event);
1409 if (f < grab_frame()) {
1410 _region->set_position (f);
1413 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
1414 so that if this region is duplicated, its duplicate starts on
1415 a snap point rather than 1 frame after a snap point. Otherwise things get
1416 a bit confusing as if a region starts 1 frame after a snap point, one cannot
1417 place snapped notes at the start of the region.
1420 framecnt_t const len = abs (f - grab_frame () - 1);
1421 _region->set_length (len < 1 ? 1 : len);
1426 void
1427 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
1429 if (!movement_occurred) {
1430 add_region ();
1431 } else {
1432 _view->playlist()->thaw ();
1435 if (_region) {
1436 _editor->commit_reversible_command ();
1440 void
1441 RegionCreateDrag::add_region ()
1443 if (_editor->session()) {
1444 const TempoMap& map (_editor->session()->tempo_map());
1445 framecnt_t pos = grab_frame();
1446 const Meter& m = map.meter_at (pos);
1447 /* not that the frame rate used here can be affected by pull up/down which
1448 might be wrong.
1450 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
1451 _region = _view->add_region (grab_frame(), len, false);
1455 void
1456 RegionCreateDrag::aborted (bool)
1458 if (_region) {
1459 _view->playlist()->thaw ();
1462 /* XXX */
1465 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
1466 : Drag (e, i)
1467 , region (0)
1469 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
1472 void
1473 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
1475 Gdk::Cursor* cursor;
1476 ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item);
1477 float x_fraction = cnote->mouse_x_fraction ();
1479 if (x_fraction > 0.0 && x_fraction < 0.25) {
1480 cursor = _editor->cursors()->left_side_trim;
1481 } else {
1482 cursor = _editor->cursors()->right_side_trim;
1485 Drag::start_grab (event, cursor);
1487 region = &cnote->region_view();
1489 double const region_start = region->get_position_pixels();
1490 double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;
1492 if (grab_x() <= middle_point) {
1493 cursor = _editor->cursors()->left_side_trim;
1494 at_front = true;
1495 } else {
1496 cursor = _editor->cursors()->right_side_trim;
1497 at_front = false;
1500 _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, *cursor, event->motion.time);
1502 if (event->motion.state & Keyboard::PrimaryModifier) {
1503 relative = false;
1504 } else {
1505 relative = true;
1508 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1510 if (ms.size() > 1) {
1511 /* has to be relative, may make no sense otherwise */
1512 relative = true;
1515 /* select this note; if it is already selected, preserve the existing selection,
1516 otherwise make this note the only one selected.
1518 region->note_selected (cnote, cnote->selected ());
1520 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
1521 MidiRegionSelection::iterator next;
1522 next = r;
1523 ++next;
1524 (*r)->begin_resizing (at_front);
1525 r = next;
1529 void
1530 NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
1532 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1533 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1534 (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1538 void
1539 NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
1541 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1542 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1543 (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1547 void
1548 NoteResizeDrag::aborted (bool)
1550 /* XXX: TODO */
1553 RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
1554 : Drag (e, i)
1556 DEBUG_TRACE (DEBUG::Drags, "New RegionGainDrag\n");
1559 void
1560 RegionGainDrag::motion (GdkEvent* /*event*/, bool)
1565 void
1566 RegionGainDrag::finished (GdkEvent *, bool)
1571 void
1572 RegionGainDrag::aborted (bool)
1574 /* XXX: TODO */
1577 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1578 : RegionDrag (e, i, p, v)
1580 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
1583 void
1584 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
1586 double speed = 1.0;
1587 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1588 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1590 if (tv && tv->is_track()) {
1591 speed = tv->track()->speed();
1594 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
1595 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
1596 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
1598 framepos_t const pf = adjusted_current_frame (event);
1600 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1601 /* Move the contents of the region around without changing the region bounds */
1602 _operation = ContentsTrim;
1603 Drag::start_grab (event, _editor->cursors()->trimmer);
1604 } else {
1605 /* These will get overridden for a point trim.*/
1606 if (pf < (region_start + region_length/2)) {
1607 /* closer to front */
1608 _operation = StartTrim;
1609 Drag::start_grab (event, _editor->cursors()->left_side_trim);
1610 } else {
1611 /* closer to end */
1612 _operation = EndTrim;
1613 Drag::start_grab (event, _editor->cursors()->right_side_trim);
1617 switch (_operation) {
1618 case StartTrim:
1619 show_verbose_cursor_time (region_start);
1620 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1621 i->view->trim_front_starting ();
1623 break;
1624 case EndTrim:
1625 show_verbose_cursor_time (region_end);
1626 break;
1627 case ContentsTrim:
1628 show_verbose_cursor_time (pf);
1629 break;
1632 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1633 i->view->region()->suspend_property_changes ();
1637 void
1638 TrimDrag::motion (GdkEvent* event, bool first_move)
1640 RegionView* rv = _primary;
1642 double speed = 1.0;
1643 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1644 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1645 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
1647 if (tv && tv->is_track()) {
1648 speed = tv->track()->speed();
1651 framecnt_t const dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
1653 if (first_move) {
1655 string trim_type;
1657 switch (_operation) {
1658 case StartTrim:
1659 trim_type = "Region start trim";
1660 break;
1661 case EndTrim:
1662 trim_type = "Region end trim";
1663 break;
1664 case ContentsTrim:
1665 trim_type = "Region content trim";
1666 break;
1669 _editor->begin_reversible_command (trim_type);
1671 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1672 RegionView* rv = i->view;
1673 rv->fake_set_opaque (false);
1674 rv->enable_display (false);
1675 rv->region()->playlist()->clear_owned_changes ();
1677 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
1679 if (arv) {
1680 arv->temporarily_hide_envelope ();
1683 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
1684 insert_result = _editor->motion_frozen_playlists.insert (pl);
1686 if (insert_result.second) {
1687 pl->freeze();
1692 bool non_overlap_trim = false;
1694 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1695 non_overlap_trim = true;
1698 switch (_operation) {
1699 case StartTrim:
1700 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1701 i->view->trim_front (i->initial_position + dt, non_overlap_trim);
1703 break;
1705 case EndTrim:
1706 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1707 i->view->trim_end (i->initial_end + dt, non_overlap_trim);
1709 break;
1711 case ContentsTrim:
1713 bool swap_direction = false;
1715 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1716 swap_direction = true;
1719 framecnt_t frame_delta = 0;
1721 bool left_direction = false;
1722 if (last_pointer_frame() > adjusted_current_frame(event)) {
1723 left_direction = true;
1726 if (left_direction) {
1727 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
1728 } else {
1729 frame_delta = (adjusted_current_frame(event) - last_pointer_frame());
1732 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1733 i->view->trim_contents (frame_delta, left_direction, swap_direction);
1736 break;
1739 switch (_operation) {
1740 case StartTrim:
1741 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
1742 break;
1743 case EndTrim:
1744 show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed));
1745 break;
1746 case ContentsTrim:
1747 show_verbose_cursor_time (adjusted_current_frame (event));
1748 break;
1753 void
1754 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
1756 if (movement_occurred) {
1757 motion (event, false);
1759 /* This must happen before the region's StatefulDiffCommand is created, as it may
1760 `correct' (ahem) the region's _start from being negative to being zero. It
1761 needs to be zero in the undo record.
1763 if (_operation == StartTrim) {
1764 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1765 i->view->trim_front_ending ();
1769 if (!_editor->selection->selected (_primary)) {
1770 _primary->thaw_after_trim ();
1771 } else {
1773 set<boost::shared_ptr<Playlist> > diffed_playlists;
1775 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1776 i->view->thaw_after_trim ();
1777 i->view->enable_display (true);
1778 i->view->fake_set_opaque (true);
1780 /* Trimming one region may affect others on the playlist, so we need
1781 to get undo Commands from the whole playlist rather than just the
1782 region. Use diffed_playlists to make sure we don't diff a given
1783 playlist more than once.
1785 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
1786 if (diffed_playlists.find (p) == diffed_playlists.end()) {
1787 vector<Command*> cmds;
1788 p->rdiff (cmds);
1789 _editor->session()->add_commands (cmds);
1790 diffed_playlists.insert (p);
1794 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
1795 (*p)->thaw ();
1798 _editor->motion_frozen_playlists.clear ();
1799 _editor->commit_reversible_command();
1801 } else {
1802 /* no mouse movement */
1803 _editor->point_trim (event, adjusted_current_frame (event));
1806 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1807 if (_operation == StartTrim) {
1808 i->view->trim_front_ending ();
1811 i->view->region()->resume_property_changes ();
1815 void
1816 TrimDrag::aborted (bool movement_occurred)
1818 /* Our motion method is changing model state, so use the Undo system
1819 to cancel. Perhaps not ideal, as this will leave an Undo point
1820 behind which may be slightly odd from the user's point of view.
1823 finished (0, true);
1825 if (movement_occurred) {
1826 _editor->undo ();
1829 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1830 i->view->region()->resume_property_changes ();
1834 void
1835 TrimDrag::setup_pointer_frame_offset ()
1837 list<DraggingView>::iterator i = _views.begin ();
1838 while (i != _views.end() && i->view != _primary) {
1839 ++i;
1842 if (i == _views.end()) {
1843 return;
1846 switch (_operation) {
1847 case StartTrim:
1848 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
1849 break;
1850 case EndTrim:
1851 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
1852 break;
1853 case ContentsTrim:
1854 break;
1858 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1859 : Drag (e, i),
1860 _copy (c)
1862 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
1864 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
1865 assert (_marker);
1868 void
1869 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1871 if (_copy) {
1872 // create a dummy marker for visual representation of moving the copy.
1873 // The actual copying is not done before we reach the finish callback.
1874 char name[64];
1875 snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
1877 MeterMarker* new_marker = new MeterMarker (
1878 *_editor,
1879 *_editor->meter_group,
1880 ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
1881 name,
1882 *new MeterSection (_marker->meter())
1885 _item = &new_marker->the_item ();
1886 _marker = new_marker;
1888 } else {
1890 MetricSection& section (_marker->meter());
1892 if (!section.movable()) {
1893 return;
1898 Drag::start_grab (event, cursor);
1900 show_verbose_cursor_time (adjusted_current_frame(event));
1903 void
1904 MeterMarkerDrag::setup_pointer_frame_offset ()
1906 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
1909 void
1910 MeterMarkerDrag::motion (GdkEvent* event, bool)
1912 framepos_t const pf = adjusted_current_frame (event);
1914 _marker->set_position (pf);
1916 show_verbose_cursor_time (pf);
1919 void
1920 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
1922 if (!movement_occurred) {
1923 return;
1926 motion (event, false);
1928 Timecode::BBT_Time when;
1930 TempoMap& map (_editor->session()->tempo_map());
1931 map.bbt_time (last_pointer_frame(), when);
1933 if (_copy == true) {
1934 _editor->begin_reversible_command (_("copy meter mark"));
1935 XMLNode &before = map.get_state();
1936 map.add_meter (_marker->meter(), when);
1937 XMLNode &after = map.get_state();
1938 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1939 _editor->commit_reversible_command ();
1941 // delete the dummy marker we used for visual representation of copying.
1942 // a new visual marker will show up automatically.
1943 delete _marker;
1944 } else {
1945 _editor->begin_reversible_command (_("move meter mark"));
1946 XMLNode &before = map.get_state();
1947 map.move_meter (_marker->meter(), when);
1948 XMLNode &after = map.get_state();
1949 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1950 _editor->commit_reversible_command ();
1954 void
1955 MeterMarkerDrag::aborted (bool)
1957 _marker->set_position (_marker->meter().frame ());
1960 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1961 : Drag (e, i),
1962 _copy (c)
1964 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
1966 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
1967 assert (_marker);
1970 void
1971 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1973 if (_copy) {
1975 // create a dummy marker for visual representation of moving the copy.
1976 // The actual copying is not done before we reach the finish callback.
1977 char name[64];
1978 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
1980 TempoMarker* new_marker = new TempoMarker (
1981 *_editor,
1982 *_editor->tempo_group,
1983 ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
1984 name,
1985 *new TempoSection (_marker->tempo())
1988 _item = &new_marker->the_item ();
1989 _marker = new_marker;
1993 Drag::start_grab (event, cursor);
1995 show_verbose_cursor_time (adjusted_current_frame (event));
1998 void
1999 TempoMarkerDrag::setup_pointer_frame_offset ()
2001 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
2004 void
2005 TempoMarkerDrag::motion (GdkEvent* event, bool)
2007 framepos_t const pf = adjusted_current_frame (event);
2008 _marker->set_position (pf);
2009 show_verbose_cursor_time (pf);
2012 void
2013 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2015 if (!movement_occurred) {
2016 return;
2019 motion (event, false);
2021 Timecode::BBT_Time when;
2023 TempoMap& map (_editor->session()->tempo_map());
2024 map.bbt_time (last_pointer_frame(), when);
2026 if (_copy == true) {
2027 _editor->begin_reversible_command (_("copy tempo mark"));
2028 XMLNode &before = map.get_state();
2029 map.add_tempo (_marker->tempo(), when);
2030 XMLNode &after = map.get_state();
2031 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2032 _editor->commit_reversible_command ();
2034 // delete the dummy marker we used for visual representation of copying.
2035 // a new visual marker will show up automatically.
2036 delete _marker;
2037 } else {
2038 _editor->begin_reversible_command (_("move tempo mark"));
2039 XMLNode &before = map.get_state();
2040 map.move_tempo (_marker->tempo(), when);
2041 XMLNode &after = map.get_state();
2042 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2043 _editor->commit_reversible_command ();
2047 void
2048 TempoMarkerDrag::aborted (bool)
2050 _marker->set_position (_marker->tempo().frame());
2053 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
2054 : Drag (e, i),
2055 _stop (s)
2057 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
2060 /** Do all the things we do when dragging the playhead to make it look as though
2061 * we have located, without actually doing the locate (because that would cause
2062 * the diskstream buffers to be refilled, which is too slow).
2064 void
2065 CursorDrag::fake_locate (framepos_t t)
2067 _editor->playhead_cursor->set_position (t);
2069 Session* s = _editor->session ();
2070 if (s->timecode_transmission_suspended ()) {
2071 framepos_t const f = _editor->playhead_cursor->current_frame;
2072 s->send_mmc_locate (f);
2073 s->send_full_time_code (f);
2076 show_verbose_cursor_time (t);
2077 _editor->UpdateAllTransportClocks (t);
2080 void
2081 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
2083 Drag::start_grab (event, c);
2085 _grab_zoom = _editor->frames_per_unit;
2087 framepos_t where = _editor->event_frame (event, 0, 0);
2088 _editor->snap_to_with_modifier (where, event);
2090 _editor->_dragging_playhead = true;
2092 Session* s = _editor->session ();
2094 if (s) {
2095 if (_was_rolling && _stop) {
2096 s->request_stop ();
2099 if (s->is_auditioning()) {
2100 s->cancel_audition ();
2103 s->request_suspend_timecode_transmission ();
2104 while (!s->timecode_transmission_suspended ()) {
2105 /* twiddle our thumbs */
2109 fake_locate (where);
2112 void
2113 CursorDrag::motion (GdkEvent* event, bool)
2115 if (_drags->current_pointer_y() != last_pointer_y()) {
2117 /* zoom when we move the pointer up and down */
2119 /* y range to operate over (pixels) */
2120 double const y_range = 512;
2121 /* we will multiply the grab zoom by a factor between scale_range and scale_range^-1 */
2122 double const scale_range = 4;
2123 /* dead zone around the grab point in which to do no zooming (pixels) */
2124 double const dead_zone = 100;
2126 /* current dy */
2127 double dy = _drags->current_pointer_y() - grab_y();
2129 if (dy < -dead_zone || dy > dead_zone) {
2130 /* we are outside the dead zone; remove it from our calculation */
2131 if (dy < 0) {
2132 dy += dead_zone;
2133 } else {
2134 dy -= dead_zone;
2137 /* get a number from -1 to 1 as dy ranges from -y_range to y_range */
2138 double udy = max (min (dy / y_range, 1.0), -1.0);
2140 /* and zoom, using playhead focus temporarily */
2141 Editing::ZoomFocus const zf = _editor->get_zoom_focus ();
2142 _editor->set_zoom_focus (Editing::ZoomFocusPlayhead);
2143 _editor->temporal_zoom (_grab_zoom * pow (scale_range, -udy));
2144 _editor->set_zoom_focus (zf);
2148 framepos_t const adjusted_frame = adjusted_current_frame (event);
2149 if (adjusted_frame != last_pointer_frame()) {
2150 fake_locate (adjusted_frame);
2151 #ifdef GTKOSX
2152 _editor->update_canvas_now ();
2153 #endif
2157 void
2158 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
2160 _editor->_dragging_playhead = false;
2162 if (!movement_occurred && _stop) {
2163 return;
2166 motion (event, false);
2168 Session* s = _editor->session ();
2169 if (s) {
2170 s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
2171 _editor->_pending_locate_request = true;
2172 s->request_resume_timecode_transmission ();
2176 void
2177 CursorDrag::aborted (bool)
2179 if (_editor->_dragging_playhead) {
2180 _editor->session()->request_resume_timecode_transmission ();
2181 _editor->_dragging_playhead = false;
2184 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
2187 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2188 : RegionDrag (e, i, p, v)
2190 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
2193 void
2194 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2196 Drag::start_grab (event, cursor);
2198 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2199 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2201 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
2203 arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
2206 void
2207 FadeInDrag::setup_pointer_frame_offset ()
2209 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2210 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2211 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
2214 void
2215 FadeInDrag::motion (GdkEvent* event, bool)
2217 framecnt_t fade_length;
2219 framepos_t const pos = adjusted_current_frame (event);
2221 boost::shared_ptr<Region> region = _primary->region ();
2223 if (pos < (region->position() + 64)) {
2224 fade_length = 64; // this should be a minimum defined somewhere
2225 } else if (pos > region->last_frame()) {
2226 fade_length = region->length();
2227 } else {
2228 fade_length = pos - region->position();
2231 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2233 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2235 if (!tmp) {
2236 continue;
2239 tmp->reset_fade_in_shape_width (fade_length);
2240 tmp->show_fade_line((framecnt_t) fade_length);
2243 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
2246 void
2247 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
2249 if (!movement_occurred) {
2250 return;
2253 framecnt_t fade_length;
2255 framepos_t const pos = adjusted_current_frame (event);
2257 boost::shared_ptr<Region> region = _primary->region ();
2259 if (pos < (region->position() + 64)) {
2260 fade_length = 64; // this should be a minimum defined somewhere
2261 } else if (pos > region->last_frame()) {
2262 fade_length = region->length();
2263 } else {
2264 fade_length = pos - region->position();
2267 _editor->begin_reversible_command (_("change fade in length"));
2269 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2271 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2273 if (!tmp) {
2274 continue;
2277 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
2278 XMLNode &before = alist->get_state();
2280 tmp->audio_region()->set_fade_in_length (fade_length);
2281 tmp->audio_region()->set_fade_in_active (true);
2282 tmp->hide_fade_line();
2284 XMLNode &after = alist->get_state();
2285 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2288 _editor->commit_reversible_command ();
2291 void
2292 FadeInDrag::aborted (bool)
2294 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2295 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2297 if (!tmp) {
2298 continue;
2301 tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
2302 tmp->hide_fade_line();
2306 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2307 : RegionDrag (e, i, p, v)
2309 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
2312 void
2313 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2315 Drag::start_grab (event, cursor);
2317 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2318 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2320 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
2322 arv->show_fade_line(r->length() - r->fade_out()->back()->when);
2325 void
2326 FadeOutDrag::setup_pointer_frame_offset ()
2328 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2329 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2330 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
2333 void
2334 FadeOutDrag::motion (GdkEvent* event, bool)
2336 framecnt_t fade_length;
2338 framepos_t const pos = adjusted_current_frame (event);
2340 boost::shared_ptr<Region> region = _primary->region ();
2342 if (pos > (region->last_frame() - 64)) {
2343 fade_length = 64; // this should really be a minimum fade defined somewhere
2345 else if (pos < region->position()) {
2346 fade_length = region->length();
2348 else {
2349 fade_length = region->last_frame() - pos;
2352 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2354 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2356 if (!tmp) {
2357 continue;
2360 tmp->reset_fade_out_shape_width (fade_length);
2361 tmp->show_fade_line(region->length() - fade_length);
2364 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
2367 void
2368 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
2370 if (!movement_occurred) {
2371 return;
2374 framecnt_t fade_length;
2376 framepos_t const pos = adjusted_current_frame (event);
2378 boost::shared_ptr<Region> region = _primary->region ();
2380 if (pos > (region->last_frame() - 64)) {
2381 fade_length = 64; // this should really be a minimum fade defined somewhere
2383 else if (pos < region->position()) {
2384 fade_length = region->length();
2386 else {
2387 fade_length = region->last_frame() - pos;
2390 _editor->begin_reversible_command (_("change fade out length"));
2392 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2394 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2396 if (!tmp) {
2397 continue;
2400 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
2401 XMLNode &before = alist->get_state();
2403 tmp->audio_region()->set_fade_out_length (fade_length);
2404 tmp->audio_region()->set_fade_out_active (true);
2405 tmp->hide_fade_line();
2407 XMLNode &after = alist->get_state();
2408 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2411 _editor->commit_reversible_command ();
2414 void
2415 FadeOutDrag::aborted (bool)
2417 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2418 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2420 if (!tmp) {
2421 continue;
2424 tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
2425 tmp->hide_fade_line();
2429 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
2430 : Drag (e, i)
2432 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
2434 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
2435 assert (_marker);
2437 _points.push_back (Gnome::Art::Point (0, 0));
2438 _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
2441 MarkerDrag::~MarkerDrag ()
2443 for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
2444 delete *i;
2448 void
2449 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2451 Drag::start_grab (event, cursor);
2453 bool is_start;
2455 Location *location = _editor->find_location_from_marker (_marker, is_start);
2456 _editor->_dragging_edit_point = true;
2458 update_item (location);
2460 // _drag_line->show();
2461 // _line->raise_to_top();
2463 if (is_start) {
2464 show_verbose_cursor_time (location->start());
2465 } else {
2466 show_verbose_cursor_time (location->end());
2469 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2471 switch (op) {
2472 case Selection::Toggle:
2473 _editor->selection->toggle (_marker);
2474 break;
2475 case Selection::Set:
2476 if (!_editor->selection->selected (_marker)) {
2477 _editor->selection->set (_marker);
2479 break;
2480 case Selection::Extend:
2482 Locations::LocationList ll;
2483 list<Marker*> to_add;
2484 framepos_t s, e;
2485 _editor->selection->markers.range (s, e);
2486 s = min (_marker->position(), s);
2487 e = max (_marker->position(), e);
2488 s = min (s, e);
2489 e = max (s, e);
2490 if (e < max_framepos) {
2491 ++e;
2493 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
2494 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
2495 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
2496 if (lm) {
2497 if (lm->start) {
2498 to_add.push_back (lm->start);
2500 if (lm->end) {
2501 to_add.push_back (lm->end);
2505 if (!to_add.empty()) {
2506 _editor->selection->add (to_add);
2508 break;
2510 case Selection::Add:
2511 _editor->selection->add (_marker);
2512 break;
2515 /* Set up copies for us to manipulate during the drag */
2517 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
2518 Location* l = _editor->find_location_from_marker (*i, is_start);
2519 _copied_locations.push_back (new Location (*l));
2523 void
2524 MarkerDrag::setup_pointer_frame_offset ()
2526 bool is_start;
2527 Location *location = _editor->find_location_from_marker (_marker, is_start);
2528 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
2531 void
2532 MarkerDrag::motion (GdkEvent* event, bool)
2534 framecnt_t f_delta = 0;
2535 bool is_start;
2536 bool move_both = false;
2537 Marker* marker;
2538 Location *real_location;
2539 Location *copy_location = 0;
2541 framepos_t const newframe = adjusted_current_frame (event);
2543 framepos_t next = newframe;
2545 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2546 move_both = true;
2549 MarkerSelection::iterator i;
2550 list<Location*>::iterator x;
2552 /* find the marker we're dragging, and compute the delta */
2554 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2555 x != _copied_locations.end() && i != _editor->selection->markers.end();
2556 ++i, ++x) {
2558 copy_location = *x;
2559 marker = *i;
2561 if (marker == _marker) {
2563 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2564 /* que pasa ?? */
2565 return;
2568 if (real_location->is_mark()) {
2569 f_delta = newframe - copy_location->start();
2570 } else {
2573 switch (marker->type()) {
2574 case Marker::SessionStart:
2575 case Marker::RangeStart:
2576 case Marker::LoopStart:
2577 case Marker::PunchIn:
2578 f_delta = newframe - copy_location->start();
2579 break;
2581 case Marker::SessionEnd:
2582 case Marker::RangeEnd:
2583 case Marker::LoopEnd:
2584 case Marker::PunchOut:
2585 f_delta = newframe - copy_location->end();
2586 break;
2587 default:
2588 /* what kind of marker is this ? */
2589 return;
2592 break;
2596 if (i == _editor->selection->markers.end()) {
2597 /* hmm, impossible - we didn't find the dragged marker */
2598 return;
2601 /* now move them all */
2603 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2604 x != _copied_locations.end() && i != _editor->selection->markers.end();
2605 ++i, ++x) {
2607 copy_location = *x;
2608 marker = *i;
2610 /* call this to find out if its the start or end */
2612 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2613 continue;
2616 if (real_location->locked()) {
2617 continue;
2620 if (copy_location->is_mark()) {
2622 /* now move it */
2624 copy_location->set_start (copy_location->start() + f_delta);
2626 } else {
2628 framepos_t new_start = copy_location->start() + f_delta;
2629 framepos_t new_end = copy_location->end() + f_delta;
2631 if (is_start) { // start-of-range marker
2633 if (move_both) {
2634 copy_location->set_start (new_start);
2635 copy_location->set_end (new_end);
2636 } else if (new_start < copy_location->end()) {
2637 copy_location->set_start (new_start);
2638 } else if (newframe > 0) {
2639 _editor->snap_to (next, 1, true);
2640 copy_location->set_end (next);
2641 copy_location->set_start (newframe);
2644 } else { // end marker
2646 if (move_both) {
2647 copy_location->set_end (new_end);
2648 copy_location->set_start (new_start);
2649 } else if (new_end > copy_location->start()) {
2650 copy_location->set_end (new_end);
2651 } else if (newframe > 0) {
2652 _editor->snap_to (next, -1, true);
2653 copy_location->set_start (next);
2654 copy_location->set_end (newframe);
2659 update_item (copy_location);
2661 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
2663 if (lm) {
2664 lm->set_position (copy_location->start(), copy_location->end());
2668 assert (!_copied_locations.empty());
2670 show_verbose_cursor_time (newframe);
2672 #ifdef GTKOSX
2673 _editor->update_canvas_now ();
2674 #endif
2677 void
2678 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2680 if (!movement_occurred) {
2682 /* just a click, do nothing but finish
2683 off the selection process
2686 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2688 switch (op) {
2689 case Selection::Set:
2690 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
2691 _editor->selection->set (_marker);
2693 break;
2695 case Selection::Toggle:
2696 case Selection::Extend:
2697 case Selection::Add:
2698 break;
2701 return;
2704 _editor->_dragging_edit_point = false;
2706 _editor->begin_reversible_command ( _("move marker") );
2707 XMLNode &before = _editor->session()->locations()->get_state();
2709 MarkerSelection::iterator i;
2710 list<Location*>::iterator x;
2711 bool is_start;
2713 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2714 x != _copied_locations.end() && i != _editor->selection->markers.end();
2715 ++i, ++x) {
2717 Location * location = _editor->find_location_from_marker (*i, is_start);
2719 if (location) {
2721 if (location->locked()) {
2722 return;
2725 if (location->is_mark()) {
2726 location->set_start ((*x)->start());
2727 } else {
2728 location->set ((*x)->start(), (*x)->end());
2733 XMLNode &after = _editor->session()->locations()->get_state();
2734 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
2735 _editor->commit_reversible_command ();
2738 void
2739 MarkerDrag::aborted (bool)
2741 /* XXX: TODO */
2744 void
2745 MarkerDrag::update_item (Location* location)
2747 /* noop */
2750 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
2751 : Drag (e, i),
2752 _cumulative_x_drag (0),
2753 _cumulative_y_drag (0)
2755 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
2757 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
2758 assert (_point);
2762 void
2763 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2765 Drag::start_grab (event, _editor->cursors()->fader);
2767 // start the grab at the center of the control point so
2768 // the point doesn't 'jump' to the mouse after the first drag
2769 _fixed_grab_x = _point->get_x();
2770 _fixed_grab_y = _point->get_y();
2772 float const fraction = 1 - (_point->get_y() / _point->line().height());
2774 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
2776 _editor->verbose_cursor()->set (_point->line().get_verbose_cursor_string (fraction),
2777 event->button.x + 10, event->button.y + 10);
2779 _editor->verbose_cursor()->show ();
2782 void
2783 ControlPointDrag::motion (GdkEvent* event, bool)
2785 double dx = _drags->current_pointer_x() - last_pointer_x();
2786 double dy = _drags->current_pointer_y() - last_pointer_y();
2788 if (event->button.state & Keyboard::SecondaryModifier) {
2789 dx *= 0.1;
2790 dy *= 0.1;
2793 /* coordinate in pixels relative to the start of the region (for region-based automation)
2794 or track (for track-based automation) */
2795 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
2796 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2798 // calculate zero crossing point. back off by .01 to stay on the
2799 // positive side of zero
2800 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
2802 // make sure we hit zero when passing through
2803 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
2804 cy = zero_gain_y;
2807 if (_x_constrained) {
2808 cx = _fixed_grab_x;
2810 if (_y_constrained) {
2811 cy = _fixed_grab_y;
2814 _cumulative_x_drag = cx - _fixed_grab_x;
2815 _cumulative_y_drag = cy - _fixed_grab_y;
2817 cx = max (0.0, cx);
2818 cy = max (0.0, cy);
2819 cy = min ((double) _point->line().height(), cy);
2821 framepos_t cx_frames = _editor->unit_to_frame (cx);
2823 if (!_x_constrained) {
2824 _editor->snap_to_with_modifier (cx_frames, event);
2827 cx_frames = min (cx_frames, _point->line().maximum_time());
2829 float const fraction = 1.0 - (cy / _point->line().height());
2831 bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
2833 _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
2835 _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
2838 void
2839 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
2841 if (!movement_occurred) {
2843 /* just a click */
2845 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2846 _editor->reset_point_selection ();
2849 } else {
2850 motion (event, false);
2853 _point->line().end_drag ();
2854 _editor->session()->commit_reversible_command ();
2857 void
2858 ControlPointDrag::aborted (bool)
2860 _point->line().reset ();
2863 bool
2864 ControlPointDrag::active (Editing::MouseMode m)
2866 if (m == Editing::MouseGain) {
2867 /* always active in mouse gain */
2868 return true;
2871 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
2872 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
2875 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
2876 : Drag (e, i),
2877 _line (0),
2878 _cumulative_y_drag (0)
2880 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
2883 void
2884 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2886 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
2887 assert (_line);
2889 _item = &_line->grab_item ();
2891 /* need to get x coordinate in terms of parent (TimeAxisItemView)
2892 origin, and ditto for y.
2895 double cx = event->button.x;
2896 double cy = event->button.y;
2898 _line->parent_group().w2i (cx, cy);
2900 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit);
2902 uint32_t before;
2903 uint32_t after;
2905 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
2906 /* no adjacent points */
2907 return;
2910 Drag::start_grab (event, _editor->cursors()->fader);
2912 /* store grab start in parent frame */
2914 _fixed_grab_x = cx;
2915 _fixed_grab_y = cy;
2917 double fraction = 1.0 - (cy / _line->height());
2919 _line->start_drag_line (before, after, fraction);
2921 _editor->verbose_cursor()->set (_line->get_verbose_cursor_string (fraction),
2922 event->button.x + 10, event->button.y + 10);
2924 _editor->verbose_cursor()->show ();
2927 void
2928 LineDrag::motion (GdkEvent* event, bool)
2930 double dy = _drags->current_pointer_y() - last_pointer_y();
2932 if (event->button.state & Keyboard::SecondaryModifier) {
2933 dy *= 0.1;
2936 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2938 _cumulative_y_drag = cy - _fixed_grab_y;
2940 cy = max (0.0, cy);
2941 cy = min ((double) _line->height(), cy);
2943 double const fraction = 1.0 - (cy / _line->height());
2945 bool push;
2947 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
2948 push = false;
2949 } else {
2950 push = true;
2953 /* we are ignoring x position for this drag, so we can just pass in anything */
2954 _line->drag_motion (0, fraction, true, push);
2956 _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
2959 void
2960 LineDrag::finished (GdkEvent* event, bool)
2962 motion (event, false);
2963 _line->end_drag ();
2964 _editor->session()->commit_reversible_command ();
2967 void
2968 LineDrag::aborted (bool)
2970 _line->reset ();
2973 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
2974 : Drag (e, i),
2975 _line (0),
2976 _cumulative_x_drag (0)
2978 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
2981 void
2982 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2984 Drag::start_grab (event);
2986 _line = reinterpret_cast<Line*> (_item);
2987 assert (_line);
2989 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
2991 double cx = event->button.x;
2992 double cy = event->button.y;
2994 _item->property_parent().get_value()->w2i(cx, cy);
2996 /* store grab start in parent frame */
2997 _region_view_grab_x = cx;
2999 _before = *(float*) _item->get_data ("position");
3001 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3003 _max_x = _editor->frame_to_pixel(_arv->get_duration());
3006 void
3007 FeatureLineDrag::motion (GdkEvent*, bool)
3009 double dx = _drags->current_pointer_x() - last_pointer_x();
3011 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
3013 _cumulative_x_drag += dx;
3015 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
3017 if (cx > _max_x){
3018 cx = _max_x;
3020 else if(cx < 0){
3021 cx = 0;
3024 ArdourCanvas::Points points;
3026 double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
3028 _line->get_bounds(x1, y2, x2, y2);
3030 points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
3031 points.push_back(Gnome::Art::Point(cx, y2 - y1));
3033 _line->property_points() = points;
3035 float *pos = new float;
3036 *pos = cx;
3038 _line->set_data ("position", pos);
3040 _before = cx;
3043 void
3044 FeatureLineDrag::finished (GdkEvent*, bool)
3046 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3047 _arv->update_transient(_before, _before);
3050 void
3051 FeatureLineDrag::aborted (bool)
3053 //_line->reset ();
3056 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
3057 : Drag (e, i)
3059 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
3062 void
3063 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3065 Drag::start_grab (event);
3066 show_verbose_cursor_time (adjusted_current_frame (event));
3069 void
3070 RubberbandSelectDrag::motion (GdkEvent* event, bool)
3072 framepos_t start;
3073 framepos_t end;
3074 double y1;
3075 double y2;
3077 framepos_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
3079 framepos_t grab = grab_frame ();
3080 if (Config->get_rubberbanding_snaps_to_grid ()) {
3081 _editor->snap_to_with_modifier (grab, event);
3084 /* base start and end on initial click position */
3086 if (pf < grab) {
3087 start = pf;
3088 end = grab;
3089 } else {
3090 end = pf;
3091 start = grab;
3094 if (_drags->current_pointer_y() < grab_y()) {
3095 y1 = _drags->current_pointer_y();
3096 y2 = grab_y();
3097 } else {
3098 y2 = _drags->current_pointer_y();
3099 y1 = grab_y();
3103 if (start != end || y1 != y2) {
3105 double x1 = _editor->frame_to_pixel (start);
3106 double x2 = _editor->frame_to_pixel (end);
3108 _editor->rubberband_rect->property_x1() = x1;
3109 _editor->rubberband_rect->property_y1() = y1;
3110 _editor->rubberband_rect->property_x2() = x2;
3111 _editor->rubberband_rect->property_y2() = y2;
3113 _editor->rubberband_rect->show();
3114 _editor->rubberband_rect->raise_to_top();
3116 show_verbose_cursor_time (pf);
3120 void
3121 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
3123 if (movement_occurred) {
3125 motion (event, false);
3127 double y1,y2;
3128 if (_drags->current_pointer_y() < grab_y()) {
3129 y1 = _drags->current_pointer_y();
3130 y2 = grab_y();
3131 } else {
3132 y2 = _drags->current_pointer_y();
3133 y1 = grab_y();
3137 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3139 _editor->begin_reversible_command (_("rubberband selection"));
3141 if (grab_frame() < last_pointer_frame()) {
3142 _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
3143 } else {
3144 _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
3147 _editor->commit_reversible_command ();
3149 } else {
3150 if (!getenv("ARDOUR_SAE")) {
3151 _editor->selection->clear_tracks();
3153 _editor->selection->clear_regions();
3154 _editor->selection->clear_points ();
3155 _editor->selection->clear_lines ();
3158 _editor->rubberband_rect->hide();
3161 void
3162 RubberbandSelectDrag::aborted (bool)
3164 _editor->rubberband_rect->hide ();
3167 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
3168 : RegionDrag (e, i, p, v)
3170 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
3173 void
3174 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3176 Drag::start_grab (event, cursor);
3178 show_verbose_cursor_time (adjusted_current_frame (event));
3181 void
3182 TimeFXDrag::motion (GdkEvent* event, bool)
3184 RegionView* rv = _primary;
3186 framepos_t const pf = adjusted_current_frame (event);
3188 if (pf > rv->region()->position()) {
3189 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
3192 show_verbose_cursor_time (pf);
3195 void
3196 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3198 _primary->get_time_axis_view().hide_timestretch ();
3200 if (!movement_occurred) {
3201 return;
3204 if (last_pointer_frame() < _primary->region()->position()) {
3205 /* backwards drag of the left edge - not usable */
3206 return;
3209 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
3211 float percentage = (double) newlen / (double) _primary->region()->length();
3213 #ifndef USE_RUBBERBAND
3214 // Soundtouch uses percentage / 100 instead of normal (/ 1)
3215 if (_primary->region()->data_type() == DataType::AUDIO) {
3216 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
3218 #endif
3220 // XXX how do timeFX on multiple regions ?
3222 RegionSelection rs;
3223 rs.add (_primary);
3225 if (_editor->time_stretch (rs, percentage) == -1) {
3226 error << _("An error occurred while executing time stretch operation") << endmsg;
3230 void
3231 TimeFXDrag::aborted (bool)
3233 _primary->get_time_axis_view().hide_timestretch ();
3236 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
3237 : Drag (e, i)
3239 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
3242 void
3243 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3245 Drag::start_grab (event);
3248 void
3249 ScrubDrag::motion (GdkEvent* /*event*/, bool)
3251 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
3254 void
3255 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3257 if (movement_occurred && _editor->session()) {
3258 /* make sure we stop */
3259 _editor->session()->request_transport_speed (0.0);
3263 void
3264 ScrubDrag::aborted (bool)
3266 /* XXX: TODO */
3269 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3270 : Drag (e, i)
3271 , _operation (o)
3272 , _copy (false)
3273 , _original_pointer_time_axis (-1)
3274 , _last_pointer_time_axis (-1)
3276 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
3279 void
3280 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
3282 if (_editor->session() == 0) {
3283 return;
3286 Gdk::Cursor* cursor = 0;
3288 switch (_operation) {
3289 case CreateSelection:
3290 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3291 _copy = true;
3292 } else {
3293 _copy = false;
3295 cursor = _editor->cursors()->selector;
3296 Drag::start_grab (event, cursor);
3297 break;
3299 case SelectionStartTrim:
3300 if (_editor->clicked_axisview) {
3301 _editor->clicked_axisview->order_selection_trims (_item, true);
3303 Drag::start_grab (event, _editor->cursors()->left_side_trim);
3304 break;
3306 case SelectionEndTrim:
3307 if (_editor->clicked_axisview) {
3308 _editor->clicked_axisview->order_selection_trims (_item, false);
3310 Drag::start_grab (event, _editor->cursors()->right_side_trim);
3311 break;
3313 case SelectionMove:
3314 Drag::start_grab (event, cursor);
3315 break;
3318 if (_operation == SelectionMove) {
3319 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
3320 } else {
3321 show_verbose_cursor_time (adjusted_current_frame (event));
3324 _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
3327 void
3328 SelectionDrag::setup_pointer_frame_offset ()
3330 switch (_operation) {
3331 case CreateSelection:
3332 _pointer_frame_offset = 0;
3333 break;
3335 case SelectionStartTrim:
3336 case SelectionMove:
3337 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
3338 break;
3340 case SelectionEndTrim:
3341 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
3342 break;
3346 void
3347 SelectionDrag::motion (GdkEvent* event, bool first_move)
3349 framepos_t start = 0;
3350 framepos_t end = 0;
3351 framecnt_t length;
3353 pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
3354 if (pending_time_axis.first == 0) {
3355 return;
3358 framepos_t const pending_position = adjusted_current_frame (event);
3360 /* only alter selection if things have changed */
3362 if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
3363 return;
3366 switch (_operation) {
3367 case CreateSelection:
3369 framepos_t grab = grab_frame ();
3371 if (first_move) {
3372 _editor->snap_to (grab);
3375 if (pending_position < grab_frame()) {
3376 start = pending_position;
3377 end = grab;
3378 } else {
3379 end = pending_position;
3380 start = grab;
3383 /* first drag: Either add to the selection
3384 or create a new selection
3387 if (first_move) {
3389 if (_copy) {
3390 /* adding to the selection */
3391 _editor->set_selected_track_as_side_effect (Selection::Add);
3392 //_editor->selection->add (_editor->clicked_axisview);
3393 _editor->clicked_selection = _editor->selection->add (start, end);
3394 _copy = false;
3395 } else {
3396 /* new selection */
3398 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3399 //_editor->selection->set (_editor->clicked_axisview);
3400 _editor->set_selected_track_as_side_effect (Selection::Set);
3403 _editor->clicked_selection = _editor->selection->set (start, end);
3407 /* select the track that we're in */
3408 if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
3409 // _editor->set_selected_track_as_side_effect (Selection::Add);
3410 _editor->selection->add (pending_time_axis.first);
3411 _added_time_axes.push_back (pending_time_axis.first);
3414 /* deselect any tracks that this drag no longer includes, being careful to only deselect
3415 tracks that we selected in the first place.
3418 int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
3419 int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
3421 list<TimeAxisView*>::iterator i = _added_time_axes.begin();
3422 while (i != _added_time_axes.end()) {
3424 list<TimeAxisView*>::iterator tmp = i;
3425 ++tmp;
3427 if ((*i)->order() < min_order || (*i)->order() > max_order) {
3428 _editor->selection->remove (*i);
3429 _added_time_axes.remove (*i);
3432 i = tmp;
3436 break;
3438 case SelectionStartTrim:
3440 start = _editor->selection->time[_editor->clicked_selection].start;
3441 end = _editor->selection->time[_editor->clicked_selection].end;
3443 if (pending_position > end) {
3444 start = end;
3445 } else {
3446 start = pending_position;
3448 break;
3450 case SelectionEndTrim:
3452 start = _editor->selection->time[_editor->clicked_selection].start;
3453 end = _editor->selection->time[_editor->clicked_selection].end;
3455 if (pending_position < start) {
3456 end = start;
3457 } else {
3458 end = pending_position;
3461 break;
3463 case SelectionMove:
3465 start = _editor->selection->time[_editor->clicked_selection].start;
3466 end = _editor->selection->time[_editor->clicked_selection].end;
3468 length = end - start;
3470 start = pending_position;
3471 _editor->snap_to (start);
3473 end = start + length;
3475 break;
3478 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3479 _editor->start_canvas_autoscroll (1, 0);
3482 if (start != end) {
3483 _editor->selection->replace (_editor->clicked_selection, start, end);
3486 if (_operation == SelectionMove) {
3487 show_verbose_cursor_time(start);
3488 } else {
3489 show_verbose_cursor_time(pending_position);
3493 void
3494 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
3496 Session* s = _editor->session();
3498 if (movement_occurred) {
3499 motion (event, false);
3500 /* XXX this is not object-oriented programming at all. ick */
3501 if (_editor->selection->time.consolidate()) {
3502 _editor->selection->TimeChanged ();
3505 /* XXX what if its a music time selection? */
3506 if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
3507 s->request_play_range (&_editor->selection->time, true);
3511 } else {
3512 /* just a click, no pointer movement.*/
3514 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
3515 _editor->selection->clear_time();
3518 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3519 _editor->selection->set (_editor->clicked_axisview);
3522 if (s && s->get_play_range () && s->transport_rolling()) {
3523 s->request_stop (false, false);
3528 _editor->stop_canvas_autoscroll ();
3531 void
3532 SelectionDrag::aborted (bool)
3534 /* XXX: TODO */
3537 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3538 : Drag (e, i),
3539 _operation (o),
3540 _copy (false)
3542 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
3544 _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
3545 physical_screen_height (_editor->get_window()));
3546 _drag_rect->hide ();
3548 _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3549 _drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3552 void
3553 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3555 if (_editor->session() == 0) {
3556 return;
3559 Gdk::Cursor* cursor = 0;
3561 if (!_editor->temp_location) {
3562 _editor->temp_location = new Location (*_editor->session());
3565 switch (_operation) {
3566 case CreateRangeMarker:
3567 case CreateTransportMarker:
3568 case CreateCDMarker:
3570 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3571 _copy = true;
3572 } else {
3573 _copy = false;
3575 cursor = _editor->cursors()->selector;
3576 break;
3579 Drag::start_grab (event, cursor);
3581 show_verbose_cursor_time (adjusted_current_frame (event));
3584 void
3585 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
3587 framepos_t start = 0;
3588 framepos_t end = 0;
3589 ArdourCanvas::SimpleRect *crect;
3591 switch (_operation) {
3592 case CreateRangeMarker:
3593 crect = _editor->range_bar_drag_rect;
3594 break;
3595 case CreateTransportMarker:
3596 crect = _editor->transport_bar_drag_rect;
3597 break;
3598 case CreateCDMarker:
3599 crect = _editor->cd_marker_bar_drag_rect;
3600 break;
3601 default:
3602 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
3603 return;
3604 break;
3607 framepos_t const pf = adjusted_current_frame (event);
3609 if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
3610 framepos_t grab = grab_frame ();
3611 _editor->snap_to (grab);
3613 if (pf < grab_frame()) {
3614 start = pf;
3615 end = grab;
3616 } else {
3617 end = pf;
3618 start = grab;
3621 /* first drag: Either add to the selection
3622 or create a new selection.
3625 if (first_move) {
3627 _editor->temp_location->set (start, end);
3629 crect->show ();
3631 update_item (_editor->temp_location);
3632 _drag_rect->show();
3633 //_drag_rect->raise_to_top();
3638 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3639 _editor->start_canvas_autoscroll (1, 0);
3642 if (start != end) {
3643 _editor->temp_location->set (start, end);
3645 double x1 = _editor->frame_to_pixel (start);
3646 double x2 = _editor->frame_to_pixel (end);
3647 crect->property_x1() = x1;
3648 crect->property_x2() = x2;
3650 update_item (_editor->temp_location);
3653 show_verbose_cursor_time (pf);
3657 void
3658 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
3660 Location * newloc = 0;
3661 string rangename;
3662 int flags;
3664 if (movement_occurred) {
3665 motion (event, false);
3666 _drag_rect->hide();
3668 switch (_operation) {
3669 case CreateRangeMarker:
3670 case CreateCDMarker:
3672 _editor->begin_reversible_command (_("new range marker"));
3673 XMLNode &before = _editor->session()->locations()->get_state();
3674 _editor->session()->locations()->next_available_name(rangename,"unnamed");
3675 if (_operation == CreateCDMarker) {
3676 flags = Location::IsRangeMarker | Location::IsCDMarker;
3677 _editor->cd_marker_bar_drag_rect->hide();
3679 else {
3680 flags = Location::IsRangeMarker;
3681 _editor->range_bar_drag_rect->hide();
3683 newloc = new Location (
3684 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
3687 _editor->session()->locations()->add (newloc, true);
3688 XMLNode &after = _editor->session()->locations()->get_state();
3689 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3690 _editor->commit_reversible_command ();
3691 break;
3694 case CreateTransportMarker:
3695 // popup menu to pick loop or punch
3696 _editor->new_transport_marker_context_menu (&event->button, _item);
3697 break;
3699 } else {
3700 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
3702 if (Keyboard::no_modifier_keys_pressed (&event->button) && _operation != CreateCDMarker) {
3704 framepos_t start;
3705 framepos_t end;
3707 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
3709 if (end == max_framepos) {
3710 end = _editor->session()->current_end_frame ();
3713 if (start == max_framepos) {
3714 start = _editor->session()->current_start_frame ();
3717 switch (_editor->mouse_mode) {
3718 case MouseObject:
3719 /* find the two markers on either side and then make the selection from it */
3720 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
3721 break;
3723 case MouseRange:
3724 /* find the two markers on either side of the click and make the range out of it */
3725 _editor->selection->set (start, end);
3726 break;
3728 default:
3729 break;
3734 _editor->stop_canvas_autoscroll ();
3737 void
3738 RangeMarkerBarDrag::aborted (bool)
3740 /* XXX: TODO */
3743 void
3744 RangeMarkerBarDrag::update_item (Location* location)
3746 double const x1 = _editor->frame_to_pixel (location->start());
3747 double const x2 = _editor->frame_to_pixel (location->end());
3749 _drag_rect->property_x1() = x1;
3750 _drag_rect->property_x2() = x2;
3753 MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i)
3754 : Drag (e, i)
3755 , _zoom_out (false)
3757 DEBUG_TRACE (DEBUG::Drags, "New MouseZoomDrag\n");
3760 void
3761 MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3763 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
3764 Drag::start_grab (event, _editor->cursors()->zoom_out);
3765 _zoom_out = true;
3766 } else {
3767 Drag::start_grab (event, _editor->cursors()->zoom_in);
3768 _zoom_out = false;
3771 show_verbose_cursor_time (adjusted_current_frame (event));
3774 void
3775 MouseZoomDrag::motion (GdkEvent* event, bool first_move)
3777 framepos_t start;
3778 framepos_t end;
3780 framepos_t const pf = adjusted_current_frame (event);
3782 framepos_t grab = grab_frame ();
3783 _editor->snap_to_with_modifier (grab, event);
3785 /* base start and end on initial click position */
3786 if (pf < grab) {
3787 start = pf;
3788 end = grab;
3789 } else {
3790 end = pf;
3791 start = grab;
3794 if (start != end) {
3796 if (first_move) {
3797 _editor->zoom_rect->show();
3798 _editor->zoom_rect->raise_to_top();
3801 _editor->reposition_zoom_rect(start, end);
3803 show_verbose_cursor_time (pf);
3807 void
3808 MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
3810 if (movement_occurred) {
3811 motion (event, false);
3813 if (grab_frame() < last_pointer_frame()) {
3814 _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
3815 } else {
3816 _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
3818 } else {
3819 if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
3820 _editor->tav_zoom_step (_zoom_out);
3821 } else {
3822 _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
3826 _editor->zoom_rect->hide();
3829 void
3830 MouseZoomDrag::aborted (bool)
3832 _editor->zoom_rect->hide ();
3835 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
3836 : Drag (e, i)
3837 , _cumulative_dx (0)
3838 , _cumulative_dy (0)
3840 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
3842 _primary = dynamic_cast<CanvasNoteEvent*> (_item);
3843 _region = &_primary->region_view ();
3844 _note_height = _region->midi_stream_view()->note_height ();
3847 void
3848 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3850 Drag::start_grab (event);
3852 if (!(_was_selected = _primary->selected())) {
3854 /* tertiary-click means extend selection - we'll do that on button release,
3855 so don't add it here, because otherwise we make it hard to figure
3856 out the "extend-to" range.
3859 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
3861 if (!extend) {
3862 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
3864 if (add) {
3865 _region->note_selected (_primary, true);
3866 } else {
3867 _region->unique_select (_primary);
3873 /** @return Current total drag x change in frames */
3874 frameoffset_t
3875 NoteDrag::total_dx () const
3877 /* dx in frames */
3878 frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
3880 /* primary note time */
3881 frameoffset_t const n = _region->beats_to_frames (_primary->note()->time ());
3883 /* new time of the primary note relative to the region position */
3884 frameoffset_t st = n + dx;
3886 /* prevent the note being dragged earlier than the region's position */
3887 if (st < 0) {
3888 st = 0;
3891 /* snap and return corresponding delta */
3892 return _region->snap_frame_to_frame (st) - n;
3895 /** @return Current total drag y change in notes */
3896 int8_t
3897 NoteDrag::total_dy () const
3899 /* this is `backwards' to make increasing note number go in the right direction */
3900 double const dy = _drags->current_pointer_y() - grab_y();
3902 /* dy in notes */
3903 int8_t ndy = 0;
3905 if (abs (dy) >= _note_height) {
3906 if (dy > 0) {
3907 ndy = (int8_t) ceil (dy / _note_height / 2.0);
3908 } else {
3909 ndy = (int8_t) floor (dy / _note_height / 2.0);
3913 /* more positive value = higher pitch and higher y-axis position on track,
3914 which is the inverse of the X-centric geometric universe
3917 return -ndy;
3920 void
3921 NoteDrag::motion (GdkEvent *, bool)
3923 /* Total change in x and y since the start of the drag */
3924 frameoffset_t const dx = total_dx ();
3925 int8_t const dy = -total_dy ();
3927 /* Now work out what we have to do to the note canvas items to set this new drag delta */
3928 double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx;
3929 double const tdy = dy * _note_height - _cumulative_dy;
3931 if (tdx || tdy) {
3932 _cumulative_dx += tdx;
3933 _cumulative_dy += tdy;
3935 int8_t note_delta = total_dy();
3937 _region->move_selection (tdx, tdy, note_delta);
3939 char buf[12];
3940 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (_primary->note()->note() + note_delta).c_str(),
3941 (int) floor (_primary->note()->note() + note_delta));
3943 show_verbose_cursor_text (buf);
3947 void
3948 NoteDrag::finished (GdkEvent* ev, bool moved)
3950 if (!moved) {
3951 if (_editor->current_mouse_mode() == Editing::MouseObject) {
3953 if (_was_selected) {
3954 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3955 if (add) {
3956 _region->note_deselected (_primary);
3958 } else {
3959 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
3960 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3962 if (!extend && !add && _region->selection_size() > 1) {
3963 _region->unique_select (_primary);
3964 } else if (extend) {
3965 _region->note_selected (_primary, true, true);
3966 } else {
3967 /* it was added during button press */
3971 } else {
3972 _region->note_dropped (_primary, total_dx(), total_dy());
3976 void
3977 NoteDrag::aborted (bool)
3979 /* XXX: TODO */
3982 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* item, list<AudioRange> const & r)
3983 : Drag (editor, item)
3984 , _ranges (r)
3985 , _nothing_to_drag (false)
3987 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
3989 _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
3990 assert (_atav);
3992 /* get all lines in the automation view */
3993 list<boost::shared_ptr<AutomationLine> > lines = _atav->lines ();
3995 /* find those that overlap the ranges being dragged */
3996 list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
3997 while (i != lines.end ()) {
3998 list<boost::shared_ptr<AutomationLine> >::iterator j = i;
3999 ++j;
4001 pair<framepos_t, framepos_t> const r = (*i)->get_point_x_range ();
4003 /* check this range against all the AudioRanges that we are using */
4004 list<AudioRange>::const_iterator k = _ranges.begin ();
4005 while (k != _ranges.end()) {
4006 if (k->coverage (r.first, r.second) != OverlapNone) {
4007 break;
4009 ++k;
4012 /* add it to our list if it overlaps at all */
4013 if (k != _ranges.end()) {
4014 Line n;
4015 n.line = *i;
4016 n.state = 0;
4017 n.range = r;
4018 _lines.push_back (n);
4021 i = j;
4024 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
4027 void
4028 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4030 Drag::start_grab (event, cursor);
4032 /* Get line states before we start changing things */
4033 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4034 i->state = &i->line->get_state ();
4037 if (_ranges.empty()) {
4039 /* No selected time ranges: drag all points */
4040 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4041 uint32_t const N = i->line->npoints ();
4042 for (uint32_t j = 0; j < N; ++j) {
4043 i->points.push_back (i->line->nth (j));
4047 } else {
4049 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
4051 framecnt_t const half = (i->start + i->end) / 2;
4053 /* find the line that this audio range starts in */
4054 list<Line>::iterator j = _lines.begin();
4055 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
4056 ++j;
4059 if (j != _lines.end()) {
4060 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4062 /* j is the line that this audio range starts in; fade into it;
4063 64 samples length plucked out of thin air.
4066 framepos_t a = i->start + 64;
4067 if (a > half) {
4068 a = half;
4071 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
4072 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
4074 the_list->add (p, the_list->eval (p));
4075 j->line->add_always_in_view (p);
4076 the_list->add (q, the_list->eval (q));
4077 j->line->add_always_in_view (q);
4080 /* same thing for the end */
4082 j = _lines.begin();
4083 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
4084 ++j;
4087 if (j != _lines.end()) {
4088 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4090 /* j is the line that this audio range starts in; fade out of it;
4091 64 samples length plucked out of thin air.
4094 framepos_t b = i->end - 64;
4095 if (b < half) {
4096 b = half;
4099 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
4100 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
4102 the_list->add (p, the_list->eval (p));
4103 j->line->add_always_in_view (p);
4104 the_list->add (q, the_list->eval (q));
4105 j->line->add_always_in_view (q);
4109 _nothing_to_drag = true;
4111 /* Find all the points that should be dragged and put them in the relevant
4112 points lists in the Line structs.
4115 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4117 uint32_t const N = i->line->npoints ();
4118 for (uint32_t j = 0; j < N; ++j) {
4120 /* here's a control point on this line */
4121 ControlPoint* p = i->line->nth (j);
4122 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
4124 /* see if it's inside a range */
4125 list<AudioRange>::const_iterator k = _ranges.begin ();
4126 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
4127 ++k;
4130 if (k != _ranges.end()) {
4131 /* dragging this point */
4132 _nothing_to_drag = false;
4133 i->points.push_back (p);
4139 if (_nothing_to_drag) {
4140 return;
4143 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4144 i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state);
4148 void
4149 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
4151 if (_nothing_to_drag) {
4152 return;
4155 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4156 float const f = 1 - (_drags->current_pointer_y() / i->line->height());
4158 /* we are ignoring x position for this drag, so we can just pass in anything */
4159 i->line->drag_motion (0, f, true, false);
4163 void
4164 AutomationRangeDrag::finished (GdkEvent* event, bool)
4166 if (_nothing_to_drag) {
4167 return;
4170 motion (event, false);
4171 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4172 i->line->end_drag ();
4173 i->line->clear_always_in_view ();
4176 _editor->session()->commit_reversible_command ();
4179 void
4180 AutomationRangeDrag::aborted (bool)
4182 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4183 i->line->clear_always_in_view ();
4184 i->line->reset ();
4188 DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
4189 : view (v)
4191 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
4192 layer = v->region()->layer ();
4193 initial_y = v->get_canvas_group()->property_y ();
4194 initial_playlist = v->region()->playlist ();
4195 initial_position = v->region()->position ();
4196 initial_end = v->region()->position () + v->region()->length ();
4199 PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
4200 : Drag (e, i)
4201 , _region_view (r)
4202 , _patch_change (i)
4203 , _cumulative_dx (0)
4205 DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
4208 void
4209 PatchChangeDrag::motion (GdkEvent* ev, bool)
4211 framepos_t f = adjusted_current_frame (ev);
4212 boost::shared_ptr<Region> r = _region_view->region ();
4213 f = max (f, r->position ());
4214 f = min (f, r->last_frame ());
4216 framecnt_t const dxf = f - grab_frame();
4217 double const dxu = _editor->frame_to_unit (dxf);
4218 _patch_change->move (dxu - _cumulative_dx, 0);
4219 _cumulative_dx = dxu;
4222 void
4223 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
4225 if (!movement_occurred) {
4226 return;
4229 boost::shared_ptr<Region> r (_region_view->region ());
4231 framepos_t f = adjusted_current_frame (ev);
4232 f = max (f, r->position ());
4233 f = min (f, r->last_frame ());
4235 _region_view->move_patch_change (
4236 *_patch_change,
4237 _region_view->frames_to_beats (f - r->position() - r->start())
4241 void
4242 PatchChangeDrag::aborted (bool)
4244 _patch_change->move (-_cumulative_dx, 0);
4247 void
4248 PatchChangeDrag::setup_pointer_frame_offset ()
4250 boost::shared_ptr<Region> region = _region_view->region ();
4251 _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_patch_change->patch()->time()) - region->position() + region->start();