Fix corruption of follow playhead state on quit (#4048).
[ardour2.git] / gtk2_ardour / editor_drag.cc
blob1784e59944ad0ab13ba5d4a6290115857dc37811
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(), this);
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, (void*) this);
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, this);
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, this);
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 framepos_t where = _editor->event_frame (event, 0, 0);
2086 _editor->snap_to_with_modifier (where, event);
2088 _editor->_dragging_playhead = true;
2090 Session* s = _editor->session ();
2092 if (s) {
2093 if (_was_rolling && _stop) {
2094 s->request_stop ();
2097 if (s->is_auditioning()) {
2098 s->cancel_audition ();
2101 s->request_suspend_timecode_transmission ();
2102 while (!s->timecode_transmission_suspended ()) {
2103 /* twiddle our thumbs */
2107 fake_locate (where);
2110 void
2111 CursorDrag::motion (GdkEvent* event, bool)
2113 framepos_t const adjusted_frame = adjusted_current_frame (event);
2115 if (adjusted_frame == last_pointer_frame()) {
2116 return;
2119 fake_locate (adjusted_frame);
2121 #ifdef GTKOSX
2122 _editor->update_canvas_now ();
2123 #endif
2126 void
2127 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
2129 _editor->_dragging_playhead = false;
2131 if (!movement_occurred && _stop) {
2132 return;
2135 motion (event, false);
2137 Session* s = _editor->session ();
2138 if (s) {
2139 s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
2140 _editor->_pending_locate_request = true;
2141 s->request_resume_timecode_transmission ();
2145 void
2146 CursorDrag::aborted (bool)
2148 if (_editor->_dragging_playhead) {
2149 _editor->session()->request_resume_timecode_transmission ();
2150 _editor->_dragging_playhead = false;
2153 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
2156 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2157 : RegionDrag (e, i, p, v)
2159 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
2162 void
2163 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2165 Drag::start_grab (event, cursor);
2167 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2168 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2170 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
2172 arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
2175 void
2176 FadeInDrag::setup_pointer_frame_offset ()
2178 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2179 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2180 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
2183 void
2184 FadeInDrag::motion (GdkEvent* event, bool)
2186 framecnt_t fade_length;
2188 framepos_t const pos = adjusted_current_frame (event);
2190 boost::shared_ptr<Region> region = _primary->region ();
2192 if (pos < (region->position() + 64)) {
2193 fade_length = 64; // this should be a minimum defined somewhere
2194 } else if (pos > region->last_frame()) {
2195 fade_length = region->length();
2196 } else {
2197 fade_length = pos - region->position();
2200 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2202 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2204 if (!tmp) {
2205 continue;
2208 tmp->reset_fade_in_shape_width (fade_length);
2209 tmp->show_fade_line((framecnt_t) fade_length);
2212 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
2215 void
2216 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
2218 if (!movement_occurred) {
2219 return;
2222 framecnt_t fade_length;
2224 framepos_t const pos = adjusted_current_frame (event);
2226 boost::shared_ptr<Region> region = _primary->region ();
2228 if (pos < (region->position() + 64)) {
2229 fade_length = 64; // this should be a minimum defined somewhere
2230 } else if (pos > region->last_frame()) {
2231 fade_length = region->length();
2232 } else {
2233 fade_length = pos - region->position();
2236 _editor->begin_reversible_command (_("change fade in length"));
2238 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2240 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2242 if (!tmp) {
2243 continue;
2246 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
2247 XMLNode &before = alist->get_state();
2249 tmp->audio_region()->set_fade_in_length (fade_length);
2250 tmp->audio_region()->set_fade_in_active (true);
2251 tmp->hide_fade_line();
2253 XMLNode &after = alist->get_state();
2254 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2257 _editor->commit_reversible_command ();
2260 void
2261 FadeInDrag::aborted (bool)
2263 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2264 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2266 if (!tmp) {
2267 continue;
2270 tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
2271 tmp->hide_fade_line();
2275 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2276 : RegionDrag (e, i, p, v)
2278 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
2281 void
2282 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2284 Drag::start_grab (event, cursor);
2286 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2287 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2289 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
2291 arv->show_fade_line(r->length() - r->fade_out()->back()->when);
2294 void
2295 FadeOutDrag::setup_pointer_frame_offset ()
2297 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2298 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2299 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
2302 void
2303 FadeOutDrag::motion (GdkEvent* event, bool)
2305 framecnt_t fade_length;
2307 framepos_t const pos = adjusted_current_frame (event);
2309 boost::shared_ptr<Region> region = _primary->region ();
2311 if (pos > (region->last_frame() - 64)) {
2312 fade_length = 64; // this should really be a minimum fade defined somewhere
2314 else if (pos < region->position()) {
2315 fade_length = region->length();
2317 else {
2318 fade_length = region->last_frame() - pos;
2321 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2323 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2325 if (!tmp) {
2326 continue;
2329 tmp->reset_fade_out_shape_width (fade_length);
2330 tmp->show_fade_line(region->length() - fade_length);
2333 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
2336 void
2337 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
2339 if (!movement_occurred) {
2340 return;
2343 framecnt_t fade_length;
2345 framepos_t const pos = adjusted_current_frame (event);
2347 boost::shared_ptr<Region> region = _primary->region ();
2349 if (pos > (region->last_frame() - 64)) {
2350 fade_length = 64; // this should really be a minimum fade defined somewhere
2352 else if (pos < region->position()) {
2353 fade_length = region->length();
2355 else {
2356 fade_length = region->last_frame() - pos;
2359 _editor->begin_reversible_command (_("change fade out length"));
2361 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2363 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2365 if (!tmp) {
2366 continue;
2369 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
2370 XMLNode &before = alist->get_state();
2372 tmp->audio_region()->set_fade_out_length (fade_length);
2373 tmp->audio_region()->set_fade_out_active (true);
2374 tmp->hide_fade_line();
2376 XMLNode &after = alist->get_state();
2377 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2380 _editor->commit_reversible_command ();
2383 void
2384 FadeOutDrag::aborted (bool)
2386 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2387 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2389 if (!tmp) {
2390 continue;
2393 tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
2394 tmp->hide_fade_line();
2398 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
2399 : Drag (e, i)
2401 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
2403 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
2404 assert (_marker);
2406 _points.push_back (Gnome::Art::Point (0, 0));
2407 _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
2410 MarkerDrag::~MarkerDrag ()
2412 for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
2413 delete *i;
2417 void
2418 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2420 Drag::start_grab (event, cursor);
2422 bool is_start;
2424 Location *location = _editor->find_location_from_marker (_marker, is_start);
2425 _editor->_dragging_edit_point = true;
2427 update_item (location);
2429 // _drag_line->show();
2430 // _line->raise_to_top();
2432 if (is_start) {
2433 show_verbose_cursor_time (location->start());
2434 } else {
2435 show_verbose_cursor_time (location->end());
2438 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2440 switch (op) {
2441 case Selection::Toggle:
2442 _editor->selection->toggle (_marker);
2443 break;
2444 case Selection::Set:
2445 if (!_editor->selection->selected (_marker)) {
2446 _editor->selection->set (_marker);
2448 break;
2449 case Selection::Extend:
2451 Locations::LocationList ll;
2452 list<Marker*> to_add;
2453 framepos_t s, e;
2454 _editor->selection->markers.range (s, e);
2455 s = min (_marker->position(), s);
2456 e = max (_marker->position(), e);
2457 s = min (s, e);
2458 e = max (s, e);
2459 if (e < max_framepos) {
2460 ++e;
2462 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
2463 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
2464 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
2465 if (lm) {
2466 if (lm->start) {
2467 to_add.push_back (lm->start);
2469 if (lm->end) {
2470 to_add.push_back (lm->end);
2474 if (!to_add.empty()) {
2475 _editor->selection->add (to_add);
2477 break;
2479 case Selection::Add:
2480 _editor->selection->add (_marker);
2481 break;
2484 /* Set up copies for us to manipulate during the drag */
2486 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
2487 Location* l = _editor->find_location_from_marker (*i, is_start);
2488 _copied_locations.push_back (new Location (*l));
2492 void
2493 MarkerDrag::setup_pointer_frame_offset ()
2495 bool is_start;
2496 Location *location = _editor->find_location_from_marker (_marker, is_start);
2497 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
2500 void
2501 MarkerDrag::motion (GdkEvent* event, bool)
2503 framecnt_t f_delta = 0;
2504 bool is_start;
2505 bool move_both = false;
2506 Marker* marker;
2507 Location *real_location;
2508 Location *copy_location = 0;
2510 framepos_t const newframe = adjusted_current_frame (event);
2512 framepos_t next = newframe;
2514 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2515 move_both = true;
2518 MarkerSelection::iterator i;
2519 list<Location*>::iterator x;
2521 /* find the marker we're dragging, and compute the delta */
2523 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2524 x != _copied_locations.end() && i != _editor->selection->markers.end();
2525 ++i, ++x) {
2527 copy_location = *x;
2528 marker = *i;
2530 if (marker == _marker) {
2532 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2533 /* que pasa ?? */
2534 return;
2537 if (real_location->is_mark()) {
2538 f_delta = newframe - copy_location->start();
2539 } else {
2542 switch (marker->type()) {
2543 case Marker::SessionStart:
2544 case Marker::RangeStart:
2545 case Marker::LoopStart:
2546 case Marker::PunchIn:
2547 f_delta = newframe - copy_location->start();
2548 break;
2550 case Marker::SessionEnd:
2551 case Marker::RangeEnd:
2552 case Marker::LoopEnd:
2553 case Marker::PunchOut:
2554 f_delta = newframe - copy_location->end();
2555 break;
2556 default:
2557 /* what kind of marker is this ? */
2558 return;
2561 break;
2565 if (i == _editor->selection->markers.end()) {
2566 /* hmm, impossible - we didn't find the dragged marker */
2567 return;
2570 /* now move them all */
2572 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2573 x != _copied_locations.end() && i != _editor->selection->markers.end();
2574 ++i, ++x) {
2576 copy_location = *x;
2577 marker = *i;
2579 /* call this to find out if its the start or end */
2581 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2582 continue;
2585 if (real_location->locked()) {
2586 continue;
2589 if (copy_location->is_mark()) {
2591 /* now move it */
2593 copy_location->set_start (copy_location->start() + f_delta);
2595 } else {
2597 framepos_t new_start = copy_location->start() + f_delta;
2598 framepos_t new_end = copy_location->end() + f_delta;
2600 if (is_start) { // start-of-range marker
2602 if (move_both) {
2603 copy_location->set_start (new_start);
2604 copy_location->set_end (new_end);
2605 } else if (new_start < copy_location->end()) {
2606 copy_location->set_start (new_start);
2607 } else if (newframe > 0) {
2608 _editor->snap_to (next, 1, true);
2609 copy_location->set_end (next);
2610 copy_location->set_start (newframe);
2613 } else { // end marker
2615 if (move_both) {
2616 copy_location->set_end (new_end);
2617 copy_location->set_start (new_start);
2618 } else if (new_end > copy_location->start()) {
2619 copy_location->set_end (new_end);
2620 } else if (newframe > 0) {
2621 _editor->snap_to (next, -1, true);
2622 copy_location->set_start (next);
2623 copy_location->set_end (newframe);
2628 update_item (copy_location);
2630 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
2632 if (lm) {
2633 lm->set_position (copy_location->start(), copy_location->end());
2637 assert (!_copied_locations.empty());
2639 show_verbose_cursor_time (newframe);
2641 #ifdef GTKOSX
2642 _editor->update_canvas_now ();
2643 #endif
2646 void
2647 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2649 if (!movement_occurred) {
2651 /* just a click, do nothing but finish
2652 off the selection process
2655 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2657 switch (op) {
2658 case Selection::Set:
2659 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
2660 _editor->selection->set (_marker);
2662 break;
2664 case Selection::Toggle:
2665 case Selection::Extend:
2666 case Selection::Add:
2667 break;
2670 return;
2673 _editor->_dragging_edit_point = false;
2675 _editor->begin_reversible_command ( _("move marker") );
2676 XMLNode &before = _editor->session()->locations()->get_state();
2678 MarkerSelection::iterator i;
2679 list<Location*>::iterator x;
2680 bool is_start;
2682 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2683 x != _copied_locations.end() && i != _editor->selection->markers.end();
2684 ++i, ++x) {
2686 Location * location = _editor->find_location_from_marker (*i, is_start);
2688 if (location) {
2690 if (location->locked()) {
2691 return;
2694 if (location->is_mark()) {
2695 location->set_start ((*x)->start());
2696 } else {
2697 location->set ((*x)->start(), (*x)->end());
2702 XMLNode &after = _editor->session()->locations()->get_state();
2703 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
2704 _editor->commit_reversible_command ();
2707 void
2708 MarkerDrag::aborted (bool)
2710 /* XXX: TODO */
2713 void
2714 MarkerDrag::update_item (Location* location)
2716 /* noop */
2719 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
2720 : Drag (e, i),
2721 _cumulative_x_drag (0),
2722 _cumulative_y_drag (0)
2724 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
2726 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
2727 assert (_point);
2731 void
2732 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2734 Drag::start_grab (event, _editor->cursors()->fader);
2736 // start the grab at the center of the control point so
2737 // the point doesn't 'jump' to the mouse after the first drag
2738 _fixed_grab_x = _point->get_x();
2739 _fixed_grab_y = _point->get_y();
2741 float const fraction = 1 - (_point->get_y() / _point->line().height());
2743 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
2745 _editor->verbose_cursor()->set (_point->line().get_verbose_cursor_string (fraction),
2746 event->button.x + 10, event->button.y + 10);
2748 _editor->verbose_cursor()->show ();
2751 void
2752 ControlPointDrag::motion (GdkEvent* event, bool)
2754 double dx = _drags->current_pointer_x() - last_pointer_x();
2755 double dy = _drags->current_pointer_y() - last_pointer_y();
2757 if (event->button.state & Keyboard::SecondaryModifier) {
2758 dx *= 0.1;
2759 dy *= 0.1;
2762 /* coordinate in pixels relative to the start of the region (for region-based automation)
2763 or track (for track-based automation) */
2764 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
2765 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2767 // calculate zero crossing point. back off by .01 to stay on the
2768 // positive side of zero
2769 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
2771 // make sure we hit zero when passing through
2772 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
2773 cy = zero_gain_y;
2776 if (_x_constrained) {
2777 cx = _fixed_grab_x;
2779 if (_y_constrained) {
2780 cy = _fixed_grab_y;
2783 _cumulative_x_drag = cx - _fixed_grab_x;
2784 _cumulative_y_drag = cy - _fixed_grab_y;
2786 cx = max (0.0, cx);
2787 cy = max (0.0, cy);
2788 cy = min ((double) _point->line().height(), cy);
2790 framepos_t cx_frames = _editor->unit_to_frame (cx);
2792 if (!_x_constrained) {
2793 _editor->snap_to_with_modifier (cx_frames, event);
2796 cx_frames = min (cx_frames, _point->line().maximum_time());
2798 float const fraction = 1.0 - (cy / _point->line().height());
2800 bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
2802 _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
2804 _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
2807 void
2808 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
2810 if (!movement_occurred) {
2812 /* just a click */
2814 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2815 _editor->reset_point_selection ();
2818 } else {
2819 motion (event, false);
2822 _point->line().end_drag ();
2823 _editor->session()->commit_reversible_command ();
2826 void
2827 ControlPointDrag::aborted (bool)
2829 _point->line().reset ();
2832 bool
2833 ControlPointDrag::active (Editing::MouseMode m)
2835 if (m == Editing::MouseGain) {
2836 /* always active in mouse gain */
2837 return true;
2840 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
2841 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
2844 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
2845 : Drag (e, i),
2846 _line (0),
2847 _cumulative_y_drag (0)
2849 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
2852 void
2853 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2855 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
2856 assert (_line);
2858 _item = &_line->grab_item ();
2860 /* need to get x coordinate in terms of parent (TimeAxisItemView)
2861 origin, and ditto for y.
2864 double cx = event->button.x;
2865 double cy = event->button.y;
2867 _line->parent_group().w2i (cx, cy);
2869 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit);
2871 uint32_t before;
2872 uint32_t after;
2874 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
2875 /* no adjacent points */
2876 return;
2879 Drag::start_grab (event, _editor->cursors()->fader);
2881 /* store grab start in parent frame */
2883 _fixed_grab_x = cx;
2884 _fixed_grab_y = cy;
2886 double fraction = 1.0 - (cy / _line->height());
2888 _line->start_drag_line (before, after, fraction);
2890 _editor->verbose_cursor()->set (_line->get_verbose_cursor_string (fraction),
2891 event->button.x + 10, event->button.y + 10);
2893 _editor->verbose_cursor()->show ();
2896 void
2897 LineDrag::motion (GdkEvent* event, bool)
2899 double dy = _drags->current_pointer_y() - last_pointer_y();
2901 if (event->button.state & Keyboard::SecondaryModifier) {
2902 dy *= 0.1;
2905 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2907 _cumulative_y_drag = cy - _fixed_grab_y;
2909 cy = max (0.0, cy);
2910 cy = min ((double) _line->height(), cy);
2912 double const fraction = 1.0 - (cy / _line->height());
2914 bool push;
2916 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
2917 push = false;
2918 } else {
2919 push = true;
2922 /* we are ignoring x position for this drag, so we can just pass in anything */
2923 _line->drag_motion (0, fraction, true, push);
2925 _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
2928 void
2929 LineDrag::finished (GdkEvent* event, bool)
2931 motion (event, false);
2932 _line->end_drag ();
2933 _editor->session()->commit_reversible_command ();
2936 void
2937 LineDrag::aborted (bool)
2939 _line->reset ();
2942 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
2943 : Drag (e, i),
2944 _line (0),
2945 _cumulative_x_drag (0)
2947 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
2950 void
2951 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2953 Drag::start_grab (event);
2955 _line = reinterpret_cast<Line*> (_item);
2956 assert (_line);
2958 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
2960 double cx = event->button.x;
2961 double cy = event->button.y;
2963 _item->property_parent().get_value()->w2i(cx, cy);
2965 /* store grab start in parent frame */
2966 _region_view_grab_x = cx;
2968 _before = *(float*) _item->get_data ("position");
2970 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
2972 _max_x = _editor->frame_to_pixel(_arv->get_duration());
2975 void
2976 FeatureLineDrag::motion (GdkEvent*, bool)
2978 double dx = _drags->current_pointer_x() - last_pointer_x();
2980 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
2982 _cumulative_x_drag += dx;
2984 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
2986 if (cx > _max_x){
2987 cx = _max_x;
2989 else if(cx < 0){
2990 cx = 0;
2993 ArdourCanvas::Points points;
2995 double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
2997 _line->get_bounds(x1, y2, x2, y2);
2999 points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
3000 points.push_back(Gnome::Art::Point(cx, y2 - y1));
3002 _line->property_points() = points;
3004 float *pos = new float;
3005 *pos = cx;
3007 _line->set_data ("position", pos);
3009 _before = cx;
3012 void
3013 FeatureLineDrag::finished (GdkEvent*, bool)
3015 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3016 _arv->update_transient(_before, _before);
3019 void
3020 FeatureLineDrag::aborted (bool)
3022 //_line->reset ();
3025 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
3026 : Drag (e, i)
3028 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
3031 void
3032 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3034 Drag::start_grab (event);
3035 show_verbose_cursor_time (adjusted_current_frame (event));
3038 void
3039 RubberbandSelectDrag::motion (GdkEvent* event, bool)
3041 framepos_t start;
3042 framepos_t end;
3043 double y1;
3044 double y2;
3046 framepos_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
3048 framepos_t grab = grab_frame ();
3049 if (Config->get_rubberbanding_snaps_to_grid ()) {
3050 _editor->snap_to_with_modifier (grab, event);
3053 /* base start and end on initial click position */
3055 if (pf < grab) {
3056 start = pf;
3057 end = grab;
3058 } else {
3059 end = pf;
3060 start = grab;
3063 if (_drags->current_pointer_y() < grab_y()) {
3064 y1 = _drags->current_pointer_y();
3065 y2 = grab_y();
3066 } else {
3067 y2 = _drags->current_pointer_y();
3068 y1 = grab_y();
3072 if (start != end || y1 != y2) {
3074 double x1 = _editor->frame_to_pixel (start);
3075 double x2 = _editor->frame_to_pixel (end);
3077 _editor->rubberband_rect->property_x1() = x1;
3078 _editor->rubberband_rect->property_y1() = y1;
3079 _editor->rubberband_rect->property_x2() = x2;
3080 _editor->rubberband_rect->property_y2() = y2;
3082 _editor->rubberband_rect->show();
3083 _editor->rubberband_rect->raise_to_top();
3085 show_verbose_cursor_time (pf);
3089 void
3090 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
3092 if (movement_occurred) {
3094 motion (event, false);
3096 double y1,y2;
3097 if (_drags->current_pointer_y() < grab_y()) {
3098 y1 = _drags->current_pointer_y();
3099 y2 = grab_y();
3100 } else {
3101 y2 = _drags->current_pointer_y();
3102 y1 = grab_y();
3106 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3108 _editor->begin_reversible_command (_("rubberband selection"));
3110 if (grab_frame() < last_pointer_frame()) {
3111 _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
3112 } else {
3113 _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
3116 _editor->commit_reversible_command ();
3118 } else {
3119 if (!getenv("ARDOUR_SAE")) {
3120 _editor->selection->clear_tracks();
3122 _editor->selection->clear_regions();
3123 _editor->selection->clear_points ();
3124 _editor->selection->clear_lines ();
3127 _editor->rubberband_rect->hide();
3130 void
3131 RubberbandSelectDrag::aborted (bool)
3133 _editor->rubberband_rect->hide ();
3136 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
3137 : RegionDrag (e, i, p, v)
3139 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
3142 void
3143 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3145 Drag::start_grab (event, cursor);
3147 show_verbose_cursor_time (adjusted_current_frame (event));
3150 void
3151 TimeFXDrag::motion (GdkEvent* event, bool)
3153 RegionView* rv = _primary;
3155 framepos_t const pf = adjusted_current_frame (event);
3157 if (pf > rv->region()->position()) {
3158 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
3161 show_verbose_cursor_time (pf);
3164 void
3165 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3167 _primary->get_time_axis_view().hide_timestretch ();
3169 if (!movement_occurred) {
3170 return;
3173 if (last_pointer_frame() < _primary->region()->position()) {
3174 /* backwards drag of the left edge - not usable */
3175 return;
3178 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
3180 float percentage = (double) newlen / (double) _primary->region()->length();
3182 #ifndef USE_RUBBERBAND
3183 // Soundtouch uses percentage / 100 instead of normal (/ 1)
3184 if (_primary->region()->data_type() == DataType::AUDIO) {
3185 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
3187 #endif
3189 // XXX how do timeFX on multiple regions ?
3191 RegionSelection rs;
3192 rs.add (_primary);
3194 if (_editor->time_stretch (rs, percentage) == -1) {
3195 error << _("An error occurred while executing time stretch operation") << endmsg;
3199 void
3200 TimeFXDrag::aborted (bool)
3202 _primary->get_time_axis_view().hide_timestretch ();
3205 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
3206 : Drag (e, i)
3208 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
3211 void
3212 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3214 Drag::start_grab (event);
3217 void
3218 ScrubDrag::motion (GdkEvent* /*event*/, bool)
3220 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
3223 void
3224 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3226 if (movement_occurred && _editor->session()) {
3227 /* make sure we stop */
3228 _editor->session()->request_transport_speed (0.0);
3232 void
3233 ScrubDrag::aborted (bool)
3235 /* XXX: TODO */
3238 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3239 : Drag (e, i)
3240 , _operation (o)
3241 , _copy (false)
3242 , _original_pointer_time_axis (-1)
3243 , _last_pointer_time_axis (-1)
3245 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
3248 void
3249 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
3251 if (_editor->session() == 0) {
3252 return;
3255 Gdk::Cursor* cursor = 0;
3257 switch (_operation) {
3258 case CreateSelection:
3259 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3260 _copy = true;
3261 } else {
3262 _copy = false;
3264 cursor = _editor->cursors()->selector;
3265 Drag::start_grab (event, cursor);
3266 break;
3268 case SelectionStartTrim:
3269 if (_editor->clicked_axisview) {
3270 _editor->clicked_axisview->order_selection_trims (_item, true);
3272 Drag::start_grab (event, _editor->cursors()->left_side_trim);
3273 break;
3275 case SelectionEndTrim:
3276 if (_editor->clicked_axisview) {
3277 _editor->clicked_axisview->order_selection_trims (_item, false);
3279 Drag::start_grab (event, _editor->cursors()->right_side_trim);
3280 break;
3282 case SelectionMove:
3283 Drag::start_grab (event, cursor);
3284 break;
3287 if (_operation == SelectionMove) {
3288 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
3289 } else {
3290 show_verbose_cursor_time (adjusted_current_frame (event));
3293 _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
3296 void
3297 SelectionDrag::setup_pointer_frame_offset ()
3299 switch (_operation) {
3300 case CreateSelection:
3301 _pointer_frame_offset = 0;
3302 break;
3304 case SelectionStartTrim:
3305 case SelectionMove:
3306 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
3307 break;
3309 case SelectionEndTrim:
3310 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
3311 break;
3315 void
3316 SelectionDrag::motion (GdkEvent* event, bool first_move)
3318 framepos_t start = 0;
3319 framepos_t end = 0;
3320 framecnt_t length;
3322 pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
3323 if (pending_time_axis.first == 0) {
3324 return;
3327 framepos_t const pending_position = adjusted_current_frame (event);
3329 /* only alter selection if things have changed */
3331 if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
3332 return;
3335 switch (_operation) {
3336 case CreateSelection:
3338 framepos_t grab = grab_frame ();
3340 if (first_move) {
3341 _editor->snap_to (grab);
3344 if (pending_position < grab_frame()) {
3345 start = pending_position;
3346 end = grab;
3347 } else {
3348 end = pending_position;
3349 start = grab;
3352 /* first drag: Either add to the selection
3353 or create a new selection
3356 if (first_move) {
3358 if (_copy) {
3359 /* adding to the selection */
3360 _editor->set_selected_track_as_side_effect (Selection::Add);
3361 //_editor->selection->add (_editor->clicked_axisview);
3362 _editor->clicked_selection = _editor->selection->add (start, end);
3363 _copy = false;
3364 } else {
3365 /* new selection */
3367 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3368 //_editor->selection->set (_editor->clicked_axisview);
3369 _editor->set_selected_track_as_side_effect (Selection::Set);
3372 _editor->clicked_selection = _editor->selection->set (start, end);
3376 /* select the track that we're in */
3377 if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
3378 // _editor->set_selected_track_as_side_effect (Selection::Add);
3379 _editor->selection->add (pending_time_axis.first);
3380 _added_time_axes.push_back (pending_time_axis.first);
3383 /* deselect any tracks that this drag no longer includes, being careful to only deselect
3384 tracks that we selected in the first place.
3387 int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
3388 int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
3390 list<TimeAxisView*>::iterator i = _added_time_axes.begin();
3391 while (i != _added_time_axes.end()) {
3393 list<TimeAxisView*>::iterator tmp = i;
3394 ++tmp;
3396 if ((*i)->order() < min_order || (*i)->order() > max_order) {
3397 _editor->selection->remove (*i);
3398 _added_time_axes.remove (*i);
3401 i = tmp;
3405 break;
3407 case SelectionStartTrim:
3409 start = _editor->selection->time[_editor->clicked_selection].start;
3410 end = _editor->selection->time[_editor->clicked_selection].end;
3412 if (pending_position > end) {
3413 start = end;
3414 } else {
3415 start = pending_position;
3417 break;
3419 case SelectionEndTrim:
3421 start = _editor->selection->time[_editor->clicked_selection].start;
3422 end = _editor->selection->time[_editor->clicked_selection].end;
3424 if (pending_position < start) {
3425 end = start;
3426 } else {
3427 end = pending_position;
3430 break;
3432 case SelectionMove:
3434 start = _editor->selection->time[_editor->clicked_selection].start;
3435 end = _editor->selection->time[_editor->clicked_selection].end;
3437 length = end - start;
3439 start = pending_position;
3440 _editor->snap_to (start);
3442 end = start + length;
3444 break;
3447 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3448 _editor->start_canvas_autoscroll (1, 0);
3451 if (start != end) {
3452 _editor->selection->replace (_editor->clicked_selection, start, end);
3455 if (_operation == SelectionMove) {
3456 show_verbose_cursor_time(start);
3457 } else {
3458 show_verbose_cursor_time(pending_position);
3462 void
3463 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
3465 Session* s = _editor->session();
3467 if (movement_occurred) {
3468 motion (event, false);
3469 /* XXX this is not object-oriented programming at all. ick */
3470 if (_editor->selection->time.consolidate()) {
3471 _editor->selection->TimeChanged ();
3474 /* XXX what if its a music time selection? */
3475 if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
3476 s->request_play_range (&_editor->selection->time, true);
3480 } else {
3481 /* just a click, no pointer movement.*/
3483 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
3484 _editor->selection->clear_time();
3487 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3488 _editor->selection->set (_editor->clicked_axisview);
3491 if (s && s->get_play_range () && s->transport_rolling()) {
3492 s->request_stop (false, false);
3497 _editor->stop_canvas_autoscroll ();
3500 void
3501 SelectionDrag::aborted (bool)
3503 /* XXX: TODO */
3506 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3507 : Drag (e, i),
3508 _operation (o),
3509 _copy (false)
3511 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
3513 _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
3514 physical_screen_height (_editor->get_window()));
3515 _drag_rect->hide ();
3517 _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3518 _drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3521 void
3522 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3524 if (_editor->session() == 0) {
3525 return;
3528 Gdk::Cursor* cursor = 0;
3530 if (!_editor->temp_location) {
3531 _editor->temp_location = new Location (*_editor->session());
3534 switch (_operation) {
3535 case CreateRangeMarker:
3536 case CreateTransportMarker:
3537 case CreateCDMarker:
3539 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3540 _copy = true;
3541 } else {
3542 _copy = false;
3544 cursor = _editor->cursors()->selector;
3545 break;
3548 Drag::start_grab (event, cursor);
3550 show_verbose_cursor_time (adjusted_current_frame (event));
3553 void
3554 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
3556 framepos_t start = 0;
3557 framepos_t end = 0;
3558 ArdourCanvas::SimpleRect *crect;
3560 switch (_operation) {
3561 case CreateRangeMarker:
3562 crect = _editor->range_bar_drag_rect;
3563 break;
3564 case CreateTransportMarker:
3565 crect = _editor->transport_bar_drag_rect;
3566 break;
3567 case CreateCDMarker:
3568 crect = _editor->cd_marker_bar_drag_rect;
3569 break;
3570 default:
3571 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
3572 return;
3573 break;
3576 framepos_t const pf = adjusted_current_frame (event);
3578 if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
3579 framepos_t grab = grab_frame ();
3580 _editor->snap_to (grab);
3582 if (pf < grab_frame()) {
3583 start = pf;
3584 end = grab;
3585 } else {
3586 end = pf;
3587 start = grab;
3590 /* first drag: Either add to the selection
3591 or create a new selection.
3594 if (first_move) {
3596 _editor->temp_location->set (start, end);
3598 crect->show ();
3600 update_item (_editor->temp_location);
3601 _drag_rect->show();
3602 //_drag_rect->raise_to_top();
3607 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3608 _editor->start_canvas_autoscroll (1, 0);
3611 if (start != end) {
3612 _editor->temp_location->set (start, end);
3614 double x1 = _editor->frame_to_pixel (start);
3615 double x2 = _editor->frame_to_pixel (end);
3616 crect->property_x1() = x1;
3617 crect->property_x2() = x2;
3619 update_item (_editor->temp_location);
3622 show_verbose_cursor_time (pf);
3626 void
3627 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
3629 Location * newloc = 0;
3630 string rangename;
3631 int flags;
3633 if (movement_occurred) {
3634 motion (event, false);
3635 _drag_rect->hide();
3637 switch (_operation) {
3638 case CreateRangeMarker:
3639 case CreateCDMarker:
3641 _editor->begin_reversible_command (_("new range marker"));
3642 XMLNode &before = _editor->session()->locations()->get_state();
3643 _editor->session()->locations()->next_available_name(rangename,"unnamed");
3644 if (_operation == CreateCDMarker) {
3645 flags = Location::IsRangeMarker | Location::IsCDMarker;
3646 _editor->cd_marker_bar_drag_rect->hide();
3648 else {
3649 flags = Location::IsRangeMarker;
3650 _editor->range_bar_drag_rect->hide();
3652 newloc = new Location (
3653 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
3656 _editor->session()->locations()->add (newloc, true);
3657 XMLNode &after = _editor->session()->locations()->get_state();
3658 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3659 _editor->commit_reversible_command ();
3660 break;
3663 case CreateTransportMarker:
3664 // popup menu to pick loop or punch
3665 _editor->new_transport_marker_context_menu (&event->button, _item);
3666 break;
3668 } else {
3669 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
3671 if (Keyboard::no_modifier_keys_pressed (&event->button) && _operation != CreateCDMarker) {
3673 framepos_t start;
3674 framepos_t end;
3676 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
3678 if (end == max_framepos) {
3679 end = _editor->session()->current_end_frame ();
3682 if (start == max_framepos) {
3683 start = _editor->session()->current_start_frame ();
3686 switch (_editor->mouse_mode) {
3687 case MouseObject:
3688 /* find the two markers on either side and then make the selection from it */
3689 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
3690 break;
3692 case MouseRange:
3693 /* find the two markers on either side of the click and make the range out of it */
3694 _editor->selection->set (start, end);
3695 break;
3697 default:
3698 break;
3703 _editor->stop_canvas_autoscroll ();
3706 void
3707 RangeMarkerBarDrag::aborted (bool)
3709 /* XXX: TODO */
3712 void
3713 RangeMarkerBarDrag::update_item (Location* location)
3715 double const x1 = _editor->frame_to_pixel (location->start());
3716 double const x2 = _editor->frame_to_pixel (location->end());
3718 _drag_rect->property_x1() = x1;
3719 _drag_rect->property_x2() = x2;
3722 MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i)
3723 : Drag (e, i)
3724 , _zoom_out (false)
3726 DEBUG_TRACE (DEBUG::Drags, "New MouseZoomDrag\n");
3729 void
3730 MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3732 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
3733 Drag::start_grab (event, _editor->cursors()->zoom_out);
3734 _zoom_out = true;
3735 } else {
3736 Drag::start_grab (event, _editor->cursors()->zoom_in);
3737 _zoom_out = false;
3740 show_verbose_cursor_time (adjusted_current_frame (event));
3743 void
3744 MouseZoomDrag::motion (GdkEvent* event, bool first_move)
3746 framepos_t start;
3747 framepos_t end;
3749 framepos_t const pf = adjusted_current_frame (event);
3751 framepos_t grab = grab_frame ();
3752 _editor->snap_to_with_modifier (grab, event);
3754 /* base start and end on initial click position */
3755 if (pf < grab) {
3756 start = pf;
3757 end = grab;
3758 } else {
3759 end = pf;
3760 start = grab;
3763 if (start != end) {
3765 if (first_move) {
3766 _editor->zoom_rect->show();
3767 _editor->zoom_rect->raise_to_top();
3770 _editor->reposition_zoom_rect(start, end);
3772 show_verbose_cursor_time (pf);
3776 void
3777 MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
3779 if (movement_occurred) {
3780 motion (event, false);
3782 if (grab_frame() < last_pointer_frame()) {
3783 _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
3784 } else {
3785 _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
3787 } else {
3788 if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
3789 _editor->tav_zoom_step (_zoom_out);
3790 } else {
3791 _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
3795 _editor->zoom_rect->hide();
3798 void
3799 MouseZoomDrag::aborted (bool)
3801 _editor->zoom_rect->hide ();
3804 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
3805 : Drag (e, i)
3806 , _cumulative_dx (0)
3807 , _cumulative_dy (0)
3809 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
3811 _primary = dynamic_cast<CanvasNoteEvent*> (_item);
3812 _region = &_primary->region_view ();
3813 _note_height = _region->midi_stream_view()->note_height ();
3816 void
3817 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3819 Drag::start_grab (event);
3821 if (!(_was_selected = _primary->selected())) {
3823 /* tertiary-click means extend selection - we'll do that on button release,
3824 so don't add it here, because otherwise we make it hard to figure
3825 out the "extend-to" range.
3828 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
3830 if (!extend) {
3831 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
3833 if (add) {
3834 _region->note_selected (_primary, true);
3835 } else {
3836 _region->unique_select (_primary);
3842 /** @return Current total drag x change in frames */
3843 frameoffset_t
3844 NoteDrag::total_dx () const
3846 /* dx in frames */
3847 frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
3849 /* primary note time */
3850 frameoffset_t const n = _region->beats_to_frames (_primary->note()->time ());
3852 /* new time of the primary note relative to the region position */
3853 frameoffset_t st = n + dx;
3855 /* prevent the note being dragged earlier than the region's position */
3856 if (st < 0) {
3857 st = 0;
3860 /* snap and return corresponding delta */
3861 return _region->snap_frame_to_frame (st) - n;
3864 /** @return Current total drag y change in notes */
3865 int8_t
3866 NoteDrag::total_dy () const
3868 /* this is `backwards' to make increasing note number go in the right direction */
3869 double const dy = _drags->current_pointer_y() - grab_y();
3871 /* dy in notes */
3872 int8_t ndy = 0;
3874 if (abs (dy) >= _note_height) {
3875 if (dy > 0) {
3876 ndy = (int8_t) ceil (dy / _note_height / 2.0);
3877 } else {
3878 ndy = (int8_t) floor (dy / _note_height / 2.0);
3882 /* more positive value = higher pitch and higher y-axis position on track,
3883 which is the inverse of the X-centric geometric universe
3886 return -ndy;
3889 void
3890 NoteDrag::motion (GdkEvent *, bool)
3892 /* Total change in x and y since the start of the drag */
3893 frameoffset_t const dx = total_dx ();
3894 int8_t const dy = -total_dy ();
3896 /* Now work out what we have to do to the note canvas items to set this new drag delta */
3897 double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx;
3898 double const tdy = dy * _note_height - _cumulative_dy;
3900 if (tdx || tdy) {
3901 _cumulative_dx += tdx;
3902 _cumulative_dy += tdy;
3904 int8_t note_delta = total_dy();
3906 _region->move_selection (tdx, tdy, note_delta);
3908 char buf[12];
3909 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (_primary->note()->note() + note_delta).c_str(),
3910 (int) floor (_primary->note()->note() + note_delta));
3912 show_verbose_cursor_text (buf);
3916 void
3917 NoteDrag::finished (GdkEvent* ev, bool moved)
3919 if (!moved) {
3920 if (_editor->current_mouse_mode() == Editing::MouseObject) {
3922 if (_was_selected) {
3923 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3924 if (add) {
3925 _region->note_deselected (_primary);
3927 } else {
3928 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
3929 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3931 if (!extend && !add && _region->selection_size() > 1) {
3932 _region->unique_select (_primary);
3933 } else if (extend) {
3934 _region->note_selected (_primary, true, true);
3935 } else {
3936 /* it was added during button press */
3940 } else {
3941 _region->note_dropped (_primary, total_dx(), total_dy());
3945 void
3946 NoteDrag::aborted (bool)
3948 /* XXX: TODO */
3951 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* item, list<AudioRange> const & r)
3952 : Drag (editor, item)
3953 , _ranges (r)
3954 , _nothing_to_drag (false)
3956 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
3958 _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
3959 assert (_atav);
3961 /* get all lines in the automation view */
3962 list<boost::shared_ptr<AutomationLine> > lines = _atav->lines ();
3964 /* find those that overlap the ranges being dragged */
3965 list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
3966 while (i != lines.end ()) {
3967 list<boost::shared_ptr<AutomationLine> >::iterator j = i;
3968 ++j;
3970 pair<framepos_t, framepos_t> const r = (*i)->get_point_x_range ();
3972 /* check this range against all the AudioRanges that we are using */
3973 list<AudioRange>::const_iterator k = _ranges.begin ();
3974 while (k != _ranges.end()) {
3975 if (k->coverage (r.first, r.second) != OverlapNone) {
3976 break;
3978 ++k;
3981 /* add it to our list if it overlaps at all */
3982 if (k != _ranges.end()) {
3983 Line n;
3984 n.line = *i;
3985 n.state = 0;
3986 n.range = r;
3987 _lines.push_back (n);
3990 i = j;
3993 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
3996 void
3997 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3999 Drag::start_grab (event, cursor);
4001 /* Get line states before we start changing things */
4002 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4003 i->state = &i->line->get_state ();
4006 if (_ranges.empty()) {
4008 /* No selected time ranges: drag all points */
4009 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4010 uint32_t const N = i->line->npoints ();
4011 for (uint32_t j = 0; j < N; ++j) {
4012 i->points.push_back (i->line->nth (j));
4016 } else {
4018 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
4020 framecnt_t const half = (i->start + i->end) / 2;
4022 /* find the line that this audio range starts in */
4023 list<Line>::iterator j = _lines.begin();
4024 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
4025 ++j;
4028 if (j != _lines.end()) {
4029 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4031 /* j is the line that this audio range starts in; fade into it;
4032 64 samples length plucked out of thin air.
4035 framepos_t a = i->start + 64;
4036 if (a > half) {
4037 a = half;
4040 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
4041 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
4043 the_list->add (p, the_list->eval (p));
4044 j->line->add_always_in_view (p);
4045 the_list->add (q, the_list->eval (q));
4046 j->line->add_always_in_view (q);
4049 /* same thing for the end */
4051 j = _lines.begin();
4052 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
4053 ++j;
4056 if (j != _lines.end()) {
4057 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4059 /* j is the line that this audio range starts in; fade out of it;
4060 64 samples length plucked out of thin air.
4063 framepos_t b = i->end - 64;
4064 if (b < half) {
4065 b = half;
4068 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
4069 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
4071 the_list->add (p, the_list->eval (p));
4072 j->line->add_always_in_view (p);
4073 the_list->add (q, the_list->eval (q));
4074 j->line->add_always_in_view (q);
4078 _nothing_to_drag = true;
4080 /* Find all the points that should be dragged and put them in the relevant
4081 points lists in the Line structs.
4084 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4086 uint32_t const N = i->line->npoints ();
4087 for (uint32_t j = 0; j < N; ++j) {
4089 /* here's a control point on this line */
4090 ControlPoint* p = i->line->nth (j);
4091 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
4093 /* see if it's inside a range */
4094 list<AudioRange>::const_iterator k = _ranges.begin ();
4095 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
4096 ++k;
4099 if (k != _ranges.end()) {
4100 /* dragging this point */
4101 _nothing_to_drag = false;
4102 i->points.push_back (p);
4108 if (_nothing_to_drag) {
4109 return;
4112 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4113 i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state);
4117 void
4118 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
4120 if (_nothing_to_drag) {
4121 return;
4124 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4125 float const f = 1 - (_drags->current_pointer_y() / i->line->height());
4127 /* we are ignoring x position for this drag, so we can just pass in anything */
4128 i->line->drag_motion (0, f, true, false);
4132 void
4133 AutomationRangeDrag::finished (GdkEvent* event, bool)
4135 if (_nothing_to_drag) {
4136 return;
4139 motion (event, false);
4140 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4141 i->line->end_drag ();
4142 i->line->clear_always_in_view ();
4145 _editor->session()->commit_reversible_command ();
4148 void
4149 AutomationRangeDrag::aborted (bool)
4151 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4152 i->line->clear_always_in_view ();
4153 i->line->reset ();
4157 DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
4158 : view (v)
4160 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
4161 layer = v->region()->layer ();
4162 initial_y = v->get_canvas_group()->property_y ();
4163 initial_playlist = v->region()->playlist ();
4164 initial_position = v->region()->position ();
4165 initial_end = v->region()->position () + v->region()->length ();
4168 PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
4169 : Drag (e, i)
4170 , _region_view (r)
4171 , _patch_change (i)
4172 , _cumulative_dx (0)
4174 DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
4177 void
4178 PatchChangeDrag::motion (GdkEvent* ev, bool)
4180 framepos_t f = adjusted_current_frame (ev);
4181 boost::shared_ptr<Region> r = _region_view->region ();
4182 f = max (f, r->position ());
4183 f = min (f, r->last_frame ());
4185 framecnt_t const dxf = f - grab_frame();
4186 double const dxu = _editor->frame_to_unit (dxf);
4187 _patch_change->move (dxu - _cumulative_dx, 0);
4188 _cumulative_dx = dxu;
4191 void
4192 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
4194 if (!movement_occurred) {
4195 return;
4198 boost::shared_ptr<Region> r (_region_view->region ());
4200 framepos_t f = adjusted_current_frame (ev);
4201 f = max (f, r->position ());
4202 f = min (f, r->last_frame ());
4204 _region_view->move_patch_change (
4205 *_patch_change,
4206 _region_view->frames_to_beats (f - r->position() - r->start())
4210 void
4211 PatchChangeDrag::aborted (bool)
4213 _patch_change->move (-_cumulative_dx, 0);
4216 void
4217 PatchChangeDrag::setup_pointer_frame_offset ()
4219 boost::shared_ptr<Region> region = _region_view->region ();
4220 _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_patch_change->patch()->time()) - region->position() + region->start();