attempt to fix deep confusion in GUI code about converting between musical and audio...
[ardour2.git] / gtk2_ardour / editor_drag.cc
blob34e77509633a6e58f37543f22cdf04ede4e9ed83
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);
1313 /* The region motion is only processed if the pointer is over
1314 an audio track.
1317 if (!tv || !tv->is_track()) {
1318 /* To make sure we hide the verbose canvas cursor when the mouse is
1319 not held over and audiotrack.
1321 _editor->verbose_cursor()->hide ();
1322 return;
1325 int dir;
1327 if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
1328 dir = 1;
1329 } else {
1330 dir = -1;
1333 RegionSelection copy (_editor->selection->regions);
1335 RegionSelectionByPosition cmp;
1336 copy.sort (cmp);
1338 framepos_t const pf = adjusted_current_frame (event);
1340 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
1342 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
1344 if (!atv) {
1345 continue;
1348 boost::shared_ptr<Playlist> playlist;
1350 if ((playlist = atv->playlist()) == 0) {
1351 continue;
1354 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
1355 continue;
1358 if (dir > 0) {
1359 if (pf < (*i)->region()->last_frame() + 1) {
1360 continue;
1362 } else {
1363 if (pf > (*i)->region()->first_frame()) {
1364 continue;
1369 playlist->shuffle ((*i)->region(), dir);
1373 void
1374 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
1376 RegionMoveDrag::finished (event, movement_occurred);
1379 void
1380 RegionSpliceDrag::aborted (bool)
1382 /* XXX: TODO */
1385 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
1386 : Drag (e, i),
1387 _view (dynamic_cast<MidiTimeAxisView*> (v))
1389 DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
1391 assert (_view);
1394 void
1395 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
1397 if (first_move) {
1398 add_region();
1399 _view->playlist()->freeze ();
1400 } else {
1401 if (_region) {
1402 framepos_t const f = adjusted_current_frame (event);
1403 if (f < grab_frame()) {
1404 _region->set_position (f);
1407 /* Don't use a zero-length region, and subtract 1 frame from the snapped length
1408 so that if this region is duplicated, its duplicate starts on
1409 a snap point rather than 1 frame after a snap point. Otherwise things get
1410 a bit confusing as if a region starts 1 frame after a snap point, one cannot
1411 place snapped notes at the start of the region.
1414 framecnt_t const len = abs (f - grab_frame () - 1);
1415 _region->set_length (len < 1 ? 1 : len);
1420 void
1421 RegionCreateDrag::finished (GdkEvent*, bool movement_occurred)
1423 if (!movement_occurred) {
1424 add_region ();
1425 } else {
1426 _view->playlist()->thaw ();
1429 if (_region) {
1430 _editor->commit_reversible_command ();
1434 void
1435 RegionCreateDrag::add_region ()
1437 if (_editor->session()) {
1438 const TempoMap& map (_editor->session()->tempo_map());
1439 framecnt_t pos = grab_frame();
1440 const Meter& m = map.meter_at (pos);
1441 /* not that the frame rate used here can be affected by pull up/down which
1442 might be wrong.
1444 framecnt_t len = m.frames_per_bar (map.tempo_at (pos), _editor->session()->frame_rate());
1445 _region = _view->add_region (grab_frame(), len, false);
1449 void
1450 RegionCreateDrag::aborted (bool)
1452 if (_region) {
1453 _view->playlist()->thaw ();
1456 /* XXX */
1459 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
1460 : Drag (e, i)
1461 , region (0)
1463 DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
1466 void
1467 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
1469 Gdk::Cursor* cursor;
1470 ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item);
1471 float x_fraction = cnote->mouse_x_fraction ();
1473 if (x_fraction > 0.0 && x_fraction < 0.25) {
1474 cursor = _editor->cursors()->left_side_trim;
1475 } else {
1476 cursor = _editor->cursors()->right_side_trim;
1479 Drag::start_grab (event, cursor);
1481 region = &cnote->region_view();
1483 double const region_start = region->get_position_pixels();
1484 double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;
1486 if (grab_x() <= middle_point) {
1487 cursor = _editor->cursors()->left_side_trim;
1488 at_front = true;
1489 } else {
1490 cursor = _editor->cursors()->right_side_trim;
1491 at_front = false;
1494 _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, *cursor, event->motion.time);
1496 if (event->motion.state & Keyboard::PrimaryModifier) {
1497 relative = false;
1498 } else {
1499 relative = true;
1502 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1504 if (ms.size() > 1) {
1505 /* has to be relative, may make no sense otherwise */
1506 relative = true;
1509 /* select this note; if it is already selected, preserve the existing selection,
1510 otherwise make this note the only one selected.
1512 region->note_selected (cnote, cnote->selected ());
1514 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
1515 MidiRegionSelection::iterator next;
1516 next = r;
1517 ++next;
1518 (*r)->begin_resizing (at_front);
1519 r = next;
1523 void
1524 NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
1526 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1527 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1528 (*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1532 void
1533 NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
1535 MidiRegionSelection& ms (_editor->get_selection().midi_regions);
1536 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
1537 (*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNoteEvent*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
1541 void
1542 NoteResizeDrag::aborted (bool)
1544 /* XXX: TODO */
1547 RegionGainDrag::RegionGainDrag (Editor* e, ArdourCanvas::Item* i)
1548 : Drag (e, i)
1550 DEBUG_TRACE (DEBUG::Drags, "New RegionGainDrag\n");
1553 void
1554 RegionGainDrag::motion (GdkEvent* /*event*/, bool)
1559 void
1560 RegionGainDrag::finished (GdkEvent *, bool)
1565 void
1566 RegionGainDrag::aborted (bool)
1568 /* XXX: TODO */
1571 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
1572 : RegionDrag (e, i, p, v)
1574 DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
1577 void
1578 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
1580 double speed = 1.0;
1581 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1582 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1584 if (tv && tv->is_track()) {
1585 speed = tv->track()->speed();
1588 framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
1589 framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
1590 framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
1592 framepos_t const pf = adjusted_current_frame (event);
1594 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1595 /* Move the contents of the region around without changing the region bounds */
1596 _operation = ContentsTrim;
1597 Drag::start_grab (event, _editor->cursors()->trimmer);
1598 } else {
1599 /* These will get overridden for a point trim.*/
1600 if (pf < (region_start + region_length/2)) {
1601 /* closer to front */
1602 _operation = StartTrim;
1603 Drag::start_grab (event, _editor->cursors()->left_side_trim);
1604 } else {
1605 /* closer to end */
1606 _operation = EndTrim;
1607 Drag::start_grab (event, _editor->cursors()->right_side_trim);
1611 switch (_operation) {
1612 case StartTrim:
1613 show_verbose_cursor_time (region_start);
1614 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1615 i->view->trim_front_starting ();
1617 break;
1618 case EndTrim:
1619 show_verbose_cursor_time (region_end);
1620 break;
1621 case ContentsTrim:
1622 show_verbose_cursor_time (pf);
1623 break;
1626 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1627 i->view->region()->suspend_property_changes ();
1631 void
1632 TrimDrag::motion (GdkEvent* event, bool first_move)
1634 RegionView* rv = _primary;
1636 double speed = 1.0;
1637 TimeAxisView* tvp = &_primary->get_time_axis_view ();
1638 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
1639 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
1641 if (tv && tv->is_track()) {
1642 speed = tv->track()->speed();
1645 framecnt_t const dt = adjusted_current_frame (event) - raw_grab_frame () + _pointer_frame_offset;
1647 if (first_move) {
1649 string trim_type;
1651 switch (_operation) {
1652 case StartTrim:
1653 trim_type = "Region start trim";
1654 break;
1655 case EndTrim:
1656 trim_type = "Region end trim";
1657 break;
1658 case ContentsTrim:
1659 trim_type = "Region content trim";
1660 break;
1663 _editor->begin_reversible_command (trim_type);
1665 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1666 RegionView* rv = i->view;
1667 rv->fake_set_opaque (false);
1668 rv->enable_display (false);
1669 rv->region()->playlist()->clear_owned_changes ();
1671 AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
1673 if (arv) {
1674 arv->temporarily_hide_envelope ();
1677 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
1678 insert_result = _editor->motion_frozen_playlists.insert (pl);
1680 if (insert_result.second) {
1681 pl->freeze();
1686 bool non_overlap_trim = false;
1688 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1689 non_overlap_trim = true;
1692 switch (_operation) {
1693 case StartTrim:
1694 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1695 i->view->trim_front (i->initial_position + dt, non_overlap_trim);
1697 break;
1699 case EndTrim:
1700 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1701 i->view->trim_end (i->initial_end + dt, non_overlap_trim);
1703 break;
1705 case ContentsTrim:
1707 bool swap_direction = false;
1709 if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
1710 swap_direction = true;
1713 framecnt_t frame_delta = 0;
1715 bool left_direction = false;
1716 if (last_pointer_frame() > adjusted_current_frame(event)) {
1717 left_direction = true;
1720 if (left_direction) {
1721 frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
1722 } else {
1723 frame_delta = (adjusted_current_frame(event) - last_pointer_frame());
1726 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1727 i->view->trim_contents (frame_delta, left_direction, swap_direction);
1730 break;
1733 switch (_operation) {
1734 case StartTrim:
1735 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
1736 break;
1737 case EndTrim:
1738 show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed));
1739 break;
1740 case ContentsTrim:
1741 show_verbose_cursor_time (adjusted_current_frame (event));
1742 break;
1747 void
1748 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
1750 if (movement_occurred) {
1751 motion (event, false);
1753 /* This must happen before the region's StatefulDiffCommand is created, as it may
1754 `correct' (ahem) the region's _start from being negative to being zero. It
1755 needs to be zero in the undo record.
1757 if (_operation == StartTrim) {
1758 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1759 i->view->trim_front_ending ();
1763 if (!_editor->selection->selected (_primary)) {
1764 _primary->thaw_after_trim ();
1765 } else {
1767 set<boost::shared_ptr<Playlist> > diffed_playlists;
1769 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1770 i->view->thaw_after_trim ();
1771 i->view->enable_display (true);
1772 i->view->fake_set_opaque (true);
1774 /* Trimming one region may affect others on the playlist, so we need
1775 to get undo Commands from the whole playlist rather than just the
1776 region. Use diffed_playlists to make sure we don't diff a given
1777 playlist more than once.
1779 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
1780 if (diffed_playlists.find (p) == diffed_playlists.end()) {
1781 vector<Command*> cmds;
1782 p->rdiff (cmds);
1783 _editor->session()->add_commands (cmds);
1784 diffed_playlists.insert (p);
1788 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
1789 (*p)->thaw ();
1792 _editor->motion_frozen_playlists.clear ();
1793 _editor->commit_reversible_command();
1795 } else {
1796 /* no mouse movement */
1797 _editor->point_trim (event, adjusted_current_frame (event));
1800 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1801 if (_operation == StartTrim) {
1802 i->view->trim_front_ending ();
1805 i->view->region()->resume_property_changes ();
1809 void
1810 TrimDrag::aborted (bool movement_occurred)
1812 /* Our motion method is changing model state, so use the Undo system
1813 to cancel. Perhaps not ideal, as this will leave an Undo point
1814 behind which may be slightly odd from the user's point of view.
1817 finished (0, true);
1819 if (movement_occurred) {
1820 _editor->undo ();
1823 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1824 i->view->region()->resume_property_changes ();
1828 void
1829 TrimDrag::setup_pointer_frame_offset ()
1831 list<DraggingView>::iterator i = _views.begin ();
1832 while (i != _views.end() && i->view != _primary) {
1833 ++i;
1836 if (i == _views.end()) {
1837 return;
1840 switch (_operation) {
1841 case StartTrim:
1842 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
1843 break;
1844 case EndTrim:
1845 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
1846 break;
1847 case ContentsTrim:
1848 break;
1852 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1853 : Drag (e, i),
1854 _copy (c)
1856 DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
1858 _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
1859 assert (_marker);
1862 void
1863 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1865 if (_copy) {
1866 // create a dummy marker for visual representation of moving the copy.
1867 // The actual copying is not done before we reach the finish callback.
1868 char name[64];
1869 snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
1871 MeterMarker* new_marker = new MeterMarker (
1872 *_editor,
1873 *_editor->meter_group,
1874 ARDOUR_UI::config()->canvasvar_MeterMarker.get(),
1875 name,
1876 *new MeterSection (_marker->meter())
1879 _item = &new_marker->the_item ();
1880 _marker = new_marker;
1882 } else {
1884 MetricSection& section (_marker->meter());
1886 if (!section.movable()) {
1887 return;
1892 Drag::start_grab (event, cursor);
1894 show_verbose_cursor_time (adjusted_current_frame(event));
1897 void
1898 MeterMarkerDrag::setup_pointer_frame_offset ()
1900 _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
1903 void
1904 MeterMarkerDrag::motion (GdkEvent* event, bool)
1906 framepos_t const pf = adjusted_current_frame (event);
1908 _marker->set_position (pf);
1910 show_verbose_cursor_time (pf);
1913 void
1914 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
1916 if (!movement_occurred) {
1917 return;
1920 motion (event, false);
1922 Timecode::BBT_Time when;
1924 TempoMap& map (_editor->session()->tempo_map());
1925 map.bbt_time (last_pointer_frame(), when);
1927 if (_copy == true) {
1928 _editor->begin_reversible_command (_("copy meter mark"));
1929 XMLNode &before = map.get_state();
1930 map.add_meter (_marker->meter(), when);
1931 XMLNode &after = map.get_state();
1932 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1933 _editor->commit_reversible_command ();
1935 // delete the dummy marker we used for visual representation of copying.
1936 // a new visual marker will show up automatically.
1937 delete _marker;
1938 } else {
1939 _editor->begin_reversible_command (_("move meter mark"));
1940 XMLNode &before = map.get_state();
1941 map.move_meter (_marker->meter(), when);
1942 XMLNode &after = map.get_state();
1943 _editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
1944 _editor->commit_reversible_command ();
1948 void
1949 MeterMarkerDrag::aborted (bool)
1951 _marker->set_position (_marker->meter().frame ());
1954 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
1955 : Drag (e, i),
1956 _copy (c)
1958 DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
1960 _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
1961 assert (_marker);
1964 void
1965 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
1967 if (_copy) {
1969 // create a dummy marker for visual representation of moving the copy.
1970 // The actual copying is not done before we reach the finish callback.
1971 char name[64];
1972 snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
1974 TempoMarker* new_marker = new TempoMarker (
1975 *_editor,
1976 *_editor->tempo_group,
1977 ARDOUR_UI::config()->canvasvar_TempoMarker.get(),
1978 name,
1979 *new TempoSection (_marker->tempo())
1982 _item = &new_marker->the_item ();
1983 _marker = new_marker;
1987 Drag::start_grab (event, cursor);
1989 show_verbose_cursor_time (adjusted_current_frame (event));
1992 void
1993 TempoMarkerDrag::setup_pointer_frame_offset ()
1995 _pointer_frame_offset = raw_grab_frame() - _marker->tempo().frame();
1998 void
1999 TempoMarkerDrag::motion (GdkEvent* event, bool)
2001 framepos_t const pf = adjusted_current_frame (event);
2002 _marker->set_position (pf);
2003 show_verbose_cursor_time (pf);
2006 void
2007 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2009 if (!movement_occurred) {
2010 return;
2013 motion (event, false);
2015 Timecode::BBT_Time when;
2017 TempoMap& map (_editor->session()->tempo_map());
2018 map.bbt_time (last_pointer_frame(), when);
2020 if (_copy == true) {
2021 _editor->begin_reversible_command (_("copy tempo mark"));
2022 XMLNode &before = map.get_state();
2023 map.add_tempo (_marker->tempo(), when);
2024 XMLNode &after = map.get_state();
2025 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2026 _editor->commit_reversible_command ();
2028 // delete the dummy marker we used for visual representation of copying.
2029 // a new visual marker will show up automatically.
2030 delete _marker;
2031 } else {
2032 _editor->begin_reversible_command (_("move tempo mark"));
2033 XMLNode &before = map.get_state();
2034 map.move_tempo (_marker->tempo(), when);
2035 XMLNode &after = map.get_state();
2036 _editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2037 _editor->commit_reversible_command ();
2041 void
2042 TempoMarkerDrag::aborted (bool)
2044 _marker->set_position (_marker->tempo().frame());
2047 CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
2048 : Drag (e, i),
2049 _stop (s)
2051 DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
2054 /** Do all the things we do when dragging the playhead to make it look as though
2055 * we have located, without actually doing the locate (because that would cause
2056 * the diskstream buffers to be refilled, which is too slow).
2058 void
2059 CursorDrag::fake_locate (framepos_t t)
2061 _editor->playhead_cursor->set_position (t);
2063 Session* s = _editor->session ();
2064 if (s->timecode_transmission_suspended ()) {
2065 framepos_t const f = _editor->playhead_cursor->current_frame;
2066 s->send_mmc_locate (f);
2067 s->send_full_time_code (f);
2070 show_verbose_cursor_time (t);
2071 _editor->UpdateAllTransportClocks (t);
2074 void
2075 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
2077 Drag::start_grab (event, c);
2079 _grab_zoom = _editor->frames_per_unit;
2081 framepos_t where = _editor->event_frame (event, 0, 0);
2082 _editor->snap_to_with_modifier (where, event);
2084 _editor->_dragging_playhead = true;
2086 Session* s = _editor->session ();
2088 if (s) {
2089 if (_was_rolling && _stop) {
2090 s->request_stop ();
2093 if (s->is_auditioning()) {
2094 s->cancel_audition ();
2097 s->request_suspend_timecode_transmission ();
2098 while (!s->timecode_transmission_suspended ()) {
2099 /* twiddle our thumbs */
2103 fake_locate (where);
2106 void
2107 CursorDrag::motion (GdkEvent* event, bool)
2109 if (_drags->current_pointer_y() != last_pointer_y()) {
2111 /* zoom when we move the pointer up and down */
2113 /* y range to operate over (pixels) */
2114 double const y_range = 512;
2115 /* we will multiply the grab zoom by a factor between scale_range and scale_range^-1 */
2116 double const scale_range = 4;
2117 /* dead zone around the grab point in which to do no zooming (pixels) */
2118 double const dead_zone = 100;
2120 /* current dy */
2121 double dy = _drags->current_pointer_y() - grab_y();
2123 if (dy < -dead_zone || dy > dead_zone) {
2124 /* we are outside the dead zone; remove it from our calculation */
2125 if (dy < 0) {
2126 dy += dead_zone;
2127 } else {
2128 dy -= dead_zone;
2131 /* get a number from -1 to 1 as dy ranges from -y_range to y_range */
2132 double udy = max (min (dy / y_range, 1.0), -1.0);
2134 /* and zoom, using playhead focus temporarily */
2135 Editing::ZoomFocus const zf = _editor->get_zoom_focus ();
2136 _editor->set_zoom_focus (Editing::ZoomFocusPlayhead);
2137 _editor->temporal_zoom (_grab_zoom * pow (scale_range, -udy));
2138 _editor->set_zoom_focus (zf);
2142 framepos_t const adjusted_frame = adjusted_current_frame (event);
2143 if (adjusted_frame != last_pointer_frame()) {
2144 fake_locate (adjusted_frame);
2145 #ifdef GTKOSX
2146 _editor->update_canvas_now ();
2147 #endif
2151 void
2152 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
2154 _editor->_dragging_playhead = false;
2156 if (!movement_occurred && _stop) {
2157 return;
2160 motion (event, false);
2162 Session* s = _editor->session ();
2163 if (s) {
2164 s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
2165 _editor->_pending_locate_request = true;
2166 s->request_resume_timecode_transmission ();
2170 void
2171 CursorDrag::aborted (bool)
2173 if (_editor->_dragging_playhead) {
2174 _editor->session()->request_resume_timecode_transmission ();
2175 _editor->_dragging_playhead = false;
2178 _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
2181 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2182 : RegionDrag (e, i, p, v)
2184 DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
2187 void
2188 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2190 Drag::start_grab (event, cursor);
2192 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2193 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2195 show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
2197 arv->show_fade_line((framepos_t) r->fade_in()->back()->when);
2200 void
2201 FadeInDrag::setup_pointer_frame_offset ()
2203 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2204 boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
2205 _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
2208 void
2209 FadeInDrag::motion (GdkEvent* event, bool)
2211 framecnt_t fade_length;
2213 framepos_t const pos = adjusted_current_frame (event);
2215 boost::shared_ptr<Region> region = _primary->region ();
2217 if (pos < (region->position() + 64)) {
2218 fade_length = 64; // this should be a minimum defined somewhere
2219 } else if (pos > region->last_frame()) {
2220 fade_length = region->length();
2221 } else {
2222 fade_length = pos - region->position();
2225 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2227 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2229 if (!tmp) {
2230 continue;
2233 tmp->reset_fade_in_shape_width (fade_length);
2234 tmp->show_fade_line((framecnt_t) fade_length);
2237 show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
2240 void
2241 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
2243 if (!movement_occurred) {
2244 return;
2247 framecnt_t fade_length;
2249 framepos_t const pos = adjusted_current_frame (event);
2251 boost::shared_ptr<Region> region = _primary->region ();
2253 if (pos < (region->position() + 64)) {
2254 fade_length = 64; // this should be a minimum defined somewhere
2255 } else if (pos > region->last_frame()) {
2256 fade_length = region->length();
2257 } else {
2258 fade_length = pos - region->position();
2261 _editor->begin_reversible_command (_("change fade in length"));
2263 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2265 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2267 if (!tmp) {
2268 continue;
2271 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
2272 XMLNode &before = alist->get_state();
2274 tmp->audio_region()->set_fade_in_length (fade_length);
2275 tmp->audio_region()->set_fade_in_active (true);
2276 tmp->hide_fade_line();
2278 XMLNode &after = alist->get_state();
2279 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2282 _editor->commit_reversible_command ();
2285 void
2286 FadeInDrag::aborted (bool)
2288 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2289 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2291 if (!tmp) {
2292 continue;
2295 tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
2296 tmp->hide_fade_line();
2300 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2301 : RegionDrag (e, i, p, v)
2303 DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
2306 void
2307 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2309 Drag::start_grab (event, cursor);
2311 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2312 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2314 show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
2316 arv->show_fade_line(r->length() - r->fade_out()->back()->when);
2319 void
2320 FadeOutDrag::setup_pointer_frame_offset ()
2322 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
2323 boost::shared_ptr<AudioRegion> r = arv->audio_region ();
2324 _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
2327 void
2328 FadeOutDrag::motion (GdkEvent* event, bool)
2330 framecnt_t fade_length;
2332 framepos_t const pos = adjusted_current_frame (event);
2334 boost::shared_ptr<Region> region = _primary->region ();
2336 if (pos > (region->last_frame() - 64)) {
2337 fade_length = 64; // this should really be a minimum fade defined somewhere
2339 else if (pos < region->position()) {
2340 fade_length = region->length();
2342 else {
2343 fade_length = region->last_frame() - pos;
2346 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2348 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2350 if (!tmp) {
2351 continue;
2354 tmp->reset_fade_out_shape_width (fade_length);
2355 tmp->show_fade_line(region->length() - fade_length);
2358 show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
2361 void
2362 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
2364 if (!movement_occurred) {
2365 return;
2368 framecnt_t fade_length;
2370 framepos_t const pos = adjusted_current_frame (event);
2372 boost::shared_ptr<Region> region = _primary->region ();
2374 if (pos > (region->last_frame() - 64)) {
2375 fade_length = 64; // this should really be a minimum fade defined somewhere
2377 else if (pos < region->position()) {
2378 fade_length = region->length();
2380 else {
2381 fade_length = region->last_frame() - pos;
2384 _editor->begin_reversible_command (_("change fade out length"));
2386 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2388 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2390 if (!tmp) {
2391 continue;
2394 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
2395 XMLNode &before = alist->get_state();
2397 tmp->audio_region()->set_fade_out_length (fade_length);
2398 tmp->audio_region()->set_fade_out_active (true);
2399 tmp->hide_fade_line();
2401 XMLNode &after = alist->get_state();
2402 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
2405 _editor->commit_reversible_command ();
2408 void
2409 FadeOutDrag::aborted (bool)
2411 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2412 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
2414 if (!tmp) {
2415 continue;
2418 tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
2419 tmp->hide_fade_line();
2423 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
2424 : Drag (e, i)
2426 DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
2428 _marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
2429 assert (_marker);
2431 _points.push_back (Gnome::Art::Point (0, 0));
2432 _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window())));
2435 MarkerDrag::~MarkerDrag ()
2437 for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
2438 delete *i;
2442 void
2443 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
2445 Drag::start_grab (event, cursor);
2447 bool is_start;
2449 Location *location = _editor->find_location_from_marker (_marker, is_start);
2450 _editor->_dragging_edit_point = true;
2452 update_item (location);
2454 // _drag_line->show();
2455 // _line->raise_to_top();
2457 if (is_start) {
2458 show_verbose_cursor_time (location->start());
2459 } else {
2460 show_verbose_cursor_time (location->end());
2463 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2465 switch (op) {
2466 case Selection::Toggle:
2467 _editor->selection->toggle (_marker);
2468 break;
2469 case Selection::Set:
2470 if (!_editor->selection->selected (_marker)) {
2471 _editor->selection->set (_marker);
2473 break;
2474 case Selection::Extend:
2476 Locations::LocationList ll;
2477 list<Marker*> to_add;
2478 framepos_t s, e;
2479 _editor->selection->markers.range (s, e);
2480 s = min (_marker->position(), s);
2481 e = max (_marker->position(), e);
2482 s = min (s, e);
2483 e = max (s, e);
2484 if (e < max_framepos) {
2485 ++e;
2487 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
2488 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
2489 Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
2490 if (lm) {
2491 if (lm->start) {
2492 to_add.push_back (lm->start);
2494 if (lm->end) {
2495 to_add.push_back (lm->end);
2499 if (!to_add.empty()) {
2500 _editor->selection->add (to_add);
2502 break;
2504 case Selection::Add:
2505 _editor->selection->add (_marker);
2506 break;
2509 /* Set up copies for us to manipulate during the drag */
2511 for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
2512 Location* l = _editor->find_location_from_marker (*i, is_start);
2513 _copied_locations.push_back (new Location (*l));
2517 void
2518 MarkerDrag::setup_pointer_frame_offset ()
2520 bool is_start;
2521 Location *location = _editor->find_location_from_marker (_marker, is_start);
2522 _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
2525 void
2526 MarkerDrag::motion (GdkEvent* event, bool)
2528 framecnt_t f_delta = 0;
2529 bool is_start;
2530 bool move_both = false;
2531 Marker* marker;
2532 Location *real_location;
2533 Location *copy_location = 0;
2535 framepos_t const newframe = adjusted_current_frame (event);
2537 framepos_t next = newframe;
2539 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2540 move_both = true;
2543 MarkerSelection::iterator i;
2544 list<Location*>::iterator x;
2546 /* find the marker we're dragging, and compute the delta */
2548 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2549 x != _copied_locations.end() && i != _editor->selection->markers.end();
2550 ++i, ++x) {
2552 copy_location = *x;
2553 marker = *i;
2555 if (marker == _marker) {
2557 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2558 /* que pasa ?? */
2559 return;
2562 if (real_location->is_mark()) {
2563 f_delta = newframe - copy_location->start();
2564 } else {
2567 switch (marker->type()) {
2568 case Marker::SessionStart:
2569 case Marker::RangeStart:
2570 case Marker::LoopStart:
2571 case Marker::PunchIn:
2572 f_delta = newframe - copy_location->start();
2573 break;
2575 case Marker::SessionEnd:
2576 case Marker::RangeEnd:
2577 case Marker::LoopEnd:
2578 case Marker::PunchOut:
2579 f_delta = newframe - copy_location->end();
2580 break;
2581 default:
2582 /* what kind of marker is this ? */
2583 return;
2586 break;
2590 if (i == _editor->selection->markers.end()) {
2591 /* hmm, impossible - we didn't find the dragged marker */
2592 return;
2595 /* now move them all */
2597 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2598 x != _copied_locations.end() && i != _editor->selection->markers.end();
2599 ++i, ++x) {
2601 copy_location = *x;
2602 marker = *i;
2604 /* call this to find out if its the start or end */
2606 if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
2607 continue;
2610 if (real_location->locked()) {
2611 continue;
2614 if (copy_location->is_mark()) {
2616 /* now move it */
2618 copy_location->set_start (copy_location->start() + f_delta);
2620 } else {
2622 framepos_t new_start = copy_location->start() + f_delta;
2623 framepos_t new_end = copy_location->end() + f_delta;
2625 if (is_start) { // start-of-range marker
2627 if (move_both) {
2628 copy_location->set_start (new_start);
2629 copy_location->set_end (new_end);
2630 } else if (new_start < copy_location->end()) {
2631 copy_location->set_start (new_start);
2632 } else if (newframe > 0) {
2633 _editor->snap_to (next, 1, true);
2634 copy_location->set_end (next);
2635 copy_location->set_start (newframe);
2638 } else { // end marker
2640 if (move_both) {
2641 copy_location->set_end (new_end);
2642 copy_location->set_start (new_start);
2643 } else if (new_end > copy_location->start()) {
2644 copy_location->set_end (new_end);
2645 } else if (newframe > 0) {
2646 _editor->snap_to (next, -1, true);
2647 copy_location->set_start (next);
2648 copy_location->set_end (newframe);
2653 update_item (copy_location);
2655 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
2657 if (lm) {
2658 lm->set_position (copy_location->start(), copy_location->end());
2662 assert (!_copied_locations.empty());
2664 show_verbose_cursor_time (newframe);
2666 #ifdef GTKOSX
2667 _editor->update_canvas_now ();
2668 #endif
2671 void
2672 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
2674 if (!movement_occurred) {
2676 /* just a click, do nothing but finish
2677 off the selection process
2680 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
2682 switch (op) {
2683 case Selection::Set:
2684 if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
2685 _editor->selection->set (_marker);
2687 break;
2689 case Selection::Toggle:
2690 case Selection::Extend:
2691 case Selection::Add:
2692 break;
2695 return;
2698 _editor->_dragging_edit_point = false;
2700 _editor->begin_reversible_command ( _("move marker") );
2701 XMLNode &before = _editor->session()->locations()->get_state();
2703 MarkerSelection::iterator i;
2704 list<Location*>::iterator x;
2705 bool is_start;
2707 for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
2708 x != _copied_locations.end() && i != _editor->selection->markers.end();
2709 ++i, ++x) {
2711 Location * location = _editor->find_location_from_marker (*i, is_start);
2713 if (location) {
2715 if (location->locked()) {
2716 return;
2719 if (location->is_mark()) {
2720 location->set_start ((*x)->start());
2721 } else {
2722 location->set ((*x)->start(), (*x)->end());
2727 XMLNode &after = _editor->session()->locations()->get_state();
2728 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
2729 _editor->commit_reversible_command ();
2732 void
2733 MarkerDrag::aborted (bool)
2735 /* XXX: TODO */
2738 void
2739 MarkerDrag::update_item (Location* location)
2741 /* noop */
2744 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
2745 : Drag (e, i),
2746 _cumulative_x_drag (0),
2747 _cumulative_y_drag (0)
2749 if (_zero_gain_fraction < 0.0) {
2750 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
2753 DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
2755 _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
2756 assert (_point);
2760 void
2761 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2763 Drag::start_grab (event, _editor->cursors()->fader);
2765 // start the grab at the center of the control point so
2766 // the point doesn't 'jump' to the mouse after the first drag
2767 _fixed_grab_x = _point->get_x();
2768 _fixed_grab_y = _point->get_y();
2770 float const fraction = 1 - (_point->get_y() / _point->line().height());
2772 _point->line().start_drag_single (_point, _fixed_grab_x, fraction);
2774 _editor->verbose_cursor()->set (_point->line().get_verbose_cursor_string (fraction),
2775 event->button.x + 10, event->button.y + 10);
2777 _editor->verbose_cursor()->show ();
2780 void
2781 ControlPointDrag::motion (GdkEvent* event, bool)
2783 double dx = _drags->current_pointer_x() - last_pointer_x();
2784 double dy = _drags->current_pointer_y() - last_pointer_y();
2786 if (event->button.state & Keyboard::SecondaryModifier) {
2787 dx *= 0.1;
2788 dy *= 0.1;
2791 /* coordinate in pixels relative to the start of the region (for region-based automation)
2792 or track (for track-based automation) */
2793 double cx = _fixed_grab_x + _cumulative_x_drag + dx;
2794 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2796 // calculate zero crossing point. back off by .01 to stay on the
2797 // positive side of zero
2798 double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
2800 // make sure we hit zero when passing through
2801 if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
2802 cy = zero_gain_y;
2805 if (_x_constrained) {
2806 cx = _fixed_grab_x;
2808 if (_y_constrained) {
2809 cy = _fixed_grab_y;
2812 _cumulative_x_drag = cx - _fixed_grab_x;
2813 _cumulative_y_drag = cy - _fixed_grab_y;
2815 cx = max (0.0, cx);
2816 cy = max (0.0, cy);
2817 cy = min ((double) _point->line().height(), cy);
2819 framepos_t cx_frames = _editor->unit_to_frame (cx);
2821 if (!_x_constrained) {
2822 _editor->snap_to_with_modifier (cx_frames, event);
2825 cx_frames = min (cx_frames, _point->line().maximum_time());
2827 float const fraction = 1.0 - (cy / _point->line().height());
2829 bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);
2831 _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);
2833 _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction));
2836 void
2837 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
2839 if (!movement_occurred) {
2841 /* just a click */
2843 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
2844 _editor->reset_point_selection ();
2847 } else {
2848 motion (event, false);
2851 _point->line().end_drag ();
2852 _editor->session()->commit_reversible_command ();
2855 void
2856 ControlPointDrag::aborted (bool)
2858 _point->line().reset ();
2861 bool
2862 ControlPointDrag::active (Editing::MouseMode m)
2864 if (m == Editing::MouseGain) {
2865 /* always active in mouse gain */
2866 return true;
2869 /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
2870 return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
2873 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
2874 : Drag (e, i),
2875 _line (0),
2876 _cumulative_y_drag (0)
2878 DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
2881 void
2882 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2884 _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
2885 assert (_line);
2887 _item = &_line->grab_item ();
2889 /* need to get x coordinate in terms of parent (TimeAxisItemView)
2890 origin, and ditto for y.
2893 double cx = event->button.x;
2894 double cy = event->button.y;
2896 _line->parent_group().w2i (cx, cy);
2898 framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit);
2900 uint32_t before;
2901 uint32_t after;
2903 if (!_line->control_points_adjacent (frame_within_region, before, after)) {
2904 /* no adjacent points */
2905 return;
2908 Drag::start_grab (event, _editor->cursors()->fader);
2910 /* store grab start in parent frame */
2912 _fixed_grab_x = cx;
2913 _fixed_grab_y = cy;
2915 double fraction = 1.0 - (cy / _line->height());
2917 _line->start_drag_line (before, after, fraction);
2919 _editor->verbose_cursor()->set (_line->get_verbose_cursor_string (fraction),
2920 event->button.x + 10, event->button.y + 10);
2922 _editor->verbose_cursor()->show ();
2925 void
2926 LineDrag::motion (GdkEvent* event, bool)
2928 double dy = _drags->current_pointer_y() - last_pointer_y();
2930 if (event->button.state & Keyboard::SecondaryModifier) {
2931 dy *= 0.1;
2934 double cy = _fixed_grab_y + _cumulative_y_drag + dy;
2936 _cumulative_y_drag = cy - _fixed_grab_y;
2938 cy = max (0.0, cy);
2939 cy = min ((double) _line->height(), cy);
2941 double const fraction = 1.0 - (cy / _line->height());
2943 bool push;
2945 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
2946 push = false;
2947 } else {
2948 push = true;
2951 /* we are ignoring x position for this drag, so we can just pass in anything */
2952 _line->drag_motion (0, fraction, true, push);
2954 _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction));
2957 void
2958 LineDrag::finished (GdkEvent* event, bool)
2960 motion (event, false);
2961 _line->end_drag ();
2962 _editor->session()->commit_reversible_command ();
2965 void
2966 LineDrag::aborted (bool)
2968 _line->reset ();
2971 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
2972 : Drag (e, i),
2973 _line (0),
2974 _cumulative_x_drag (0)
2976 DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
2979 void
2980 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
2982 Drag::start_grab (event);
2984 _line = reinterpret_cast<Line*> (_item);
2985 assert (_line);
2987 /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
2989 double cx = event->button.x;
2990 double cy = event->button.y;
2992 _item->property_parent().get_value()->w2i(cx, cy);
2994 /* store grab start in parent frame */
2995 _region_view_grab_x = cx;
2997 _before = *(float*) _item->get_data ("position");
2999 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3001 _max_x = _editor->frame_to_pixel(_arv->get_duration());
3004 void
3005 FeatureLineDrag::motion (GdkEvent*, bool)
3007 double dx = _drags->current_pointer_x() - last_pointer_x();
3009 double cx = _region_view_grab_x + _cumulative_x_drag + dx;
3011 _cumulative_x_drag += dx;
3013 /* Clamp the min and max extent of the drag to keep it within the region view bounds */
3015 if (cx > _max_x){
3016 cx = _max_x;
3018 else if(cx < 0){
3019 cx = 0;
3022 ArdourCanvas::Points points;
3024 double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
3026 _line->get_bounds(x1, y2, x2, y2);
3028 points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value
3029 points.push_back(Gnome::Art::Point(cx, y2 - y1));
3031 _line->property_points() = points;
3033 float *pos = new float;
3034 *pos = cx;
3036 _line->set_data ("position", pos);
3038 _before = cx;
3041 void
3042 FeatureLineDrag::finished (GdkEvent*, bool)
3044 _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
3045 _arv->update_transient(_before, _before);
3048 void
3049 FeatureLineDrag::aborted (bool)
3051 //_line->reset ();
3054 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
3055 : Drag (e, i)
3057 DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
3060 void
3061 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3063 Drag::start_grab (event);
3064 show_verbose_cursor_time (adjusted_current_frame (event));
3067 void
3068 RubberbandSelectDrag::motion (GdkEvent* event, bool)
3070 framepos_t start;
3071 framepos_t end;
3072 double y1;
3073 double y2;
3075 framepos_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());
3077 framepos_t grab = grab_frame ();
3078 if (Config->get_rubberbanding_snaps_to_grid ()) {
3079 _editor->snap_to_with_modifier (grab, event);
3082 /* base start and end on initial click position */
3084 if (pf < grab) {
3085 start = pf;
3086 end = grab;
3087 } else {
3088 end = pf;
3089 start = grab;
3092 if (_drags->current_pointer_y() < grab_y()) {
3093 y1 = _drags->current_pointer_y();
3094 y2 = grab_y();
3095 } else {
3096 y2 = _drags->current_pointer_y();
3097 y1 = grab_y();
3101 if (start != end || y1 != y2) {
3103 double x1 = _editor->frame_to_pixel (start);
3104 double x2 = _editor->frame_to_pixel (end);
3106 _editor->rubberband_rect->property_x1() = x1;
3107 _editor->rubberband_rect->property_y1() = y1;
3108 _editor->rubberband_rect->property_x2() = x2;
3109 _editor->rubberband_rect->property_y2() = y2;
3111 _editor->rubberband_rect->show();
3112 _editor->rubberband_rect->raise_to_top();
3114 show_verbose_cursor_time (pf);
3118 void
3119 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
3121 if (movement_occurred) {
3123 motion (event, false);
3125 double y1,y2;
3126 if (_drags->current_pointer_y() < grab_y()) {
3127 y1 = _drags->current_pointer_y();
3128 y2 = grab_y();
3129 } else {
3130 y2 = _drags->current_pointer_y();
3131 y1 = grab_y();
3135 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
3137 _editor->begin_reversible_command (_("rubberband selection"));
3139 if (grab_frame() < last_pointer_frame()) {
3140 _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op, false);
3141 } else {
3142 _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op, false);
3145 _editor->commit_reversible_command ();
3147 } else {
3148 if (!getenv("ARDOUR_SAE")) {
3149 _editor->selection->clear_tracks();
3151 _editor->selection->clear_regions();
3152 _editor->selection->clear_points ();
3153 _editor->selection->clear_lines ();
3156 _editor->rubberband_rect->hide();
3159 void
3160 RubberbandSelectDrag::aborted (bool)
3162 _editor->rubberband_rect->hide ();
3165 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
3166 : RegionDrag (e, i, p, v)
3168 DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
3171 void
3172 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3174 Drag::start_grab (event, cursor);
3176 show_verbose_cursor_time (adjusted_current_frame (event));
3179 void
3180 TimeFXDrag::motion (GdkEvent* event, bool)
3182 RegionView* rv = _primary;
3184 framepos_t const pf = adjusted_current_frame (event);
3186 if (pf > rv->region()->position()) {
3187 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
3190 show_verbose_cursor_time (pf);
3193 void
3194 TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3196 _primary->get_time_axis_view().hide_timestretch ();
3198 if (!movement_occurred) {
3199 return;
3202 if (last_pointer_frame() < _primary->region()->position()) {
3203 /* backwards drag of the left edge - not usable */
3204 return;
3207 framecnt_t newlen = last_pointer_frame() - _primary->region()->position();
3209 float percentage = (double) newlen / (double) _primary->region()->length();
3211 #ifndef USE_RUBBERBAND
3212 // Soundtouch uses percentage / 100 instead of normal (/ 1)
3213 if (_primary->region()->data_type() == DataType::AUDIO) {
3214 percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
3216 #endif
3218 // XXX how do timeFX on multiple regions ?
3220 RegionSelection rs;
3221 rs.add (_primary);
3223 if (_editor->time_stretch (rs, percentage) == -1) {
3224 error << _("An error occurred while executing time stretch operation") << endmsg;
3228 void
3229 TimeFXDrag::aborted (bool)
3231 _primary->get_time_axis_view().hide_timestretch ();
3234 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
3235 : Drag (e, i)
3237 DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
3240 void
3241 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3243 Drag::start_grab (event);
3246 void
3247 ScrubDrag::motion (GdkEvent* /*event*/, bool)
3249 _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
3252 void
3253 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
3255 if (movement_occurred && _editor->session()) {
3256 /* make sure we stop */
3257 _editor->session()->request_transport_speed (0.0);
3261 void
3262 ScrubDrag::aborted (bool)
3264 /* XXX: TODO */
3267 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3268 : Drag (e, i)
3269 , _operation (o)
3270 , _copy (false)
3271 , _original_pointer_time_axis (-1)
3272 , _last_pointer_time_axis (-1)
3274 DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
3277 void
3278 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
3280 if (_editor->session() == 0) {
3281 return;
3284 Gdk::Cursor* cursor = 0;
3286 switch (_operation) {
3287 case CreateSelection:
3288 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3289 _copy = true;
3290 } else {
3291 _copy = false;
3293 cursor = _editor->cursors()->selector;
3294 Drag::start_grab (event, cursor);
3295 break;
3297 case SelectionStartTrim:
3298 if (_editor->clicked_axisview) {
3299 _editor->clicked_axisview->order_selection_trims (_item, true);
3301 Drag::start_grab (event, _editor->cursors()->left_side_trim);
3302 break;
3304 case SelectionEndTrim:
3305 if (_editor->clicked_axisview) {
3306 _editor->clicked_axisview->order_selection_trims (_item, false);
3308 Drag::start_grab (event, _editor->cursors()->right_side_trim);
3309 break;
3311 case SelectionMove:
3312 Drag::start_grab (event, cursor);
3313 break;
3316 if (_operation == SelectionMove) {
3317 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
3318 } else {
3319 show_verbose_cursor_time (adjusted_current_frame (event));
3322 _original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
3325 void
3326 SelectionDrag::setup_pointer_frame_offset ()
3328 switch (_operation) {
3329 case CreateSelection:
3330 _pointer_frame_offset = 0;
3331 break;
3333 case SelectionStartTrim:
3334 case SelectionMove:
3335 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
3336 break;
3338 case SelectionEndTrim:
3339 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
3340 break;
3344 void
3345 SelectionDrag::motion (GdkEvent* event, bool first_move)
3347 framepos_t start = 0;
3348 framepos_t end = 0;
3349 framecnt_t length;
3351 pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
3352 if (pending_time_axis.first == 0) {
3353 return;
3356 framepos_t const pending_position = adjusted_current_frame (event);
3358 /* only alter selection if things have changed */
3360 if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
3361 return;
3364 switch (_operation) {
3365 case CreateSelection:
3367 framepos_t grab = grab_frame ();
3369 if (first_move) {
3370 _editor->snap_to (grab);
3373 if (pending_position < grab_frame()) {
3374 start = pending_position;
3375 end = grab;
3376 } else {
3377 end = pending_position;
3378 start = grab;
3381 /* first drag: Either add to the selection
3382 or create a new selection
3385 if (first_move) {
3387 if (_copy) {
3388 /* adding to the selection */
3389 _editor->set_selected_track_as_side_effect (Selection::Add);
3390 //_editor->selection->add (_editor->clicked_axisview);
3391 _editor->clicked_selection = _editor->selection->add (start, end);
3392 _copy = false;
3393 } else {
3394 /* new selection */
3396 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3397 //_editor->selection->set (_editor->clicked_axisview);
3398 _editor->set_selected_track_as_side_effect (Selection::Set);
3401 _editor->clicked_selection = _editor->selection->set (start, end);
3405 /* select the track that we're in */
3406 if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
3407 // _editor->set_selected_track_as_side_effect (Selection::Add);
3408 _editor->selection->add (pending_time_axis.first);
3409 _added_time_axes.push_back (pending_time_axis.first);
3412 /* deselect any tracks that this drag no longer includes, being careful to only deselect
3413 tracks that we selected in the first place.
3416 int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
3417 int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());
3419 list<TimeAxisView*>::iterator i = _added_time_axes.begin();
3420 while (i != _added_time_axes.end()) {
3422 list<TimeAxisView*>::iterator tmp = i;
3423 ++tmp;
3425 if ((*i)->order() < min_order || (*i)->order() > max_order) {
3426 _editor->selection->remove (*i);
3427 _added_time_axes.remove (*i);
3430 i = tmp;
3434 break;
3436 case SelectionStartTrim:
3438 start = _editor->selection->time[_editor->clicked_selection].start;
3439 end = _editor->selection->time[_editor->clicked_selection].end;
3441 if (pending_position > end) {
3442 start = end;
3443 } else {
3444 start = pending_position;
3446 break;
3448 case SelectionEndTrim:
3450 start = _editor->selection->time[_editor->clicked_selection].start;
3451 end = _editor->selection->time[_editor->clicked_selection].end;
3453 if (pending_position < start) {
3454 end = start;
3455 } else {
3456 end = pending_position;
3459 break;
3461 case SelectionMove:
3463 start = _editor->selection->time[_editor->clicked_selection].start;
3464 end = _editor->selection->time[_editor->clicked_selection].end;
3466 length = end - start;
3468 start = pending_position;
3469 _editor->snap_to (start);
3471 end = start + length;
3473 break;
3476 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3477 _editor->start_canvas_autoscroll (1, 0);
3480 if (start != end) {
3481 _editor->selection->replace (_editor->clicked_selection, start, end);
3484 if (_operation == SelectionMove) {
3485 show_verbose_cursor_time(start);
3486 } else {
3487 show_verbose_cursor_time(pending_position);
3491 void
3492 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
3494 Session* s = _editor->session();
3496 if (movement_occurred) {
3497 motion (event, false);
3498 /* XXX this is not object-oriented programming at all. ick */
3499 if (_editor->selection->time.consolidate()) {
3500 _editor->selection->TimeChanged ();
3503 /* XXX what if its a music time selection? */
3504 if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
3505 s->request_play_range (&_editor->selection->time, true);
3509 } else {
3510 /* just a click, no pointer movement.*/
3512 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
3513 _editor->selection->clear_time();
3516 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
3517 _editor->selection->set (_editor->clicked_axisview);
3520 if (s && s->get_play_range () && s->transport_rolling()) {
3521 s->request_stop (false, false);
3526 _editor->stop_canvas_autoscroll ();
3529 void
3530 SelectionDrag::aborted (bool)
3532 /* XXX: TODO */
3535 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
3536 : Drag (e, i),
3537 _operation (o),
3538 _copy (false)
3540 DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
3542 _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0,
3543 physical_screen_height (_editor->get_window()));
3544 _drag_rect->hide ();
3546 _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3547 _drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
3550 void
3551 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3553 if (_editor->session() == 0) {
3554 return;
3557 Gdk::Cursor* cursor = 0;
3559 if (!_editor->temp_location) {
3560 _editor->temp_location = new Location (*_editor->session());
3563 switch (_operation) {
3564 case CreateRangeMarker:
3565 case CreateTransportMarker:
3566 case CreateCDMarker:
3568 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3569 _copy = true;
3570 } else {
3571 _copy = false;
3573 cursor = _editor->cursors()->selector;
3574 break;
3577 Drag::start_grab (event, cursor);
3579 show_verbose_cursor_time (adjusted_current_frame (event));
3582 void
3583 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
3585 framepos_t start = 0;
3586 framepos_t end = 0;
3587 ArdourCanvas::SimpleRect *crect;
3589 switch (_operation) {
3590 case CreateRangeMarker:
3591 crect = _editor->range_bar_drag_rect;
3592 break;
3593 case CreateTransportMarker:
3594 crect = _editor->transport_bar_drag_rect;
3595 break;
3596 case CreateCDMarker:
3597 crect = _editor->cd_marker_bar_drag_rect;
3598 break;
3599 default:
3600 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
3601 return;
3602 break;
3605 framepos_t const pf = adjusted_current_frame (event);
3607 if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
3608 framepos_t grab = grab_frame ();
3609 _editor->snap_to (grab);
3611 if (pf < grab_frame()) {
3612 start = pf;
3613 end = grab;
3614 } else {
3615 end = pf;
3616 start = grab;
3619 /* first drag: Either add to the selection
3620 or create a new selection.
3623 if (first_move) {
3625 _editor->temp_location->set (start, end);
3627 crect->show ();
3629 update_item (_editor->temp_location);
3630 _drag_rect->show();
3631 //_drag_rect->raise_to_top();
3636 if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
3637 _editor->start_canvas_autoscroll (1, 0);
3640 if (start != end) {
3641 _editor->temp_location->set (start, end);
3643 double x1 = _editor->frame_to_pixel (start);
3644 double x2 = _editor->frame_to_pixel (end);
3645 crect->property_x1() = x1;
3646 crect->property_x2() = x2;
3648 update_item (_editor->temp_location);
3651 show_verbose_cursor_time (pf);
3655 void
3656 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
3658 Location * newloc = 0;
3659 string rangename;
3660 int flags;
3662 if (movement_occurred) {
3663 motion (event, false);
3664 _drag_rect->hide();
3666 switch (_operation) {
3667 case CreateRangeMarker:
3668 case CreateCDMarker:
3670 _editor->begin_reversible_command (_("new range marker"));
3671 XMLNode &before = _editor->session()->locations()->get_state();
3672 _editor->session()->locations()->next_available_name(rangename,"unnamed");
3673 if (_operation == CreateCDMarker) {
3674 flags = Location::IsRangeMarker | Location::IsCDMarker;
3675 _editor->cd_marker_bar_drag_rect->hide();
3677 else {
3678 flags = Location::IsRangeMarker;
3679 _editor->range_bar_drag_rect->hide();
3681 newloc = new Location (
3682 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
3685 _editor->session()->locations()->add (newloc, true);
3686 XMLNode &after = _editor->session()->locations()->get_state();
3687 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
3688 _editor->commit_reversible_command ();
3689 break;
3692 case CreateTransportMarker:
3693 // popup menu to pick loop or punch
3694 _editor->new_transport_marker_context_menu (&event->button, _item);
3695 break;
3697 } else {
3698 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
3700 if (Keyboard::no_modifier_keys_pressed (&event->button) && _operation != CreateCDMarker) {
3702 framepos_t start;
3703 framepos_t end;
3705 _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
3707 if (end == max_framepos) {
3708 end = _editor->session()->current_end_frame ();
3711 if (start == max_framepos) {
3712 start = _editor->session()->current_start_frame ();
3715 switch (_editor->mouse_mode) {
3716 case MouseObject:
3717 /* find the two markers on either side and then make the selection from it */
3718 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
3719 break;
3721 case MouseRange:
3722 /* find the two markers on either side of the click and make the range out of it */
3723 _editor->selection->set (start, end);
3724 break;
3726 default:
3727 break;
3732 _editor->stop_canvas_autoscroll ();
3735 void
3736 RangeMarkerBarDrag::aborted (bool)
3738 /* XXX: TODO */
3741 void
3742 RangeMarkerBarDrag::update_item (Location* location)
3744 double const x1 = _editor->frame_to_pixel (location->start());
3745 double const x2 = _editor->frame_to_pixel (location->end());
3747 _drag_rect->property_x1() = x1;
3748 _drag_rect->property_x2() = x2;
3751 MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i)
3752 : Drag (e, i)
3753 , _zoom_out (false)
3755 DEBUG_TRACE (DEBUG::Drags, "New MouseZoomDrag\n");
3758 void
3759 MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3761 if (Keyboard::the_keyboard().key_is_down (GDK_Control_L)) {
3762 Drag::start_grab (event, _editor->cursors()->zoom_out);
3763 _zoom_out = true;
3764 } else {
3765 Drag::start_grab (event, _editor->cursors()->zoom_in);
3766 _zoom_out = false;
3769 show_verbose_cursor_time (adjusted_current_frame (event));
3772 void
3773 MouseZoomDrag::motion (GdkEvent* event, bool first_move)
3775 framepos_t start;
3776 framepos_t end;
3778 framepos_t const pf = adjusted_current_frame (event);
3780 framepos_t grab = grab_frame ();
3781 _editor->snap_to_with_modifier (grab, event);
3783 /* base start and end on initial click position */
3784 if (pf < grab) {
3785 start = pf;
3786 end = grab;
3787 } else {
3788 end = pf;
3789 start = grab;
3792 if (start != end) {
3794 if (first_move) {
3795 _editor->zoom_rect->show();
3796 _editor->zoom_rect->raise_to_top();
3799 _editor->reposition_zoom_rect(start, end);
3801 show_verbose_cursor_time (pf);
3805 void
3806 MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
3808 if (movement_occurred) {
3809 motion (event, false);
3811 if (grab_frame() < last_pointer_frame()) {
3812 _editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
3813 } else {
3814 _editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
3816 } else {
3817 if (Keyboard::the_keyboard().key_is_down (GDK_Shift_L)) {
3818 _editor->tav_zoom_step (_zoom_out);
3819 } else {
3820 _editor->temporal_zoom_to_frame (_zoom_out, grab_frame());
3824 _editor->zoom_rect->hide();
3827 void
3828 MouseZoomDrag::aborted (bool)
3830 _editor->zoom_rect->hide ();
3833 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
3834 : Drag (e, i)
3835 , _cumulative_dx (0)
3836 , _cumulative_dy (0)
3838 DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
3840 _primary = dynamic_cast<CanvasNoteEvent*> (_item);
3841 _region = &_primary->region_view ();
3842 _note_height = _region->midi_stream_view()->note_height ();
3845 void
3846 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
3848 Drag::start_grab (event);
3850 if (!(_was_selected = _primary->selected())) {
3852 /* tertiary-click means extend selection - we'll do that on button release,
3853 so don't add it here, because otherwise we make it hard to figure
3854 out the "extend-to" range.
3857 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
3859 if (!extend) {
3860 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
3862 if (add) {
3863 _region->note_selected (_primary, true);
3864 } else {
3865 _region->unique_select (_primary);
3871 /** @return Current total drag x change in frames */
3872 frameoffset_t
3873 NoteDrag::total_dx () const
3875 /* dx in frames */
3876 frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x());
3878 /* primary note time */
3879 frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ());
3881 /* new time of the primary note relative to the region position */
3882 frameoffset_t st = n + dx;
3884 /* prevent the note being dragged earlier than the region's position */
3885 if (st < 0) {
3886 st = 0;
3889 /* snap and return corresponding delta */
3890 return _region->snap_frame_to_frame (st) - n;
3893 /** @return Current total drag y change in note number */
3894 int8_t
3895 NoteDrag::total_dy () const
3897 return ((int8_t) (grab_y() / _note_height)) - ((int8_t) (_drags->current_pointer_y() / _note_height));
3900 void
3901 NoteDrag::motion (GdkEvent *, bool)
3903 /* Total change in x and y since the start of the drag */
3904 frameoffset_t const dx = total_dx ();
3905 int8_t const dy = total_dy ();
3907 /* Now work out what we have to do to the note canvas items to set this new drag delta */
3908 double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx;
3909 double const tdy = -dy * _note_height - _cumulative_dy;
3911 if (tdx || tdy) {
3912 _cumulative_dx += tdx;
3913 _cumulative_dy += tdy;
3915 int8_t note_delta = total_dy();
3917 _region->move_selection (tdx, tdy, note_delta);
3919 /* the new note value may be the same as the old one, but we
3920 * don't know what that means because the selection may have
3921 * involved more than one note and we might be doing something
3922 * odd with them. so show the note value anyway, always.
3925 char buf[12];
3926 uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
3928 snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (new_note).c_str(),
3929 (int) floor (new_note));
3931 show_verbose_cursor_text (buf);
3935 void
3936 NoteDrag::finished (GdkEvent* ev, bool moved)
3938 if (!moved) {
3939 if (_editor->current_mouse_mode() == Editing::MouseObject) {
3941 if (_was_selected) {
3942 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3943 if (add) {
3944 _region->note_deselected (_primary);
3946 } else {
3947 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
3948 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
3950 if (!extend && !add && _region->selection_size() > 1) {
3951 _region->unique_select (_primary);
3952 } else if (extend) {
3953 _region->note_selected (_primary, true, true);
3954 } else {
3955 /* it was added during button press */
3959 } else {
3960 _region->note_dropped (_primary, total_dx(), total_dy());
3964 void
3965 NoteDrag::aborted (bool)
3967 /* XXX: TODO */
3970 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, ArdourCanvas::Item* item, list<AudioRange> const & r)
3971 : Drag (editor, item)
3972 , _ranges (r)
3973 , _nothing_to_drag (false)
3975 DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
3977 _atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
3978 assert (_atav);
3980 /* get all lines in the automation view */
3981 list<boost::shared_ptr<AutomationLine> > lines = _atav->lines ();
3983 /* find those that overlap the ranges being dragged */
3984 list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin ();
3985 while (i != lines.end ()) {
3986 list<boost::shared_ptr<AutomationLine> >::iterator j = i;
3987 ++j;
3989 pair<framepos_t, framepos_t> const r = (*i)->get_point_x_range ();
3991 /* check this range against all the AudioRanges that we are using */
3992 list<AudioRange>::const_iterator k = _ranges.begin ();
3993 while (k != _ranges.end()) {
3994 if (k->coverage (r.first, r.second) != OverlapNone) {
3995 break;
3997 ++k;
4000 /* add it to our list if it overlaps at all */
4001 if (k != _ranges.end()) {
4002 Line n;
4003 n.line = *i;
4004 n.state = 0;
4005 n.range = r;
4006 _lines.push_back (n);
4009 i = j;
4012 /* Now ::lines contains the AutomationLines that somehow overlap our drag */
4015 void
4016 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4018 Drag::start_grab (event, cursor);
4020 /* Get line states before we start changing things */
4021 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4022 i->state = &i->line->get_state ();
4025 if (_ranges.empty()) {
4027 /* No selected time ranges: drag all points */
4028 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4029 uint32_t const N = i->line->npoints ();
4030 for (uint32_t j = 0; j < N; ++j) {
4031 i->points.push_back (i->line->nth (j));
4035 } else {
4037 for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
4039 framecnt_t const half = (i->start + i->end) / 2;
4041 /* find the line that this audio range starts in */
4042 list<Line>::iterator j = _lines.begin();
4043 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
4044 ++j;
4047 if (j != _lines.end()) {
4048 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4050 /* j is the line that this audio range starts in; fade into it;
4051 64 samples length plucked out of thin air.
4054 framepos_t a = i->start + 64;
4055 if (a > half) {
4056 a = half;
4059 double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
4060 double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
4062 the_list->add (p, the_list->eval (p));
4063 j->line->add_always_in_view (p);
4064 the_list->add (q, the_list->eval (q));
4065 j->line->add_always_in_view (q);
4068 /* same thing for the end */
4070 j = _lines.begin();
4071 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
4072 ++j;
4075 if (j != _lines.end()) {
4076 boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
4078 /* j is the line that this audio range starts in; fade out of it;
4079 64 samples length plucked out of thin air.
4082 framepos_t b = i->end - 64;
4083 if (b < half) {
4084 b = half;
4087 double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
4088 double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
4090 the_list->add (p, the_list->eval (p));
4091 j->line->add_always_in_view (p);
4092 the_list->add (q, the_list->eval (q));
4093 j->line->add_always_in_view (q);
4097 _nothing_to_drag = true;
4099 /* Find all the points that should be dragged and put them in the relevant
4100 points lists in the Line structs.
4103 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4105 uint32_t const N = i->line->npoints ();
4106 for (uint32_t j = 0; j < N; ++j) {
4108 /* here's a control point on this line */
4109 ControlPoint* p = i->line->nth (j);
4110 double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
4112 /* see if it's inside a range */
4113 list<AudioRange>::const_iterator k = _ranges.begin ();
4114 while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
4115 ++k;
4118 if (k != _ranges.end()) {
4119 /* dragging this point */
4120 _nothing_to_drag = false;
4121 i->points.push_back (p);
4127 if (_nothing_to_drag) {
4128 return;
4131 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4132 i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state);
4136 void
4137 AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/)
4139 if (_nothing_to_drag) {
4140 return;
4143 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4144 float const f = 1 - (_drags->current_pointer_y() / i->line->height());
4146 /* we are ignoring x position for this drag, so we can just pass in anything */
4147 i->line->drag_motion (0, f, true, false);
4151 void
4152 AutomationRangeDrag::finished (GdkEvent* event, bool)
4154 if (_nothing_to_drag) {
4155 return;
4158 motion (event, false);
4159 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4160 i->line->end_drag ();
4161 i->line->clear_always_in_view ();
4164 _editor->session()->commit_reversible_command ();
4167 void
4168 AutomationRangeDrag::aborted (bool)
4170 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
4171 i->line->clear_always_in_view ();
4172 i->line->reset ();
4176 DraggingView::DraggingView (RegionView* v, RegionDrag* parent)
4177 : view (v)
4179 time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
4180 layer = v->region()->layer ();
4181 initial_y = v->get_canvas_group()->property_y ();
4182 initial_playlist = v->region()->playlist ();
4183 initial_position = v->region()->position ();
4184 initial_end = v->region()->position () + v->region()->length ();
4187 PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r)
4188 : Drag (e, i)
4189 , _region_view (r)
4190 , _patch_change (i)
4191 , _cumulative_dx (0)
4193 DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n");
4196 void
4197 PatchChangeDrag::motion (GdkEvent* ev, bool)
4199 framepos_t f = adjusted_current_frame (ev);
4200 boost::shared_ptr<Region> r = _region_view->region ();
4201 f = max (f, r->position ());
4202 f = min (f, r->last_frame ());
4204 framecnt_t const dxf = f - grab_frame();
4205 double const dxu = _editor->frame_to_unit (dxf);
4206 _patch_change->move (dxu - _cumulative_dx, 0);
4207 _cumulative_dx = dxu;
4210 void
4211 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
4213 if (!movement_occurred) {
4214 return;
4217 boost::shared_ptr<Region> r (_region_view->region ());
4219 framepos_t f = adjusted_current_frame (ev);
4220 f = max (f, r->position ());
4221 f = min (f, r->last_frame ());
4223 _region_view->move_patch_change (
4224 *_patch_change,
4225 _region_view->region_frames_to_region_beats (f - r->position() - r->start())
4229 void
4230 PatchChangeDrag::aborted (bool)
4232 _patch_change->move (-_cumulative_dx, 0);
4235 void
4236 PatchChangeDrag::setup_pointer_frame_offset ()
4238 boost::shared_ptr<Region> region = _region_view->region ();
4239 _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());