much ado about nothing when it comes to gain control
[ardour2.git] / gtk2_ardour / editor_drag.cc
blob42765e1d8740095644e856313e59bfadda789f85
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 ControlPointDrag::_zero_gain_fraction = -1.0;
71 DragManager::DragManager (Editor* e)
72 : _editor (e)
73 , _ending (false)
74 , _current_pointer_frame (0)
78 DragManager::~DragManager ()
80 abort ();
83 /** Call abort for each active drag */
84 void
85 DragManager::abort ()
87 _ending = true;
89 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
90 (*i)->abort ();
91 delete *i;
94 if (!_drags.empty ()) {
95 _editor->set_follow_playhead (_old_follow_playhead, false);
98 _drags.clear ();
100 _ending = false;
103 void
104 DragManager::add (Drag* d)
106 d->set_manager (this);
107 _drags.push_back (d);
110 void
111 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
113 d->set_manager (this);
114 _drags.push_back (d);
115 start_grab (e, c);
118 void
119 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
121 /* Prevent follow playhead during the drag to be nice to the user */
122 _old_follow_playhead = _editor->follow_playhead ();
123 _editor->set_follow_playhead (false);
125 _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
127 for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
128 (*i)->start_grab (e, c);
132 /** Call end_grab for each active drag.
133 * @return true if any drag reported movement having occurred.
135 bool
136 DragManager::end_grab (GdkEvent* e)
138 _ending = true;
140 bool r = false;
141 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
142 bool const t = (*i)->end_grab (e);
143 if (t) {
144 r = true;
146 delete *i;
149 _drags.clear ();
151 _ending = false;
153 _editor->set_follow_playhead (_old_follow_playhead, false);
155 return r;
158 bool
159 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
161 bool r = false;
163 _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
165 for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
166 bool const t = (*i)->motion_handler (e, from_autoscroll);
167 if (t) {
168 r = true;
173 return r;
176 bool
177 DragManager::have_item (ArdourCanvas::Item* i) const
179 list<Drag*>::const_iterator j = _drags.begin ();
180 while (j != _drags.end() && (*j)->item () != i) {
181 ++j;
184 return j != _drags.end ();
187 Drag::Drag (Editor* e, ArdourCanvas::Item* i)
188 : _editor (e)
189 , _item (i)
190 , _pointer_frame_offset (0)
191 , _move_threshold_passed (false)
192 , _raw_grab_frame (0)
193 , _grab_frame (0)
194 , _last_pointer_frame (0)
199 void
200 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time)
202 _item->ungrab (0);
203 _item = new_item;
205 if (cursor == 0) {
206 cursor = _editor->which_grabber_cursor ();
209 _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
212 void
213 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
215 if (cursor == 0) {
216 cursor = _editor->which_grabber_cursor ();
219 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
221 if (Keyboard::is_button2_event (&event->button)) {
222 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
223 _y_constrained = true;
224 _x_constrained = false;
225 } else {
226 _y_constrained = false;
227 _x_constrained = true;
229 } else {
230 _x_constrained = false;
231 _y_constrained = false;
234 _raw_grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
235 setup_pointer_frame_offset ();
236 _grab_frame = adjusted_frame (_raw_grab_frame, event);
237 _last_pointer_frame = _grab_frame;
238 _last_pointer_x = _grab_x;
239 _last_pointer_y = _grab_y;
241 _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
242 *cursor,
243 event->button.time);
245 if (_editor->session() && _editor->session()->transport_rolling()) {
246 _was_rolling = true;
247 } else {
248 _was_rolling = false;
251 switch (_editor->snap_type()) {
252 case SnapToRegionStart:
253 case SnapToRegionEnd:
254 case SnapToRegionSync:
255 case SnapToRegionBoundary:
256 _editor->build_region_boundary_cache ();
257 break;
258 default:
259 break;
263 /** Call to end a drag `successfully'. Ungrabs item and calls
264 * subclass' finished() method.
266 * @param event GDK event, or 0.
267 * @return true if some movement occurred, otherwise false.
269 bool
270 Drag::end_grab (GdkEvent* event)
272 _editor->stop_canvas_autoscroll ();
274 _item->ungrab (event ? event->button.time : 0);
276 finished (event, _move_threshold_passed);
278 _editor->verbose_cursor()->hide ();
280 return _move_threshold_passed;
283 framepos_t
284 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
286 framepos_t pos = 0;
288 if (f > _pointer_frame_offset) {
289 pos = f - _pointer_frame_offset;
292 if (snap) {
293 _editor->snap_to_with_modifier (pos, event);
296 return pos;
299 framepos_t
300 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
302 return adjusted_frame (_drags->current_pointer_frame (), event, snap);
305 bool
306 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
308 /* check to see if we have moved in any way that matters since the last motion event */
309 if (_move_threshold_passed &&
310 (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
311 (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
312 return false;
315 pair<framecnt_t, int> const threshold = move_threshold ();
317 bool const old_move_threshold_passed = _move_threshold_passed;
319 if (!from_autoscroll && !_move_threshold_passed) {
321 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
322 bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);
324 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
327 if (active (_editor->mouse_mode) && _move_threshold_passed) {
329 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
330 if (!from_autoscroll) {
331 _editor->maybe_autoscroll (true, allow_vertical_autoscroll ());
334 motion (event, _move_threshold_passed != old_move_threshold_passed);
336 _last_pointer_x = _drags->current_pointer_x ();
337 _last_pointer_y = _drags->current_pointer_y ();
338 _last_pointer_frame = adjusted_current_frame (event);
340 return true;
343 return false;
346 /** Call to abort a drag. Ungrabs item and calls subclass's aborted () */
347 void
348 Drag::abort ()
350 if (_item) {
351 _item->ungrab (0);
354 aborted (_move_threshold_passed);
356 _editor->stop_canvas_autoscroll ();
357 _editor->verbose_cursor()->hide ();
360 void
361 Drag::show_verbose_cursor_time (framepos_t frame)
363 _editor->verbose_cursor()->set_time (
364 frame,
365 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
366 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
369 _editor->verbose_cursor()->show ();
372 void
373 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double xoffset)
375 _editor->verbose_cursor()->show (xoffset);
377 _editor->verbose_cursor()->set_duration (
378 start, end,
379 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
380 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
384 void
385 Drag::show_verbose_cursor_text (string const & text)
387 _editor->verbose_cursor()->show ();
389 _editor->verbose_cursor()->set (
390 text,
391 _drags->current_pointer_x() + 10 - _editor->horizontal_position(),
392 _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize
397 struct EditorOrderTimeAxisViewSorter {
398 bool operator() (TimeAxisView* a, TimeAxisView* b) {
399 RouteTimeAxisView* ra = dynamic_cast<RouteTimeAxisView*> (a);
400 RouteTimeAxisView* rb = dynamic_cast<RouteTimeAxisView*> (b);
401 assert (ra && rb);
402 return ra->route()->order_key (N_ ("editor")) < rb->route()->order_key (N_ ("editor"));
406 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
407 : Drag (e, i),
408 _primary (p)
410 _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
412 /* Make a list of non-hidden tracks to refer to during the drag */
414 TrackViewList track_views = _editor->track_views;
415 track_views.sort (EditorOrderTimeAxisViewSorter ());
417 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
418 if (!(*i)->hidden()) {
420 _time_axis_views.push_back (*i);
422 TimeAxisView::Children children_list = (*i)->get_child_list ();
423 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
424 _time_axis_views.push_back (j->get());
429 /* the list of views can be empty at this point if this is a region list-insert drag
432 for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
433 _views.push_back (DraggingView (*i, this));
436 RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
439 void
440 RegionDrag::region_going_away (RegionView* v)
442 list<DraggingView>::iterator i = _views.begin ();
443 while (i != _views.end() && i->view != v) {
444 ++i;
447 if (i != _views.end()) {
448 _views.erase (i);
452 /** Given a non-hidden TimeAxisView, return the index of it into the _time_axis_views vector */
454 RegionDrag::find_time_axis_view (TimeAxisView* t) const
456 int i = 0;
457 int const N = _time_axis_views.size ();
458 while (i < N && _time_axis_views[i] != t) {
459 ++i;
462 if (i == N) {
463 return -1;
466 return i;
469 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
470 : RegionDrag (e, i, p, v),
471 _brushing (b),
472 _total_x_delta (0)
478 void
479 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
481 Drag::start_grab (event, cursor);
483 show_verbose_cursor_time (_last_frame_position);
485 pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
486 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
487 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
490 double
491 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
493 /* compute the amount of pointer motion in frames, and where
494 the region would be if we moved it by that much.
496 *pending_region_position = adjusted_current_frame (event);
498 framepos_t sync_frame;
499 framecnt_t sync_offset;
500 int32_t sync_dir;
502 sync_offset = _primary->region()->sync_offset (sync_dir);
504 /* we don't handle a sync point that lies before zero.
506 if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
508 sync_frame = *pending_region_position + (sync_dir*sync_offset);
510 _editor->snap_to_with_modifier (sync_frame, event);
512 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
514 } else {
515 *pending_region_position = _last_frame_position;
518 if (*pending_region_position > max_framepos - _primary->region()->length()) {
519 *pending_region_position = _last_frame_position;
522 double dx = 0;
524 /* in locked edit mode, reverse the usual meaning of _x_constrained */
525 bool const x_move_allowed = Config->get_edit_mode() == Lock ? _x_constrained : !_x_constrained;
527 if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
529 /* x movement since last time (in pixels) */
530 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;
532 /* total x movement */
533 framecnt_t total_dx = *pending_region_position;
534 if (regions_came_from_canvas()) {
535 total_dx = total_dx - grab_frame ();
538 /* check that no regions have gone off the start of the session */
539 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
540 if ((i->view->region()->position() + total_dx) < 0) {
541 dx = 0;
542 *pending_region_position = _last_frame_position;
543 break;
547 _last_frame_position = *pending_region_position;
550 return dx;
553 bool
554 RegionMotionDrag::y_movement_allowed (int delta_track, layer_t delta_layer) const
556 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
557 int const n = i->time_axis_view + delta_track;
558 if (n < 0 || n >= int (_time_axis_views.size())) {
559 /* off the top or bottom track */
560 return false;
563 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
564 if (to == 0 || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
565 /* not a track, or the wrong type */
566 return false;
569 int const l = i->layer + delta_layer;
570 if (delta_track == 0 && (l < 0 || l >= int (to->view()->layers()))) {
571 /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
572 If it has, the layers will be munged later anyway, so it's ok.
574 return false;
578 /* all regions being dragged are ok with this change */
579 return true;
582 void
583 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
585 assert (!_views.empty ());
587 /* Find the TimeAxisView that the pointer is now over */
588 pair<TimeAxisView*, int> const tv = _editor->trackview_by_y_position (_drags->current_pointer_y ());
590 /* Bail early if we're not over a track */
591 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv.first);
592 if (!rtv || !rtv->is_track()) {
593 _editor->verbose_cursor()->hide ();
594 return;
597 /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
599 /* Here's the current pointer position in terms of time axis view and layer */
600 int const current_pointer_time_axis_view = find_time_axis_view (tv.first);
601 layer_t const current_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
603 /* Work out the change in x */
604 framepos_t pending_region_position;
605 double const x_delta = compute_x_delta (event, &pending_region_position);
607 /* Work out the change in y */
608 int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
609 int delta_layer = current_pointer_layer - _last_pointer_layer;
611 if (!y_movement_allowed (delta_time_axis_view, delta_layer)) {
612 /* this y movement is not allowed, so do no y movement this time */
613 delta_time_axis_view = 0;
614 delta_layer = 0;
617 if (x_delta == 0 && delta_time_axis_view == 0 && delta_layer == 0 && !first_move) {
618 /* haven't reached next snap point, and we're not switching
619 trackviews nor layers. nothing to do.
621 return;
624 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
626 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
628 RegionView* rv = i->view;
630 if (rv->region()->locked()) {
631 continue;
634 if (first_move) {
636 /* here we are calculating the y distance from the
637 top of the first track view to the top of the region
638 area of the track view that we're working on */
640 /* this x value is just a dummy value so that we have something
641 to pass to i2w () */
643 double ix1 = 0;
645 /* distance from the top of this track view to the region area
646 of our track view is always 1 */
648 double iy1 = 1;
650 /* convert to world coordinates, ie distance from the top of
651 the ruler section */
653 rv->get_canvas_frame()->i2w (ix1, iy1);
655 /* compensate for the ruler section and the vertical scrollbar position */
656 iy1 += _editor->get_trackview_group_vertical_offset ();
658 // hide any dependent views
660 rv->get_time_axis_view().hide_dependent_views (*rv);
663 reparent to a non scrolling group so that we can keep the
664 region selection above all time axis views.
665 reparenting means we have to move the rv as the two
666 parent groups have different coordinates.
669 rv->get_canvas_group()->property_y() = iy1 - 1;
670 rv->get_canvas_group()->reparent (*(_editor->_region_motion_group));
672 rv->fake_set_opaque (true);
675 /* Work out the change in y position of this region view */
677 double y_delta = 0;
679 /* If we have moved tracks, we'll fudge the layer delta so that the
680 region gets moved back onto layer 0 on its new track; this avoids
681 confusion when dragging regions from non-zero layers onto different
682 tracks.
684 int this_delta_layer = delta_layer;
685 if (delta_time_axis_view != 0) {
686 this_delta_layer = - i->layer;
689 /* Move this region to layer 0 on its old track */
690 StreamView* lv = _time_axis_views[i->time_axis_view]->view ();
691 if (lv->layer_display() == Stacked) {
692 y_delta -= (lv->layers() - i->layer - 1) * lv->child_height ();
695 /* Now move it to its right layer on the current track */
696 StreamView* cv = _time_axis_views[i->time_axis_view + delta_time_axis_view]->view ();
697 if (cv->layer_display() == Stacked) {
698 y_delta += (cv->layers() - (i->layer + this_delta_layer) - 1) * cv->child_height ();
701 /* Move tracks */
702 if (delta_time_axis_view > 0) {
703 for (int j = 0; j < delta_time_axis_view; ++j) {
704 y_delta += _time_axis_views[i->time_axis_view + j]->current_height ();
706 } else {
707 /* start by subtracting the height of the track above where we are now */
708 for (int j = 1; j <= -delta_time_axis_view; ++j) {
709 y_delta -= _time_axis_views[i->time_axis_view - j]->current_height ();
713 /* Set height */
714 rv->set_height (_time_axis_views[i->time_axis_view + delta_time_axis_view]->view()->child_height ());
716 /* Update the DraggingView */
717 i->time_axis_view += delta_time_axis_view;
718 i->layer += this_delta_layer;
720 if (_brushing) {
721 _editor->mouse_brush_insert_region (rv, pending_region_position);
722 } else {
723 rv->move (x_delta, y_delta);
726 } /* foreach region */
728 _total_x_delta += x_delta;
730 if (first_move) {
731 _editor->cursor_group->raise_to_top();
734 if (x_delta != 0 && !_brushing) {
735 show_verbose_cursor_time (_last_frame_position);
738 _last_pointer_time_axis_view += delta_time_axis_view;
739 _last_pointer_layer += delta_layer;
742 void
743 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
745 if (_copy && first_move) {
747 /* duplicate the regionview(s) and region(s) */
749 list<DraggingView> new_regionviews;
751 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
753 RegionView* rv = i->view;
754 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
755 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
757 const boost::shared_ptr<const Region> original = rv->region();
758 boost::shared_ptr<Region> region_copy = RegionFactory::create (original, true);
759 region_copy->set_position (original->position());
761 RegionView* nrv;
762 if (arv) {
763 boost::shared_ptr<AudioRegion> audioregion_copy
764 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
766 nrv = new AudioRegionView (*arv, audioregion_copy);
767 } else if (mrv) {
768 boost::shared_ptr<MidiRegion> midiregion_copy
769 = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
770 nrv = new MidiRegionView (*mrv, midiregion_copy);
771 } else {
772 continue;
775 nrv->get_canvas_group()->show ();
776 new_regionviews.push_back (DraggingView (nrv, this));
778 /* swap _primary to the copy */
780 if (rv == _primary) {
781 _primary = nrv;
784 /* ..and deselect the one we copied */
786 rv->set_selected (false);
789 if (!new_regionviews.empty()) {
791 /* reflect the fact that we are dragging the copies */
793 _views = new_regionviews;
795 swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
798 sync the canvas to what we think is its current state
799 without it, the canvas seems to
800 "forget" to update properly after the upcoming reparent()
801 ..only if the mouse is in rapid motion at the time of the grab.
802 something to do with regionview creation taking so long?
804 _editor->update_canvas_now();
808 RegionMotionDrag::motion (event, first_move);
811 void
812 RegionMoveDrag::finished (GdkEvent *, bool movement_occurred)
814 if (!movement_occurred) {
815 /* just a click */
816 return;
819 /* reverse this here so that we have the correct logic to finalize
820 the drag.
823 if (Config->get_edit_mode() == Lock) {
824 _x_constrained = !_x_constrained;
827 assert (!_views.empty ());
829 bool const changed_position = (_last_frame_position != _primary->region()->position());
830 bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
831 framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
833 _editor->update_canvas_now ();
835 if (_copy) {
837 finished_copy (
838 changed_position,
839 changed_tracks,
840 drag_delta
843 } else {
845 finished_no_copy (
846 changed_position,
847 changed_tracks,
848 drag_delta
854 void
855 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta)
857 RegionSelection new_views;
858 PlaylistSet modified_playlists;
859 list<RegionView*> views_to_delete;
861 if (_brushing) {
862 /* all changes were made during motion event handlers */
864 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
865 delete i->view;
868 _editor->commit_reversible_command ();
869 return;
872 if (_x_constrained) {
873 _editor->begin_reversible_command (Operations::fixed_time_region_copy);
874 } else {
875 _editor->begin_reversible_command (Operations::region_copy);
878 /* insert the regions into their new playlists */
879 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
881 if (i->view->region()->locked()) {
882 continue;
885 framepos_t where;
887 if (changed_position && !_x_constrained) {
888 where = i->view->region()->position() - drag_delta;
889 } else {
890 where = i->view->region()->position();
893 RegionView* new_view = insert_region_into_playlist (
894 i->view->region(), dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]), i->layer, where, modified_playlists
897 if (new_view == 0) {
898 continue;
901 new_views.push_back (new_view);
903 /* we don't need the copied RegionView any more */
904 views_to_delete.push_back (i->view);
907 /* Delete views that are no longer needed; we can't do this directly in the iteration over _views
908 because when views are deleted they are automagically removed from _views, which messes
909 up the iteration.
911 for (list<RegionView*>::iterator i = views_to_delete.begin(); i != views_to_delete.end(); ++i) {
912 delete *i;
915 /* If we've created new regions either by copying or moving
916 to a new track, we want to replace the old selection with the new ones
919 if (new_views.size() > 0) {
920 _editor->selection->set (new_views);
923 /* write commands for the accumulated diffs for all our modified playlists */
924 add_stateful_diff_commands_for_playlists (modified_playlists);
926 _editor->commit_reversible_command ();
929 void
930 RegionMoveDrag::finished_no_copy (
931 bool const changed_position,
932 bool const changed_tracks,
933 framecnt_t const drag_delta
936 RegionSelection new_views;
937 PlaylistSet modified_playlists;
938 PlaylistSet frozen_playlists;
940 if (_brushing) {
941 /* all changes were made during motion event handlers */
942 _editor->commit_reversible_command ();
943 return;
946 if (_x_constrained) {
947 _editor->begin_reversible_command (_("fixed time region drag"));
948 } else {
949 _editor->begin_reversible_command (Operations::region_drag);
952 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
954 RegionView* rv = i->view;
956 RouteTimeAxisView* const dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
957 layer_t const dest_layer = i->layer;
959 if (rv->region()->locked()) {
960 ++i;
961 continue;
964 framepos_t where;
966 if (changed_position && !_x_constrained) {
967 where = rv->region()->position() - drag_delta;
968 } else {
969 where = rv->region()->position();
972 if (changed_tracks) {
974 /* insert into new playlist */
976 RegionView* new_view = insert_region_into_playlist (
977 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where, modified_playlists
980 if (new_view == 0) {
981 ++i;
982 continue;
985 new_views.push_back (new_view);
987 /* remove from old playlist */
989 /* the region that used to be in the old playlist is not
990 moved to the new one - we use a copy of it. as a result,
991 any existing editor for the region should no longer be
992 visible.
994 rv->hide_region_editor();
995 rv->fake_set_opaque (false);
997 remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
999 } else {
1001 rv->region()->clear_changes ();
1004 motion on the same track. plonk the previously reparented region
1005 back to its original canvas group (its streamview).
1006 No need to do anything for copies as they are fake regions which will be deleted.
1009 rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
1010 rv->get_canvas_group()->property_y() = i->initial_y;
1011 rv->get_time_axis_view().reveal_dependent_views (*rv);
1013 /* just change the model */
1015 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1017 if (dest_rtv->view()->layer_display() == Stacked) {
1018 rv->region()->set_layer (dest_layer);
1019 rv->region()->set_pending_explicit_relayer (true);
1022 /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1024 pair<PlaylistSet::iterator, bool> r = frozen_playlists.insert (playlist);
1026 if (r.second) {
1027 playlist->freeze ();
1030 /* this movement may result in a crossfade being modified, so we need to get undo
1031 data from the playlist as well as the region.
1034 r = modified_playlists.insert (playlist);
1035 if (r.second) {
1036 playlist->clear_changes ();
1039 rv->region()->set_position (where);
1041 _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1044 if (changed_tracks) {
1046 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1047 was selected in all of them, then removing it from a playlist will have removed all
1048 trace of it from _views (i.e. there were N regions selected, we removed 1,
1049 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1050 corresponding regionview, and _views is now empty).
1052 This could have invalidated any and all iterators into _views.
1054 The heuristic we use here is: if the region selection is empty, break out of the loop
1055 here. if the region selection is not empty, then restart the loop because we know that
1056 we must have removed at least the region(view) we've just been working on as well as any
1057 that we processed on previous iterations.
1059 EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1060 we can just iterate.
1064 if (_views.empty()) {
1065 break;
1066 } else {
1067 i = _views.begin();
1070 } else {
1071 ++i;
1075 /* If we've created new regions either by copying or moving
1076 to a new track, we want to replace the old selection with the new ones
1079 if (new_views.size() > 0) {
1080 _editor->selection->set (new_views);
1083 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1084 (*p)->thaw();
1087 /* write commands for the accumulated diffs for all our modified playlists */
1088 add_stateful_diff_commands_for_playlists (modified_playlists);
1090 _editor->commit_reversible_command ();
1093 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1094 * @param region Region to remove.
1095 * @param playlist playlist To remove from.
1096 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1097 * that clear_changes () is only called once per playlist.
1099 void
1100 RegionMoveDrag::remove_region_from_playlist (
1101 boost::shared_ptr<Region> region,
1102 boost::shared_ptr<Playlist> playlist,
1103 PlaylistSet& modified_playlists
1106 pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1108 if (r.second) {
1109 playlist->clear_changes ();
1112 playlist->remove_region (region);
1116 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1117 * clearing the playlist's diff history first if necessary.
1118 * @param region Region to insert.
1119 * @param dest_rtv Destination RouteTimeAxisView.
1120 * @param dest_layer Destination layer.
1121 * @param where Destination position.
1122 * @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1123 * that clear_changes () is only called once per playlist.
1124 * @return New RegionView, or 0 if no insert was performed.
1126 RegionView *
1127 RegionMoveDrag::insert_region_into_playlist (
1128 boost::shared_ptr<Region> region,
1129 RouteTimeAxisView* dest_rtv,
1130 layer_t dest_layer,
1131 framecnt_t where,
1132 PlaylistSet& modified_playlists
1135 boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1136 if (!dest_playlist) {
1137 return 0;
1140 /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1141 _new_region_view = 0;
1142 sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1144 /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1145 pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1146 if (r.second) {
1147 dest_playlist->clear_changes ();
1150 dest_playlist->add_region (region, where);
1152 if (dest_rtv->view()->layer_display() == Stacked) {
1153 region->set_layer (dest_layer);
1154 region->set_pending_explicit_relayer (true);
1157 c.disconnect ();
1159 assert (_new_region_view);
1161 return _new_region_view;
1164 void
1165 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1167 _new_region_view = rv;
1170 void
1171 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1173 for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1174 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1175 if (!c->empty()) {
1176 _editor->session()->add_command (c);
1177 } else {
1178 delete c;
1184 void
1185 RegionMoveDrag::aborted (bool movement_occurred)
1187 if (_copy) {
1189 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1190 delete i->view;
1193 _views.clear ();
1195 } else {
1196 RegionMotionDrag::aborted (movement_occurred);
1200 void
1201 RegionMotionDrag::aborted (bool)
1203 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1204 RegionView* rv = i->view;
1205 TimeAxisView* tv = &(rv->get_time_axis_view ());
1206 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1207 assert (rtv);
1208 rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
1209 rv->get_canvas_group()->property_y() = 0;
1210 rv->get_time_axis_view().reveal_dependent_views (*rv);
1211 rv->fake_set_opaque (false);
1212 rv->move (-_total_x_delta, 0);
1213 rv->set_height (rtv->view()->child_height ());
1216 _editor->update_canvas_now ();
1219 /** @param b true to brush, otherwise false.
1220 * @param c true to make copies of the regions being moved, otherwise false.
1222 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1223 : RegionMotionDrag (e, i, p, v, b),
1224 _copy (c)
1226 DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1228 double speed = 1;
1229 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1230 if (rtv && rtv->is_track()) {
1231 speed = rtv->track()->speed ();
1234 _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1237 void
1238 RegionMoveDrag::setup_pointer_frame_offset ()
1240 _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1243 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1244 : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1246 DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1248 assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1249 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1251 _primary = v->view()->create_region_view (r, false, false);
1253 _primary->get_canvas_group()->show ();
1254 _primary->set_position (pos, 0);
1255 _views.push_back (DraggingView (_primary, this));
1257 _last_frame_position = pos;
1259 _item = _primary->get_canvas_group ();
1262 void
1263 RegionInsertDrag::finished (GdkEvent *, bool)
1265 _editor->update_canvas_now ();
1267 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[_views.front().time_axis_view]);
1269 _primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
1270 _primary->get_canvas_group()->property_y() = 0;
1272 boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1274 _editor->begin_reversible_command (Operations::insert_region);
1275 playlist->clear_changes ();
1276 playlist->add_region (_primary->region (), _last_frame_position);
1277 _editor->session()->add_command (new StatefulDiffCommand (playlist));
1278 _editor->commit_reversible_command ();
1280 delete _primary;
1281 _primary = 0;
1282 _views.clear ();
1285 void
1286 RegionInsertDrag::aborted (bool)
1288 delete _primary;
1289 _primary = 0;
1290 _views.clear ();
1293 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1294 : RegionMoveDrag (e, i, p, v, false, false)
1296 DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
1299 struct RegionSelectionByPosition {
1300 bool operator() (RegionView*a, RegionView* b) {
1301 return a->region()->position () < b->region()->position();
1305 void
1306 RegionSpliceDrag::motion (GdkEvent* event, bool)
1308 /* Which trackview is this ? */
1310 pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
1311 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
1312 layer_t layer = tvp.second;
1314 if (tv && tv->layer_display() == Overlaid) {
1315 layer = 0;
1318 /* The region motion is only processed if the pointer is over
1319 an audio track.
1322 if (!tv || !tv->is_track()) {
1323 /* To make sure we hide the verbose canvas cursor when the mouse is
1324 not held over and audiotrack.
1326 _editor->verbose_cursor()->hide ();
1327 return;
1330 int dir;
1332 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1333 dir = 1;
1334 } else {
1335 dir = -1;
1338 RegionSelection copy (_editor->selection->regions);
1340 RegionSelectionByPosition cmp;
1341 copy.sort (cmp);
1343 framepos_t const pf = adjusted_current_frame (event);
1345 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1347 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1349 if (!atv) {
1350 continue;
1353 boost::shared_ptr<Playlist> playlist;
1355 if ((playlist = atv->playlist()) == 0) {
1356 continue;
1359 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1360 continue;
1363 if (dir > 0) {
1364 if (pf < (*i)->region()->last_frame() + 1) {
1365 continue;
1367 } else {
1368 if (pf > (*i)->region()->first_frame()) {
1369 continue;
1374 playlist->shuffle ((*i)->region(), dir);
1378 void
1379 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1381 RegionMoveDrag::finished (event, movement_occurred);
1384 void
1385 RegionSpliceDrag::aborted (bool)
1387 /* XXX: TODO */
1390 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
1391 : Drag (e, i),
1392 _view (dynamic_cast<MidiTimeAxisView*> (v))
1394 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
1396 assert (_view);
1399 void
1400 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
1402 if (first_move) {
1403 add_region();
1404 _view->playlist()->freeze ();
1405 } else {
1406 if (_region) {
1407 framepos_t const f = adjusted_current_frame (event);
1408 if (f < grab_frame()) {
1409 _region->set_position (f);
1412 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
1413 so that if this region is duplicated, its duplicate starts on
1414 a snap point rather than 1 frame after a snap point. Otherwise things get
1415 a bit confusing as if a region starts 1 frame after a snap point, one cannot
1416 place snapped notes at the start of the region.
1419 framecnt_t const len = abs (f - grab_frame () - 1);
1420 _region->set_length (len < 1 ? 1 : len);
1425 void
1426 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
1428 if (!movement_occurred) {
1429 add_region ();
1430 } else {
1431 _view->playlist()->thaw ();
1434 if (_region) {
1435 _editor->commit_reversible_command ();
1439 void
1440 RegionCreateDrag::add_region ()
1442 if (_editor->session()) {
1443 const TempoMap& map (_editor->session()->tempo_map());
1444 framecnt_t pos = grab_frame();
1445 const Meter& m = map.meter_at (pos);
1446 /* not that the frame rate used here can be affected by pull up/down which
1447 might be wrong.
1449 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
1450 _region = _view->add_region (grab_frame(), len, false);
1454 void
1455 RegionCreateDrag::aborted (bool)
1457 if (_region) {
1458 _view->playlist()->thaw ();
1461 /* XXX */
1464 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
1465 : Drag (e, i)
1466 , region (0)
1468 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
1471 void
1472 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
1474 Gdk::Cursor* cursor;
1475 ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item);
1476 float x_fraction = cnote->mouse_x_fraction ();
1478 if (x_fraction > 0.0 && x_fraction < 0.25) {
1479 cursor = _editor->cursors()->left_side_trim;
1480 } else {
1481 cursor = _editor->cursors()->right_side_trim;
1484 Drag::start_grab (event, cursor);
1486 region = &cnote->region_view();
1488 double const region_start = region->get_position_pixels();
1489 double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;
1491 if (grab_x() <= middle_point) {
1492 cursor = _editor->cursors()->left_side_trim;
1493 at_front = true;
1494 } else {
1495 cursor = _editor->cursors()->right_side_trim;
1496 at_front = false;
1499 _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, *cursor, event->motion.time);
1501 if (event->motion.state & Keyboard::PrimaryModifier) {
1502 relative = false;
1503 } else {
1504 relative = true;
1507 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1509 if (ms.size() > 1) {
1510 /* has to be relative, may make no sense otherwise */
1511 relative = true;
1514 /* select this note; if it is already selected, preserve the existing selection,
1515 otherwise make this note the only one selected.
1517 region->note_selected (cnote, cnote->selected ());
1519 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
1520 MidiRegionSelection::iterator next;
1521 next = r;
1522 ++next;
1523 (*r)->begin_resizing (at_front);
1524 r = next;
1528 void
1529 NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
1531 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1532 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1533 (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1537 void
1538 NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
1540 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1541 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1542 (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1546 void
1547 NoteResizeDrag::aborted (bool)
1549 /* XXX: TODO */
1552 RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
1553 : Drag (e, i)
1555 DEBUG_TRACE (DEBUG::Drags, "New RegionGainDrag\n");
1558 void
1559 RegionGainDrag::motion (GdkEvent* /*event*/, bool)
1564 void
1565 RegionGainDrag::finished (GdkEvent *, bool)
1570 void
1571 RegionGainDrag::aborted (bool)
1573 /* XXX: TODO */
1576 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1577 : RegionDrag (e, i, p, v)
1579 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
1582 void
1583 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
1585 double speed = 1.0;
1586 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1587 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1589 if (tv && tv->is_track()) {
1590 speed = tv->track()->speed();
1593 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
1594 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
1595 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
1597 framepos_t const pf = adjusted_current_frame (event);
1599 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1600 /* Move the contents of the region around without changing the region bounds */
1601 _operation = ContentsTrim;
1602 Drag::start_grab (event, _editor->cursors()->trimmer);
1603 } else {
1604 /* These will get overridden for a point trim.*/
1605 if (pf < (region_start + region_length/2)) {
1606 /* closer to front */
1607 _operation = StartTrim;
1608 Drag::start_grab (event, _editor->cursors()->left_side_trim);
1609 } else {
1610 /* closer to end */
1611 _operation = EndTrim;
1612 Drag::start_grab (event, _editor->cursors()->right_side_trim);
1616 switch (_operation) {
1617 case StartTrim:
1618 show_verbose_cursor_time (region_start);
1619 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1620 i->view->trim_front_starting ();
1622 break;
1623 case EndTrim:
1624 show_verbose_cursor_time (region_end);
1625 break;
1626 case ContentsTrim:
1627 show_verbose_cursor_time (pf);
1628 break;
1631 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1632 i->view->region()->suspend_property_changes ();
1636 void
1637 TrimDrag::motion (GdkEvent* event, bool first_move)
1639 RegionView* rv = _primary;
1641 double speed = 1.0;
1642 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1643 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1644 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
1646 if (tv && tv->is_track()) {
1647 speed = tv->track()->speed();
1650 framecnt_t const dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
1652 if (first_move) {
1654 string trim_type;
1656 switch (_operation) {
1657 case StartTrim:
1658 trim_type = "Region start trim";
1659 break;
1660 case EndTrim:
1661 trim_type = "Region end trim";
1662 break;
1663 case ContentsTrim:
1664 trim_type = "Region content trim";
1665 break;
1668 _editor->begin_reversible_command (trim_type);
1670 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1671 RegionView* rv = i->view;
1672 rv->fake_set_opaque (false);
1673 rv->enable_display (false);
1674 rv->region()->playlist()->clear_owned_changes ();
1676 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
1678 if (arv) {
1679 arv->temporarily_hide_envelope ();
1682 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
1683 insert_result = _editor->motion_frozen_playlists.insert (pl);
1685 if (insert_result.second) {
1686 pl->freeze();
1691 bool non_overlap_trim = false;
1693 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1694 non_overlap_trim = true;
1697 switch (_operation) {
1698 case StartTrim:
1699 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1700 i->view->trim_front (i->initial_position + dt, non_overlap_trim);
1702 break;
1704 case EndTrim:
1705 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1706 i->view->trim_end (i->initial_end + dt, non_overlap_trim);
1708 break;
1710 case ContentsTrim:
1712 bool swap_direction = false;
1714 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1715 swap_direction = true;
1718 framecnt_t frame_delta = 0;
1720 bool left_direction = false;
1721 if (last_pointer_frame() > adjusted_current_frame(event)) {
1722 left_direction = true;
1725 if (left_direction) {
1726 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
1727 } else {
1728 frame_delta = (adjusted_current_frame(event) - last_pointer_frame());
1731 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1732 i->view->trim_contents (frame_delta, left_direction, swap_direction);
1735 break;
1738 switch (_operation) {
1739 case StartTrim:
1740 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
1741 break;
1742 case EndTrim:
1743 show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed));
1744 break;
1745 case ContentsTrim:
1746 show_verbose_cursor_time (adjusted_current_frame (event));
1747 break;
1752 void
1753 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
1755 if (movement_occurred) {
1756 motion (event, false);
1758 /* This must happen before the region's StatefulDiffCommand is created, as it may
1759 `correct' (ahem) the region's _start from being negative to being zero. It
1760 needs to be zero in the undo record.
1762 if (_operation == StartTrim) {
1763 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1764 i->view->trim_front_ending ();
1768 if (!_editor->selection->selected (_primary)) {
1769 _primary->thaw_after_trim ();
1770 } else {
1772 set<boost::shared_ptr<Playlist> > diffed_playlists;
1774 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1775 i->view->thaw_after_trim ();
1776 i->view->enable_display (true);
1777 i->view->fake_set_opaque (true);
1779 /* Trimming one region may affect others on the playlist, so we need
1780 to get undo Commands from the whole playlist rather than just the
1781 region. Use diffed_playlists to make sure we don't diff a given
1782 playlist more than once.
1784 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
1785 if (diffed_playlists.find (p) == diffed_playlists.end()) {
1786 vector<Command*> cmds;
1787 p->rdiff (cmds);
1788 _editor->session()->add_commands (cmds);
1789 diffed_playlists.insert (p);
1793 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
1794 (*p)->thaw ();
1797 _editor->motion_frozen_playlists.clear ();
1798 _editor->commit_reversible_command();
1800 } else {
1801 /* no mouse movement */
1802 _editor->point_trim (event, adjusted_current_frame (event));
1805 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1806 if (_operation == StartTrim) {
1807 i->view->trim_front_ending ();
1810 i->view->region()->resume_property_changes ();
1814 void
1815 TrimDrag::aborted (bool movement_occurred)
1817 /* Our motion method is changing model state, so use the Undo system
1818 to cancel. Perhaps not ideal, as this will leave an Undo point
1819 behind which may be slightly odd from the user's point of view.
1822 finished (0, true);
1824 if (movement_occurred) {
1825 _editor->undo ();
1828 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1829 i->view->region()->resume_property_changes ();
1833 void
1834 TrimDrag::setup_pointer_frame_offset ()
1836 list<DraggingView>::iterator i = _views.begin ();
1837 while (i != _views.end() && i->view != _primary) {
1838 ++i;
1841 if (i == _views.end()) {
1842 return;
1845 switch (_operation) {
1846 case StartTrim:
1847 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
1848 break;
1849 case EndTrim:
1850 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
1851 break;
1852 case ContentsTrim:
1853 break;
1857 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1858 : Drag (e, i),
1859 _copy (c)
1861 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
1863 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
1864 assert (_marker);
1867 void
1868 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1870 if (_copy) {
1871 // create a dummy marker for visual representation of moving the copy.
1872 // The actual copying is not done before we reach the finish callback.
1873 char name[64];
1874 snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
1876 MeterMarker* new_marker = new MeterMarker (
1877 *_editor,
1878 *_editor->meter_group,
1879 ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
1880 name,
1881 *new MeterSection (_marker->meter())
1884 _item = &new_marker->the_item ();
1885 _marker = new_marker;
1887 } else {
1889 MetricSection& section (_marker->meter());
1891 if (!section.movable()) {
1892 return;
1897 Drag::start_grab (event, cursor);
1899 show_verbose_cursor_time (adjusted_current_frame(event));
1902 void
1903 MeterMarkerDrag::setup_pointer_frame_offset ()
1905 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
1908 void
1909 MeterMarkerDrag::motion (GdkEvent* event, bool)
1911 framepos_t const pf = adjusted_current_frame (event);
1913 _marker->set_position (pf);
1915 show_verbose_cursor_time (pf);
1918 void
1919 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
1921 if (!movement_occurred) {
1922 return;
1925 motion (event, false);
1927 Timecode::BBT_Time when;
1929 TempoMap& map (_editor->session()->tempo_map());
1930 map.bbt_time (last_pointer_frame(), when);
1932 if (_copy == true) {
1933 _editor->begin_reversible_command (_("copy meter mark"));
1934 XMLNode &before = map.get_state();
1935 map.add_meter (_marker->meter(), when);
1936 XMLNode &after = map.get_state();
1937 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1938 _editor->commit_reversible_command ();
1940 // delete the dummy marker we used for visual representation of copying.
1941 // a new visual marker will show up automatically.
1942 delete _marker;
1943 } else {
1944 _editor->begin_reversible_command (_("move meter mark"));
1945 XMLNode &before = map.get_state();
1946 map.move_meter (_marker->meter(), when);
1947 XMLNode &after = map.get_state();
1948 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1949 _editor->commit_reversible_command ();
1953 void
1954 MeterMarkerDrag::aborted (bool)
1956 _marker->set_position (_marker->meter().frame ());
1959 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1960 : Drag (e, i),
1961 _copy (c)
1963 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
1965 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
1966 assert (_marker);
1969 void
1970 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1972 if (_copy) {
1974 // create a dummy marker for visual representation of moving the copy.
1975 // The actual copying is not done before we reach the finish callback.
1976 char name[64];
1977 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
1979 TempoMarker* new_marker = new TempoMarker (
1980 *_editor,
1981 *_editor->tempo_group,
1982 ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
1983 name,
1984 *new TempoSection (_marker->tempo())
1987 _item = &new_marker->the_item ();
1988 _marker = new_marker;
1992 Drag::start_grab (event, cursor);
1994 show_verbose_cursor_time (adjusted_current_frame (event));
1997 void
1998 TempoMarkerDrag::setup_pointer_frame_offset ()
2000 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
2003 void
2004 TempoMarkerDrag::motion (GdkEvent* event, bool)
2006 framepos_t const pf = adjusted_current_frame (event);
2007 _marker->set_position (pf);
2008 show_verbose_cursor_time (pf);
2011 void
2012 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2014 if (!movement_occurred) {
2015 return;
2018 motion (event, false);
2020 Timecode::BBT_Time when;
2022 TempoMap& map (_editor->session()->tempo_map());
2023 map.bbt_time (last_pointer_frame(), when);
2025 if (_copy == true) {
2026 _editor->begin_reversible_command (_("copy tempo mark"));
2027 XMLNode &before = map.get_state();
2028 map.add_tempo (_marker->tempo(), when);
2029 XMLNode &after = map.get_state();
2030 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2031 _editor->commit_reversible_command ();
2033 // delete the dummy marker we used for visual representation of copying.
2034 // a new visual marker will show up automatically.
2035 delete _marker;
2036 } else {
2037 _editor->begin_reversible_command (_("move tempo mark"));
2038 XMLNode &before = map.get_state();
2039 map.move_tempo (_marker->tempo(), when);
2040 XMLNode &after = map.get_state();
2041 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2042 _editor->commit_reversible_command ();
2046 void
2047 TempoMarkerDrag::aborted (bool)
2049 _marker->set_position (_marker->tempo().frame());
2052 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
2053 : Drag (e, i),
2054 _stop (s)
2056 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
2059 /** Do all the things we do when dragging the playhead to make it look as though
2060 * we have located, without actually doing the locate (because that would cause
2061 * the diskstream buffers to be refilled, which is too slow).
2063 void
2064 CursorDrag::fake_locate (framepos_t t)
2066 _editor->playhead_cursor->set_position (t);
2068 Session* s = _editor->session ();
2069 if (s->timecode_transmission_suspended ()) {
2070 framepos_t const f = _editor->playhead_cursor->current_frame;
2071 s->send_mmc_locate (f);
2072 s->send_full_time_code (f);
2075 show_verbose_cursor_time (t);
2076 _editor->UpdateAllTransportClocks (t);
2079 void
2080 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
2082 Drag::start_grab (event, c);
2084 _grab_zoom = _editor->frames_per_unit;
2086 framepos_t where = _editor->event_frame (event, 0, 0);
2087 _editor->snap_to_with_modifier (where, event);
2089 _editor->_dragging_playhead = true;
2091 Session* s = _editor->session ();
2093 if (s) {
2094 if (_was_rolling && _stop) {
2095 s->request_stop ();
2098 if (s->is_auditioning()) {
2099 s->cancel_audition ();
2102 s->request_suspend_timecode_transmission ();
2103 while (!s->timecode_transmission_suspended ()) {
2104 /* twiddle our thumbs */
2108 fake_locate (where);
2111 void
2112 CursorDrag::motion (GdkEvent* event, bool)
2114 if (_drags->current_pointer_y() != last_pointer_y()) {
2116 /* zoom when we move the pointer up and down */
2118 /* y range to operate over (pixels) */
2119 double const y_range = 512;
2120 /* we will multiply the grab zoom by a factor between scale_range and scale_range^-1 */
2121 double const scale_range = 4;
2122 /* dead zone around the grab point in which to do no zooming (pixels) */
2123 double const dead_zone = 100;
2125 /* current dy */
2126 double dy = _drags->current_pointer_y() - grab_y();
2128 if (dy < -dead_zone || dy > dead_zone) {
2129 /* we are outside the dead zone; remove it from our calculation */
2130 if (dy < 0) {
2131 dy += dead_zone;
2132 } else {
2133 dy -= dead_zone;
2136 /* get a number from -1 to 1 as dy ranges from -y_range to y_range */
2137 double udy = max (min (dy / y_range, 1.0), -1.0);
2139 /* and zoom, using playhead focus temporarily */
2140 Editing::ZoomFocus const zf = _editor->get_zoom_focus ();
2141 _editor->set_zoom_focus (Editing::ZoomFocusPlayhead);
2142 _editor->temporal_zoom (_grab_zoom * pow (scale_range, -udy));
2143 _editor->set_zoom_focus (zf);
2147 framepos_t const adjusted_frame = adjusted_current_frame (event);
2148 if (adjusted_frame != last_pointer_frame()) {
2149 fake_locate (adjusted_frame);
2150 #ifdef GTKOSX
2151 _editor->update_canvas_now ();
2152 #endif
2156 void
2157 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
2159 _editor->_dragging_playhead = false;
2161 if (!movement_occurred && _stop) {
2162 return;
2165 motion (event, false);
2167 Session* s = _editor->session ();
2168 if (s) {
2169 s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
2170 _editor->_pending_locate_request = true;
2171 s->request_resume_timecode_transmission ();
2175 void
2176 CursorDrag::aborted (bool)
2178 if (_editor->_dragging_playhead) {
2179 _editor->session()->request_resume_timecode_transmission ();
2180 _editor->_dragging_playhead = false;
2183 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
2186 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2187 : RegionDrag (e, i, p, v)
2189 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
2192 void
2193 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2195 Drag::start_grab (event, cursor);
2197 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2198 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2200 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
2202 arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
2205 void
2206 FadeInDrag::setup_pointer_frame_offset ()
2208 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2209 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2210 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
2213 void
2214 FadeInDrag::motion (GdkEvent* event, bool)
2216 framecnt_t fade_length;
2218 framepos_t const pos = adjusted_current_frame (event);
2220 boost::shared_ptr<Region> region = _primary->region ();
2222 if (pos < (region->position() + 64)) {
2223 fade_length = 64; // this should be a minimum defined somewhere
2224 } else if (pos > region->last_frame()) {
2225 fade_length = region->length();
2226 } else {
2227 fade_length = pos - region->position();
2230 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2232 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2234 if (!tmp) {
2235 continue;
2238 tmp->reset_fade_in_shape_width (fade_length);
2239 tmp->show_fade_line((framecnt_t) fade_length);
2242 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
2245 void
2246 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
2248 if (!movement_occurred) {
2249 return;
2252 framecnt_t fade_length;
2254 framepos_t const pos = adjusted_current_frame (event);
2256 boost::shared_ptr<Region> region = _primary->region ();
2258 if (pos < (region->position() + 64)) {
2259 fade_length = 64; // this should be a minimum defined somewhere
2260 } else if (pos > region->last_frame()) {
2261 fade_length = region->length();
2262 } else {
2263 fade_length = pos - region->position();
2266 _editor->begin_reversible_command (_("change fade in length"));
2268 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2270 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2272 if (!tmp) {
2273 continue;
2276 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
2277 XMLNode &before = alist->get_state();
2279 tmp->audio_region()->set_fade_in_length (fade_length);
2280 tmp->audio_region()->set_fade_in_active (true);
2281 tmp->hide_fade_line();
2283 XMLNode &after = alist->get_state();
2284 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2287 _editor->commit_reversible_command ();
2290 void
2291 FadeInDrag::aborted (bool)
2293 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2294 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2296 if (!tmp) {
2297 continue;
2300 tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
2301 tmp->hide_fade_line();
2305 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2306 : RegionDrag (e, i, p, v)
2308 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
2311 void
2312 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2314 Drag::start_grab (event, cursor);
2316 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2317 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2319 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
2321 arv->show_fade_line(r->length() - r->fade_out()->back()->when);
2324 void
2325 FadeOutDrag::setup_pointer_frame_offset ()
2327 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2328 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2329 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
2332 void
2333 FadeOutDrag::motion (GdkEvent* event, bool)
2335 framecnt_t fade_length;
2337 framepos_t const pos = adjusted_current_frame (event);
2339 boost::shared_ptr<Region> region = _primary->region ();
2341 if (pos > (region->last_frame() - 64)) {
2342 fade_length = 64; // this should really be a minimum fade defined somewhere
2344 else if (pos < region->position()) {
2345 fade_length = region->length();
2347 else {
2348 fade_length = region->last_frame() - pos;
2351 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2353 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2355 if (!tmp) {
2356 continue;
2359 tmp->reset_fade_out_shape_width (fade_length);
2360 tmp->show_fade_line(region->length() - fade_length);
2363 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
2366 void
2367 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
2369 if (!movement_occurred) {
2370 return;
2373 framecnt_t fade_length;
2375 framepos_t const pos = adjusted_current_frame (event);
2377 boost::shared_ptr<Region> region = _primary->region ();
2379 if (pos > (region->last_frame() - 64)) {
2380 fade_length = 64; // this should really be a minimum fade defined somewhere
2382 else if (pos < region->position()) {
2383 fade_length = region->length();
2385 else {
2386 fade_length = region->last_frame() - pos;
2389 _editor->begin_reversible_command (_("change fade out length"));
2391 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2393 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2395 if (!tmp) {
2396 continue;
2399 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
2400 XMLNode &before = alist->get_state();
2402 tmp->audio_region()->set_fade_out_length (fade_length);
2403 tmp->audio_region()->set_fade_out_active (true);
2404 tmp->hide_fade_line();
2406 XMLNode &after = alist->get_state();
2407 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2410 _editor->commit_reversible_command ();
2413 void
2414 FadeOutDrag::aborted (bool)
2416 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2417 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2419 if (!tmp) {
2420 continue;
2423 tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
2424 tmp->hide_fade_line();
2428 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
2429 : Drag (e, i)
2431 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
2433 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
2434 assert (_marker);
2436 _points.push_back (Gnome::Art::Point (0, 0));
2437 _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
2440 MarkerDrag::~MarkerDrag ()
2442 for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
2443 delete *i;
2447 void
2448 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2450 Drag::start_grab (event, cursor);
2452 bool is_start;
2454 Location *location = _editor->find_location_from_marker (_marker, is_start);
2455 _editor->_dragging_edit_point = true;
2457 update_item (location);
2459 // _drag_line->show();
2460 // _line->raise_to_top();
2462 if (is_start) {
2463 show_verbose_cursor_time (location->start());
2464 } else {
2465 show_verbose_cursor_time (location->end());
2468 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2470 switch (op) {
2471 case Selection::Toggle:
2472 _editor->selection->toggle (_marker);
2473 break;
2474 case Selection::Set:
2475 if (!_editor->selection->selected (_marker)) {
2476 _editor->selection->set (_marker);
2478 break;
2479 case Selection::Extend:
2481 Locations::LocationList ll;
2482 list<Marker*> to_add;
2483 framepos_t s, e;
2484 _editor->selection->markers.range (s, e);
2485 s = min (_marker->position(), s);
2486 e = max (_marker->position(), e);
2487 s = min (s, e);
2488 e = max (s, e);
2489 if (e < max_framepos) {
2490 ++e;
2492 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
2493 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
2494 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
2495 if (lm) {
2496 if (lm->start) {
2497 to_add.push_back (lm->start);
2499 if (lm->end) {
2500 to_add.push_back (lm->end);
2504 if (!to_add.empty()) {
2505 _editor->selection->add (to_add);
2507 break;
2509 case Selection::Add:
2510 _editor->selection->add (_marker);
2511 break;
2514 /* Set up copies for us to manipulate during the drag */
2516 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
2517 Location* l = _editor->find_location_from_marker (*i, is_start);
2518 _copied_locations.push_back (new Location (*l));
2522 void
2523 MarkerDrag::setup_pointer_frame_offset ()
2525 bool is_start;
2526 Location *location = _editor->find_location_from_marker (_marker, is_start);
2527 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
2530 void
2531 MarkerDrag::motion (GdkEvent* event, bool)
2533 framecnt_t f_delta = 0;
2534 bool is_start;
2535 bool move_both = false;
2536 Marker* marker;
2537 Location *real_location;
2538 Location *copy_location = 0;
2540 framepos_t const newframe = adjusted_current_frame (event);
2542 framepos_t next = newframe;
2544 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2545 move_both = true;
2548 MarkerSelection::iterator i;
2549 list<Location*>::iterator x;
2551 /* find the marker we're dragging, and compute the delta */
2553 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2554 x != _copied_locations.end() && i != _editor->selection->markers.end();
2555 ++i, ++x) {
2557 copy_location = *x;
2558 marker = *i;
2560 if (marker == _marker) {
2562 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2563 /* que pasa ?? */
2564 return;
2567 if (real_location->is_mark()) {
2568 f_delta = newframe - copy_location->start();
2569 } else {
2572 switch (marker->type()) {
2573 case Marker::SessionStart:
2574 case Marker::RangeStart:
2575 case Marker::LoopStart:
2576 case Marker::PunchIn:
2577 f_delta = newframe - copy_location->start();
2578 break;
2580 case Marker::SessionEnd:
2581 case Marker::RangeEnd:
2582 case Marker::LoopEnd:
2583 case Marker::PunchOut:
2584 f_delta = newframe - copy_location->end();
2585 break;
2586 default:
2587 /* what kind of marker is this ? */
2588 return;
2591 break;
2595 if (i == _editor->selection->markers.end()) {
2596 /* hmm, impossible - we didn't find the dragged marker */
2597 return;
2600 /* now move them all */
2602 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2603 x != _copied_locations.end() && i != _editor->selection->markers.end();
2604 ++i, ++x) {
2606 copy_location = *x;
2607 marker = *i;
2609 /* call this to find out if its the start or end */
2611 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2612 continue;
2615 if (real_location->locked()) {
2616 continue;
2619 if (copy_location->is_mark()) {
2621 /* now move it */
2623 copy_location->set_start (copy_location->start() + f_delta);
2625 } else {
2627 framepos_t new_start = copy_location->start() + f_delta;
2628 framepos_t new_end = copy_location->end() + f_delta;
2630 if (is_start) { // start-of-range marker
2632 if (move_both) {
2633 copy_location->set_start (new_start);
2634 copy_location->set_end (new_end);
2635 } else if (new_start < copy_location->end()) {
2636 copy_location->set_start (new_start);
2637 } else if (newframe > 0) {
2638 _editor->snap_to (next, 1, true);
2639 copy_location->set_end (next);
2640 copy_location->set_start (newframe);
2643 } else { // end marker
2645 if (move_both) {
2646 copy_location->set_end (new_end);
2647 copy_location->set_start (new_start);
2648 } else if (new_end > copy_location->start()) {
2649 copy_location->set_end (new_end);
2650 } else if (newframe > 0) {
2651 _editor->snap_to (next, -1, true);
2652 copy_location->set_start (next);
2653 copy_location->set_end (newframe);
2658 update_item (copy_location);
2660 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
2662 if (lm) {
2663 lm->set_position (copy_location->start(), copy_location->end());
2667 assert (!_copied_locations.empty());
2669 show_verbose_cursor_time (newframe);
2671 #ifdef GTKOSX
2672 _editor->update_canvas_now ();
2673 #endif
2676 void
2677 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2679 if (!movement_occurred) {
2681 /* just a click, do nothing but finish
2682 off the selection process
2685 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2687 switch (op) {
2688 case Selection::Set:
2689 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
2690 _editor->selection->set (_marker);
2692 break;
2694 case Selection::Toggle:
2695 case Selection::Extend:
2696 case Selection::Add:
2697 break;
2700 return;
2703 _editor->_dragging_edit_point = false;
2705 _editor->begin_reversible_command ( _("move marker") );
2706 XMLNode &before = _editor->session()->locations()->get_state();
2708 MarkerSelection::iterator i;
2709 list<Location*>::iterator x;
2710 bool is_start;
2712 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2713 x != _copied_locations.end() && i != _editor->selection->markers.end();
2714 ++i, ++x) {
2716 Location * location = _editor->find_location_from_marker (*i, is_start);
2718 if (location) {
2720 if (location->locked()) {
2721 return;
2724 if (location->is_mark()) {
2725 location->set_start ((*x)->start());
2726 } else {
2727 location->set ((*x)->start(), (*x)->end());
2732 XMLNode &after = _editor->session()->locations()->get_state();
2733 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
2734 _editor->commit_reversible_command ();
2737 void
2738 MarkerDrag::aborted (bool)
2740 /* XXX: TODO */
2743 void
2744 MarkerDrag::update_item (Location* location)
2746 /* noop */
2749 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
2750 : Drag (e, i),
2751 _cumulative_x_drag (0),
2752 _cumulative_y_drag (0)
2754 if (_zero_gain_fraction < 0.0) {
2755 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
2758 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
2760 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
2761 assert (_point);
2765 void
2766 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2768 Drag::start_grab (event, _editor->cursors()->fader);
2770 // start the grab at the center of the control point so
2771 // the point doesn't 'jump' to the mouse after the first drag
2772 _fixed_grab_x = _point->get_x();
2773 _fixed_grab_y = _point->get_y();
2775 float const fraction = 1 - (_point->get_y() / _point->line().height());
2777 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
2779 _editor->verbose_cursor()->set (_point->line().get_verbose_cursor_string (fraction),
2780 event->button.x + 10, event->button.y + 10);
2782 _editor->verbose_cursor()->show ();
2785 void
2786 ControlPointDrag::motion (GdkEvent* event, bool)
2788 double dx = _drags->current_pointer_x() - last_pointer_x();
2789 double dy = _drags->current_pointer_y() - last_pointer_y();
2791 if (event->button.state & Keyboard::SecondaryModifier) {
2792 dx *= 0.1;
2793 dy *= 0.1;
2796 /* coordinate in pixels relative to the start of the region (for region-based automation)
2797 or track (for track-based automation) */
2798 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
2799 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2801 // calculate zero crossing point. back off by .01 to stay on the
2802 // positive side of zero
2803 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
2805 // make sure we hit zero when passing through
2806 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
2807 cy = zero_gain_y;
2810 if (_x_constrained) {
2811 cx = _fixed_grab_x;
2813 if (_y_constrained) {
2814 cy = _fixed_grab_y;
2817 _cumulative_x_drag = cx - _fixed_grab_x;
2818 _cumulative_y_drag = cy - _fixed_grab_y;
2820 cx = max (0.0, cx);
2821 cy = max (0.0, cy);
2822 cy = min ((double) _point->line().height(), cy);
2824 framepos_t cx_frames = _editor->unit_to_frame (cx);
2826 if (!_x_constrained) {
2827 _editor->snap_to_with_modifier (cx_frames, event);
2830 cx_frames = min (cx_frames, _point->line().maximum_time());
2832 float const fraction = 1.0 - (cy / _point->line().height());
2834 bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
2836 _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
2838 _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
2841 void
2842 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
2844 if (!movement_occurred) {
2846 /* just a click */
2848 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2849 _editor->reset_point_selection ();
2852 } else {
2853 motion (event, false);
2856 _point->line().end_drag ();
2857 _editor->session()->commit_reversible_command ();
2860 void
2861 ControlPointDrag::aborted (bool)
2863 _point->line().reset ();
2866 bool
2867 ControlPointDrag::active (Editing::MouseMode m)
2869 if (m == Editing::MouseGain) {
2870 /* always active in mouse gain */
2871 return true;
2874 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
2875 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
2878 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
2879 : Drag (e, i),
2880 _line (0),
2881 _cumulative_y_drag (0)
2883 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
2886 void
2887 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2889 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
2890 assert (_line);
2892 _item = &_line->grab_item ();
2894 /* need to get x coordinate in terms of parent (TimeAxisItemView)
2895 origin, and ditto for y.
2898 double cx = event->button.x;
2899 double cy = event->button.y;
2901 _line->parent_group().w2i (cx, cy);
2903 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit);
2905 uint32_t before;
2906 uint32_t after;
2908 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
2909 /* no adjacent points */
2910 return;
2913 Drag::start_grab (event, _editor->cursors()->fader);
2915 /* store grab start in parent frame */
2917 _fixed_grab_x = cx;
2918 _fixed_grab_y = cy;
2920 double fraction = 1.0 - (cy / _line->height());
2922 _line->start_drag_line (before, after, fraction);
2924 _editor->verbose_cursor()->set (_line->get_verbose_cursor_string (fraction),
2925 event->button.x + 10, event->button.y + 10);
2927 _editor->verbose_cursor()->show ();
2930 void
2931 LineDrag::motion (GdkEvent* event, bool)
2933 double dy = _drags->current_pointer_y() - last_pointer_y();
2935 if (event->button.state & Keyboard::SecondaryModifier) {
2936 dy *= 0.1;
2939 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2941 _cumulative_y_drag = cy - _fixed_grab_y;
2943 cy = max (0.0, cy);
2944 cy = min ((double) _line->height(), cy);
2946 double const fraction = 1.0 - (cy / _line->height());
2948 bool push;
2950 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
2951 push = false;
2952 } else {
2953 push = true;
2956 /* we are ignoring x position for this drag, so we can just pass in anything */
2957 _line->drag_motion (0, fraction, true, push);
2959 _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
2962 void
2963 LineDrag::finished (GdkEvent* event, bool)
2965 motion (event, false);
2966 _line->end_drag ();
2967 _editor->session()->commit_reversible_command ();
2970 void
2971 LineDrag::aborted (bool)
2973 _line->reset ();
2976 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
2977 : Drag (e, i),
2978 _line (0),
2979 _cumulative_x_drag (0)
2981 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
2984 void
2985 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2987 Drag::start_grab (event);
2989 _line = reinterpret_cast<Line*> (_item);
2990 assert (_line);
2992 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
2994 double cx = event->button.x;
2995 double cy = event->button.y;
2997 _item->property_parent().get_value()->w2i(cx, cy);
2999 /* store grab start in parent frame */
3000 _region_view_grab_x = cx;
3002 _before = *(float*) _item->get_data ("position");
3004 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3006 _max_x = _editor->frame_to_pixel(_arv->get_duration());
3009 void
3010 FeatureLineDrag::motion (GdkEvent*, bool)
3012 double dx = _drags->current_pointer_x() - last_pointer_x();
3014 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
3016 _cumulative_x_drag += dx;
3018 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
3020 if (cx > _max_x){
3021 cx = _max_x;
3023 else if(cx < 0){
3024 cx = 0;
3027 ArdourCanvas::Points points;
3029 double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
3031 _line->get_bounds(x1, y2, x2, y2);
3033 points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
3034 points.push_back(Gnome::Art::Point(cx, y2 - y1));
3036 _line->property_points() = points;
3038 float *pos = new float;
3039 *pos = cx;
3041 _line->set_data ("position", pos);
3043 _before = cx;
3046 void
3047 FeatureLineDrag::finished (GdkEvent*, bool)
3049 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3050 _arv->update_transient(_before, _before);
3053 void
3054 FeatureLineDrag::aborted (bool)
3056 //_line->reset ();
3059 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
3060 : Drag (e, i)
3062 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
3065 void
3066 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3068 Drag::start_grab (event);
3069 show_verbose_cursor_time (adjusted_current_frame (event));
3072 void
3073 RubberbandSelectDrag::motion (GdkEvent* event, bool)
3075 framepos_t start;
3076 framepos_t end;
3077 double y1;
3078 double y2;
3080 framepos_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
3082 framepos_t grab = grab_frame ();
3083 if (Config->get_rubberbanding_snaps_to_grid ()) {
3084 _editor->snap_to_with_modifier (grab, event);
3087 /* base start and end on initial click position */
3089 if (pf < grab) {
3090 start = pf;
3091 end = grab;
3092 } else {
3093 end = pf;
3094 start = grab;
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 if (start != end || y1 != y2) {
3108 double x1 = _editor->frame_to_pixel (start);
3109 double x2 = _editor->frame_to_pixel (end);
3111 _editor->rubberband_rect->property_x1() = x1;
3112 _editor->rubberband_rect->property_y1() = y1;
3113 _editor->rubberband_rect->property_x2() = x2;
3114 _editor->rubberband_rect->property_y2() = y2;
3116 _editor->rubberband_rect->show();
3117 _editor->rubberband_rect->raise_to_top();
3119 show_verbose_cursor_time (pf);
3123 void
3124 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
3126 if (movement_occurred) {
3128 motion (event, false);
3130 double y1,y2;
3131 if (_drags->current_pointer_y() < grab_y()) {
3132 y1 = _drags->current_pointer_y();
3133 y2 = grab_y();
3134 } else {
3135 y2 = _drags->current_pointer_y();
3136 y1 = grab_y();
3140 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3142 _editor->begin_reversible_command (_("rubberband selection"));
3144 if (grab_frame() < last_pointer_frame()) {
3145 _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
3146 } else {
3147 _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
3150 _editor->commit_reversible_command ();
3152 } else {
3153 if (!getenv("ARDOUR_SAE")) {
3154 _editor->selection->clear_tracks();
3156 _editor->selection->clear_regions();
3157 _editor->selection->clear_points ();
3158 _editor->selection->clear_lines ();
3161 _editor->rubberband_rect->hide();
3164 void
3165 RubberbandSelectDrag::aborted (bool)
3167 _editor->rubberband_rect->hide ();
3170 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
3171 : RegionDrag (e, i, p, v)
3173 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
3176 void
3177 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3179 Drag::start_grab (event, cursor);
3181 show_verbose_cursor_time (adjusted_current_frame (event));
3184 void
3185 TimeFXDrag::motion (GdkEvent* event, bool)
3187 RegionView* rv = _primary;
3189 framepos_t const pf = adjusted_current_frame (event);
3191 if (pf > rv->region()->position()) {
3192 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
3195 show_verbose_cursor_time (pf);
3198 void
3199 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3201 _primary->get_time_axis_view().hide_timestretch ();
3203 if (!movement_occurred) {
3204 return;
3207 if (last_pointer_frame() < _primary->region()->position()) {
3208 /* backwards drag of the left edge - not usable */
3209 return;
3212 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
3214 float percentage = (double) newlen / (double) _primary->region()->length();
3216 #ifndef USE_RUBBERBAND
3217 // Soundtouch uses percentage / 100 instead of normal (/ 1)
3218 if (_primary->region()->data_type() == DataType::AUDIO) {
3219 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
3221 #endif
3223 // XXX how do timeFX on multiple regions ?
3225 RegionSelection rs;
3226 rs.add (_primary);
3228 if (_editor->time_stretch (rs, percentage) == -1) {
3229 error << _("An error occurred while executing time stretch operation") << endmsg;
3233 void
3234 TimeFXDrag::aborted (bool)
3236 _primary->get_time_axis_view().hide_timestretch ();
3239 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
3240 : Drag (e, i)
3242 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
3245 void
3246 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3248 Drag::start_grab (event);
3251 void
3252 ScrubDrag::motion (GdkEvent* /*event*/, bool)
3254 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
3257 void
3258 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3260 if (movement_occurred && _editor->session()) {
3261 /* make sure we stop */
3262 _editor->session()->request_transport_speed (0.0);
3266 void
3267 ScrubDrag::aborted (bool)
3269 /* XXX: TODO */
3272 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3273 : Drag (e, i)
3274 , _operation (o)
3275 , _copy (false)
3276 , _original_pointer_time_axis (-1)
3277 , _last_pointer_time_axis (-1)
3279 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
3282 void
3283 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
3285 if (_editor->session() == 0) {
3286 return;
3289 Gdk::Cursor* cursor = 0;
3291 switch (_operation) {
3292 case CreateSelection:
3293 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3294 _copy = true;
3295 } else {
3296 _copy = false;
3298 cursor = _editor->cursors()->selector;
3299 Drag::start_grab (event, cursor);
3300 break;
3302 case SelectionStartTrim:
3303 if (_editor->clicked_axisview) {
3304 _editor->clicked_axisview->order_selection_trims (_item, true);
3306 Drag::start_grab (event, _editor->cursors()->left_side_trim);
3307 break;
3309 case SelectionEndTrim:
3310 if (_editor->clicked_axisview) {
3311 _editor->clicked_axisview->order_selection_trims (_item, false);
3313 Drag::start_grab (event, _editor->cursors()->right_side_trim);
3314 break;
3316 case SelectionMove:
3317 Drag::start_grab (event, cursor);
3318 break;
3321 if (_operation == SelectionMove) {
3322 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
3323 } else {
3324 show_verbose_cursor_time (adjusted_current_frame (event));
3327 _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
3330 void
3331 SelectionDrag::setup_pointer_frame_offset ()
3333 switch (_operation) {
3334 case CreateSelection:
3335 _pointer_frame_offset = 0;
3336 break;
3338 case SelectionStartTrim:
3339 case SelectionMove:
3340 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
3341 break;
3343 case SelectionEndTrim:
3344 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
3345 break;
3349 void
3350 SelectionDrag::motion (GdkEvent* event, bool first_move)
3352 framepos_t start = 0;
3353 framepos_t end = 0;
3354 framecnt_t length;
3356 pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
3357 if (pending_time_axis.first == 0) {
3358 return;
3361 framepos_t const pending_position = adjusted_current_frame (event);
3363 /* only alter selection if things have changed */
3365 if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
3366 return;
3369 switch (_operation) {
3370 case CreateSelection:
3372 framepos_t grab = grab_frame ();
3374 if (first_move) {
3375 _editor->snap_to (grab);
3378 if (pending_position < grab_frame()) {
3379 start = pending_position;
3380 end = grab;
3381 } else {
3382 end = pending_position;
3383 start = grab;
3386 /* first drag: Either add to the selection
3387 or create a new selection
3390 if (first_move) {
3392 if (_copy) {
3393 /* adding to the selection */
3394 _editor->set_selected_track_as_side_effect (Selection::Add);
3395 //_editor->selection->add (_editor->clicked_axisview);
3396 _editor->clicked_selection = _editor->selection->add (start, end);
3397 _copy = false;
3398 } else {
3399 /* new selection */
3401 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3402 //_editor->selection->set (_editor->clicked_axisview);
3403 _editor->set_selected_track_as_side_effect (Selection::Set);
3406 _editor->clicked_selection = _editor->selection->set (start, end);
3410 /* select the track that we're in */
3411 if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
3412 // _editor->set_selected_track_as_side_effect (Selection::Add);
3413 _editor->selection->add (pending_time_axis.first);
3414 _added_time_axes.push_back (pending_time_axis.first);
3417 /* deselect any tracks that this drag no longer includes, being careful to only deselect
3418 tracks that we selected in the first place.
3421 int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
3422 int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
3424 list<TimeAxisView*>::iterator i = _added_time_axes.begin();
3425 while (i != _added_time_axes.end()) {
3427 list<TimeAxisView*>::iterator tmp = i;
3428 ++tmp;
3430 if ((*i)->order() < min_order || (*i)->order() > max_order) {
3431 _editor->selection->remove (*i);
3432 _added_time_axes.remove (*i);
3435 i = tmp;
3439 break;
3441 case SelectionStartTrim:
3443 start = _editor->selection->time[_editor->clicked_selection].start;
3444 end = _editor->selection->time[_editor->clicked_selection].end;
3446 if (pending_position > end) {
3447 start = end;
3448 } else {
3449 start = pending_position;
3451 break;
3453 case SelectionEndTrim:
3455 start = _editor->selection->time[_editor->clicked_selection].start;
3456 end = _editor->selection->time[_editor->clicked_selection].end;
3458 if (pending_position < start) {
3459 end = start;
3460 } else {
3461 end = pending_position;
3464 break;
3466 case SelectionMove:
3468 start = _editor->selection->time[_editor->clicked_selection].start;
3469 end = _editor->selection->time[_editor->clicked_selection].end;
3471 length = end - start;
3473 start = pending_position;
3474 _editor->snap_to (start);
3476 end = start + length;
3478 break;
3481 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3482 _editor->start_canvas_autoscroll (1, 0);
3485 if (start != end) {
3486 _editor->selection->replace (_editor->clicked_selection, start, end);
3489 if (_operation == SelectionMove) {
3490 show_verbose_cursor_time(start);
3491 } else {
3492 show_verbose_cursor_time(pending_position);
3496 void
3497 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
3499 Session* s = _editor->session();
3501 if (movement_occurred) {
3502 motion (event, false);
3503 /* XXX this is not object-oriented programming at all. ick */
3504 if (_editor->selection->time.consolidate()) {
3505 _editor->selection->TimeChanged ();
3508 /* XXX what if its a music time selection? */
3509 if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
3510 s->request_play_range (&_editor->selection->time, true);
3514 } else {
3515 /* just a click, no pointer movement.*/
3517 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
3518 _editor->selection->clear_time();
3521 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3522 _editor->selection->set (_editor->clicked_axisview);
3525 if (s && s->get_play_range () && s->transport_rolling()) {
3526 s->request_stop (false, false);
3531 _editor->stop_canvas_autoscroll ();
3534 void
3535 SelectionDrag::aborted (bool)
3537 /* XXX: TODO */
3540 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3541 : Drag (e, i),
3542 _operation (o),
3543 _copy (false)
3545 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
3547 _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
3548 physical_screen_height (_editor->get_window()));
3549 _drag_rect->hide ();
3551 _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3552 _drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3555 void
3556 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3558 if (_editor->session() == 0) {
3559 return;
3562 Gdk::Cursor* cursor = 0;
3564 if (!_editor->temp_location) {
3565 _editor->temp_location = new Location (*_editor->session());
3568 switch (_operation) {
3569 case CreateRangeMarker:
3570 case CreateTransportMarker:
3571 case CreateCDMarker:
3573 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3574 _copy = true;
3575 } else {
3576 _copy = false;
3578 cursor = _editor->cursors()->selector;
3579 break;
3582 Drag::start_grab (event, cursor);
3584 show_verbose_cursor_time (adjusted_current_frame (event));
3587 void
3588 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
3590 framepos_t start = 0;
3591 framepos_t end = 0;
3592 ArdourCanvas::SimpleRect *crect;
3594 switch (_operation) {
3595 case CreateRangeMarker:
3596 crect = _editor->range_bar_drag_rect;
3597 break;
3598 case CreateTransportMarker:
3599 crect = _editor->transport_bar_drag_rect;
3600 break;
3601 case CreateCDMarker:
3602 crect = _editor->cd_marker_bar_drag_rect;
3603 break;
3604 default:
3605 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
3606 return;
3607 break;
3610 framepos_t const pf = adjusted_current_frame (event);
3612 if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
3613 framepos_t grab = grab_frame ();
3614 _editor->snap_to (grab);
3616 if (pf < grab_frame()) {
3617 start = pf;
3618 end = grab;
3619 } else {
3620 end = pf;
3621 start = grab;
3624 /* first drag: Either add to the selection
3625 or create a new selection.
3628 if (first_move) {
3630 _editor->temp_location->set (start, end);
3632 crect->show ();
3634 update_item (_editor->temp_location);
3635 _drag_rect->show();
3636 //_drag_rect->raise_to_top();
3641 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3642 _editor->start_canvas_autoscroll (1, 0);
3645 if (start != end) {
3646 _editor->temp_location->set (start, end);
3648 double x1 = _editor->frame_to_pixel (start);
3649 double x2 = _editor->frame_to_pixel (end);
3650 crect->property_x1() = x1;
3651 crect->property_x2() = x2;
3653 update_item (_editor->temp_location);
3656 show_verbose_cursor_time (pf);
3660 void
3661 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
3663 Location * newloc = 0;
3664 string rangename;
3665 int flags;
3667 if (movement_occurred) {
3668 motion (event, false);
3669 _drag_rect->hide();
3671 switch (_operation) {
3672 case CreateRangeMarker:
3673 case CreateCDMarker:
3675 _editor->begin_reversible_command (_("new range marker"));
3676 XMLNode &before = _editor->session()->locations()->get_state();
3677 _editor->session()->locations()->next_available_name(rangename,"unnamed");
3678 if (_operation == CreateCDMarker) {
3679 flags = Location::IsRangeMarker | Location::IsCDMarker;
3680 _editor->cd_marker_bar_drag_rect->hide();
3682 else {
3683 flags = Location::IsRangeMarker;
3684 _editor->range_bar_drag_rect->hide();
3686 newloc = new Location (
3687 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
3690 _editor->session()->locations()->add (newloc, true);
3691 XMLNode &after = _editor->session()->locations()->get_state();
3692 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3693 _editor->commit_reversible_command ();
3694 break;
3697 case CreateTransportMarker:
3698 // popup menu to pick loop or punch
3699 _editor->new_transport_marker_context_menu (&event->button, _item);
3700 break;
3702 } else {
3703 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
3705 if (Keyboard::no_modifier_keys_pressed (&event->button) && _operation != CreateCDMarker) {
3707 framepos_t start;
3708 framepos_t end;
3710 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
3712 if (end == max_framepos) {
3713 end = _editor->session()->current_end_frame ();
3716 if (start == max_framepos) {
3717 start = _editor->session()->current_start_frame ();
3720 switch (_editor->mouse_mode) {
3721 case MouseObject:
3722 /* find the two markers on either side and then make the selection from it */
3723 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
3724 break;
3726 case MouseRange:
3727 /* find the two markers on either side of the click and make the range out of it */
3728 _editor->selection->set (start, end);
3729 break;
3731 default:
3732 break;
3737 _editor->stop_canvas_autoscroll ();
3740 void
3741 RangeMarkerBarDrag::aborted (bool)
3743 /* XXX: TODO */
3746 void
3747 RangeMarkerBarDrag::update_item (Location* location)
3749 double const x1 = _editor->frame_to_pixel (location->start());
3750 double const x2 = _editor->frame_to_pixel (location->end());
3752 _drag_rect->property_x1() = x1;
3753 _drag_rect->property_x2() = x2;
3756 MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i)
3757 : Drag (e, i)
3758 , _zoom_out (false)
3760 DEBUG_TRACE (DEBUG::Drags, "New MouseZoomDrag\n");
3763 void
3764 MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3766 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
3767 Drag::start_grab (event, _editor->cursors()->zoom_out);
3768 _zoom_out = true;
3769 } else {
3770 Drag::start_grab (event, _editor->cursors()->zoom_in);
3771 _zoom_out = false;
3774 show_verbose_cursor_time (adjusted_current_frame (event));
3777 void
3778 MouseZoomDrag::motion (GdkEvent* event, bool first_move)
3780 framepos_t start;
3781 framepos_t end;
3783 framepos_t const pf = adjusted_current_frame (event);
3785 framepos_t grab = grab_frame ();
3786 _editor->snap_to_with_modifier (grab, event);
3788 /* base start and end on initial click position */
3789 if (pf < grab) {
3790 start = pf;
3791 end = grab;
3792 } else {
3793 end = pf;
3794 start = grab;
3797 if (start != end) {
3799 if (first_move) {
3800 _editor->zoom_rect->show();
3801 _editor->zoom_rect->raise_to_top();
3804 _editor->reposition_zoom_rect(start, end);
3806 show_verbose_cursor_time (pf);
3810 void
3811 MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
3813 if (movement_occurred) {
3814 motion (event, false);
3816 if (grab_frame() < last_pointer_frame()) {
3817 _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
3818 } else {
3819 _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
3821 } else {
3822 if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
3823 _editor->tav_zoom_step (_zoom_out);
3824 } else {
3825 _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
3829 _editor->zoom_rect->hide();
3832 void
3833 MouseZoomDrag::aborted (bool)
3835 _editor->zoom_rect->hide ();
3838 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
3839 : Drag (e, i)
3840 , _cumulative_dx (0)
3841 , _cumulative_dy (0)
3843 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
3845 _primary = dynamic_cast<CanvasNoteEvent*> (_item);
3846 _region = &_primary->region_view ();
3847 _note_height = _region->midi_stream_view()->note_height ();
3850 void
3851 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3853 Drag::start_grab (event);
3855 if (!(_was_selected = _primary->selected())) {
3857 /* tertiary-click means extend selection - we'll do that on button release,
3858 so don't add it here, because otherwise we make it hard to figure
3859 out the "extend-to" range.
3862 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
3864 if (!extend) {
3865 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
3867 if (add) {
3868 _region->note_selected (_primary, true);
3869 } else {
3870 _region->unique_select (_primary);
3876 /** @return Current total drag x change in frames */
3877 frameoffset_t
3878 NoteDrag::total_dx () const
3880 /* dx in frames */
3881 frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
3883 /* primary note time */
3884 frameoffset_t const n = _region->beats_to_frames (_primary->note()->time ());
3886 /* new time of the primary note relative to the region position */
3887 frameoffset_t st = n + dx;
3889 /* prevent the note being dragged earlier than the region's position */
3890 if (st < 0) {
3891 st = 0;
3894 /* snap and return corresponding delta */
3895 return _region->snap_frame_to_frame (st) - n;
3898 /** @return Current total drag y change in notes */
3899 int8_t
3900 NoteDrag::total_dy () const
3902 /* this is `backwards' to make increasing note number go in the right direction */
3903 double const dy = _drags->current_pointer_y() - grab_y();
3905 /* dy in notes */
3906 int8_t ndy = 0;
3908 if (abs (dy) >= _note_height) {
3909 if (dy > 0) {
3910 ndy = (int8_t) ceil (dy / _note_height / 2.0);
3911 } else {
3912 ndy = (int8_t) floor (dy / _note_height / 2.0);
3916 /* more positive value = higher pitch and higher y-axis position on track,
3917 which is the inverse of the X-centric geometric universe
3920 return -ndy;
3923 void
3924 NoteDrag::motion (GdkEvent *, bool)
3926 /* Total change in x and y since the start of the drag */
3927 frameoffset_t const dx = total_dx ();
3928 int8_t const dy = -total_dy ();
3930 /* Now work out what we have to do to the note canvas items to set this new drag delta */
3931 double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx;
3932 double const tdy = dy * _note_height - _cumulative_dy;
3934 if (tdx || tdy) {
3935 _cumulative_dx += tdx;
3936 _cumulative_dy += tdy;
3938 int8_t note_delta = total_dy();
3940 _region->move_selection (tdx, tdy, note_delta);
3942 char buf[12];
3943 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (_primary->note()->note() + note_delta).c_str(),
3944 (int) floor (_primary->note()->note() + note_delta));
3946 show_verbose_cursor_text (buf);
3950 void
3951 NoteDrag::finished (GdkEvent* ev, bool moved)
3953 if (!moved) {
3954 if (_editor->current_mouse_mode() == Editing::MouseObject) {
3956 if (_was_selected) {
3957 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3958 if (add) {
3959 _region->note_deselected (_primary);
3961 } else {
3962 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
3963 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3965 if (!extend && !add && _region->selection_size() > 1) {
3966 _region->unique_select (_primary);
3967 } else if (extend) {
3968 _region->note_selected (_primary, true, true);
3969 } else {
3970 /* it was added during button press */
3974 } else {
3975 _region->note_dropped (_primary, total_dx(), total_dy());
3979 void
3980 NoteDrag::aborted (bool)
3982 /* XXX: TODO */
3985 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* item, list<AudioRange> const & r)
3986 : Drag (editor, item)
3987 , _ranges (r)
3988 , _nothing_to_drag (false)
3990 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
3992 _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
3993 assert (_atav);
3995 /* get all lines in the automation view */
3996 list<boost::shared_ptr<AutomationLine> > lines = _atav->lines ();
3998 /* find those that overlap the ranges being dragged */
3999 list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
4000 while (i != lines.end ()) {
4001 list<boost::shared_ptr<AutomationLine> >::iterator j = i;
4002 ++j;
4004 pair<framepos_t, framepos_t> const r = (*i)->get_point_x_range ();
4006 /* check this range against all the AudioRanges that we are using */
4007 list<AudioRange>::const_iterator k = _ranges.begin ();
4008 while (k != _ranges.end()) {
4009 if (k->coverage (r.first, r.second) != OverlapNone) {
4010 break;
4012 ++k;
4015 /* add it to our list if it overlaps at all */
4016 if (k != _ranges.end()) {
4017 Line n;
4018 n.line = *i;
4019 n.state = 0;
4020 n.range = r;
4021 _lines.push_back (n);
4024 i = j;
4027 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
4030 void
4031 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4033 Drag::start_grab (event, cursor);
4035 /* Get line states before we start changing things */
4036 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4037 i->state = &i->line->get_state ();
4040 if (_ranges.empty()) {
4042 /* No selected time ranges: drag all points */
4043 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4044 uint32_t const N = i->line->npoints ();
4045 for (uint32_t j = 0; j < N; ++j) {
4046 i->points.push_back (i->line->nth (j));
4050 } else {
4052 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
4054 framecnt_t const half = (i->start + i->end) / 2;
4056 /* find the line that this audio range starts in */
4057 list<Line>::iterator j = _lines.begin();
4058 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
4059 ++j;
4062 if (j != _lines.end()) {
4063 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4065 /* j is the line that this audio range starts in; fade into it;
4066 64 samples length plucked out of thin air.
4069 framepos_t a = i->start + 64;
4070 if (a > half) {
4071 a = half;
4074 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
4075 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
4077 the_list->add (p, the_list->eval (p));
4078 j->line->add_always_in_view (p);
4079 the_list->add (q, the_list->eval (q));
4080 j->line->add_always_in_view (q);
4083 /* same thing for the end */
4085 j = _lines.begin();
4086 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
4087 ++j;
4090 if (j != _lines.end()) {
4091 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4093 /* j is the line that this audio range starts in; fade out of it;
4094 64 samples length plucked out of thin air.
4097 framepos_t b = i->end - 64;
4098 if (b < half) {
4099 b = half;
4102 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
4103 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
4105 the_list->add (p, the_list->eval (p));
4106 j->line->add_always_in_view (p);
4107 the_list->add (q, the_list->eval (q));
4108 j->line->add_always_in_view (q);
4112 _nothing_to_drag = true;
4114 /* Find all the points that should be dragged and put them in the relevant
4115 points lists in the Line structs.
4118 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4120 uint32_t const N = i->line->npoints ();
4121 for (uint32_t j = 0; j < N; ++j) {
4123 /* here's a control point on this line */
4124 ControlPoint* p = i->line->nth (j);
4125 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
4127 /* see if it's inside a range */
4128 list<AudioRange>::const_iterator k = _ranges.begin ();
4129 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
4130 ++k;
4133 if (k != _ranges.end()) {
4134 /* dragging this point */
4135 _nothing_to_drag = false;
4136 i->points.push_back (p);
4142 if (_nothing_to_drag) {
4143 return;
4146 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4147 i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state);
4151 void
4152 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
4154 if (_nothing_to_drag) {
4155 return;
4158 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4159 float const f = 1 - (_drags->current_pointer_y() / i->line->height());
4161 /* we are ignoring x position for this drag, so we can just pass in anything */
4162 i->line->drag_motion (0, f, true, false);
4166 void
4167 AutomationRangeDrag::finished (GdkEvent* event, bool)
4169 if (_nothing_to_drag) {
4170 return;
4173 motion (event, false);
4174 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4175 i->line->end_drag ();
4176 i->line->clear_always_in_view ();
4179 _editor->session()->commit_reversible_command ();
4182 void
4183 AutomationRangeDrag::aborted (bool)
4185 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4186 i->line->clear_always_in_view ();
4187 i->line->reset ();
4191 DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
4192 : view (v)
4194 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
4195 layer = v->region()->layer ();
4196 initial_y = v->get_canvas_group()->property_y ();
4197 initial_playlist = v->region()->playlist ();
4198 initial_position = v->region()->position ();
4199 initial_end = v->region()->position () + v->region()->length ();
4202 PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
4203 : Drag (e, i)
4204 , _region_view (r)
4205 , _patch_change (i)
4206 , _cumulative_dx (0)
4208 DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
4211 void
4212 PatchChangeDrag::motion (GdkEvent* ev, bool)
4214 framepos_t f = adjusted_current_frame (ev);
4215 boost::shared_ptr<Region> r = _region_view->region ();
4216 f = max (f, r->position ());
4217 f = min (f, r->last_frame ());
4219 framecnt_t const dxf = f - grab_frame();
4220 double const dxu = _editor->frame_to_unit (dxf);
4221 _patch_change->move (dxu - _cumulative_dx, 0);
4222 _cumulative_dx = dxu;
4225 void
4226 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
4228 if (!movement_occurred) {
4229 return;
4232 boost::shared_ptr<Region> r (_region_view->region ());
4234 framepos_t f = adjusted_current_frame (ev);
4235 f = max (f, r->position ());
4236 f = min (f, r->last_frame ());
4238 _region_view->move_patch_change (
4239 *_patch_change,
4240 _region_view->frames_to_beats (f - r->position() - r->start())
4244 void
4245 PatchChangeDrag::aborted (bool)
4247 _patch_change->move (-_cumulative_dx, 0);
4250 void
4251 PatchChangeDrag::setup_pointer_frame_offset ()
4253 boost::shared_ptr<Region> region = _region_view->region ();
4254 _pointer_frame_offset = raw_grab_frame() - _region_view->beats_to_frames (_patch_change->patch()->time()) - region->position() + region->start();