GUI follow up for new-MIDI-track API change
[ardour.git] / gtk2_ardour / editor_drag.cc
1 /*
2     Copyright (C) 2009 Paul Davis
3
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.
8
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.
13
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.
17
18 */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <stdint.h>
25 #include <algorithm>
26
27 #include "pbd/memento_command.h"
28 #include "pbd/basename.h"
29 #include "pbd/stateful_diff_command.h"
30
31 #include "gtkmm2ext/utils.h"
32
33 #include "ardour/audioengine.h"
34 #include "ardour/audioregion.h"
35 #include "ardour/audio_track.h"
36 #include "ardour/dB.h"
37 #include "ardour/midi_region.h"
38 #include "ardour/midi_track.h"
39 #include "ardour/operations.h"
40 #include "ardour/profile.h"
41 #include "ardour/region_factory.h"
42 #include "ardour/session.h"
43
44 #include "canvas/canvas.h"
45 #include "canvas/scroll_group.h"
46
47 #include "editor.h"
48 #include "pbd/i18n.h"
49 #include "keyboard.h"
50 #include "audio_region_view.h"
51 #include "automation_region_view.h"
52 #include "midi_region_view.h"
53 #include "ardour_ui.h"
54 #include "gui_thread.h"
55 #include "control_point.h"
56 #include "region_gain_line.h"
57 #include "editor_drag.h"
58 #include "audio_time_axis.h"
59 #include "midi_time_axis.h"
60 #include "selection.h"
61 #include "midi_selection.h"
62 #include "automation_time_axis.h"
63 #include "debug.h"
64 #include "editor_cursors.h"
65 #include "mouse_cursors.h"
66 #include "note_base.h"
67 #include "patch_change.h"
68 #include "ui_config.h"
69 #include "verbose_cursor.h"
70
71 using namespace std;
72 using namespace ARDOUR;
73 using namespace PBD;
74 using namespace Gtk;
75 using namespace Gtkmm2ext;
76 using namespace Editing;
77 using namespace ArdourCanvas;
78
79 using Gtkmm2ext::Keyboard;
80
81 double ControlPointDrag::_zero_gain_fraction = -1.0;
82
83 DragManager::DragManager (Editor* e)
84         : _editor (e)
85         , _ending (false)
86         , _current_pointer_x (0.0)
87         , _current_pointer_y (0.0)
88         , _current_pointer_frame (0)
89         , _old_follow_playhead (false)
90 {
91 }
92
93 DragManager::~DragManager ()
94 {
95         abort ();
96 }
97
98 /** Call abort for each active drag */
99 void
100 DragManager::abort ()
101 {
102         _ending = true;
103
104         for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
105                 (*i)->abort ();
106                 delete *i;
107         }
108
109         if (!_drags.empty ()) {
110                 _editor->set_follow_playhead (_old_follow_playhead, false);
111         }
112
113         _drags.clear ();
114         _editor->abort_reversible_command();
115
116         _ending = false;
117 }
118
119 void
120 DragManager::add (Drag* d)
121 {
122         d->set_manager (this);
123         _drags.push_back (d);
124 }
125
126 void
127 DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
128 {
129         d->set_manager (this);
130         _drags.push_back (d);
131         start_grab (e, c);
132 }
133
134 void
135 DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c)
136 {
137         /* Prevent follow playhead during the drag to be nice to the user */
138         _old_follow_playhead = _editor->follow_playhead ();
139         _editor->set_follow_playhead (false);
140
141         _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
142
143         for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
144                 (*i)->start_grab (e, c);
145         }
146 }
147
148 /** Call end_grab for each active drag.
149  *  @return true if any drag reported movement having occurred.
150  */
151 bool
152 DragManager::end_grab (GdkEvent* e)
153 {
154         _ending = true;
155
156         bool r = false;
157         for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
158                 bool const t = (*i)->end_grab (e);
159                 if (t) {
160                         r = true;
161                 }
162                 delete *i;
163         }
164
165         _drags.clear ();
166
167         _ending = false;
168
169         _editor->set_follow_playhead (_old_follow_playhead, false);
170
171         return r;
172 }
173
174 void
175 DragManager::mark_double_click ()
176 {
177         for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
178                 (*i)->set_double_click (true);
179         }
180 }
181
182 bool
183 DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
184 {
185         bool r = false;
186
187         /* calling this implies that we expect the event to have canvas
188          * coordinates
189          *
190          * Can we guarantee that this is true?
191          */
192
193         _current_pointer_frame = _editor->canvas_event_sample (e, &_current_pointer_x, &_current_pointer_y);
194
195         for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
196                 bool const t = (*i)->motion_handler (e, from_autoscroll);
197                 /* run all handlers; return true if at least one of them
198                    returns true (indicating that the event has been handled).
199                 */
200                 if (t) {
201                         r = true;
202                 }
203
204         }
205
206         return r;
207 }
208
209 bool
210 DragManager::have_item (ArdourCanvas::Item* i) const
211 {
212         list<Drag*>::const_iterator j = _drags.begin ();
213         while (j != _drags.end() && (*j)->item () != i) {
214                 ++j;
215         }
216
217         return j != _drags.end ();
218 }
219
220 Drag::Drag (Editor* e, ArdourCanvas::Item* i, bool trackview_only)
221         : _editor (e)
222         , _drags (0)
223         , _item (i)
224         , _pointer_frame_offset (0)
225         , _x_constrained (false)
226         , _y_constrained (false)
227         , _was_rolling (false)
228         , _trackview_only (trackview_only)
229         , _move_threshold_passed (false)
230         , _starting_point_passed (false)
231         , _initially_vertical (false)
232         , _was_double_click (false)
233         , _grab_x (0.0)
234         , _grab_y (0.0)
235         , _last_pointer_x (0.0)
236         , _last_pointer_y (0.0)
237         , _raw_grab_frame (0)
238         , _grab_frame (0)
239         , _last_pointer_frame (0)
240         , _snap_delta (0)
241         , _constraint_pressed (false)
242 {
243
244 }
245
246 void
247 Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/)
248 {
249         _item->ungrab ();
250         _item = new_item;
251
252         if (!_cursor_ctx) {
253                 _cursor_ctx = CursorContext::create (*_editor, cursor);
254         } else {
255                 _cursor_ctx->change (cursor);
256         }
257
258         _item->grab ();
259 }
260
261 void
262 Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
263 {
264
265         /* we set up x/y dragging constraints on first move */
266         _constraint_pressed = ArdourKeyboard::indicates_constraint (event->button.state);
267
268         _raw_grab_frame = _editor->canvas_event_sample (event, &_grab_x, &_grab_y);
269
270         setup_pointer_frame_offset ();
271         _grab_frame = adjusted_frame (_raw_grab_frame, event);
272         _last_pointer_frame = _grab_frame;
273         _last_pointer_x = _grab_x;
274
275         if (_trackview_only) {
276                 _grab_y = _grab_y - _editor->get_trackview_group()->canvas_origin().y;
277         }
278
279         _last_pointer_y = _grab_y;
280
281         _item->grab ();
282
283         if (!_editor->cursors()->is_invalid (cursor)) {
284                 /* CAIROCANVAS need a variant here that passes *cursor */
285                 _cursor_ctx = CursorContext::create (*_editor, cursor);
286         }
287
288         if (_editor->session() && _editor->session()->transport_rolling()) {
289                 _was_rolling = true;
290         } else {
291                 _was_rolling = false;
292         }
293
294         switch (_editor->snap_type()) {
295         case SnapToRegionStart:
296         case SnapToRegionEnd:
297         case SnapToRegionSync:
298         case SnapToRegionBoundary:
299                 _editor->build_region_boundary_cache ();
300                 break;
301         default:
302                 break;
303         }
304 }
305
306 /** Call to end a drag `successfully'.  Ungrabs item and calls
307  *  subclass' finished() method.
308  *
309  *  @param event GDK event, or 0.
310  *  @return true if some movement occurred, otherwise false.
311  */
312 bool
313 Drag::end_grab (GdkEvent* event)
314 {
315         _editor->stop_canvas_autoscroll ();
316
317         _item->ungrab ();
318
319         finished (event, _move_threshold_passed);
320
321         _editor->verbose_cursor()->hide ();
322         _cursor_ctx.reset();
323
324         return _move_threshold_passed;
325 }
326
327 framepos_t
328 Drag::adjusted_frame (framepos_t f, GdkEvent const * event, bool snap) const
329 {
330         framepos_t pos = 0;
331
332         if (f > _pointer_frame_offset) {
333                 pos = f - _pointer_frame_offset;
334         }
335
336         if (snap) {
337                 _editor->snap_to_with_modifier (pos, event);
338         }
339
340         return pos;
341 }
342
343 framepos_t
344 Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
345 {
346         return adjusted_frame (_drags->current_pointer_frame (), event, snap);
347 }
348
349 frameoffset_t
350 Drag::snap_delta (guint state) const
351 {
352         if (ArdourKeyboard::indicates_snap_delta (state)) {
353                 return _snap_delta;
354         }
355
356         return 0;
357 }
358
359 double
360 Drag::current_pointer_x() const
361 {
362         return _drags->current_pointer_x ();
363 }
364
365 double
366 Drag::current_pointer_y () const
367 {
368         if (!_trackview_only) {
369                 return _drags->current_pointer_y ();
370         }
371
372         return _drags->current_pointer_y () - _editor->get_trackview_group()->canvas_origin().y;
373 }
374
375 void
376 Drag::setup_snap_delta (framepos_t pos)
377 {
378         framepos_t temp = pos;
379         _editor->snap_to (temp, ARDOUR::RoundNearest, false, true);
380         _snap_delta = temp - pos;
381 }
382
383 bool
384 Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
385 {
386         /* check to see if we have moved in any way that matters since the last motion event */
387         if (_move_threshold_passed &&
388             (!x_movement_matters() || _last_pointer_x == current_pointer_x ()) &&
389             (!y_movement_matters() || _last_pointer_y == current_pointer_y ()) ) {
390                 return false;
391         }
392
393         pair<framecnt_t, int> const threshold = move_threshold ();
394
395         bool const old_move_threshold_passed = _move_threshold_passed;
396
397         if (!_move_threshold_passed) {
398
399                 bool const xp = (::llabs (_drags->current_pointer_frame () - _raw_grab_frame) >= threshold.first);
400                 bool const yp = (::fabs ((current_pointer_y () - _grab_y)) >= threshold.second);
401
402                 _move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
403         }
404
405         if (active (_editor->mouse_mode) && _move_threshold_passed) {
406
407                 if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
408
409                         if (old_move_threshold_passed != _move_threshold_passed) {
410
411                                 /* just changed */
412
413                                 if (fabs (current_pointer_y() - _grab_y) > fabs (current_pointer_x() - _grab_x)) {
414                                         _initially_vertical = true;
415                                 } else {
416                                         _initially_vertical = false;
417                                 }
418                                 /** check constraints for this drag.
419                                  *  Note that the current convention is to use "contains" for
420                                  *  key modifiers during motion and "equals" when initiating a drag.
421                                  *  In this case we haven't moved yet, so "equals" applies here.
422                                  */
423                                 if (Config->get_edit_mode() != Lock) {
424                                         if (event->motion.state & Gdk::BUTTON2_MASK) {
425                                                 // if dragging with button2, the motion is x constrained, with constraint modifier it is y constrained
426                                                 if (_constraint_pressed) {
427                                                         _x_constrained = false;
428                                                         _y_constrained = true;
429                                                 } else {
430                                                         _x_constrained = true;
431                                                         _y_constrained = false;
432                                                 }
433                                         } else if (_constraint_pressed) {
434                                                 // if dragging normally, the motion is constrained to the first direction of movement.
435                                                 if (_initially_vertical) {
436                                                         _x_constrained = true;
437                                                         _y_constrained = false;
438                                                 } else {
439                                                         _x_constrained = false;
440                                                         _y_constrained = true;
441                                                 }
442                                         }
443                                 } else {
444                                         if (event->button.state & Gdk::BUTTON2_MASK) {
445                                                 _x_constrained = false;
446                                         } else {
447                                                 _x_constrained = true;
448                                         }
449                                         _y_constrained = false;
450                                 }
451                         }
452
453                         if (!from_autoscroll) {
454                                 _editor->maybe_autoscroll (true, allow_vertical_autoscroll (), false);
455                         }
456
457                         if (!_editor->autoscroll_active() || from_autoscroll) {
458
459
460                                 bool first_move = (_move_threshold_passed != old_move_threshold_passed) || from_autoscroll;
461
462                                 motion (event, first_move && !_starting_point_passed);
463
464                                 if (first_move && !_starting_point_passed) {
465                                         _starting_point_passed = true;
466                                 }
467
468                                 _last_pointer_x = _drags->current_pointer_x ();
469                                 _last_pointer_y = current_pointer_y ();
470                                 _last_pointer_frame = adjusted_current_frame (event, false);
471                         }
472
473                         return true;
474                 }
475         }
476
477         return false;
478 }
479
480 /** Call to abort a drag.  Ungrabs item and calls subclass's aborted () */
481 void
482 Drag::abort ()
483 {
484         if (_item) {
485                 _item->ungrab ();
486         }
487
488         aborted (_move_threshold_passed);
489
490         _editor->stop_canvas_autoscroll ();
491         _editor->verbose_cursor()->hide ();
492 }
493
494 void
495 Drag::show_verbose_cursor_time (framepos_t frame)
496 {
497         _editor->verbose_cursor()->set_time (frame);
498         _editor->verbose_cursor()->show ();
499 }
500
501 void
502 Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double /*xoffset*/)
503 {
504         _editor->verbose_cursor()->set_duration (start, end);
505         _editor->verbose_cursor()->show ();
506 }
507
508 void
509 Drag::show_verbose_cursor_text (string const & text)
510 {
511         _editor->verbose_cursor()->set (text);
512         _editor->verbose_cursor()->show ();
513 }
514
515 boost::shared_ptr<Region>
516 Drag::add_midi_region (MidiTimeAxisView* view, bool commit, const int32_t sub_num)
517 {
518         if (_editor->session()) {
519                 const TempoMap& map (_editor->session()->tempo_map());
520                 framecnt_t pos = grab_frame();
521                 /* not that the frame rate used here can be affected by pull up/down which
522                    might be wrong.
523                 */
524                 framecnt_t len = map.frame_at_beat (max (0.0, map.beat_at_frame (pos)) + 1.0) - pos;
525                 return view->add_region (grab_frame(), len, commit, sub_num);
526         }
527
528         return boost::shared_ptr<Region>();
529 }
530
531 struct PresentationInfoTimeAxisViewSorter {
532         bool operator() (TimeAxisView* a, TimeAxisView* b) {
533                 return a->stripable()->presentation_info().order() < b->stripable()->presentation_info().order();
534         }
535 };
536
537 RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
538         : Drag (e, i)
539         , _primary (p)
540         , _ntracks (0)
541 {
542         _editor->visible_order_range (&_visible_y_low, &_visible_y_high);
543
544         /* Make a list of tracks to refer to during the drag; we include hidden tracks,
545            as some of the regions we are dragging may be on such tracks.
546         */
547
548         TrackViewList track_views = _editor->track_views;
549         track_views.sort (PresentationInfoTimeAxisViewSorter ());
550
551         for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
552                 _time_axis_views.push_back (*i);
553
554                 TimeAxisView::Children children_list = (*i)->get_child_list ();
555                 for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
556                         _time_axis_views.push_back (j->get());
557                 }
558         }
559
560         /* the list of views can be empty at this point if this is a region list-insert drag
561          */
562
563         for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
564                 _views.push_back (DraggingView (*i, this, &(*i)->get_time_axis_view()));
565         }
566
567         RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), boost::bind (&RegionDrag::region_going_away, this, _1), gui_context());
568 }
569
570 void
571 RegionDrag::region_going_away (RegionView* v)
572 {
573         list<DraggingView>::iterator i = _views.begin ();
574         while (i != _views.end() && i->view != v) {
575                 ++i;
576         }
577
578         if (i != _views.end()) {
579                 _views.erase (i);
580         }
581 }
582
583 /** Given a TimeAxisView, return the index of it into the _time_axis_views vector,
584  *  or -1 if it is not found.
585  */
586 int
587 RegionDrag::find_time_axis_view (TimeAxisView* t) const
588 {
589         int i = 0;
590         int const N = _time_axis_views.size ();
591         while (i < N && _time_axis_views[i] != t) {
592                 ++i;
593         }
594
595         if (_time_axis_views[i] != t) {
596                 return -1;
597         }
598
599         return i;
600 }
601
602 /** determines if @pos is the closest frame to an exact musical division when using SnapMagnetic.
603  *  (SnapMagnetic may snap to an exact division or no division at all).
604  *  this is a hotfix for musical region position/trim. we should really
605  *  deliver musical divisors when snapping magnetically to avoid this.
606 */
607 int32_t
608 RegionDrag::current_music_divisor (framepos_t pos, int32_t button_state)
609 {
610         int32_t divisions = _editor->get_grid_music_divisions (button_state);
611
612         if (_editor->snap_mode() == Editing::SnapMagnetic && !ArdourKeyboard::indicates_snap (button_state)) {
613                 TempoMap& tmap (_editor->session()->tempo_map());
614                 const framepos_t exact_qn_pos = tmap.frame_at_quarter_note (tmap.exact_qn_at_frame (pos, divisions));
615
616                 if (pos != exact_qn_pos) {
617                         /* magnetic has not snapped */
618                         divisions = 0;
619                 }
620         }
621
622         return divisions;
623 }
624
625 RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
626         : RegionDrag (e, i, p, v)
627         , _brushing (b)
628         , _ignore_video_lock (false)
629         , _total_x_delta (0)
630         , _last_pointer_time_axis_view (0)
631         , _last_pointer_layer (0)
632         , _ndropzone (0)
633         , _pdropzone (0)
634         , _ddropzone (0)
635 {
636         DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n");
637 }
638
639 void
640 RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
641 {
642         Drag::start_grab (event, cursor);
643         setup_snap_delta (_last_frame_position);
644
645         show_verbose_cursor_time (_last_frame_position);
646
647         pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (current_pointer_y ());
648         if (tv.first) {
649                 _last_pointer_time_axis_view = find_time_axis_view (tv.first);
650                 assert(_last_pointer_time_axis_view >= 0);
651                 _last_pointer_layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
652         }
653
654         if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
655                 _ignore_video_lock = true;
656         }
657
658         if (_brushing) {
659                 /* cross track dragging seems broken here. disabled for now. */
660                 _y_constrained = true;
661         }
662 }
663
664 double
665 RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_region_position)
666 {
667         /* compute the amount of pointer motion in frames, and where
668            the region would be if we moved it by that much.
669         */
670         *pending_region_position = adjusted_frame (_drags->current_pointer_frame (), event, false);
671
672         framepos_t sync_frame;
673         framecnt_t sync_offset;
674         int32_t sync_dir;
675
676         sync_offset = _primary->region()->sync_offset (sync_dir);
677
678         /* we don't handle a sync point that lies before zero.
679          */
680         if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
681
682                 framecnt_t const sd = snap_delta (event->button.state);
683                 sync_frame = *pending_region_position + (sync_dir * sync_offset) + sd;
684
685                 _editor->snap_to_with_modifier (sync_frame, event);
686
687                 *pending_region_position = _primary->region()->adjust_to_sync (sync_frame) - sd;
688
689         } else {
690                 *pending_region_position = _last_frame_position;
691         }
692
693         if (*pending_region_position > max_framepos - _primary->region()->length()) {
694                 *pending_region_position = _last_frame_position;
695         }
696
697         double dx = 0;
698
699         bool const x_move_allowed = !_x_constrained;
700
701         if ((*pending_region_position != _last_frame_position) && x_move_allowed) {
702
703                 /* x movement since last time (in pixels) */
704                 dx = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel;
705
706                 /* total x movement */
707                 framecnt_t total_dx = *pending_region_position;
708                 if (regions_came_from_canvas()) {
709                         total_dx = total_dx - grab_frame ();
710                 }
711
712                 /* check that no regions have gone off the start of the session */
713                 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
714                         if ((i->view->region()->position() + total_dx) < 0) {
715                                 dx = 0;
716                                 *pending_region_position = _last_frame_position;
717                                 break;
718                         }
719                 }
720
721         }
722
723         return dx;
724 }
725
726 int
727 RegionDrag::apply_track_delta (const int start, const int delta, const int skip, const bool distance_only) const
728 {
729         if (delta == 0) {
730                 return start;
731         }
732
733         const int tavsize  = _time_axis_views.size();
734         const int dt = delta > 0 ? +1 : -1;
735         int current  = start;
736         int target   = start + delta - skip;
737
738         assert (current < 0 || current >= tavsize || !_time_axis_views[current]->hidden());
739         assert (skip == 0 || (skip < 0 && delta < 0) || (skip > 0 && delta > 0));
740
741         while (current >= 0 && current != target) {
742                 current += dt;
743                 if (current < 0 && dt < 0) {
744                         break;
745                 }
746                 if (current >= tavsize && dt > 0) {
747                         break;
748                 }
749                 if (current < 0 || current >= tavsize) {
750                         continue;
751                 }
752
753                 RouteTimeAxisView const * rtav = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[current]);
754                 if (_time_axis_views[current]->hidden() || !rtav || !rtav->is_track()) {
755                         target += dt;
756                 }
757
758                 if (distance_only && current == start + delta) {
759                         break;
760                 }
761         }
762         return target;
763 }
764
765 bool
766 RegionMotionDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
767 {
768         if (_y_constrained) {
769                 return false;
770         }
771
772         const int tavsize  = _time_axis_views.size();
773         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
774                 int n = apply_track_delta (i->time_axis_view, delta_track, skip_invisible);
775                 assert (n < 0 || n >= tavsize || !_time_axis_views[n]->hidden());
776
777                 if (i->time_axis_view < 0 || i->time_axis_view >= tavsize) {
778                         /* already in the drop zone */
779                         if (delta_track >= 0) {
780                                 /* downward motion - OK if others are still not in the dropzone */
781                                 continue;
782                         }
783
784                 }
785
786                 if (n < 0) {
787                         /* off the top */
788                         return false;
789                 } else if (n >= tavsize) {
790                         /* downward motion into drop zone. That's fine. */
791                         continue;
792                 }
793
794                 RouteTimeAxisView const * to = dynamic_cast<RouteTimeAxisView const *> (_time_axis_views[n]);
795                 if (to == 0 || to->hidden() || !to->is_track() || to->track()->data_type() != i->view->region()->data_type()) {
796                         /* not a track, or the wrong type */
797                         return false;
798                 }
799
800                 double const l = i->layer + delta_layer;
801
802                 /* Note that we allow layer to be up to 0.5 below zero, as this is used by `Expanded'
803                    mode to allow the user to place a region below another on layer 0.
804                 */
805                 if (delta_track == 0 && (l < -0.5 || l >= int (to->view()->layers()))) {
806                         /* Off the top or bottom layer; note that we only refuse if the track hasn't changed.
807                            If it has, the layers will be munged later anyway, so it's ok.
808                         */
809                         return false;
810                 }
811         }
812
813         /* all regions being dragged are ok with this change */
814         return true;
815 }
816
817 struct DraggingViewSorter {
818         bool operator() (const DraggingView& a, const DraggingView& b) {
819                 return a.time_axis_view < b.time_axis_view;
820         }
821 };
822
823 void
824 RegionMotionDrag::motion (GdkEvent* event, bool first_move)
825 {
826         double delta_layer = 0;
827         int delta_time_axis_view = 0;
828         int current_pointer_time_axis_view = -1;
829
830         assert (!_views.empty ());
831
832         /* Note: time axis views in this method are often expressed as an index into the _time_axis_views vector */
833
834         /* Find the TimeAxisView that the pointer is now over */
835         const double cur_y = current_pointer_y ();
836         pair<TimeAxisView*, double> const r = _editor->trackview_by_y_position (cur_y);
837         TimeAxisView* tv = r.first;
838
839         if (!tv && cur_y < 0) {
840                 /* above trackview area, autoscroll hasn't moved us since last time, nothing to do */
841                 return;
842         }
843
844         /* find drop-zone y-position */
845         Coord last_track_bottom_edge;
846         last_track_bottom_edge = 0;
847         for (std::vector<TimeAxisView*>::reverse_iterator t = _time_axis_views.rbegin(); t != _time_axis_views.rend(); ++t) {
848                 if (!(*t)->hidden()) {
849                         last_track_bottom_edge = (*t)->canvas_display()->canvas_origin ().y + (*t)->effective_height();
850                         break;
851                 }
852         }
853
854         if (tv && tv->view()) {
855                 /* the mouse is over a track */
856                 double layer = r.second;
857
858                 if (first_move && tv->view()->layer_display() == Stacked) {
859                         tv->view()->set_layer_display (Expanded);
860                 }
861
862                 /* Here's the current pointer position in terms of time axis view and layer */
863                 current_pointer_time_axis_view = find_time_axis_view (tv);
864                 assert(current_pointer_time_axis_view >= 0);
865
866                 double const current_pointer_layer = tv->layer_display() == Overlaid ? 0 : layer;
867
868                 /* Work out the change in y */
869
870                 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
871                 if (!rtv || !rtv->is_track()) {
872                         /* ignore non-tracks early on. we can't move any regions on them */
873                 } else if (_last_pointer_time_axis_view < 0) {
874                         /* Was in the drop-zone, now over a track.
875                          * Hence it must be an upward move (from the bottom)
876                          *
877                          * track_index is still -1, so delta must be set to
878                          * move up the correct number of tracks from the bottom.
879                          *
880                          * This is necessary because steps may be skipped if
881                          * the bottom-most track is not a valid target and/or
882                          * if there are hidden tracks at the bottom.
883                          * Hence the initial offset (_ddropzone) as well as the
884                          * last valid pointer position (_pdropzone) need to be
885                          * taken into account.
886                          */
887                         delta_time_axis_view = current_pointer_time_axis_view - _time_axis_views.size () + _ddropzone - _pdropzone;
888                 } else {
889                         delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view;
890                 }
891
892                 /* TODO needs adjustment per DraggingView,
893                  *
894                  * e.g. select one region on the top-layer of a track
895                  * and one region which is at the bottom-layer of another track
896                  * drag both.
897                  *
898                  * Indicated drop-zones and layering is wrong.
899                  * and may infer additional layers on the target-track
900                  * (depending how many layers the original track had).
901                  *
902                  * Or select two regions (different layers) on a same track,
903                  * move across a non-layer track.. -> layering info is lost.
904                  * on drop either of the regions may be on top.
905                  *
906                  * Proposed solution: screw it :) well,
907                  *   don't use delta_layer, use an absolute value
908                  *   1) remember DraggingView's layer  as float 0..1  [current layer / all layers of source]
909                  *   2) calculate current mouse pos, y-pos inside track divided by height of mouse-over track.
910                  *   3) iterate over all DraggingView, find the one that is over the track with most layers
911                  *   4) proportionally scale layer to layers available on target
912                  */
913                 delta_layer = current_pointer_layer - _last_pointer_layer;
914
915         }
916         /* for automation lanes, there is a TimeAxisView but no ->view()
917          * if (!tv) -> dropzone
918          */
919         else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view >= 0) {
920                 /* Moving into the drop-zone.. */
921                 delta_time_axis_view = _time_axis_views.size () - _last_pointer_time_axis_view;
922                 /* delta_time_axis_view may not be sufficient to move into the DZ
923                  * the mouse may enter it, but it may not be a valid move due to
924                  * constraints.
925                  *
926                  * -> remember the delta needed to move into the dropzone
927                  */
928                 _ddropzone = delta_time_axis_view;
929                 /* ..but subtract hidden tracks (or routes) at the bottom.
930                  * we silently move mover them
931                  */
932                 _ddropzone -= apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
933                               - _time_axis_views.size();
934         }
935         else if (!tv && cur_y >= 0 && _last_pointer_time_axis_view < 0) {
936                 /* move around inside the zone.
937                  * This allows to move further down until all regions are in the zone.
938                  */
939                 const double ptr_y = cur_y + _editor->get_trackview_group()->canvas_origin().y;
940                 assert(ptr_y >= last_track_bottom_edge);
941                 assert(_ddropzone > 0);
942
943                 /* calculate mouse position in 'tracks' below last track. */
944                 const double dzi_h = TimeAxisView::preset_height (HeightNormal);
945                 uint32_t dzpos = _ddropzone + floor((1 + ptr_y - last_track_bottom_edge) / dzi_h);
946
947                 if (dzpos > _pdropzone && _ndropzone < _ntracks) {
948                         // move further down
949                         delta_time_axis_view =  dzpos - _pdropzone;
950                 } else if (dzpos < _pdropzone && _ndropzone > 0) {
951                         // move up inside the DZ
952                         delta_time_axis_view =  dzpos - _pdropzone;
953                 }
954         }
955
956         /* Work out the change in x */
957         framepos_t pending_region_position;
958         double const x_delta = compute_x_delta (event, &pending_region_position);
959         _last_frame_position = pending_region_position;
960
961         /* calculate hidden tracks in current y-axis delta */
962         int delta_skip = 0;
963         if (_last_pointer_time_axis_view < 0 && _pdropzone > 0) {
964                 /* The mouse is more than one track below the dropzone.
965                  * distance calculation is not needed (and would not work, either
966                  * because the dropzone is "packed").
967                  *
968                  * Except when [partially] moving regions out of dropzone in a large step.
969                  * (the mouse may or may not remain in the DZ)
970                  * Hidden tracks at the bottom of the TAV need to be skipped.
971                  *
972                  * This also handles the case if the mouse entered the DZ
973                  * in a large step (exessive delta), either due to fast-movement,
974                  * autoscroll, laggy UI. _ddropzone copensates for that (see "move into dz" above)
975                  */
976                 if (delta_time_axis_view < 0 && (int)_ddropzone - delta_time_axis_view >= (int)_pdropzone) {
977                         const int dt = delta_time_axis_view + (int)_pdropzone - (int)_ddropzone;
978                         assert(dt <= 0);
979                         delta_skip = apply_track_delta(_time_axis_views.size(), dt, 0, true)
980                                 -_time_axis_views.size() - dt;
981                 }
982         }
983         else if (_last_pointer_time_axis_view < 0) {
984                 /* Moving out of the zone. Check for hidden tracks at the bottom. */
985                 delta_skip = apply_track_delta(_time_axis_views.size(), delta_time_axis_view, 0, true)
986                              -_time_axis_views.size() - delta_time_axis_view;
987         } else {
988                 /* calculate hidden tracks that are skipped by the pointer movement */
989                 delta_skip = apply_track_delta(_last_pointer_time_axis_view, delta_time_axis_view, 0, true)
990                              - _last_pointer_time_axis_view
991                              - delta_time_axis_view;
992         }
993
994         /* Verify change in y */
995         if (!y_movement_allowed (delta_time_axis_view, delta_layer, delta_skip)) {
996                 /* this y movement is not allowed, so do no y movement this time */
997                 delta_time_axis_view = 0;
998                 delta_layer = 0;
999                 delta_skip = 0;
1000         }
1001
1002         if (x_delta == 0 && (tv && tv->view() && delta_time_axis_view == 0) && delta_layer == 0 && !first_move) {
1003                 /* haven't reached next snap point, and we're not switching
1004                    trackviews nor layers. nothing to do.
1005                 */
1006                 return;
1007         }
1008
1009         typedef map<boost::shared_ptr<Playlist>, double> PlaylistDropzoneMap;
1010         PlaylistDropzoneMap playlist_dropzone_map;
1011         _ndropzone = 0; // number of elements currently in the dropzone
1012
1013         if (first_move) {
1014                 /* sort views by time_axis.
1015                  * This retains track order in the dropzone, regardless
1016                  * of actual selection order
1017                  */
1018                 _views.sort (DraggingViewSorter());
1019
1020                 /* count number of distinct tracks of all regions
1021                  * being dragged, used for dropzone.
1022                  */
1023                 int prev_track = -1;
1024                 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1025                         if (i->time_axis_view != prev_track) {
1026                                 prev_track = i->time_axis_view;
1027                                 ++_ntracks;
1028                         }
1029                 }
1030 #ifndef NDEBUG
1031                 int spread =
1032                         _views.back().time_axis_view -
1033                         _views.front().time_axis_view;
1034
1035                 spread -= apply_track_delta (_views.front().time_axis_view, spread, 0, true)
1036                           -  _views.back().time_axis_view;
1037
1038                 printf("Dragging region(s) from %d different track(s), max dist: %d\n", _ntracks, spread);
1039 #endif
1040         }
1041
1042         for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1043
1044                 RegionView* rv = i->view;
1045                 double y_delta;
1046
1047                 y_delta = 0;
1048
1049                 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1050                         continue;
1051                 }
1052
1053                 if (first_move) {
1054                         rv->drag_start ();
1055
1056                         /* reparent the regionview into a group above all
1057                          * others
1058                          */
1059
1060                         ArdourCanvas::Item* rvg = rv->get_canvas_group();
1061                         Duple rv_canvas_offset = rvg->parent()->canvas_origin ();
1062                         Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
1063                         rv->get_canvas_group()->reparent (_editor->_drag_motion_group);
1064                         /* move the item so that it continues to appear at the
1065                            same location now that its parent has changed.
1066                            */
1067                         rvg->move (rv_canvas_offset - dmg_canvas_offset);
1068                 }
1069
1070                 /* If we have moved tracks, we'll fudge the layer delta so that the
1071                    region gets moved back onto layer 0 on its new track; this avoids
1072                    confusion when dragging regions from non-zero layers onto different
1073                    tracks.
1074                 */
1075                 double this_delta_layer = delta_layer;
1076                 if (delta_time_axis_view != 0) {
1077                         this_delta_layer = - i->layer;
1078                 }
1079
1080                 int this_delta_time_axis_view = apply_track_delta(i->time_axis_view, delta_time_axis_view, delta_skip) - i->time_axis_view;
1081
1082                 int track_index = i->time_axis_view + this_delta_time_axis_view;
1083                 assert(track_index >= 0);
1084
1085                 if (track_index < 0 || track_index >= (int) _time_axis_views.size()) {
1086                         /* Track is in the Dropzone */
1087
1088                         i->time_axis_view = track_index;
1089                         assert(i->time_axis_view >= (int) _time_axis_views.size());
1090                         if (cur_y >= 0) {
1091
1092                                 double yposition = 0;
1093                                 PlaylistDropzoneMap::iterator pdz = playlist_dropzone_map.find (i->view->region()->playlist());
1094                                 rv->set_height (TimeAxisView::preset_height (HeightNormal));
1095                                 ++_ndropzone;
1096
1097                                 /* store index of each new playlist as a negative count, starting at -1 */
1098
1099                                 if (pdz == playlist_dropzone_map.end()) {
1100                                         /* compute where this new track (which doesn't exist yet) will live
1101                                            on the y-axis.
1102                                         */
1103                                         yposition = last_track_bottom_edge; /* where to place the top edge of the regionview */
1104
1105                                         /* How high is this region view ? */
1106
1107                                         boost::optional<ArdourCanvas::Rect> obbox = rv->get_canvas_group()->bounding_box ();
1108                                         ArdourCanvas::Rect bbox;
1109
1110                                         if (obbox) {
1111                                                 bbox = obbox.get ();
1112                                         }
1113
1114                                         last_track_bottom_edge += bbox.height();
1115
1116                                         playlist_dropzone_map.insert (make_pair (i->view->region()->playlist(), yposition));
1117
1118                                 } else {
1119                                         yposition = pdz->second;
1120                                 }
1121
1122                                 /* values are zero or negative, hence the use of min() */
1123                                 y_delta = yposition - rv->get_canvas_group()->canvas_origin().y;
1124                         }
1125
1126                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1127                         if (mrv) {
1128                                 mrv->apply_note_range (60, 71, true);
1129                         }
1130                 } else {
1131
1132                         /* The TimeAxisView that this region is now over */
1133                         TimeAxisView* current_tv = _time_axis_views[track_index];
1134
1135                         /* Ensure it is moved from stacked -> expanded if appropriate */
1136                         if (current_tv->view()->layer_display() == Stacked) {
1137                                 current_tv->view()->set_layer_display (Expanded);
1138                         }
1139
1140                         /* We're only allowed to go -ve in layer on Expanded views */
1141                         if (current_tv->view()->layer_display() != Expanded && (i->layer + this_delta_layer) < 0) {
1142                                 this_delta_layer = - i->layer;
1143                         }
1144
1145                         /* Set height */
1146                         rv->set_height (current_tv->view()->child_height ());
1147
1148                         /* Update show/hidden status as the region view may have come from a hidden track,
1149                            or have moved to one.
1150                         */
1151                         if (current_tv->hidden ()) {
1152                                 rv->get_canvas_group()->hide ();
1153                         } else {
1154                                 rv->get_canvas_group()->show ();
1155                         }
1156
1157                         /* Update the DraggingView */
1158                         i->time_axis_view = track_index;
1159                         i->layer += this_delta_layer;
1160
1161                         if (_brushing) {
1162                                 _editor->mouse_brush_insert_region (rv, pending_region_position);
1163                         } else {
1164                                 Duple track_origin;
1165
1166                                 /* Get the y coordinate of the top of the track that this region is now over */
1167                                 track_origin = current_tv->canvas_display()->item_to_canvas (track_origin);
1168
1169                                 /* And adjust for the layer that it should be on */
1170                                 StreamView* cv = current_tv->view ();
1171                                 switch (cv->layer_display ()) {
1172                                 case Overlaid:
1173                                         break;
1174                                 case Stacked:
1175                                         track_origin.y += (cv->layers() - i->layer - 1) * cv->child_height ();
1176                                         break;
1177                                 case Expanded:
1178                                         track_origin.y += (cv->layers() - i->layer - 0.5) * 2 * cv->child_height ();
1179                                         break;
1180                                 }
1181
1182                                 /* need to get the parent of the regionview
1183                                  * canvas group and get its position in
1184                                  * equivalent coordinate space as the trackview
1185                                  * we are now dragging over.
1186                                  */
1187
1188                                 y_delta = track_origin.y - rv->get_canvas_group()->canvas_origin().y;
1189
1190                         }
1191
1192                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1193                         if (mrv) {
1194                                 MidiStreamView* msv;
1195                                 if ((msv = dynamic_cast <MidiStreamView*> (current_tv->view())) != 0) {
1196                                         mrv->apply_note_range (msv->lowest_note(), msv->highest_note(), true);
1197                                 }
1198                         }
1199                 }
1200
1201                 /* Now move the region view */
1202                 rv->move (x_delta, y_delta);
1203
1204         } /* foreach region */
1205
1206         _total_x_delta += x_delta;
1207
1208         if (x_delta != 0 && !_brushing) {
1209                 show_verbose_cursor_time (_last_frame_position);
1210         }
1211
1212         /* keep track of pointer movement */
1213         if (tv) {
1214                 /* the pointer is currently over a time axis view */
1215
1216                 if (_last_pointer_time_axis_view < 0) {
1217                         /* last motion event was not over a time axis view
1218                          * or last y-movement out of the dropzone was not valid
1219                          */
1220                         int dtz = 0;
1221                         if (delta_time_axis_view < 0) {
1222                                 /* in the drop zone, moving up */
1223
1224                                 /* _pdropzone is the last known pointer y-axis position inside the DZ.
1225                                  * We do not use negative _last_pointer_time_axis_view because
1226                                  * the dropzone is "packed" (the actual track offset is ignored)
1227                                  *
1228                                  * As opposed to the actual number
1229                                  * of elements in the dropzone (_ndropzone)
1230                                  * _pdropzone is not constrained. This is necessary
1231                                  * to allow moving multiple regions with y-distance
1232                                  * into the DZ.
1233                                  *
1234                                  * There can be 0 elements in the dropzone,
1235                                  * even though the drag-pointer is inside the DZ.
1236                                  *
1237                                  * example:
1238                                  * [ Audio-track, Midi-track, Audio-track, DZ ]
1239                                  * move regions from both audio tracks at the same time into the
1240                                  * DZ by grabbing the region in the bottom track.
1241                                  */
1242                                 assert(current_pointer_time_axis_view >= 0);
1243                                 dtz = std::min((int)_pdropzone, (int)_ddropzone - delta_time_axis_view);
1244                                 _pdropzone -= dtz;
1245                         }
1246
1247                         /* only move out of the zone if the movement is OK */
1248                         if (_pdropzone == 0 && delta_time_axis_view != 0) {
1249                                 assert(delta_time_axis_view < 0);
1250                                 _last_pointer_time_axis_view = current_pointer_time_axis_view;
1251                                 /* if all logic and maths are correct, there is no need to assign the 'current' pointer.
1252                                  * the current position can be calculated as follows:
1253                                  */
1254                                 // a well placed oofus attack can still throw this off.
1255                                 // likley auto-scroll related, printf() debugging may tell, commented out for now.
1256                                 //assert (current_pointer_time_axis_view == _time_axis_views.size() - dtz + _ddropzone + delta_time_axis_view);
1257                         }
1258                 } else {
1259                         /* last motion event was also over a time axis view */
1260                         _last_pointer_time_axis_view += delta_time_axis_view;
1261                         assert(_last_pointer_time_axis_view >= 0);
1262                 }
1263
1264         } else {
1265
1266                 /* the pointer is not over a time axis view */
1267                 assert ((delta_time_axis_view > 0) || (((int)_pdropzone) >= (delta_skip - delta_time_axis_view)));
1268                 _pdropzone += delta_time_axis_view - delta_skip;
1269                 _last_pointer_time_axis_view = -1; // <0 : we're in the zone, value does not matter.
1270         }
1271
1272         _last_pointer_layer += delta_layer;
1273 }
1274
1275 void
1276 RegionMoveDrag::motion (GdkEvent* event, bool first_move)
1277 {
1278         if (_copy && first_move) {
1279                 if (_x_constrained && !_brushing) {
1280                         _editor->begin_reversible_command (Operations::fixed_time_region_copy);
1281                 } else if (!_brushing) {
1282                         _editor->begin_reversible_command (Operations::region_copy);
1283                 } else if (_brushing) {
1284                         _editor->begin_reversible_command (Operations::drag_region_brush);
1285                 }
1286                 /* duplicate the regionview(s) and region(s) */
1287
1288                 list<DraggingView> new_regionviews;
1289
1290                 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1291
1292                         RegionView* rv = i->view;
1293                         AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
1294                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);
1295
1296                         const boost::shared_ptr<const Region> original = rv->region();
1297                         boost::shared_ptr<Region> region_copy;
1298
1299                         if (rv == _primary) {
1300                                 region_copy = RegionFactory::create (original, true
1301                                                                      , current_music_divisor (original->position(), event->button.state));
1302                         } else {
1303                                 region_copy = RegionFactory::create (original, true, 0);
1304                         }
1305
1306                         /* need to set this so that the drop zone code can work. This doesn't
1307                            actually put the region into the playlist, but just sets a weak pointer
1308                            to it.
1309                         */
1310                         region_copy->set_playlist (original->playlist());
1311
1312                         RegionView* nrv;
1313                         if (arv) {
1314                                 boost::shared_ptr<AudioRegion> audioregion_copy
1315                                 = boost::dynamic_pointer_cast<AudioRegion>(region_copy);
1316
1317                                 nrv = new AudioRegionView (*arv, audioregion_copy);
1318                         } else if (mrv) {
1319                                 boost::shared_ptr<MidiRegion> midiregion_copy
1320                                         = boost::dynamic_pointer_cast<MidiRegion>(region_copy);
1321                                 nrv = new MidiRegionView (*mrv, midiregion_copy);
1322                         } else {
1323                                 continue;
1324                         }
1325
1326                         nrv->get_canvas_group()->show ();
1327                         new_regionviews.push_back (DraggingView (nrv, this, i->initial_time_axis_view));
1328
1329                         /* swap _primary to the copy */
1330
1331                         if (rv == _primary) {
1332                                 _primary = nrv;
1333                         }
1334
1335                         /* ..and deselect the one we copied */
1336
1337                         rv->set_selected (false);
1338                 }
1339
1340                 if (!new_regionviews.empty()) {
1341
1342                         /* reflect the fact that we are dragging the copies */
1343
1344                         _views = new_regionviews;
1345
1346                         swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);
1347                 }
1348
1349         } else if (!_copy && first_move) {
1350                 if (_x_constrained && !_brushing) {
1351                         _editor->begin_reversible_command (_("fixed time region drag"));
1352                 } else if (!_brushing) {
1353                         _editor->begin_reversible_command (Operations::region_drag);
1354                 } else if (_brushing) {
1355                         _editor->begin_reversible_command (Operations::drag_region_brush);
1356                 }
1357         }
1358         RegionMotionDrag::motion (event, first_move);
1359 }
1360
1361 void
1362 RegionMotionDrag::finished (GdkEvent *, bool)
1363 {
1364         for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1365                 if (!(*i)->view()) {
1366                         continue;
1367                 }
1368
1369                 if ((*i)->view()->layer_display() == Expanded) {
1370                         (*i)->view()->set_layer_display (Stacked);
1371                 }
1372         }
1373 }
1374
1375 void
1376 RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred)
1377 {
1378         RegionMotionDrag::finished (ev, movement_occurred);
1379
1380         if (!movement_occurred) {
1381
1382                 /* just a click */
1383
1384                 if (was_double_click() && !_views.empty()) {
1385                         DraggingView dv = _views.front();
1386                         _editor->edit_region (dv.view);
1387                 }
1388
1389                 return;
1390         }
1391
1392         assert (!_views.empty ());
1393
1394         /* We might have hidden region views so that they weren't visible during the drag
1395            (when they have been reparented).  Now everything can be shown again, as region
1396            views are back in their track parent groups.
1397         */
1398         for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1399                 i->view->get_canvas_group()->show ();
1400         }
1401
1402         bool const changed_position = (_last_frame_position != _primary->region()->position());
1403         bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view());
1404         framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position;
1405
1406         if (_copy) {
1407
1408                 finished_copy (
1409                         changed_position,
1410                         changed_tracks,
1411                         drag_delta,
1412                         ev->button.state
1413                         );
1414
1415         } else {
1416
1417                 finished_no_copy (
1418                         changed_position,
1419                         changed_tracks,
1420                         drag_delta,
1421                         ev->button.state
1422                         );
1423
1424         }
1425 }
1426
1427 RouteTimeAxisView*
1428 RegionMoveDrag::create_destination_time_axis (boost::shared_ptr<Region> region, TimeAxisView* original)
1429 {
1430         /* Add a new track of the correct type, and return the RouteTimeAxisView that is created to display the
1431            new track.
1432          */
1433         TimeAxisView* tav = 0;
1434         try {
1435                 if (boost::dynamic_pointer_cast<AudioRegion> (region)) {
1436                         list<boost::shared_ptr<AudioTrack> > audio_tracks;
1437                         uint32_t output_chan = region->n_channels();
1438                         if ((Config->get_output_auto_connect() & AutoConnectMaster) && _editor->session()->master_out()) {
1439                                 output_chan =  _editor->session()->master_out()->n_inputs().n_audio();
1440                         }
1441                         audio_tracks = _editor->session()->new_audio_track (region->n_channels(), output_chan, 0, 1, region->name(), PresentationInfo::max_order);
1442                         tav =_editor->axis_view_from_stripable (audio_tracks.front());
1443                 } else {
1444                         ChanCount one_midi_port (DataType::MIDI, 1);
1445                         list<boost::shared_ptr<MidiTrack> > midi_tracks;
1446                         midi_tracks = _editor->session()->new_midi_track (one_midi_port, one_midi_port,
1447                                                                           Config->get_strict_io () || Profile->get_mixbus (),
1448                                                                           boost::shared_ptr<ARDOUR::PluginInfo>(),
1449                                                                           (ARDOUR::Plugin::PresetRecord*) 0,
1450                                                                           (ARDOUR::RouteGroup*) 0, 1, region->name(), PresentationInfo::max_order);
1451                         tav = _editor->axis_view_from_stripable (midi_tracks.front());
1452                 }
1453
1454                 if (tav) {
1455                         tav->set_height (original->current_height());
1456                 }
1457         } catch (...) {
1458                 error << _("Could not create new track after region placed in the drop zone") << endmsg;
1459         }
1460
1461         return dynamic_cast<RouteTimeAxisView*> (tav);
1462 }
1463
1464 void
1465 RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed_tracks*/, framecnt_t const drag_delta, int32_t const ev_state)
1466 {
1467         RegionSelection new_views;
1468         PlaylistSet modified_playlists;
1469         RouteTimeAxisView* new_time_axis_view = 0;
1470
1471         int32_t divisor = current_music_divisor (_primary->region()->position() - drag_delta, ev_state);
1472         TempoMap& tmap (_editor->session()->tempo_map());
1473         double qn_delta = _primary->region()->quarter_note() - tmap.exact_qn_at_frame (_primary->region()->position() - drag_delta, divisor);
1474
1475         if (_brushing) {
1476                 /* all changes were made during motion event handlers */
1477
1478                 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
1479                         delete i->view;
1480                 }
1481
1482                 _editor->commit_reversible_command ();
1483                 return;
1484         }
1485
1486         typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1487         PlaylistMapping playlist_mapping;
1488
1489         /* insert the regions into their new playlists */
1490         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1491
1492                 RouteTimeAxisView* dest_rtv = 0;
1493
1494                 if (i->view->region()->locked() || (i->view->region()->video_locked() && !_ignore_video_lock)) {
1495                         continue;
1496                 }
1497
1498                 framepos_t where;
1499
1500                 if (changed_position && !_x_constrained) {
1501                         where = i->view->region()->position() - drag_delta;
1502                 } else {
1503                         where = i->view->region()->position();
1504                 }
1505
1506                 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1507                         /* dragged to drop zone */
1508
1509                         PlaylistMapping::iterator pm;
1510
1511                         if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1512                                 /* first region from this original playlist: create a new track */
1513                                 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1514                                 if(!new_time_axis_view) {
1515                                         Drag::abort();
1516                                         return;
1517                                 }
1518                                 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1519                                 dest_rtv = new_time_axis_view;
1520                         } else {
1521                                 /* we already created a new track for regions from this playlist, use it */
1522                                 dest_rtv = pm->second;
1523                         }
1524                 } else {
1525                         /* destination time axis view is the one we dragged to */
1526                         dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1527                 }
1528
1529                 if (dest_rtv != 0) {
1530                         RegionView* new_view;
1531                         if (i->view == _primary) {
1532                                 new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1533                                                                         modified_playlists, current_music_divisor (where, ev_state));
1534                         } else {
1535                                 if (i->view->region()->position_lock_style() == AudioTime) {
1536                                         new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1537                                                                                 modified_playlists, 0);
1538                                 } else {
1539                                         where = tmap.frame_at_quarter_note (i->view->region()->quarter_note() - qn_delta);
1540                                         new_view = insert_region_into_playlist (i->view->region(), dest_rtv, i->layer, where,
1541                                                                                 modified_playlists, 0);
1542                                 }
1543                         }
1544
1545                         if (new_view != 0) {
1546                                 new_views.push_back (new_view);
1547                         }
1548                 }
1549
1550                 /* Delete the copy of the view that was used for dragging. Need to play safe with the iterator
1551                    since deletion will automagically remove it from _views, thus invalidating i as an iterator.
1552                  */
1553
1554                 list<DraggingView>::const_iterator next = i;
1555                 ++next;
1556                 delete i->view;
1557                 i = next;
1558         }
1559
1560         /* If we've created new regions either by copying or moving
1561            to a new track, we want to replace the old selection with the new ones
1562         */
1563
1564         if (new_views.size() > 0) {
1565                 _editor->selection->set (new_views);
1566         }
1567
1568         /* write commands for the accumulated diffs for all our modified playlists */
1569         add_stateful_diff_commands_for_playlists (modified_playlists);
1570
1571         _editor->commit_reversible_command ();
1572 }
1573
1574 void
1575 RegionMoveDrag::finished_no_copy (
1576         bool const changed_position,
1577         bool const changed_tracks,
1578         framecnt_t const drag_delta,
1579         int32_t const ev_state
1580         )
1581 {
1582         RegionSelection new_views;
1583         PlaylistSet modified_playlists;
1584         PlaylistSet frozen_playlists;
1585         set<RouteTimeAxisView*> views_to_update;
1586         RouteTimeAxisView* new_time_axis_view = 0;
1587
1588         typedef map<boost::shared_ptr<Playlist>, RouteTimeAxisView*> PlaylistMapping;
1589         PlaylistMapping playlist_mapping;
1590
1591         int32_t divisor = current_music_divisor (_primary->region()->position() - drag_delta, ev_state);
1592         TempoMap& tmap (_editor->session()->tempo_map());
1593         double qn_delta = _primary->region()->quarter_note() - tmap.exact_qn_at_frame (_primary->region()->position() - drag_delta, divisor);
1594
1595         std::set<boost::shared_ptr<const Region> > uniq;
1596         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {
1597
1598                 RegionView* rv = i->view;
1599                 RouteTimeAxisView* dest_rtv = 0;
1600
1601                 if (rv->region()->locked() || (rv->region()->video_locked() && !_ignore_video_lock)) {
1602                         ++i;
1603                         continue;
1604                 }
1605
1606                 if (uniq.find (rv->region()) != uniq.end()) {
1607                         /* prevent duplicate moves when selecting regions from shared playlists */
1608                         ++i;
1609                         continue;
1610                 }
1611                 uniq.insert(rv->region());
1612
1613                 if (i->time_axis_view < 0 || i->time_axis_view >= (int)_time_axis_views.size()) {
1614                         /* dragged to drop zone */
1615
1616                         PlaylistMapping::iterator pm;
1617
1618                         if ((pm = playlist_mapping.find (i->view->region()->playlist())) == playlist_mapping.end()) {
1619                                 /* first region from this original playlist: create a new track */
1620                                 new_time_axis_view = create_destination_time_axis (i->view->region(), i->initial_time_axis_view);
1621                                 if(!new_time_axis_view) { // New track creation failed
1622                                         Drag::abort();
1623                                         return;
1624                                 }
1625                                 playlist_mapping.insert (make_pair (i->view->region()->playlist(), new_time_axis_view));
1626                                 dest_rtv = new_time_axis_view;
1627                         } else {
1628                                 /* we already created a new track for regions from this playlist, use it */
1629                                 dest_rtv = pm->second;
1630                         }
1631
1632                 } else {
1633                         /* destination time axis view is the one we dragged to */
1634                         dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[i->time_axis_view]);
1635                 }
1636
1637                 assert (dest_rtv);
1638
1639                 double const dest_layer = i->layer;
1640
1641                 views_to_update.insert (dest_rtv);
1642
1643                 framepos_t where;
1644
1645                 if (changed_position && !_x_constrained) {
1646                         where = rv->region()->position() - drag_delta;
1647                 } else {
1648                         where = rv->region()->position();
1649                 }
1650
1651                 if (changed_tracks) {
1652
1653                         /* insert into new playlist */
1654                         RegionView* new_view;
1655                         if (rv == _primary) {
1656                                 new_view = insert_region_into_playlist (
1657                                         RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1658                                         modified_playlists, current_music_divisor (where, ev_state)
1659                                         );
1660                         } else {
1661                                 if (rv->region()->position_lock_style() == AudioTime) {
1662
1663                                         new_view = insert_region_into_playlist (
1664                                                 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1665                                                 modified_playlists, 0
1666                                                 );
1667                                 } else {
1668                                         where = tmap.frame_at_quarter_note (rv->region()->quarter_note() - qn_delta);
1669                                         new_view = insert_region_into_playlist (
1670                                                 RegionFactory::create (rv->region (), true), dest_rtv, dest_layer, where,
1671                                                 modified_playlists, 0
1672                                                 );
1673                                 }
1674                         }
1675
1676                         if (new_view == 0) {
1677                                 ++i;
1678                                 continue;
1679                         }
1680
1681                         new_views.push_back (new_view);
1682
1683                         /* remove from old playlist */
1684
1685                         /* the region that used to be in the old playlist is not
1686                            moved to the new one - we use a copy of it. as a result,
1687                            any existing editor for the region should no longer be
1688                            visible.
1689                         */
1690                         rv->hide_region_editor();
1691
1692
1693                         remove_region_from_playlist (rv->region(), i->initial_playlist, modified_playlists);
1694
1695                 } else {
1696
1697                         boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1698
1699                         /* this movement may result in a crossfade being modified, or a layering change,
1700                            so we need to get undo data from the playlist as well as the region.
1701                         */
1702
1703                         pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (playlist);
1704                         if (r.second) {
1705                                 playlist->clear_changes ();
1706                         }
1707
1708                         rv->region()->clear_changes ();
1709
1710                         /*
1711                            motion on the same track. plonk the previously reparented region
1712                            back to its original canvas group (its streamview).
1713                            No need to do anything for copies as they are fake regions which will be deleted.
1714                         */
1715
1716                         rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1717                         rv->get_canvas_group()->set_y_position (i->initial_y);
1718                         rv->drag_end ();
1719
1720                         /* just change the model */
1721                         if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1722                                 playlist->set_layer (rv->region(), dest_layer);
1723                         }
1724
1725                         /* freeze playlist to avoid lots of relayering in the case of a multi-region drag */
1726
1727                         r = frozen_playlists.insert (playlist);
1728
1729                         if (r.second) {
1730                                 playlist->freeze ();
1731                         }
1732                         if (rv == _primary) {
1733                                 rv->region()->set_position (where, current_music_divisor (where, ev_state));
1734                         } else {
1735                                 if (rv->region()->position_lock_style() == AudioTime) {
1736                                         rv->region()->set_position (where, 0);
1737                                 } else {
1738                                         rv->region()->set_position (tmap.frame_at_quarter_note (rv->region()->quarter_note() - qn_delta), 0);
1739
1740                                 }
1741                         }
1742                         _editor->session()->add_command (new StatefulDiffCommand (rv->region()));
1743                 }
1744
1745                 if (changed_tracks) {
1746
1747                         /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
1748                            was selected in all of them, then removing it from a playlist will have removed all
1749                            trace of it from _views (i.e. there were N regions selected, we removed 1,
1750                            but since its the same playlist for N tracks, all N tracks updated themselves, removed the
1751                            corresponding regionview, and _views is now empty).
1752
1753                            This could have invalidated any and all iterators into _views.
1754
1755                            The heuristic we use here is: if the region selection is empty, break out of the loop
1756                            here. if the region selection is not empty, then restart the loop because we know that
1757                            we must have removed at least the region(view) we've just been working on as well as any
1758                            that we processed on previous iterations.
1759
1760                            EXCEPT .... if we are doing a copy drag, then _views hasn't been modified and
1761                            we can just iterate.
1762                         */
1763
1764
1765                         if (_views.empty()) {
1766                                 break;
1767                         } else {
1768                                 i = _views.begin();
1769                         }
1770
1771                 } else {
1772                         ++i;
1773                 }
1774         }
1775
1776         /* If we've created new regions either by copying or moving
1777            to a new track, we want to replace the old selection with the new ones
1778         */
1779
1780         if (new_views.size() > 0) {
1781                 _editor->selection->set (new_views);
1782         }
1783
1784         for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
1785                 (*p)->thaw();
1786         }
1787
1788         /* write commands for the accumulated diffs for all our modified playlists */
1789         add_stateful_diff_commands_for_playlists (modified_playlists);
1790         /* applies to _brushing */
1791         _editor->commit_reversible_command ();
1792
1793         /* We have futzed with the layering of canvas items on our streamviews.
1794            If any region changed layer, this will have resulted in the stream
1795            views being asked to set up their region views, and all will be well.
1796            If not, we might now have badly-ordered region views.  Ask the StreamViews
1797            involved to sort themselves out, just in case.
1798         */
1799
1800         for (set<RouteTimeAxisView*>::iterator i = views_to_update.begin(); i != views_to_update.end(); ++i) {
1801                 (*i)->view()->playlist_layered ((*i)->track ());
1802         }
1803 }
1804
1805 /** Remove a region from a playlist, clearing the diff history of the playlist first if necessary.
1806  *  @param region Region to remove.
1807  *  @param playlist playlist To remove from.
1808  *  @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1809  *  that clear_changes () is only called once per playlist.
1810  */
1811 void
1812 RegionMoveDrag::remove_region_from_playlist (
1813         boost::shared_ptr<Region> region,
1814         boost::shared_ptr<Playlist> playlist,
1815         PlaylistSet& modified_playlists
1816         )
1817 {
1818         pair<set<boost::shared_ptr<Playlist> >::iterator, bool> r = modified_playlists.insert (playlist);
1819
1820         if (r.second) {
1821                 playlist->clear_changes ();
1822         }
1823
1824         playlist->remove_region (region); // should be no need to ripple; we better already have rippled the playlist in RegionRippleDrag
1825 }
1826
1827
1828 /** Insert a region into a playlist, handling the recovery of the resulting new RegionView, and
1829  *  clearing the playlist's diff history first if necessary.
1830  *  @param region Region to insert.
1831  *  @param dest_rtv Destination RouteTimeAxisView.
1832  *  @param dest_layer Destination layer.
1833  *  @param where Destination position.
1834  *  @param modified_playlists The playlist will be added to this if it is not there already; used to ensure
1835  *  that clear_changes () is only called once per playlist.
1836  *  @return New RegionView, or 0 if no insert was performed.
1837  */
1838 RegionView *
1839 RegionMoveDrag::insert_region_into_playlist (
1840         boost::shared_ptr<Region> region,
1841         RouteTimeAxisView* dest_rtv,
1842         layer_t dest_layer,
1843         framecnt_t where,
1844         PlaylistSet& modified_playlists,
1845         const int32_t sub_num
1846         )
1847 {
1848         boost::shared_ptr<Playlist> dest_playlist = dest_rtv->playlist ();
1849         if (!dest_playlist) {
1850                 return 0;
1851         }
1852
1853         /* arrange to collect the new region view that will be created as a result of our playlist insertion */
1854         _new_region_view = 0;
1855         sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun (*this, &RegionMoveDrag::collect_new_region_view));
1856
1857         /* clear history for the playlist we are about to insert to, provided we haven't already done so */
1858         pair<PlaylistSet::iterator, bool> r = modified_playlists.insert (dest_playlist);
1859         if (r.second) {
1860                 dest_playlist->clear_changes ();
1861         }
1862         dest_playlist->add_region (region, where, 1.0, false, sub_num);
1863
1864         if (dest_rtv->view()->layer_display() == Stacked || dest_rtv->view()->layer_display() == Expanded) {
1865                 dest_playlist->set_layer (region, dest_layer);
1866         }
1867
1868         c.disconnect ();
1869
1870         assert (_new_region_view);
1871
1872         return _new_region_view;
1873 }
1874
1875 void
1876 RegionMoveDrag::collect_new_region_view (RegionView* rv)
1877 {
1878         _new_region_view = rv;
1879 }
1880
1881 void
1882 RegionMoveDrag::add_stateful_diff_commands_for_playlists (PlaylistSet const & playlists)
1883 {
1884         for (PlaylistSet::const_iterator i = playlists.begin(); i != playlists.end(); ++i) {
1885                 StatefulDiffCommand* c = new StatefulDiffCommand (*i);
1886                 if (!c->empty()) {
1887                         _editor->session()->add_command (c);
1888                 } else {
1889                         delete c;
1890                 }
1891         }
1892 }
1893
1894
1895 void
1896 RegionMoveDrag::aborted (bool movement_occurred)
1897 {
1898         if (_copy) {
1899
1900                 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end();) {
1901                         list<DraggingView>::const_iterator next = i;
1902                         ++next;
1903                         delete i->view;
1904                         i = next;
1905                 }
1906
1907                 _views.clear ();
1908
1909         } else {
1910                 RegionMotionDrag::aborted (movement_occurred);
1911         }
1912 }
1913
1914 void
1915 RegionMotionDrag::aborted (bool)
1916 {
1917         for (vector<TimeAxisView*>::iterator i = _time_axis_views.begin(); i != _time_axis_views.end(); ++i) {
1918
1919                 StreamView* sview = (*i)->view();
1920
1921                 if (sview) {
1922                         if (sview->layer_display() == Expanded) {
1923                                 sview->set_layer_display (Stacked);
1924                         }
1925                 }
1926         }
1927
1928         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
1929                 RegionView* rv = i->view;
1930                 TimeAxisView* tv = &(rv->get_time_axis_view ());
1931                 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
1932                 assert (rtv);
1933                 rv->get_canvas_group()->reparent (rtv->view()->canvas_item());
1934                 rv->get_canvas_group()->set_y_position (0);
1935                 rv->drag_end ();
1936                 rv->move (-_total_x_delta, 0);
1937                 rv->set_height (rtv->view()->child_height ());
1938         }
1939 }
1940
1941 /** @param b true to brush, otherwise false.
1942  *  @param c true to make copies of the regions being moved, otherwise false.
1943  */
1944 RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
1945         : RegionMotionDrag (e, i, p, v, b)
1946         , _copy (c)
1947         , _new_region_view (0)
1948 {
1949         DEBUG_TRACE (DEBUG::Drags, "New RegionMoveDrag\n");
1950
1951         double speed = 1;
1952         RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (&_primary->get_time_axis_view ());
1953         if (rtv && rtv->is_track()) {
1954                 speed = rtv->track()->speed ();
1955         }
1956
1957         _last_frame_position = static_cast<framepos_t> (_primary->region()->position() / speed);
1958 }
1959
1960 void
1961 RegionMoveDrag::setup_pointer_frame_offset ()
1962 {
1963         _pointer_frame_offset = raw_grab_frame() - _last_frame_position;
1964 }
1965
1966 RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, framepos_t pos)
1967         : RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
1968 {
1969         DEBUG_TRACE (DEBUG::Drags, "New RegionInsertDrag\n");
1970
1971         assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
1972                 (boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));
1973
1974         _primary = v->view()->create_region_view (r, false, false);
1975
1976         _primary->get_canvas_group()->show ();
1977         _primary->set_position (pos, 0);
1978         _views.push_back (DraggingView (_primary, this, v));
1979
1980         _last_frame_position = pos;
1981
1982         _item = _primary->get_canvas_group ();
1983 }
1984
1985 void
1986 RegionInsertDrag::finished (GdkEvent * event, bool)
1987 {
1988         int pos = _views.front().time_axis_view;
1989         assert(pos >= 0 && pos < (int)_time_axis_views.size());
1990
1991         RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (_time_axis_views[pos]);
1992
1993         _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item());
1994         _primary->get_canvas_group()->set_y_position (0);
1995
1996         boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
1997
1998         _editor->begin_reversible_command (Operations::insert_region);
1999         playlist->clear_changes ();
2000         playlist->add_region (_primary->region (), _last_frame_position);
2001
2002         // Mixbus doesn't seem to ripple when inserting regions from the list: should we? yes, probably
2003         if (Config->get_edit_mode() == Ripple) {
2004                 playlist->ripple (_last_frame_position, _primary->region()->length(), _primary->region());
2005         }
2006
2007         _editor->session()->add_command (new StatefulDiffCommand (playlist));
2008         _editor->commit_reversible_command ();
2009
2010         delete _primary;
2011         _primary = 0;
2012         _views.clear ();
2013 }
2014
2015 void
2016 RegionInsertDrag::aborted (bool)
2017 {
2018         delete _primary;
2019         _primary = 0;
2020         _views.clear ();
2021 }
2022
2023 RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2024         : RegionMoveDrag (e, i, p, v, false, false)
2025 {
2026         DEBUG_TRACE (DEBUG::Drags, "New RegionSpliceDrag\n");
2027 }
2028
2029 struct RegionSelectionByPosition {
2030         bool operator() (RegionView*a, RegionView* b) {
2031                 return a->region()->position () < b->region()->position();
2032         }
2033 };
2034
2035 void
2036 RegionSpliceDrag::motion (GdkEvent* event, bool)
2037 {
2038         /* Which trackview is this ? */
2039
2040         pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2041         RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2042
2043         /* The region motion is only processed if the pointer is over
2044            an audio track.
2045         */
2046
2047         if (!tv || !tv->is_track()) {
2048                 /* To make sure we hide the verbose canvas cursor when the mouse is
2049                    not held over an audio track.
2050                 */
2051                 _editor->verbose_cursor()->hide ();
2052                 return;
2053         } else {
2054                 _editor->verbose_cursor()->show ();
2055         }
2056
2057         int dir;
2058
2059         if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
2060                 dir = 1;
2061         } else {
2062                 dir = -1;
2063         }
2064
2065         RegionSelection copy;
2066         _editor->selection->regions.by_position(copy);
2067
2068         framepos_t const pf = adjusted_current_frame (event);
2069
2070         for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2071
2072                 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());
2073
2074                 if (!atv) {
2075                         continue;
2076                 }
2077
2078                 boost::shared_ptr<Playlist> playlist;
2079
2080                 if ((playlist = atv->playlist()) == 0) {
2081                         continue;
2082                 }
2083
2084                 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
2085                         continue;
2086                 }
2087
2088                 if (dir > 0) {
2089                         if (pf < (*i)->region()->last_frame() + 1) {
2090                                 continue;
2091                         }
2092                 } else {
2093                         if (pf > (*i)->region()->first_frame()) {
2094                                 continue;
2095                         }
2096                 }
2097
2098
2099                 playlist->shuffle ((*i)->region(), dir);
2100         }
2101 }
2102
2103 void
2104 RegionSpliceDrag::finished (GdkEvent* event, bool movement_occurred)
2105 {
2106         RegionMoveDrag::finished (event, movement_occurred);
2107 }
2108
2109 void
2110 RegionSpliceDrag::aborted (bool)
2111 {
2112         /* XXX: TODO */
2113 }
2114
2115 /***
2116  * ripple mode...
2117  */
2118
2119 void
2120 RegionRippleDrag::add_all_after_to_views(TimeAxisView *tav, framepos_t where, const RegionSelection &exclude, bool drag_in_progress)
2121 {
2122
2123         boost::shared_ptr<RegionList> rl = tav->playlist()->regions_with_start_within (Evoral::Range<framepos_t>(where, max_framepos));
2124
2125         RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(tav);
2126         RegionSelection to_ripple;
2127         for (RegionList::iterator i = rl->begin(); i != rl->end(); ++i) {
2128                 if ((*i)->position() >= where) {
2129                         to_ripple.push_back (rtv->view()->find_view(*i));
2130                 }
2131         }
2132
2133         for (RegionSelection::iterator i = to_ripple.begin(); i != to_ripple.end(); ++i) {
2134                 if (!exclude.contains (*i)) {
2135                         // the selection has already been added to _views
2136
2137                         if (drag_in_progress) {
2138                                 // do the same things that RegionMotionDrag::motion does when
2139                                 // first_move is true, for the region views that we're adding
2140                                 // to _views this time
2141
2142                                 (*i)->drag_start();
2143                                 ArdourCanvas::Item* rvg = (*i)->get_canvas_group();
2144                                 Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0));
2145                                 Duple dmg_canvas_offset = _editor->_drag_motion_group->canvas_origin ();
2146                                 rvg->reparent (_editor->_drag_motion_group);
2147
2148                                 // we only need to move in the y direction
2149                                 Duple fudge = rv_canvas_offset - dmg_canvas_offset;
2150                                 fudge.x = 0;
2151                                 rvg->move (fudge);
2152
2153                         }
2154                         _views.push_back (DraggingView (*i, this, tav));
2155                 }
2156         }
2157 }
2158
2159 void
2160 RegionRippleDrag::remove_unselected_from_views(framecnt_t amount, bool move_regions)
2161 {
2162
2163         for (std::list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ) {
2164                 // we added all the regions after the selection
2165
2166                 std::list<DraggingView>::iterator to_erase = i++;
2167                 if (!_editor->selection->regions.contains (to_erase->view)) {
2168                         // restore the non-selected regions to their original playlist & positions,
2169                         // and then ripple them back by the length of the regions that were dragged away
2170                         // do the same things as RegionMotionDrag::aborted
2171
2172                         RegionView *rv = to_erase->view;
2173                         TimeAxisView* tv = &(rv->get_time_axis_view ());
2174                         RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
2175                         assert (rtv);
2176
2177                         // plonk them back onto their own track
2178                         rv->get_canvas_group()->reparent(rtv->view()->canvas_item());
2179                         rv->get_canvas_group()->set_y_position (0);
2180                         rv->drag_end ();
2181
2182                         if (move_regions) {
2183                                 // move the underlying region to match the view
2184                                 rv->region()->set_position (rv->region()->position() + amount);
2185                         } else {
2186                                 // restore the view to match the underlying region's original position
2187                                 rv->move(-amount, 0);   // second parameter is y delta - seems 0 is OK
2188                         }
2189
2190                         rv->set_height (rtv->view()->child_height ());
2191                         _views.erase (to_erase);
2192                 }
2193         }
2194 }
2195
2196 bool
2197 RegionRippleDrag::y_movement_allowed (int delta_track, double delta_layer, int skip_invisible) const
2198 {
2199         if (RegionMotionDrag::y_movement_allowed (delta_track, delta_layer, skip_invisible)) {
2200                 if (delta_track) {
2201                         return allow_moves_across_tracks;
2202                 } else {
2203                         return true;
2204                 }
2205         }
2206         return false;
2207 }
2208
2209 RegionRippleDrag::RegionRippleDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
2210         : RegionMoveDrag (e, i, p, v, false, false)
2211 {
2212         DEBUG_TRACE (DEBUG::Drags, "New RegionRippleDrag\n");
2213         // compute length of selection
2214         RegionSelection selected_regions = _editor->selection->regions;
2215         selection_length = selected_regions.end_frame() - selected_regions.start();
2216
2217         // we'll only allow dragging to another track in ripple mode if all the regions
2218         // being dragged start off on the same track
2219         allow_moves_across_tracks = (selected_regions.playlists().size() == 1);
2220         prev_tav = NULL;
2221         prev_amount = 0;
2222         exclude = new RegionList;
2223         for (RegionSelection::iterator i =selected_regions.begin(); i != selected_regions.end(); ++i) {
2224                 exclude->push_back((*i)->region());
2225         }
2226
2227         // also add regions before start of selection to exclude, to be consistent with how Mixbus does ripple
2228         RegionSelection copy;
2229         selected_regions.by_position(copy); // get selected regions sorted by position into copy
2230
2231         std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = copy.playlists();
2232         std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2233
2234         for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2235                 // find ripple start point on each applicable playlist
2236                 RegionView *first_selected_on_this_track = NULL;
2237                 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
2238                         if ((*i)->region()->playlist() == (*pi)) {
2239                                 // region is on this playlist - it's the first, because they're sorted
2240                                 first_selected_on_this_track = *i;
2241                                 break;
2242                         }
2243                 }
2244                 assert (first_selected_on_this_track); // we should always find the region in one of the playlists...
2245                 add_all_after_to_views (
2246                                 &first_selected_on_this_track->get_time_axis_view(),
2247                                 first_selected_on_this_track->region()->position(),
2248                                 selected_regions, false);
2249         }
2250
2251         if (allow_moves_across_tracks) {
2252                 orig_tav = &(*selected_regions.begin())->get_time_axis_view();
2253         } else {
2254                 orig_tav = NULL;
2255         }
2256
2257 }
2258
2259 void
2260 RegionRippleDrag::motion (GdkEvent* event, bool first_move)
2261 {
2262         /* Which trackview is this ? */
2263
2264         pair<TimeAxisView*, double> const tvp = _editor->trackview_by_y_position (current_pointer_y ());
2265         RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*> (tvp.first);
2266
2267         /* The region motion is only processed if the pointer is over
2268            an audio track.
2269          */
2270
2271         if (!tv || !tv->is_track()) {
2272                 /* To make sure we hide the verbose canvas cursor when the mouse is
2273                    not held over an audiotrack.
2274                  */
2275                 _editor->verbose_cursor()->hide ();
2276                 return;
2277         }
2278
2279         framepos_t where = adjusted_current_frame (event);
2280         assert (where >= 0);
2281         framepos_t after;
2282         double delta = compute_x_delta (event, &after);
2283
2284         framecnt_t amount = _editor->pixel_to_sample (delta);
2285
2286         if (allow_moves_across_tracks) {
2287                 // all the originally selected regions were on the same track
2288
2289                 framecnt_t adjust = 0;
2290                 if (prev_tav && tv != prev_tav) {
2291                         // dragged onto a different track
2292                         // remove the unselected regions from _views, restore them to their original positions
2293                         // and add the regions after the drop point on the new playlist to _views instead.
2294                         // undo the effect of rippling the previous playlist, and include the effect of removing
2295                         // the dragged region(s) from this track
2296
2297                         remove_unselected_from_views (prev_amount, false);
2298                         // ripple previous playlist according to the regions that have been removed onto the new playlist
2299                         prev_tav->playlist()->ripple(prev_position, -selection_length, exclude);
2300                         prev_amount = 0;
2301
2302                         // move just the selected regions
2303                         RegionMoveDrag::motion(event, first_move);
2304
2305                         // ensure that the ripple operation on the new playlist inserts selection_length time
2306                         adjust = selection_length;
2307                         // ripple the new current playlist
2308                         tv->playlist()->ripple (where, amount+adjust, exclude);
2309
2310                         // add regions after point where drag entered this track to subsequent ripples
2311                         add_all_after_to_views (tv, where, _editor->selection->regions, true);
2312
2313                 } else {
2314                         // motion on same track
2315                         RegionMoveDrag::motion(event, first_move);
2316                 }
2317                 prev_tav = tv;
2318
2319                 // remember what we've done to this playlist so we can undo it if the selection is dragged to another track
2320                 prev_position = where;
2321         } else {
2322                 // selection encompasses multiple tracks - just drag
2323                 // cross-track drags are forbidden
2324                 RegionMoveDrag::motion(event, first_move);
2325         }
2326
2327         if (!_x_constrained) {
2328                 prev_amount += amount;
2329         }
2330
2331         _last_frame_position = after;
2332 }
2333
2334 void
2335 RegionRippleDrag::finished (GdkEvent* event, bool movement_occurred)
2336 {
2337         if (!movement_occurred) {
2338
2339                 /* just a click */
2340
2341                 if (was_double_click() && !_views.empty()) {
2342                         DraggingView dv = _views.front();
2343                         _editor->edit_region (dv.view);
2344                 }
2345
2346                 return;
2347         }
2348
2349         _editor->begin_reversible_command(_("Ripple drag"));
2350
2351         // remove the regions being rippled from the dragging view, updating them to
2352         // their new positions
2353         remove_unselected_from_views (prev_amount, true);
2354
2355         if (allow_moves_across_tracks) {
2356                 if (orig_tav) {
2357                         // if regions were dragged across tracks, we've rippled any later
2358                         // regions on the track the regions were dragged off, so we need
2359                         // to add the original track to the undo record
2360                         orig_tav->playlist()->clear_changes();
2361                         vector<Command*> cmds;
2362                         orig_tav->playlist()->rdiff (cmds);
2363                         _editor->session()->add_commands (cmds);
2364                 }
2365                 if (prev_tav && prev_tav != orig_tav) {
2366                         prev_tav->playlist()->clear_changes();
2367                         vector<Command*> cmds;
2368                         prev_tav->playlist()->rdiff (cmds);
2369                         _editor->session()->add_commands (cmds);
2370                 }
2371         } else {
2372                 // selection spanned multiple tracks - all will need adding to undo record
2373
2374                 std::set<boost::shared_ptr<ARDOUR::Playlist> > playlists = _editor->selection->regions.playlists();
2375                 std::set<boost::shared_ptr<ARDOUR::Playlist> >::const_iterator pi;
2376
2377                 for (pi = playlists.begin(); pi != playlists.end(); ++pi) {
2378                         (*pi)->clear_changes();
2379                         vector<Command*> cmds;
2380                         (*pi)->rdiff (cmds);
2381                         _editor->session()->add_commands (cmds);
2382                 }
2383         }
2384
2385         // other modified playlists are added to undo by RegionMoveDrag::finished()
2386         RegionMoveDrag::finished (event, movement_occurred);
2387         _editor->commit_reversible_command();
2388 }
2389
2390 void
2391 RegionRippleDrag::aborted (bool movement_occurred)
2392 {
2393         RegionMoveDrag::aborted (movement_occurred);
2394         _views.clear ();
2395 }
2396
2397
2398 RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
2399         : Drag (e, i),
2400           _view (dynamic_cast<MidiTimeAxisView*> (v))
2401 {
2402         DEBUG_TRACE (DEBUG::Drags, "New RegionCreateDrag\n");
2403
2404         assert (_view);
2405 }
2406
2407 void
2408 RegionCreateDrag::motion (GdkEvent* event, bool first_move)
2409 {
2410         if (first_move) {
2411                 _editor->begin_reversible_command (_("create region"));
2412                 _region = add_midi_region (_view, false, _editor->get_grid_music_divisions (event->button.state));
2413                 _view->playlist()->freeze ();
2414         } else {
2415                 if (_region) {
2416                         framepos_t const f = adjusted_current_frame (event);
2417                         if (f < grab_frame()) {
2418                                 _region->set_initial_position (f);
2419                         }
2420
2421                         /* Don't use a zero-length region, and subtract 1 frame from the snapped length
2422                            so that if this region is duplicated, its duplicate starts on
2423                            a snap point rather than 1 frame after a snap point.  Otherwise things get
2424                            a bit confusing as if a region starts 1 frame after a snap point, one cannot
2425                            place snapped notes at the start of the region.
2426                         */
2427
2428                         framecnt_t const len = (framecnt_t) fabs ((double)(f - grab_frame () - 1));
2429                         _region->set_length (len < 1 ? 1 : len, _editor->get_grid_music_divisions (event->button.state));
2430                 }
2431         }
2432 }
2433
2434 void
2435 RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
2436 {
2437         if (!movement_occurred) {
2438                 add_midi_region (_view, true, _editor->get_grid_music_divisions (event->button.state));
2439         } else {
2440                 _view->playlist()->thaw ();
2441                 _editor->commit_reversible_command();
2442         }
2443 }
2444
2445 void
2446 RegionCreateDrag::aborted (bool)
2447 {
2448         if (_region) {
2449                 _view->playlist()->thaw ();
2450         }
2451
2452         /* XXX */
2453 }
2454
2455 NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
2456         : Drag (e, i)
2457         , region (0)
2458         , relative (false)
2459         , at_front (true)
2460         , _was_selected (false)
2461         , _snap_delta (0)
2462 {
2463         DEBUG_TRACE (DEBUG::Drags, "New NoteResizeDrag\n");
2464 }
2465
2466 void
2467 NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
2468 {
2469         Gdk::Cursor* cursor;
2470         NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2471         assert (cnote);
2472         float x_fraction = cnote->mouse_x_fraction ();
2473
2474         if (x_fraction > 0.0 && x_fraction < 0.25) {
2475                 cursor = _editor->cursors()->left_side_trim;
2476                 at_front = true;
2477         } else  {
2478                 cursor = _editor->cursors()->right_side_trim;
2479                 at_front = false;
2480         }
2481
2482         Drag::start_grab (event, cursor);
2483
2484         region = &cnote->region_view();
2485
2486         double temp;
2487         temp = region->snap_to_pixel (cnote->x0 (), true);
2488         _snap_delta = temp - cnote->x0 ();
2489
2490         _item->grab ();
2491
2492         if (event->motion.state & ArdourKeyboard::note_size_relative_modifier ()) {
2493                 relative = false;
2494         } else {
2495                 relative = true;
2496         }
2497         MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2498         if (ms.size() > 1) {
2499                 /* has to be relative, may make no sense otherwise */
2500                 relative = true;
2501         }
2502
2503         if (!(_was_selected = cnote->selected())) {
2504
2505                 /* tertiary-click means extend selection - we'll do that on button release,
2506                    so don't add it here, because otherwise we make it hard to figure
2507                    out the "extend-to" range.
2508                 */
2509
2510                 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2511
2512                 if (!extend) {
2513                         bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2514
2515                         if (add) {
2516                                 region->note_selected (cnote, true);
2517                         } else {
2518                                 _editor->get_selection().clear_points();
2519                                 region->unique_select (cnote);
2520                         }
2521                 }
2522         }
2523 }
2524
2525 void
2526 NoteResizeDrag::motion (GdkEvent* event, bool first_move)
2527 {
2528         MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2529         if (first_move) {
2530                 _editor->begin_reversible_command (_("resize notes"));
2531
2532                 for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
2533                         MidiRegionSelection::iterator next;
2534                         next = r;
2535                         ++next;
2536                         MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2537                         if (mrv) {
2538                                 mrv->begin_resizing (at_front);
2539                         }
2540                         r = next;
2541                 }
2542         }
2543
2544         for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2545                 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2546                 assert (nb);
2547                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2548                 if (mrv) {
2549                         double sd = 0.0;
2550                         bool snap = true;
2551                         bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2552
2553                         if (ArdourKeyboard::indicates_snap (event->button.state)) {
2554                                 if (_editor->snap_mode () != SnapOff) {
2555                                         snap = false;
2556                                 }
2557                         } else {
2558                                 if (_editor->snap_mode () == SnapOff) {
2559                                         snap = false;
2560                                         /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2561                                         if (apply_snap_delta) {
2562                                                 snap = true;
2563                                         }
2564                                 }
2565                         }
2566
2567                         if (apply_snap_delta) {
2568                                 sd = _snap_delta;
2569                         }
2570
2571                         mrv->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2572                 }
2573         }
2574 }
2575
2576 void
2577 NoteResizeDrag::finished (GdkEvent* event, bool movement_occurred)
2578 {
2579         if (!movement_occurred) {
2580                 /* no motion - select note */
2581                 NoteBase* cnote = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2582                 if (_editor->current_mouse_mode() == Editing::MouseContent ||
2583                     _editor->current_mouse_mode() == Editing::MouseDraw) {
2584
2585                         bool changed = false;
2586
2587                         if (_was_selected) {
2588                                 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2589                                 if (add) {
2590                                         region->note_deselected (cnote);
2591                                         changed = true;
2592                                 } else {
2593                                         _editor->get_selection().clear_points();
2594                                         region->unique_select (cnote);
2595                                         changed = true;
2596                                 }
2597                         } else {
2598                                 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
2599                                 bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
2600
2601                                 if (!extend && !add && region->selection_size() > 1) {
2602                                         _editor->get_selection().clear_points();
2603                                         region->unique_select (cnote);
2604                                         changed = true;
2605                                 } else if (extend) {
2606                                         region->note_selected (cnote, true, true);
2607                                         changed = true;
2608                                 } else {
2609                                         /* it was added during button press */
2610                                         changed = true;
2611                                 }
2612                         }
2613
2614                         if (changed) {
2615                                 _editor->begin_reversible_selection_op(X_("Resize Select Note Release"));
2616                                 _editor->commit_reversible_selection_op();
2617                         }
2618                 }
2619
2620                 return;
2621         }
2622
2623         MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2624         for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2625                 NoteBase* nb = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
2626                 assert (nb);
2627                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2628                 double sd = 0.0;
2629                 bool snap = true;
2630                 bool apply_snap_delta = ArdourKeyboard::indicates_snap_delta (event->button.state);
2631                 if (mrv) {
2632                         if (ArdourKeyboard::indicates_snap (event->button.state)) {
2633                                 if (_editor->snap_mode () != SnapOff) {
2634                                         snap = false;
2635                                 }
2636                         } else {
2637                                 if (_editor->snap_mode () == SnapOff) {
2638                                         snap = false;
2639                                         /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
2640                                         if (apply_snap_delta) {
2641                                                 snap = true;
2642                                         }
2643                                 }
2644                         }
2645
2646                         if (apply_snap_delta) {
2647                                 sd = _snap_delta;
2648                         }
2649
2650                         mrv->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative, sd, snap);
2651                 }
2652         }
2653
2654         _editor->commit_reversible_command ();
2655 }
2656
2657 void
2658 NoteResizeDrag::aborted (bool)
2659 {
2660         MidiRegionSelection& ms (_editor->get_selection().midi_regions);
2661         for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
2662                 MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(*r);
2663                 if (mrv) {
2664                         mrv->abort_resizing ();
2665                 }
2666         }
2667 }
2668
2669 AVDraggingView::AVDraggingView (RegionView* v)
2670         : view (v)
2671 {
2672         initial_position = v->region()->position ();
2673 }
2674
2675 VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i)
2676         : Drag (e, i)
2677 {
2678         DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n");
2679
2680         RegionSelection rs;
2681         TrackViewList empty;
2682         empty.clear();
2683         _editor->get_regions_after(rs, (framepos_t) 0, empty);
2684         std::list<RegionView*> views = rs.by_layer();
2685
2686         _stuck = false;
2687         for (list<RegionView*>::iterator i = views.begin(); i != views.end(); ++i) {
2688                 RegionView* rv = (*i);
2689                 if (!rv->region()->video_locked()) {
2690                         continue;
2691                 }
2692                 if (rv->region()->locked()) {
2693                         _stuck = true;
2694                 }
2695                 _views.push_back (AVDraggingView (rv));
2696         }
2697 }
2698
2699 void
2700 VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2701 {
2702         Drag::start_grab (event);
2703         if (_editor->session() == 0) {
2704                 return;
2705         }
2706
2707         if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
2708                 _stuck = false;
2709                 _views.clear();
2710         }
2711
2712         if (_stuck) {
2713                 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2714                 return;
2715         }
2716
2717         _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset();
2718         _max_backwards_drag = (
2719                           ARDOUR_UI::instance()->video_timeline->get_duration()
2720                         + ARDOUR_UI::instance()->video_timeline->get_offset()
2721                         - ceil(ARDOUR_UI::instance()->video_timeline->get_apv())
2722                         );
2723
2724         for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2725                 if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) {
2726                         _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position);
2727                 }
2728         }
2729         DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag));
2730
2731         char buf[128];
2732         Timecode::Time timecode;
2733         _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ );
2734         snprintf (buf, sizeof (buf), "Video Start:\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, (_startdrag_video_offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames);
2735         show_verbose_cursor_text (buf);
2736 }
2737
2738 void
2739 VideoTimeLineDrag::motion (GdkEvent* event, bool first_move)
2740 {
2741         if (_editor->session() == 0) {
2742                 return;
2743         }
2744         if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2745                 return;
2746         }
2747         if (_stuck) {
2748                 show_verbose_cursor_text (_("One or more Audio Regions\nare both Locked and\nLocked to Video.\nThe video cannot me moved."));
2749                 return;
2750         }
2751
2752         framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset;
2753         dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(_startdrag_video_offset+dt) - _startdrag_video_offset;
2754
2755         if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) {
2756                 dt = - _max_backwards_drag;
2757         }
2758
2759         ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt);
2760         ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2761
2762         for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2763                 RegionView* rv = i->view;
2764                 DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt));
2765                 if (first_move) {
2766                         rv->drag_start ();
2767                         rv->region()->clear_changes ();
2768                         rv->region()->suspend_property_changes();
2769                 }
2770                 rv->region()->set_position(i->initial_position + dt);
2771                 rv->region_changed(ARDOUR::Properties::position);
2772         }
2773
2774         const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset();
2775         Timecode::Time timecode;
2776         Timecode::Time timediff;
2777         char buf[128];
2778         _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ );
2779         _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ );
2780         snprintf (buf, sizeof (buf),
2781                         "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2782                         "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32
2783                         , _("Video Start:"),
2784                                 (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames
2785                         , _("Diff:"),
2786                                 (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames
2787                                 );
2788         show_verbose_cursor_text (buf);
2789 }
2790
2791 void
2792 VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred)
2793 {
2794         if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2795                 return;
2796         }
2797         if (_stuck) {
2798                 return;
2799         }
2800
2801         if (!movement_occurred || ! _editor->session()) {
2802                 return;
2803         }
2804
2805         ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2806
2807         _editor->begin_reversible_command (_("Move Video"));
2808
2809         XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state();
2810         ARDOUR_UI::instance()->video_timeline->save_undo();
2811         XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state();
2812         _editor->session()->add_command(new MementoCommand<VideoTimeLine>(*(ARDOUR_UI::instance()->video_timeline), &before, &after));
2813
2814         for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2815                 i->view->drag_end();
2816                 i->view->region()->resume_property_changes ();
2817
2818                 _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
2819         }
2820
2821         _editor->session()->maybe_update_session_range(
2822                         std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0),
2823                         std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0)
2824                         );
2825
2826
2827         _editor->commit_reversible_command ();
2828 }
2829
2830 void
2831 VideoTimeLineDrag::aborted (bool)
2832 {
2833         if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) {
2834                 return;
2835         }
2836         ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset);
2837         ARDOUR_UI::instance()->flush_videotimeline_cache(true);
2838
2839         for (list<AVDraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
2840                 i->view->region()->resume_property_changes ();
2841                 i->view->region()->set_position(i->initial_position);
2842         }
2843 }
2844
2845 TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool preserve_fade_anchor)
2846         : RegionDrag (e, i, p, v)
2847         , _operation (StartTrim)
2848         , _preserve_fade_anchor (preserve_fade_anchor)
2849         , _jump_position_when_done (false)
2850 {
2851         DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n");
2852 }
2853
2854 void
2855 TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
2856 {
2857         double speed = 1.0;
2858         TimeAxisView* tvp = &_primary->get_time_axis_view ();
2859         RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2860
2861         if (tv && tv->is_track()) {
2862                 speed = tv->track()->speed();
2863         }
2864
2865         framepos_t const region_start = (framepos_t) (_primary->region()->position() / speed);
2866         framepos_t const region_end = (framepos_t) (_primary->region()->last_frame() / speed);
2867         framecnt_t const region_length = (framecnt_t) (_primary->region()->length() / speed);
2868
2869         framepos_t const pf = adjusted_current_frame (event);
2870         setup_snap_delta (region_start);
2871
2872         if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_contents_modifier ())) {
2873                 /* Move the contents of the region around without changing the region bounds */
2874                 _operation = ContentsTrim;
2875                 Drag::start_grab (event, _editor->cursors()->trimmer);
2876         } else {
2877                 /* These will get overridden for a point trim.*/
2878                 if (pf < (region_start + region_length/2)) {
2879                         /* closer to front */
2880                         _operation = StartTrim;
2881                         if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2882                                 Drag::start_grab (event, _editor->cursors()->anchored_left_side_trim);
2883                         } else {
2884                                 Drag::start_grab (event, _editor->cursors()->left_side_trim);
2885                         }
2886                 } else {
2887                         /* closer to end */
2888                         _operation = EndTrim;
2889                         if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::trim_anchored_modifier ())) {
2890                                 Drag::start_grab (event, _editor->cursors()->anchored_right_side_trim);
2891                         } else {
2892                                 Drag::start_grab (event, _editor->cursors()->right_side_trim);
2893                         }
2894                 }
2895         }
2896         /* jump trim disabled for now
2897         if (Keyboard::modifier_state_equals (event->button.state, Keyboard::trim_jump_modifier ())) {
2898                 _jump_position_when_done = true;
2899         }
2900         */
2901
2902         switch (_operation) {
2903         case StartTrim:
2904                 show_verbose_cursor_time (region_start);
2905                 break;
2906         case EndTrim:
2907                 show_verbose_cursor_duration (region_start, region_end);
2908                 break;
2909         case ContentsTrim:
2910                 show_verbose_cursor_time (pf);
2911                 break;
2912         }
2913
2914         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2915                 i->view->region()->suspend_property_changes ();
2916         }
2917 }
2918
2919 void
2920 TrimDrag::motion (GdkEvent* event, bool first_move)
2921 {
2922         RegionView* rv = _primary;
2923
2924         double speed = 1.0;
2925         TimeAxisView* tvp = &_primary->get_time_axis_view ();
2926         RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
2927         pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
2928         frameoffset_t frame_delta = 0;
2929
2930         if (tv && tv->is_track()) {
2931                 speed = tv->track()->speed();
2932         }
2933         framecnt_t adj_frame = adjusted_frame (_drags->current_pointer_frame () + snap_delta (event->button.state), event, true);
2934         framecnt_t dt = adj_frame - raw_grab_frame () + _pointer_frame_offset - snap_delta (event->button.state);
2935
2936         if (first_move) {
2937
2938                 string trim_type;
2939
2940                 switch (_operation) {
2941                 case StartTrim:
2942                         trim_type = "Region start trim";
2943                         break;
2944                 case EndTrim:
2945                         trim_type = "Region end trim";
2946                         break;
2947                 case ContentsTrim:
2948                         trim_type = "Region content trim";
2949                         break;
2950                 default:
2951                         assert(0);
2952                         break;
2953                 }
2954
2955                 _editor->begin_reversible_command (trim_type);
2956
2957                 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2958                         RegionView* rv = i->view;
2959                         rv->region()->playlist()->clear_owned_changes ();
2960
2961                         if (_operation == StartTrim) {
2962                                 rv->trim_front_starting ();
2963                         }
2964
2965                         AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);
2966
2967                         if (arv) {
2968                                 arv->temporarily_hide_envelope ();
2969                                 arv->drag_start ();
2970                         }
2971
2972                         boost::shared_ptr<Playlist> pl = rv->region()->playlist();
2973                         insert_result = _editor->motion_frozen_playlists.insert (pl);
2974
2975                         if (insert_result.second) {
2976                                 pl->freeze();
2977                         }
2978                 }
2979         }
2980
2981         bool non_overlap_trim = false;
2982
2983         if (event && Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::trim_overlap_modifier ())) {
2984                 non_overlap_trim = true;
2985         }
2986
2987         /* contstrain trim to fade length */
2988         if (_preserve_fade_anchor) {
2989                 switch (_operation) {
2990                         case StartTrim:
2991                                 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
2992                                         AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
2993                                         if (!arv) continue;
2994                                         boost::shared_ptr<AudioRegion> ar (arv->audio_region());
2995                                         if (ar->locked()) continue;
2996                                         framecnt_t len = ar->fade_in()->back()->when;
2997                                         if (len < dt) dt = min(dt, len);
2998                                 }
2999                                 break;
3000                         case EndTrim:
3001                                 for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3002                                         AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3003                                         if (!arv) continue;
3004                                         boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3005                                         if (ar->locked()) continue;
3006                                         framecnt_t len = ar->fade_out()->back()->when;
3007                                         if (len < -dt) dt = max(dt, -len);
3008                                 }
3009                                 break;
3010                         case ContentsTrim:
3011                                 break;
3012                 }
3013         }
3014
3015         int32_t divisions = current_music_divisor (adj_frame, event->button.state);
3016
3017         switch (_operation) {
3018         case StartTrim:
3019                 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3020                         bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim
3021                                                             , divisions);
3022
3023                         if (changed && _preserve_fade_anchor) {
3024                                 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3025                                 if (arv) {
3026                                         boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3027                                         framecnt_t len = ar->fade_in()->back()->when;
3028                                         framecnt_t diff = ar->first_frame() - i->initial_position;
3029                                         framepos_t new_length = len - diff;
3030                                         i->anchored_fade_length = min (ar->length(), new_length);
3031                                         //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, true  /*START*/ );
3032                                         arv->reset_fade_in_shape_width (ar, i->anchored_fade_length, true);
3033                                 }
3034                         }
3035                 }
3036                 break;
3037
3038         case EndTrim:
3039                 for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3040                         bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim, divisions);
3041                         if (changed && _preserve_fade_anchor) {
3042                                 AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3043                                 if (arv) {
3044                                         boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3045                                         framecnt_t len = ar->fade_out()->back()->when;
3046                                         framecnt_t diff = 1 + ar->last_frame() - i->initial_end;
3047                                         framepos_t new_length = len + diff;
3048                                         i->anchored_fade_length = min (ar->length(), new_length);
3049                                         //i->anchored_fade_length = ar->verify_xfade_bounds (new_length, false  /*END*/ );
3050                                         arv->reset_fade_out_shape_width (ar, i->anchored_fade_length, true);
3051                                 }
3052                         }
3053                 }
3054                 break;
3055
3056         case ContentsTrim:
3057                 {
3058                         frame_delta = (last_pointer_frame() - adjusted_current_frame(event));
3059
3060                         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3061                                 i->view->move_contents (frame_delta);
3062                         }
3063                 }
3064                 break;
3065         }
3066
3067         switch (_operation) {
3068         case StartTrim:
3069                 show_verbose_cursor_time ((framepos_t) (rv->region()->position() / speed));
3070                 break;
3071         case EndTrim:
3072                 show_verbose_cursor_duration ((framepos_t)  rv->region()->position() / speed, (framepos_t) rv->region()->last_frame() / speed);
3073                 break;
3074         case ContentsTrim:
3075                 // show_verbose_cursor_time (frame_delta);
3076                 break;
3077         }
3078 }
3079
3080 void
3081 TrimDrag::finished (GdkEvent* event, bool movement_occurred)
3082 {
3083         if (movement_occurred) {
3084                 motion (event, false);
3085
3086                 if (_operation == StartTrim) {
3087                         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3088                                 {
3089                                         /* This must happen before the region's StatefulDiffCommand is created, as it may
3090                                            `correct' (ahem) the region's _start from being negative to being zero.  It
3091                                            needs to be zero in the undo record.
3092                                         */
3093                                         i->view->trim_front_ending ();
3094                                 }
3095                                 if (_preserve_fade_anchor) {
3096                                         AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3097                                         if (arv) {
3098                                                 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3099                                                 arv->reset_fade_in_shape_width (ar, i->anchored_fade_length);
3100                                                 ar->set_fade_in_length(i->anchored_fade_length);
3101                                                 ar->set_fade_in_active(true);
3102                                         }
3103                                 }
3104                                 if (_jump_position_when_done) {
3105                                         i->view->region()->set_position (i->initial_position);
3106                                 }
3107                         }
3108                 } else if (_operation == EndTrim) {
3109                         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3110                                 if (_preserve_fade_anchor) {
3111                                         AudioRegionView* arv = dynamic_cast<AudioRegionView*> (i->view);
3112                                         if (arv) {
3113                                                 boost::shared_ptr<AudioRegion> ar (arv->audio_region());
3114                                                 arv->reset_fade_out_shape_width (ar, i->anchored_fade_length);
3115                                                 ar->set_fade_out_length(i->anchored_fade_length);
3116                                                 ar->set_fade_out_active(true);
3117                                         }
3118                                 }
3119                                 if (_jump_position_when_done) {
3120                                         i->view->region()->set_position (i->initial_end - i->view->region()->length());
3121                                 }
3122                         }
3123                 }
3124
3125                 if (!_editor->selection->selected (_primary)) {
3126                         _primary->thaw_after_trim ();
3127                 } else {
3128
3129                         set<boost::shared_ptr<Playlist> > diffed_playlists;
3130
3131                         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3132                                 i->view->thaw_after_trim ();
3133                                 i->view->enable_display (true);
3134
3135                                 /* Trimming one region may affect others on the playlist, so we need
3136                                    to get undo Commands from the whole playlist rather than just the
3137                                    region.  Use diffed_playlists to make sure we don't diff a given
3138                                    playlist more than once.
3139                                 */
3140                                 boost::shared_ptr<Playlist> p = i->view->region()->playlist ();
3141                                 if (diffed_playlists.find (p) == diffed_playlists.end()) {
3142                                         vector<Command*> cmds;
3143                                         p->rdiff (cmds);
3144                                         _editor->session()->add_commands (cmds);
3145                                         diffed_playlists.insert (p);
3146                                 }
3147                         }
3148                 }
3149
3150                 for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
3151                         (*p)->thaw ();
3152                 }
3153
3154                 _editor->motion_frozen_playlists.clear ();
3155                 _editor->commit_reversible_command();
3156
3157         } else {
3158                 /* no mouse movement */
3159                 if (adjusted_current_frame (event) != adjusted_frame (_drags->current_pointer_frame(), event, false)) {
3160                         _editor->point_trim (event, adjusted_current_frame (event));
3161                 }
3162         }
3163
3164         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3165                 i->view->region()->resume_property_changes ();
3166         }
3167 }
3168
3169 void
3170 TrimDrag::aborted (bool movement_occurred)
3171 {
3172         /* Our motion method is changing model state, so use the Undo system
3173            to cancel.  Perhaps not ideal, as this will leave an Undo point
3174            behind which may be slightly odd from the user's point of view.
3175         */
3176
3177         GdkEvent ev;
3178         finished (&ev, true);
3179
3180         if (movement_occurred) {
3181                 _editor->session()->undo (1);
3182         }
3183
3184         for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
3185                 i->view->region()->resume_property_changes ();
3186         }
3187 }
3188
3189 void
3190 TrimDrag::setup_pointer_frame_offset ()
3191 {
3192         list<DraggingView>::iterator i = _views.begin ();
3193         while (i != _views.end() && i->view != _primary) {
3194                 ++i;
3195         }
3196
3197         if (i == _views.end()) {
3198                 return;
3199         }
3200
3201         switch (_operation) {
3202         case StartTrim:
3203                 _pointer_frame_offset = raw_grab_frame() - i->initial_position;
3204                 break;
3205         case EndTrim:
3206                 _pointer_frame_offset = raw_grab_frame() - i->initial_end;
3207                 break;
3208         case ContentsTrim:
3209                 break;
3210         }
3211 }
3212
3213 MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3214         : Drag (e, i)
3215         , _copy (c)
3216         , _old_snap_type (e->snap_type())
3217         , _old_snap_mode (e->snap_mode())
3218         , before_state (0)
3219 {
3220         DEBUG_TRACE (DEBUG::Drags, "New MeterMarkerDrag\n");
3221         _marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
3222         assert (_marker);
3223         _real_section = &_marker->meter();
3224
3225 }
3226
3227 void
3228 MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3229 {
3230         Drag::start_grab (event, cursor);
3231         show_verbose_cursor_time (adjusted_current_frame(event));
3232 }
3233
3234 void
3235 MeterMarkerDrag::setup_pointer_frame_offset ()
3236 {
3237         _pointer_frame_offset = raw_grab_frame() - _marker->meter().frame();
3238 }
3239
3240 void
3241 MeterMarkerDrag::motion (GdkEvent* event, bool first_move)
3242 {
3243         if (first_move) {
3244                 // create a dummy marker to catch events, then hide it.
3245
3246                 char name[64];
3247                 snprintf (name, sizeof(name), "%g/%g", _marker->meter().divisions_per_bar(), _marker->meter().note_divisor ());
3248
3249                 _marker = new MeterMarker (
3250                         *_editor,
3251                         *_editor->meter_group,
3252                         UIConfiguration::instance().color ("meter marker"),
3253                         name,
3254                         *new MeterSection (_marker->meter())
3255                 );
3256
3257                 /* use the new marker for the grab */
3258                 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3259                 _marker->hide();
3260
3261                 TempoMap& map (_editor->session()->tempo_map());
3262                 /* get current state */
3263                 before_state = &map.get_state();
3264
3265                 if (!_copy) {
3266                         _editor->begin_reversible_command (_("move meter mark"));
3267                 } else {
3268                         _editor->begin_reversible_command (_("copy meter mark"));
3269
3270                         Timecode::BBT_Time bbt = _real_section->bbt();
3271
3272                         /* we can't add a meter where one currently exists */
3273                         if (_real_section->frame() < adjusted_current_frame (event, false)) {
3274                                 ++bbt.bars;
3275                         } else {
3276                                 --bbt.bars;
3277                         }
3278                         const double beat = map.beat_at_bbt (bbt);
3279                         const framepos_t frame = map.frame_at_beat (beat);
3280                         _real_section = map.add_meter (Meter (_marker->meter().divisions_per_bar(), _marker->meter().note_divisor())
3281                                                        , beat, bbt, frame, _real_section->position_lock_style());
3282                         if (!_real_section) {
3283                                 aborted (true);
3284                                 return;
3285                         }
3286
3287                 }
3288                 /* only snap to bars. leave snap mode alone for audio locked meters.*/
3289                 if (_real_section->position_lock_style() != AudioTime) {
3290                         _editor->set_snap_to (SnapToBar);
3291                         _editor->set_snap_mode (SnapNormal);
3292                 }
3293         }
3294
3295         framepos_t pf = adjusted_current_frame (event);
3296
3297         if (_real_section->position_lock_style() == AudioTime && _editor->snap_musical()) {
3298                 /* never snap to music for audio locked */
3299                 pf = adjusted_current_frame (event, false);
3300         }
3301
3302         _editor->session()->tempo_map().gui_set_meter_position (_real_section, pf);
3303
3304         /* fake marker meeds to stay under the mouse, unlike the real one. */
3305         _marker->set_position (adjusted_current_frame (event, false));
3306
3307         show_verbose_cursor_time (_real_section->frame());
3308 }
3309
3310 void
3311 MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3312 {
3313         if (!movement_occurred) {
3314                 if (was_double_click()) {
3315                         _editor->edit_meter_marker (*_marker);
3316                 }
3317                 return;
3318         }
3319
3320         /* reinstate old snap setting */
3321         _editor->set_snap_to (_old_snap_type);
3322         _editor->set_snap_mode (_old_snap_mode);
3323
3324         TempoMap& map (_editor->session()->tempo_map());
3325
3326         XMLNode &after = map.get_state();
3327         _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3328         _editor->commit_reversible_command ();
3329
3330         // delete the dummy marker we used for visual representation while moving.
3331         // a new visual marker will show up automatically.
3332         delete _marker;
3333 }
3334
3335 void
3336 MeterMarkerDrag::aborted (bool moved)
3337 {
3338         _marker->set_position (_marker->meter().frame ());
3339         if (moved) {
3340                 /* reinstate old snap setting */
3341                 _editor->set_snap_to (_old_snap_type);
3342                 _editor->set_snap_mode (_old_snap_mode);
3343
3344                 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3345                 // delete the dummy marker we used for visual representation while moving.
3346                 // a new visual marker will show up automatically.
3347                 delete _marker;
3348         }
3349 }
3350
3351 TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
3352         : Drag (e, i)
3353         , _copy (c)
3354         , _grab_bpm (0.0)
3355         , before_state (0)
3356 {
3357         DEBUG_TRACE (DEBUG::Drags, "New TempoMarkerDrag\n");
3358
3359         _marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
3360         _real_section = &_marker->tempo();
3361         _movable = !_real_section->initial();
3362         _grab_bpm = _real_section->note_types_per_minute();
3363         assert (_marker);
3364 }
3365
3366 void
3367 TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3368 {
3369         Drag::start_grab (event, cursor);
3370         if (!_real_section->active()) {
3371                 show_verbose_cursor_text (_("inactive"));
3372         } else {
3373                 show_verbose_cursor_time (adjusted_current_frame (event));
3374         }
3375 }
3376
3377 void
3378 TempoMarkerDrag::setup_pointer_frame_offset ()
3379 {
3380         _pointer_frame_offset = raw_grab_frame() - _real_section->frame();
3381 }
3382
3383 void
3384 TempoMarkerDrag::motion (GdkEvent* event, bool first_move)
3385 {
3386         if (!_real_section->active()) {
3387                 return;
3388         }
3389
3390         if (first_move) {
3391
3392                 // mvc drag - create a dummy marker to catch events, hide it.
3393
3394                 char name[64];
3395                 snprintf (name, sizeof (name), "%.2f", _marker->tempo().note_types_per_minute());
3396
3397                 TempoSection section (_marker->tempo());
3398
3399                 _marker = new TempoMarker (
3400                         *_editor,
3401                         *_editor->tempo_group,
3402                         UIConfiguration::instance().color ("tempo marker"),
3403                         name,
3404                         *new TempoSection (_marker->tempo())
3405                         );
3406
3407                 /* use the new marker for the grab */
3408                 swap_grab (&_marker->the_item(), 0, GDK_CURRENT_TIME);
3409                 _marker->hide();
3410
3411                 TempoMap& map (_editor->session()->tempo_map());
3412                 /* get current state */
3413                 before_state = &map.get_state();
3414
3415                 if (!_copy) {
3416                         _editor->begin_reversible_command (_("move tempo mark"));
3417
3418                 } else {
3419                         const Tempo tempo (_marker->tempo());
3420                         const framepos_t frame = adjusted_current_frame (event) + 1;
3421                         const TempoSection::Type type = _real_section->type();
3422
3423                         _editor->begin_reversible_command (_("copy tempo mark"));
3424
3425                         if (_real_section->position_lock_style() == MusicTime) {
3426                                 const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
3427                                 _real_section = map.add_tempo (tempo, map.exact_qn_at_frame (frame, divisions), 0, type, MusicTime);
3428                         } else {
3429                                 _real_section = map.add_tempo (tempo, 0.0, frame, type, AudioTime);
3430                         }
3431
3432                         if (!_real_section) {
3433                                 aborted (true);
3434                                 return;
3435                         }
3436                 }
3437
3438         }
3439
3440         if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3441                 /* use vertical movement to alter tempo .. should be log */
3442                 double new_bpm = max (1.5, _grab_bpm + ((grab_y() - min (-1.0, current_pointer_y())) / 5.0));
3443                 stringstream strs;
3444                 _editor->session()->tempo_map().gui_change_tempo (_real_section, Tempo (new_bpm, _real_section->note_type()));
3445                 strs << new_bpm;
3446                 show_verbose_cursor_text (strs.str());
3447
3448         } else if (_movable && !_real_section->locked_to_meter()) {
3449                 framepos_t pf;
3450
3451                 if (_editor->snap_musical()) {
3452                         /* we can't snap to a grid that we are about to move.
3453                          * gui_move_tempo() will sort out snap using the supplied beat divisions.
3454                         */
3455                         pf = adjusted_current_frame (event, false);
3456                 } else {
3457                         pf = adjusted_current_frame (event);
3458                 }
3459
3460                 TempoMap& map (_editor->session()->tempo_map());
3461
3462                 /* snap to beat is 1, snap to bar is -1 (sorry) */
3463                 const int sub_num = _editor->get_grid_music_divisions (event->button.state);
3464
3465                 map.gui_set_tempo_position (_real_section, pf, sub_num);
3466
3467                 show_verbose_cursor_time (_real_section->frame());
3468         }
3469         _marker->set_position (adjusted_current_frame (event, false));
3470 }
3471
3472 void
3473 TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
3474 {
3475         if (!_real_section->active()) {
3476                 return;
3477         }
3478         if (!movement_occurred) {
3479                 if (was_double_click()) {
3480                         _editor->edit_tempo_marker (*_marker);
3481                 }
3482                 return;
3483         }
3484
3485         TempoMap& map (_editor->session()->tempo_map());
3486
3487         XMLNode &after = map.get_state();
3488         _editor->session()->add_command (new MementoCommand<TempoMap>(map, before_state, &after));
3489         _editor->commit_reversible_command ();
3490
3491         // delete the dummy marker we used for visual representation while moving.
3492         // a new visual marker will show up automatically.
3493         delete _marker;
3494 }
3495
3496 void
3497 TempoMarkerDrag::aborted (bool moved)
3498 {
3499         _marker->set_position (_marker->tempo().frame());
3500         if (moved) {
3501                 TempoMap& map (_editor->session()->tempo_map());
3502                 map.set_state (*before_state, Stateful::current_state_version);
3503                 // delete the dummy (hidden) marker we used for events while moving.
3504                 delete _marker;
3505         }
3506 }
3507
3508 BBTRulerDrag::BBTRulerDrag (Editor* e, ArdourCanvas::Item* i)
3509         : Drag (e, i)
3510         , _grab_qn (0.0)
3511         , _tempo (0)
3512         , before_state (0)
3513 {
3514         DEBUG_TRACE (DEBUG::Drags, "New BBTRulerDrag\n");
3515
3516 }
3517
3518 void
3519 BBTRulerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3520 {
3521         Drag::start_grab (event, cursor);
3522         TempoMap& map (_editor->session()->tempo_map());
3523         _tempo = const_cast<TempoSection*> (&map.tempo_section_at_frame (raw_grab_frame()));
3524         ostringstream sstr;
3525
3526         sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (adjusted_current_frame (event)).note_types_per_minute() << "\n";
3527         sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3528         show_verbose_cursor_text (sstr.str());
3529         finished (event, false);
3530 }
3531
3532 void
3533 BBTRulerDrag::setup_pointer_frame_offset ()
3534 {
3535         TempoMap& map (_editor->session()->tempo_map());
3536         const double beat_at_frame = max (0.0, map.beat_at_frame (raw_grab_frame()));
3537         const uint32_t divisions = _editor->get_grid_beat_divisions (0);
3538         double beat = 0.0;
3539
3540         if (divisions > 0) {
3541                 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * divisions)) / divisions);
3542         } else {
3543                 /* while it makes some sense for the user to determine the division to 'grab',
3544                    grabbing a bar often leads to confusing results wrt the actual tempo section being altered
3545                    and the result over steep tempo curves. Use sixteenths.
3546                 */
3547                 beat = floor (beat_at_frame) + (floor (((beat_at_frame - floor (beat_at_frame)) * 4)) / 4);
3548         }
3549
3550         _grab_qn = map.quarter_note_at_beat (beat);
3551
3552         _pointer_frame_offset = raw_grab_frame() - map.frame_at_quarter_note (_grab_qn);
3553
3554 }
3555
3556 void
3557 BBTRulerDrag::motion (GdkEvent* event, bool first_move)
3558 {
3559         TempoMap& map (_editor->session()->tempo_map());
3560
3561         if (first_move) {
3562                 /* get current state */
3563                 before_state = &map.get_state();
3564                 _editor->begin_reversible_command (_("stretch tempo"));
3565         }
3566
3567         framepos_t pf;
3568
3569         if (_editor->snap_musical()) {
3570                 pf = adjusted_current_frame (event, false);
3571         } else {
3572                 pf = adjusted_current_frame (event);
3573         }
3574
3575         if (ArdourKeyboard::indicates_constraint (event->button.state)) {
3576                 /* adjust previous tempo to match pointer frame */
3577                 _editor->session()->tempo_map().gui_stretch_tempo (_tempo, map.frame_at_quarter_note (_grab_qn), pf);
3578         }
3579         ostringstream sstr;
3580         sstr << "^" << fixed << setprecision(3) << map.tempo_at_frame (pf).note_types_per_minute() << "\n";
3581         sstr << "<" << fixed << setprecision(3) << _tempo->note_types_per_minute();
3582         show_verbose_cursor_text (sstr.str());
3583 }
3584
3585 void
3586 BBTRulerDrag::finished (GdkEvent* event, bool movement_occurred)
3587 {
3588         if (!movement_occurred) {
3589                 return;
3590         }
3591
3592         TempoMap& map (_editor->session()->tempo_map());
3593
3594         XMLNode &after = map.get_state();
3595         _editor->session()->add_command(new MementoCommand<TempoMap>(map, before_state, &after));
3596         _editor->commit_reversible_command ();
3597 }
3598
3599 void
3600 BBTRulerDrag::aborted (bool moved)
3601 {
3602         if (moved) {
3603                 _editor->session()->tempo_map().set_state (*before_state, Stateful::current_state_version);
3604         }
3605 }
3606
3607
3608 CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s)
3609         : Drag (e, &c.track_canvas_item(), false)
3610         , _cursor (c)
3611         , _stop (s)
3612         , _grab_zoom (0.0)
3613 {
3614         DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n");
3615 }
3616
3617 /** Do all the things we do when dragging the playhead to make it look as though
3618  *  we have located, without actually doing the locate (because that would cause
3619  *  the diskstream buffers to be refilled, which is too slow).
3620  */
3621 void
3622 CursorDrag::fake_locate (framepos_t t)
3623 {
3624         if (_editor->session () == 0) {
3625                 return;
3626         }
3627
3628         _editor->playhead_cursor->set_position (t);
3629
3630         Session* s = _editor->session ();
3631         if (s->timecode_transmission_suspended ()) {
3632                 framepos_t const f = _editor->playhead_cursor->current_frame ();
3633                 /* This is asynchronous so it will be sent "now"
3634                  */
3635                 s->send_mmc_locate (f);
3636                 /* These are synchronous and will be sent during the next
3637                    process cycle
3638                 */
3639                 s->queue_full_time_code ();
3640                 s->queue_song_position_pointer ();
3641         }
3642
3643         show_verbose_cursor_time (t);
3644         _editor->UpdateAllTransportClocks (t);
3645 }
3646
3647 void
3648 CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
3649 {
3650         Drag::start_grab (event, c);
3651         setup_snap_delta (_editor->playhead_cursor->current_frame ());
3652
3653         _grab_zoom = _editor->samples_per_pixel;
3654
3655         framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3656
3657         _editor->snap_to_with_modifier (where, event);
3658
3659         _editor->_dragging_playhead = true;
3660
3661         Session* s = _editor->session ();
3662
3663         /* grab the track canvas item as well */
3664
3665         _cursor.track_canvas_item().grab();
3666
3667         if (s) {
3668                 if (_was_rolling && _stop) {
3669                         s->request_stop ();
3670                 }
3671
3672                 if (s->is_auditioning()) {
3673                         s->cancel_audition ();
3674                 }
3675
3676
3677                 if (AudioEngine::instance()->connected()) {
3678
3679                         /* do this only if we're the engine is connected
3680                          * because otherwise this request will never be
3681                          * serviced and we'll busy wait forever. likewise,
3682                          * notice if we are disconnected while waiting for the
3683                          * request to be serviced.
3684                          */
3685
3686                         s->request_suspend_timecode_transmission ();
3687                         while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) {
3688                                 /* twiddle our thumbs */
3689                         }
3690                 }
3691         }
3692
3693         fake_locate (where - snap_delta (event->button.state));
3694 }
3695
3696 void
3697 CursorDrag::motion (GdkEvent* event, bool)
3698 {
3699         framepos_t where = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3700         _editor->snap_to_with_modifier (where, event);
3701         if (where != last_pointer_frame()) {
3702                 fake_locate (where - snap_delta (event->button.state));
3703         }
3704 }
3705
3706 void
3707 CursorDrag::finished (GdkEvent* event, bool movement_occurred)
3708 {
3709         _editor->_dragging_playhead = false;
3710
3711         _cursor.track_canvas_item().ungrab();
3712
3713         if (!movement_occurred && _stop) {
3714                 return;
3715         }
3716
3717         motion (event, false);
3718
3719         Session* s = _editor->session ();
3720         if (s) {
3721                 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
3722                 _editor->_pending_locate_request = true;
3723                 s->request_resume_timecode_transmission ();
3724         }
3725 }
3726
3727 void
3728 CursorDrag::aborted (bool)
3729 {
3730         _cursor.track_canvas_item().ungrab();
3731
3732         if (_editor->_dragging_playhead) {
3733                 _editor->session()->request_resume_timecode_transmission ();
3734                 _editor->_dragging_playhead = false;
3735         }
3736
3737         _editor->playhead_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
3738 }
3739
3740 FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3741         : RegionDrag (e, i, p, v)
3742 {
3743         DEBUG_TRACE (DEBUG::Drags, "New FadeInDrag\n");
3744 }
3745
3746 void
3747 FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3748 {
3749         Drag::start_grab (event, cursor);
3750
3751         AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3752         boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3753         setup_snap_delta (r->position ());
3754
3755         show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32);
3756 }
3757
3758 void
3759 FadeInDrag::setup_pointer_frame_offset ()
3760 {
3761         AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3762         boost::shared_ptr<AudioRegion> const r = arv->audio_region ();
3763         _pointer_frame_offset = raw_grab_frame() - ((framecnt_t) r->fade_in()->back()->when + r->position());
3764 }
3765
3766 void
3767 FadeInDrag::motion (GdkEvent* event, bool)
3768 {
3769         framecnt_t fade_length;
3770
3771         framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3772         _editor->snap_to_with_modifier (pos, event);
3773         pos -= snap_delta (event->button.state);
3774
3775         boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3776
3777         if (pos < (region->position() + 64)) {
3778                 fade_length = 64; // this should be a minimum defined somewhere
3779         } else if (pos > region->position() + region->length() - region->fade_out()->back()->when) {
3780                 fade_length = region->length() - region->fade_out()->back()->when - 1;
3781         } else {
3782                 fade_length = pos - region->position();
3783         }
3784
3785         for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3786
3787                 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3788
3789                 if (!tmp) {
3790                         continue;
3791                 }
3792
3793                 tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length);
3794         }
3795
3796         show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32);
3797 }
3798
3799 void
3800 FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
3801 {
3802         if (!movement_occurred) {
3803                 return;
3804         }
3805
3806         framecnt_t fade_length;
3807         framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3808         _editor->snap_to_with_modifier (pos, event);
3809         pos -= snap_delta (event->button.state);
3810
3811         boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3812
3813         if (pos < (region->position() + 64)) {
3814                 fade_length = 64; // this should be a minimum defined somewhere
3815         } else if (pos >= region->position() + region->length() - region->fade_out()->back()->when) {
3816                 fade_length = region->length() - region->fade_out()->back()->when - 1;
3817         } else {
3818                 fade_length = pos - region->position();
3819         }
3820
3821         bool in_command = false;
3822
3823         for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3824
3825                 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3826
3827                 if (!tmp) {
3828                         continue;
3829                 }
3830
3831                 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
3832                 XMLNode &before = alist->get_state();
3833
3834                 tmp->audio_region()->set_fade_in_length (fade_length);
3835                 tmp->audio_region()->set_fade_in_active (true);
3836
3837                 if (!in_command) {
3838                         _editor->begin_reversible_command (_("change fade in length"));
3839                         in_command = true;
3840                 }
3841                 XMLNode &after = alist->get_state();
3842                 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3843         }
3844
3845         if (in_command) {
3846                 _editor->commit_reversible_command ();
3847         }
3848 }
3849
3850 void
3851 FadeInDrag::aborted (bool)
3852 {
3853         for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3854                 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3855
3856                 if (!tmp) {
3857                         continue;
3858                 }
3859
3860                 tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when);
3861         }
3862 }
3863
3864 FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
3865         : RegionDrag (e, i, p, v)
3866 {
3867         DEBUG_TRACE (DEBUG::Drags, "New FadeOutDrag\n");
3868 }
3869
3870 void
3871 FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
3872 {
3873         Drag::start_grab (event, cursor);
3874
3875         AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3876         boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3877         setup_snap_delta (r->last_frame ());
3878
3879         show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame());
3880 }
3881
3882 void
3883 FadeOutDrag::setup_pointer_frame_offset ()
3884 {
3885         AudioRegionView* arv = dynamic_cast<AudioRegionView*> (_primary);
3886         boost::shared_ptr<AudioRegion> r = arv->audio_region ();
3887         _pointer_frame_offset = raw_grab_frame() - (r->length() - (framecnt_t) r->fade_out()->back()->when + r->position());
3888 }
3889
3890 void
3891 FadeOutDrag::motion (GdkEvent* event, bool)
3892 {
3893         framecnt_t fade_length;
3894
3895         framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3896         _editor->snap_to_with_modifier (pos, event);
3897         pos -= snap_delta (event->button.state);
3898
3899         boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3900
3901         if (pos > (region->last_frame() - 64)) {
3902                 fade_length = 64; // this should really be a minimum fade defined somewhere
3903         } else if (pos <= region->position() + region->fade_in()->back()->when) {
3904                 fade_length = region->length() - region->fade_in()->back()->when - 1;
3905         } else {
3906                 fade_length = region->last_frame() - pos;
3907         }
3908
3909         for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3910
3911                 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3912
3913                 if (!tmp) {
3914                         continue;
3915                 }
3916
3917                 tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length);
3918         }
3919
3920         show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame());
3921 }
3922
3923 void
3924 FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
3925 {
3926         if (!movement_occurred) {
3927                 return;
3928         }
3929
3930         framecnt_t fade_length;
3931
3932         framepos_t pos = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
3933         _editor->snap_to_with_modifier (pos, event);
3934         pos -= snap_delta (event->button.state);
3935
3936         boost::shared_ptr<AudioRegion> region = boost::dynamic_pointer_cast<AudioRegion> (_primary->region ());
3937
3938         if (pos > (region->last_frame() - 64)) {
3939                 fade_length = 64; // this should really be a minimum fade defined somewhere
3940         } else if (pos <= region->position() + region->fade_in()->back()->when) {
3941                 fade_length = region->length() - region->fade_in()->back()->when - 1;
3942         } else {
3943                 fade_length = region->last_frame() - pos;
3944         }
3945
3946         bool in_command = false;
3947
3948         for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3949
3950                 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3951
3952                 if (!tmp) {
3953                         continue;
3954                 }
3955
3956                 boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
3957                 XMLNode &before = alist->get_state();
3958
3959                 tmp->audio_region()->set_fade_out_length (fade_length);
3960                 tmp->audio_region()->set_fade_out_active (true);
3961
3962                 if (!in_command) {
3963                         _editor->begin_reversible_command (_("change fade out length"));
3964                         in_command = true;
3965                 }
3966                 XMLNode &after = alist->get_state();
3967                 _editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
3968         }
3969
3970         if (in_command) {
3971                 _editor->commit_reversible_command ();
3972         }
3973 }
3974
3975 void
3976 FadeOutDrag::aborted (bool)
3977 {
3978         for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
3979                 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);
3980
3981                 if (!tmp) {
3982                         continue;
3983                 }
3984
3985                 tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when);
3986         }
3987 }
3988
3989 MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
3990         : Drag (e, i)
3991         , _selection_changed (false)
3992 {
3993         DEBUG_TRACE (DEBUG::Drags, "New MarkerDrag\n");
3994         Gtk::Window* toplevel = _editor->current_toplevel();
3995         _marker = reinterpret_cast<ArdourMarker*> (_item->get_data ("marker"));
3996
3997         assert (_marker);
3998
3999         _points.push_back (ArdourCanvas::Duple (0, 0));
4000
4001         _points.push_back (ArdourCanvas::Duple (0, toplevel ? physical_screen_height (toplevel->get_window()) : 900));
4002 }
4003
4004 MarkerDrag::~MarkerDrag ()
4005 {
4006         for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
4007                 delete i->location;
4008         }
4009 }
4010
4011 MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, ArdourMarker* m)
4012 {
4013         location = new Location (*l);
4014         markers.push_back (m);
4015         move_both = false;
4016 }
4017
4018 void
4019 MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4020 {
4021         Drag::start_grab (event, cursor);
4022
4023         bool is_start;
4024
4025         Location *location = _editor->find_location_from_marker (_marker, is_start);
4026         _editor->_dragging_edit_point = true;
4027
4028         update_item (location);
4029
4030         // _drag_line->show();
4031         // _line->raise_to_top();
4032
4033         if (is_start) {
4034                 show_verbose_cursor_time (location->start());
4035         } else {
4036                 show_verbose_cursor_time (location->end());
4037         }
4038         setup_snap_delta (is_start ? location->start() : location->end());
4039
4040         Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4041
4042         switch (op) {
4043         case Selection::Toggle:
4044                 /* we toggle on the button release */
4045                 break;
4046         case Selection::Set:
4047                 if (!_editor->selection->selected (_marker)) {
4048                         _editor->selection->set (_marker);
4049                         _selection_changed = true;
4050                 }
4051                 break;
4052         case Selection::Extend:
4053         {
4054                 Locations::LocationList ll;
4055                 list<ArdourMarker*> to_add;
4056                 framepos_t s, e;
4057                 _editor->selection->markers.range (s, e);
4058                 s = min (_marker->position(), s);
4059                 e = max (_marker->position(), e);
4060                 s = min (s, e);
4061                 e = max (s, e);
4062                 if (e < max_framepos) {
4063                         ++e;
4064                 }
4065                 _editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
4066                 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
4067                         Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
4068                         if (lm) {
4069                                 if (lm->start) {
4070                                         to_add.push_back (lm->start);
4071                                 }
4072                                 if (lm->end) {
4073                                         to_add.push_back (lm->end);
4074                                 }
4075                         }
4076                 }
4077                 if (!to_add.empty()) {
4078                         _editor->selection->add (to_add);
4079                         _selection_changed = true;
4080                 }
4081                 break;
4082         }
4083         case Selection::Add:
4084                 _editor->selection->add (_marker);
4085                 _selection_changed = true;
4086
4087                 break;
4088         }
4089
4090         /* Set up copies for us to manipulate during the drag
4091          */
4092
4093         for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
4094
4095                 Location* l = _editor->find_location_from_marker (*i, is_start);
4096
4097                 if (!l) {
4098                         continue;
4099                 }
4100
4101                 if (l->is_mark()) {
4102                         _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4103                 } else {
4104                         /* range: check that the other end of the range isn't
4105                            already there.
4106                         */
4107                         CopiedLocationInfo::iterator x;
4108                         for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4109                                 if (*(*x).location == *l) {
4110                                         break;
4111                                 }
4112                         }
4113                         if (x == _copied_locations.end()) {
4114                                 _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i));
4115                         } else {
4116                                 (*x).markers.push_back (*i);
4117                                 (*x).move_both = true;
4118                         }
4119                 }
4120
4121         }
4122 }
4123
4124 void
4125 MarkerDrag::setup_pointer_frame_offset ()
4126 {
4127         bool is_start;
4128         Location *location = _editor->find_location_from_marker (_marker, is_start);
4129         _pointer_frame_offset = raw_grab_frame() - (is_start ? location->start() : location->end());
4130 }
4131
4132 void
4133 MarkerDrag::motion (GdkEvent* event, bool)
4134 {
4135         framecnt_t f_delta = 0;
4136         bool is_start;
4137         bool move_both = false;
4138         Location *real_location;
4139         Location *copy_location = 0;
4140         framecnt_t const sd = snap_delta (event->button.state);
4141
4142         framecnt_t const newframe = adjusted_frame (_drags->current_pointer_frame () + sd, event, true) - sd;
4143         framepos_t next = newframe;
4144
4145         if (Keyboard::modifier_state_contains (event->button.state, ArdourKeyboard::push_points_modifier ())) {
4146                 move_both = true;
4147         }
4148
4149         CopiedLocationInfo::iterator x;
4150
4151         /* find the marker we're dragging, and compute the delta */
4152
4153         for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4154
4155                 copy_location = (*x).location;
4156
4157                 if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) {
4158
4159                         /* this marker is represented by this
4160                          * CopiedLocationMarkerInfo
4161                          */
4162
4163                         if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) {
4164                                 /* que pasa ?? */
4165                                 return;
4166                         }
4167
4168                         if (real_location->is_mark()) {
4169                                 f_delta = newframe - copy_location->start();
4170                         } else {
4171
4172
4173                                 switch (_marker->type()) {
4174                                 case ArdourMarker::SessionStart:
4175                                 case ArdourMarker::RangeStart:
4176                                 case ArdourMarker::LoopStart:
4177                                 case ArdourMarker::PunchIn:
4178                                         f_delta = newframe - copy_location->start();
4179                                         break;
4180
4181                                 case ArdourMarker::SessionEnd:
4182                                 case ArdourMarker::RangeEnd:
4183                                 case ArdourMarker::LoopEnd:
4184                                 case ArdourMarker::PunchOut:
4185                                         f_delta = newframe - copy_location->end();
4186                                         break;
4187                                 default:
4188                                         /* what kind of marker is this ? */
4189                                         return;
4190                                 }
4191                         }
4192
4193                         break;
4194                 }
4195         }
4196
4197         if (x == _copied_locations.end()) {
4198                 /* hmm, impossible - we didn't find the dragged marker */
4199                 return;
4200         }
4201
4202         const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4203
4204         /* now move them all */
4205
4206         for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4207
4208                 copy_location = x->location;
4209
4210                 if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) {
4211                         continue;
4212                 }
4213
4214                 if (real_location->locked()) {
4215                         continue;
4216                 }
4217
4218                 if (copy_location->is_mark()) {
4219
4220                         /* now move it */
4221                         copy_location->set_start (copy_location->start() + f_delta, false, true, divisions);
4222
4223                 } else {
4224
4225                         framepos_t new_start = copy_location->start() + f_delta;
4226                         framepos_t new_end = copy_location->end() + f_delta;
4227
4228                         if (is_start) { // start-of-range marker
4229
4230                                 if (move_both || (*x).move_both) {
4231                                         copy_location->set_start (new_start, false, true, divisions);
4232                                         copy_location->set_end (new_end, false, true, divisions);
4233                                 } else  if (new_start < copy_location->end()) {
4234                                         copy_location->set_start (new_start, false, true, divisions);
4235                                 } else if (newframe > 0) {
4236                                         //_editor->snap_to (next, RoundUpAlways, true);
4237                                         copy_location->set_end (next, false, true, divisions);
4238                                         copy_location->set_start (newframe, false, true, divisions);
4239                                 }
4240
4241                         } else { // end marker
4242
4243                                 if (move_both || (*x).move_both) {
4244                                         copy_location->set_end (new_end, divisions);
4245                                         copy_location->set_start (new_start, false, true, divisions);
4246                                 } else if (new_end > copy_location->start()) {
4247                                         copy_location->set_end (new_end, false, true, divisions);
4248                                 } else if (newframe > 0) {
4249                                         //_editor->snap_to (next, RoundDownAlways, true);
4250                                         copy_location->set_start (next, false, true, divisions);
4251                                         copy_location->set_end (newframe, false, true, divisions);
4252                                 }
4253                         }
4254                 }
4255
4256                 update_item (copy_location);
4257
4258                 /* now lookup the actual GUI items used to display this
4259                  * location and move them to wherever the copy of the location
4260                  * is now. This means that the logic in ARDOUR::Location is
4261                  * still enforced, even though we are not (yet) modifying
4262                  * the real Location itself.
4263                  */
4264
4265                 Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);
4266
4267                 if (lm) {
4268                         lm->set_position (copy_location->start(), copy_location->end());
4269                 }
4270
4271         }
4272
4273         assert (!_copied_locations.empty());
4274
4275         show_verbose_cursor_time (newframe);
4276 }
4277
4278 void
4279 MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
4280 {
4281         if (!movement_occurred) {
4282
4283                 if (was_double_click()) {
4284                         _editor->rename_marker (_marker);
4285                         return;
4286                 }
4287
4288                 /* just a click, do nothing but finish
4289                    off the selection process
4290                 */
4291
4292                 Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
4293                 switch (op) {
4294                 case Selection::Set:
4295                         if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
4296                                 _editor->selection->set (_marker);
4297                                 _selection_changed = true;
4298                         }
4299                         break;
4300
4301                 case Selection::Toggle:
4302                         /* we toggle on the button release, click only */
4303                         _editor->selection->toggle (_marker);
4304                         _selection_changed = true;
4305
4306                         break;
4307
4308                 case Selection::Extend:
4309                 case Selection::Add:
4310                         break;
4311                 }
4312
4313                 if (_selection_changed) {
4314                         _editor->begin_reversible_selection_op(X_("Select Marker Release"));
4315                         _editor->commit_reversible_selection_op();
4316                 }
4317
4318                 return;
4319         }
4320
4321         _editor->_dragging_edit_point = false;
4322
4323         XMLNode &before = _editor->session()->locations()->get_state();
4324         bool in_command = false;
4325
4326         MarkerSelection::iterator i;
4327         CopiedLocationInfo::iterator x;
4328         const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
4329         bool is_start;
4330
4331         for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
4332              x != _copied_locations.end() && i != _editor->selection->markers.end();
4333              ++i, ++x) {
4334
4335                 Location * location = _editor->find_location_from_marker (*i, is_start);
4336
4337                 if (location) {
4338
4339                         if (location->locked()) {
4340                                 continue;
4341                         }
4342                         if (!in_command) {
4343                                 _editor->begin_reversible_command ( _("move marker") );
4344                                 in_command = true;
4345                         }
4346                         if (location->is_mark()) {
4347                                 location->set_start (((*x).location)->start(), false, true, divisions);
4348                         } else {
4349                                 location->set (((*x).location)->start(), ((*x).location)->end(), true, divisions);
4350                         }
4351
4352                         if (location->is_session_range()) {
4353                                 _editor->session()->set_end_is_free (false);
4354                         }
4355                 }
4356         }
4357
4358         if (in_command) {
4359                 XMLNode &after = _editor->session()->locations()->get_state();
4360                 _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
4361                 _editor->commit_reversible_command ();
4362         }
4363 }
4364
4365 void
4366 MarkerDrag::aborted (bool movement_occurred)
4367 {
4368         if (!movement_occurred) {
4369                 return;
4370         }
4371
4372         for (CopiedLocationInfo::iterator x = _copied_locations.begin(); x != _copied_locations.end(); ++x) {
4373
4374                 /* move all markers to their original location */
4375
4376
4377                 for (vector<ArdourMarker*>::iterator m = x->markers.begin(); m != x->markers.end(); ++m) {
4378
4379                         bool is_start;
4380                         Location * location = _editor->find_location_from_marker (*m, is_start);
4381
4382                         if (location) {
4383                                 (*m)->set_position (is_start ? location->start() : location->end());
4384                         }
4385                 }
4386         }
4387 }
4388
4389 void
4390 MarkerDrag::update_item (Location*)
4391 {
4392         /* noop */
4393 }
4394
4395 ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
4396         : Drag (e, i)
4397         , _fixed_grab_x (0.0)
4398         , _fixed_grab_y (0.0)
4399         , _cumulative_x_drag (0.0)
4400         , _cumulative_y_drag (0.0)
4401         , _pushing (false)
4402         , _final_index (0)
4403 {
4404         if (_zero_gain_fraction < 0.0) {
4405                 _zero_gain_fraction = gain_to_slider_position_with_max (dB_to_coefficient (0.0), Config->get_max_gain());
4406         }
4407
4408         DEBUG_TRACE (DEBUG::Drags, "New ControlPointDrag\n");
4409
4410         _point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
4411         assert (_point);
4412 }
4413
4414
4415 void
4416 ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4417 {
4418         Drag::start_grab (event, _editor->cursors()->fader);
4419
4420         // start the grab at the center of the control point so
4421         // the point doesn't 'jump' to the mouse after the first drag
4422         _fixed_grab_x = _point->get_x() + _editor->sample_to_pixel_unrounded (_point->line().offset());
4423         _fixed_grab_y = _point->get_y();
4424
4425         framepos_t pos = _editor->pixel_to_sample (_fixed_grab_x);
4426         setup_snap_delta (pos);
4427
4428         float const fraction = 1 - (_point->get_y() / _point->line().height());
4429         show_verbose_cursor_text (_point->line().get_verbose_cursor_string (fraction));
4430
4431         _pushing = Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::push_points_modifier ());
4432
4433         if (!_point->can_slide ()) {
4434                 _x_constrained = true;
4435         }
4436 }
4437
4438 void
4439 ControlPointDrag::motion (GdkEvent* event, bool first_motion)
4440 {
4441         double dx = _drags->current_pointer_x() - last_pointer_x();
4442         double dy = current_pointer_y() - last_pointer_y();
4443         bool need_snap = true;
4444
4445         if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4446                 dx *= 0.1;
4447                 dy *= 0.1;
4448                 need_snap = false;
4449         }
4450
4451         /* coordinate in pixels relative to the start of the region (for region-based automation)
4452            or track (for track-based automation) */
4453         double cx = _fixed_grab_x + _cumulative_x_drag + dx;
4454         double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4455
4456         // calculate zero crossing point. back off by .01 to stay on the
4457         // positive side of zero
4458         double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;
4459
4460         if (_x_constrained) {
4461                 cx = _fixed_grab_x;
4462         }
4463         if (_y_constrained) {
4464                 cy = _fixed_grab_y;
4465         }
4466
4467         _cumulative_x_drag = cx - _fixed_grab_x;
4468         _cumulative_y_drag = cy - _fixed_grab_y;
4469
4470         cx = max (0.0, cx);
4471         cy = max (0.0, cy);
4472         cy = min ((double) _point->line().height(), cy);
4473
4474         // make sure we hit zero when passing through
4475         if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
4476                 cy = zero_gain_y;
4477         }
4478
4479         framepos_t cx_frames = _editor->pixel_to_sample (cx) + snap_delta (event->button.state);
4480         if (!_x_constrained && need_snap) {
4481                 _editor->snap_to_with_modifier (cx_frames, event);
4482         }
4483
4484         cx_frames -= snap_delta (event->button.state);
4485         cx_frames = min (cx_frames, _point->line().maximum_time() + _point->line().offset());
4486
4487         float const fraction = 1.0 - (cy / _point->line().height());
4488
4489         if (first_motion) {
4490                 float const initial_fraction = 1.0 - (_fixed_grab_y / _point->line().height());
4491                 _editor->begin_reversible_command (_("automation event move"));
4492                 _point->line().start_drag_single (_point, _fixed_grab_x, initial_fraction);
4493         }
4494         pair<double, float> result;
4495         result = _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index);
4496
4497         show_verbose_cursor_text (_point->line().get_verbose_cursor_string (result.second));
4498 }
4499
4500 void
4501 ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
4502 {
4503         if (!movement_occurred) {
4504
4505                 /* just a click */
4506                 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier))) {
4507                         _editor->reset_point_selection ();
4508                 }
4509
4510         } else {
4511                 _point->line().end_drag (_pushing, _final_index);
4512                 _editor->commit_reversible_command ();
4513         }
4514 }
4515
4516 void
4517 ControlPointDrag::aborted (bool)
4518 {
4519         _point->line().reset ();
4520 }
4521
4522 bool
4523 ControlPointDrag::active (Editing::MouseMode m)
4524 {
4525         if (m == Editing::MouseDraw) {
4526                 /* always active in mouse draw */
4527                 return true;
4528         }
4529
4530         /* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
4531         return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
4532 }
4533
4534 LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
4535         : Drag (e, i)
4536         , _line (0)
4537         , _fixed_grab_x (0.0)
4538         , _fixed_grab_y (0.0)
4539         , _cumulative_y_drag (0)
4540         , _before (0)
4541         , _after (0)
4542 {
4543         DEBUG_TRACE (DEBUG::Drags, "New LineDrag\n");
4544 }
4545
4546 void
4547 LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4548 {
4549         _line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
4550         assert (_line);
4551
4552         _item = &_line->grab_item ();
4553
4554         /* need to get x coordinate in terms of parent (TimeAxisItemView)
4555            origin, and ditto for y.
4556         */
4557
4558         double mx = event->button.x;
4559         double my = event->button.y;
4560
4561         _line->grab_item().canvas_to_item (mx, my);
4562
4563         framecnt_t const frame_within_region = (framecnt_t) floor (mx * _editor->samples_per_pixel);
4564
4565         if (!_line->control_points_adjacent (frame_within_region, _before, _after)) {
4566                 /* no adjacent points */
4567                 return;
4568         }
4569
4570         Drag::start_grab (event, _editor->cursors()->fader);
4571
4572         /* store grab start in item frame */
4573         double const bx = _line->nth (_before)->get_x();
4574         double const ax = _line->nth (_after)->get_x();
4575         double const click_ratio = (ax - mx) / (ax - bx);
4576
4577         double const cy = ((_line->nth (_before)->get_y() * click_ratio) + (_line->nth (_after)->get_y() * (1 - click_ratio)));
4578
4579         _fixed_grab_x = mx;
4580         _fixed_grab_y = cy;
4581
4582         double fraction = 1.0 - (cy / _line->height());
4583
4584         show_verbose_cursor_text (_line->get_verbose_cursor_string (fraction));
4585 }
4586
4587 void
4588 LineDrag::motion (GdkEvent* event, bool first_move)
4589 {
4590         double dy = current_pointer_y() - last_pointer_y();
4591
4592         if (Keyboard::modifier_state_equals (event->button.state, ArdourKeyboard::fine_adjust_modifier ())) {
4593                 dy *= 0.1;
4594         }
4595
4596         double cy = _fixed_grab_y + _cumulative_y_drag + dy;
4597
4598         _cumulative_y_drag = cy - _fixed_grab_y;
4599
4600         cy = max (0.0, cy);
4601         cy = min ((double) _line->height(), cy);
4602
4603         double const fraction = 1.0 - (cy / _line->height());
4604         uint32_t ignored;
4605
4606         if (first_move) {
4607                 float const initial_fraction = 1.0 - (_fixed_grab_y / _line->height());
4608
4609                 _editor->begin_reversible_command (_("automation range move"));
4610                 _line->start_drag_line (_before, _after, initial_fraction);
4611         }
4612
4613         /* we are ignoring x position for this drag, so we can just pass in anything */
4614         pair<double, float> result;
4615
4616         result = _line->drag_motion (0, fraction, true, false, ignored);
4617         show_verbose_cursor_text (_line->get_verbose_cursor_string (result.second));
4618 }
4619
4620 void
4621 LineDrag::finished (GdkEvent* event, bool movement_occurred)
4622 {
4623         if (movement_occurred) {
4624                 motion (event, false);
4625                 _line->end_drag (false, 0);
4626                 _editor->commit_reversible_command ();
4627         } else {
4628                 /* add a new control point on the line */
4629
4630                 AutomationTimeAxisView* atv;
4631
4632                 if ((atv = dynamic_cast<AutomationTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4633                         framepos_t where = grab_frame ();
4634
4635                         double cx = 0;
4636                         double cy = _fixed_grab_y;
4637
4638                         _line->grab_item().item_to_canvas (cx, cy);
4639
4640                         atv->add_automation_event (event, where, cy, false);
4641                 } else if (dynamic_cast<AudioTimeAxisView*>(_editor->clicked_axisview) != 0) {
4642                         AudioRegionView* arv;
4643
4644                         if ((arv = dynamic_cast<AudioRegionView*>(_editor->clicked_regionview)) != 0) {
4645                                 arv->add_gain_point_event (&arv->get_gain_line()->grab_item(), event, false);
4646                         }
4647                 }
4648         }
4649 }
4650
4651 void
4652 LineDrag::aborted (bool)
4653 {
4654         _line->reset ();
4655 }
4656
4657 FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
4658         : Drag (e, i),
4659           _line (0),
4660           _arv (0),
4661           _region_view_grab_x (0.0),
4662           _cumulative_x_drag (0),
4663           _before (0.0),
4664           _max_x (0)
4665 {
4666         DEBUG_TRACE (DEBUG::Drags, "New FeatureLineDrag\n");
4667 }
4668
4669 void
4670 FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
4671 {
4672         Drag::start_grab (event);
4673
4674         _line = reinterpret_cast<Line*> (_item);
4675         assert (_line);
4676
4677         /* need to get x coordinate in terms of parent (AudioRegionView) origin. */
4678
4679         double cx = event->button.x;
4680         double cy = event->button.y;
4681
4682         _item->parent()->canvas_to_item (cx, cy);
4683
4684         /* store grab start in parent frame */
4685         _region_view_grab_x = cx;
4686
4687         _before = *(float*) _item->get_data ("position");
4688
4689         _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4690
4691         _max_x = _editor->sample_to_pixel(_arv->get_duration());
4692 }
4693
4694 void
4695 FeatureLineDrag::motion (GdkEvent*, bool)
4696 {
4697         double dx = _drags->current_pointer_x() - last_pointer_x();
4698
4699         double cx = _region_view_grab_x + _cumulative_x_drag + dx;
4700
4701         _cumulative_x_drag += dx;
4702
4703         /* Clamp the min and max extent of the drag to keep it within the region view bounds */
4704
4705         if (cx > _max_x){
4706                 cx = _max_x;
4707         }
4708         else if(cx < 0){
4709                 cx = 0;
4710         }
4711
4712         boost::optional<ArdourCanvas::Rect> bbox = _line->bounding_box ();
4713         assert (bbox);
4714         _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ()));
4715
4716         float *pos = new float;
4717         *pos = cx;
4718
4719         _line->set_data ("position", pos);
4720
4721         _before = cx;
4722 }
4723
4724 void
4725 FeatureLineDrag::finished (GdkEvent*, bool)
4726 {
4727         _arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
4728         _arv->update_transient(_before, _before);
4729 }
4730
4731 void
4732 FeatureLineDrag::aborted (bool)
4733 {
4734         //_line->reset ();
4735 }
4736
4737 RubberbandSelectDrag::RubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
4738         : Drag (e, i)
4739         , _vertical_only (false)
4740 {
4741         DEBUG_TRACE (DEBUG::Drags, "New RubberbandSelectDrag\n");
4742 }
4743
4744 void
4745 RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
4746 {
4747         Drag::start_grab (event);
4748         show_verbose_cursor_time (adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid()));
4749 }
4750
4751 void
4752 RubberbandSelectDrag::motion (GdkEvent* event, bool)
4753 {
4754         framepos_t start;
4755         framepos_t end;
4756         double y1;
4757         double y2;
4758
4759         framepos_t const pf = adjusted_current_frame (event, UIConfiguration::instance().get_rubberbanding_snaps_to_grid());
4760
4761         framepos_t grab = grab_frame ();
4762         if (UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4763                 _editor->snap_to_with_modifier (grab, event);
4764         } else {
4765                 grab = raw_grab_frame ();
4766         }
4767
4768         /* base start and end on initial click position */
4769
4770         if (pf < grab) {
4771                 start = pf;
4772                 end = grab;
4773         } else {
4774                 end = pf;
4775                 start = grab;
4776         }
4777
4778         if (current_pointer_y() < grab_y()) {
4779                 y1 = current_pointer_y();
4780                 y2 = grab_y();
4781         } else {
4782                 y2 = current_pointer_y();
4783                 y1 = grab_y();
4784         }
4785
4786         if (start != end || y1 != y2) {
4787
4788                 double x1 = _editor->sample_to_pixel (start);
4789                 double x2 = _editor->sample_to_pixel (end);
4790                 const double min_dimension = 2.0;
4791
4792                 if (_vertical_only) {
4793                         /* fixed 10 pixel width */
4794                         x2 = x1 + 10;
4795                 } else {
4796                         if (x2 < x1) {
4797                                 x2 = min (x1 - min_dimension, x2);
4798                         } else {
4799                                 x2 = max (x1 + min_dimension, x2);
4800                         }
4801                 }
4802
4803                 if (y2 < y1) {
4804                         y2 = min (y1 - min_dimension, y2);
4805                 } else {
4806                         y2 = max (y1 + min_dimension, y2);
4807                 }
4808
4809                 /* translate rect into item space and set */
4810
4811                 ArdourCanvas::Rect r (x1, y1, x2, y2);
4812
4813                 /* this drag is a _trackview_only == true drag, so the y1 and
4814                  * y2 (computed using current_pointer_y() and grab_y()) will be
4815                  * relative to the top of the trackview group). The
4816                  * rubberband rect has the same parent/scroll offset as the
4817                  * the trackview group, so we can use the "r" rect directly
4818                  * to set the shape of the rubberband.
4819                  */
4820
4821                 _editor->rubberband_rect->set (r);
4822                 _editor->rubberband_rect->show();
4823                 _editor->rubberband_rect->raise_to_top();
4824
4825                 show_verbose_cursor_time (pf);
4826
4827                 do_select_things (event, true);
4828         }
4829 }
4830
4831 void
4832 RubberbandSelectDrag::do_select_things (GdkEvent* event, bool drag_in_progress)
4833 {
4834         framepos_t x1;
4835         framepos_t x2;
4836         framepos_t grab = grab_frame ();
4837         framepos_t lpf = last_pointer_frame ();
4838
4839         if (!UIConfiguration::instance().get_rubberbanding_snaps_to_grid ()) {
4840                 grab = raw_grab_frame ();
4841                 lpf = _editor->pixel_to_sample_from_event (last_pointer_x());
4842         }
4843
4844         if (grab < lpf) {
4845                 x1 = grab;
4846                 x2 = lpf;
4847         } else {
4848                 x2 = grab;
4849                 x1 = lpf;
4850         }
4851
4852         double y1;
4853         double y2;
4854
4855         if (current_pointer_y() < grab_y()) {
4856                 y1 = current_pointer_y();
4857                 y2 = grab_y();
4858         } else {
4859                 y2 = current_pointer_y();
4860                 y1 = grab_y();
4861         }
4862
4863         select_things (event->button.state, x1, x2, y1, y2, drag_in_progress);
4864 }
4865
4866 void
4867 RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
4868 {
4869         if (movement_occurred) {
4870
4871                 motion (event, false);
4872                 do_select_things (event, false);
4873
4874         } else {
4875
4876                 /* just a click */
4877
4878                 bool do_deselect = true;
4879                 MidiTimeAxisView* mtv;
4880
4881                 if ((mtv = dynamic_cast<MidiTimeAxisView*>(_editor->clicked_axisview)) != 0) {
4882                         /* MIDI track */
4883                         if (_editor->selection->empty() && _editor->mouse_mode == MouseDraw) {
4884                                 /* nothing selected */
4885                                 add_midi_region (mtv, true, _editor->get_grid_music_divisions(event->button.state));
4886                                 do_deselect = false;
4887                         }
4888                 }
4889
4890                 /* do not deselect if Primary or Tertiary (toggle-select or
4891                  * extend-select are pressed.
4892                  */
4893
4894                 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) &&
4895                     !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) &&
4896                     do_deselect) {
4897                         deselect_things ();
4898                 }
4899
4900         }
4901
4902         _editor->rubberband_rect->hide();
4903 }
4904
4905 void
4906 RubberbandSelectDrag::aborted (bool)
4907 {
4908         _editor->rubberband_rect->hide ();
4909 }
4910
4911 TimeFXDrag::TimeFXDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, std::list<RegionView*> const & v)
4912         : RegionDrag (e, i, p, v)
4913 {
4914         DEBUG_TRACE (DEBUG::Drags, "New TimeFXDrag\n");
4915 }
4916
4917 void
4918 TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
4919 {
4920         Drag::start_grab (event, cursor);
4921
4922         _editor->get_selection().add (_primary);
4923
4924         framepos_t where = _primary->region()->position();
4925         setup_snap_delta (where);
4926
4927         show_verbose_cursor_duration (where, adjusted_current_frame (event), 0);
4928 }
4929
4930 void
4931 TimeFXDrag::motion (GdkEvent* event, bool)
4932 {
4933         RegionView* rv = _primary;
4934         StreamView* cv = rv->get_time_axis_view().view ();
4935
4936         pair<TimeAxisView*, double> const tv = _editor->trackview_by_y_position (grab_y());
4937         int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second;
4938         int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers();
4939         framepos_t pf = _editor->canvas_event_sample (event) + snap_delta (event->button.state);
4940         _editor->snap_to_with_modifier (pf, event);
4941         pf -= snap_delta (event->button.state);
4942
4943         if (pf > rv->region()->position()) {
4944                 rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer);
4945         }
4946
4947         show_verbose_cursor_duration (_primary->region()->position(), pf, 0);
4948 }
4949
4950 void
4951 TimeFXDrag::finished (GdkEvent* event, bool movement_occurred)
4952 {
4953         /* this may have been a single click, no drag. We still want the dialog
4954            to show up in that case, so that the user can manually edit the
4955            parameters for the timestretch.
4956         */
4957
4958         float fraction = 1.0;
4959
4960         if (movement_occurred) {
4961
4962                 motion (event, false);
4963
4964                 _primary->get_time_axis_view().hide_timestretch ();
4965
4966                 framepos_t adjusted_frame_pos = adjusted_current_frame (event);
4967
4968                 if (adjusted_frame_pos < _primary->region()->position()) {
4969                         /* backwards drag of the left edge - not usable */
4970                         return;
4971                 }
4972
4973                 framecnt_t newlen = adjusted_frame_pos - _primary->region()->position();
4974
4975                 fraction = (double) newlen / (double) _primary->region()->length();
4976
4977 #ifndef USE_RUBBERBAND
4978                 // Soundtouch uses fraction / 100 instead of normal (/ 1)
4979                 if (_primary->region()->data_type() == DataType::AUDIO) {
4980                         fraction = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
4981                 }
4982 #endif
4983         }
4984
4985         if (!_editor->get_selection().regions.empty()) {
4986                 /* primary will already be included in the selection, and edit
4987                    group shared editing will propagate selection across
4988                    equivalent regions, so just use the current region
4989                    selection.
4990                 */
4991
4992                 if (_editor->time_stretch (_editor->get_selection().regions, fraction) == -1) {
4993                         error << _("An error occurred while executing time stretch operation") << endmsg;
4994                 }
4995         }
4996 }
4997
4998 void
4999 TimeFXDrag::aborted (bool)
5000 {
5001         _primary->get_time_axis_view().hide_timestretch ();
5002 }
5003
5004 ScrubDrag::ScrubDrag (Editor* e, ArdourCanvas::Item* i)
5005         : Drag (e, i)
5006 {
5007         DEBUG_TRACE (DEBUG::Drags, "New ScrubDrag\n");
5008 }
5009
5010 void
5011 ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5012 {
5013         Drag::start_grab (event);
5014 }
5015
5016 void
5017 ScrubDrag::motion (GdkEvent* /*event*/, bool)
5018 {
5019         _editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
5020 }
5021
5022 void
5023 ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
5024 {
5025         if (movement_occurred && _editor->session()) {
5026                 /* make sure we stop */
5027                 _editor->session()->request_transport_speed (0.0);
5028         }
5029 }
5030
5031 void
5032 ScrubDrag::aborted (bool)
5033 {
5034         /* XXX: TODO */
5035 }
5036
5037 SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5038         : Drag (e, i)
5039         , _operation (o)
5040         , _add (false)
5041         , _time_selection_at_start (!_editor->get_selection().time.empty())
5042 {
5043         DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n");
5044
5045         if (_time_selection_at_start) {
5046                 start_at_start = _editor->get_selection().time.start();
5047                 end_at_start = _editor->get_selection().time.end_frame();
5048         }
5049 }
5050
5051 void
5052 SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
5053 {
5054         if (_editor->session() == 0) {
5055                 return;
5056         }
5057
5058         Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5059
5060         switch (_operation) {
5061         case CreateSelection:
5062                 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5063                         _add = true;
5064                 } else {
5065                         _add = false;
5066                 }
5067                 cursor = _editor->cursors()->selector;
5068                 Drag::start_grab (event, cursor);
5069                 break;
5070
5071         case SelectionStartTrim:
5072                 if (_editor->clicked_axisview) {
5073                         _editor->clicked_axisview->order_selection_trims (_item, true);
5074                 }
5075                 Drag::start_grab (event, _editor->cursors()->left_side_trim);
5076                 break;
5077
5078         case SelectionEndTrim:
5079                 if (_editor->clicked_axisview) {
5080                         _editor->clicked_axisview->order_selection_trims (_item, false);
5081                 }
5082                 Drag::start_grab (event, _editor->cursors()->right_side_trim);
5083                 break;
5084
5085         case SelectionMove:
5086                 Drag::start_grab (event, cursor);
5087                 break;
5088
5089         case SelectionExtend:
5090                 Drag::start_grab (event, cursor);
5091                 break;
5092         }
5093
5094         if (_operation == SelectionMove) {
5095                 show_verbose_cursor_time (_editor->selection->time[_editor->clicked_selection].start);
5096         } else {
5097                 show_verbose_cursor_time (adjusted_current_frame (event));
5098         }
5099 }
5100
5101 void
5102 SelectionDrag::setup_pointer_frame_offset ()
5103 {
5104         switch (_operation) {
5105         case CreateSelection:
5106                 _pointer_frame_offset = 0;
5107                 break;
5108
5109         case SelectionStartTrim:
5110         case SelectionMove:
5111                 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].start;
5112                 break;
5113
5114         case SelectionEndTrim:
5115                 _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end;
5116                 break;
5117
5118         case SelectionExtend:
5119                 break;
5120         }
5121 }
5122
5123 void
5124 SelectionDrag::motion (GdkEvent* event, bool first_move)
5125 {
5126         framepos_t start = 0;
5127         framepos_t end = 0;
5128         framecnt_t length = 0;
5129         framecnt_t distance = 0;
5130
5131         framepos_t const pending_position = adjusted_current_frame (event);
5132
5133         if (_operation != CreateSelection && pending_position == last_pointer_frame()) {
5134                 return;
5135         }
5136
5137         switch (_operation) {
5138         case CreateSelection:
5139         {
5140                 framepos_t grab = grab_frame ();
5141
5142                 if (first_move) {
5143                         grab = adjusted_current_frame (event, false);
5144                         if (grab < pending_position) {
5145                                 _editor->snap_to (grab, RoundDownMaybe);
5146                         }  else {
5147                                 _editor->snap_to (grab, RoundUpMaybe);
5148                         }
5149                 }
5150
5151                 if (pending_position < grab) {
5152                         start = pending_position;
5153                         end = grab;
5154                 } else {
5155                         end = pending_position;
5156                         start = grab;
5157                 }
5158
5159                 /* first drag: Either add to the selection
5160                    or create a new selection
5161                 */
5162
5163                 if (first_move) {
5164
5165                         if (_add) {
5166
5167                                 /* adding to the selection */
5168                                 _editor->set_selected_track_as_side_effect (Selection::Add);
5169                                 _editor->clicked_selection = _editor->selection->add (start, end);
5170                                 _add = false;
5171
5172                         } else {
5173
5174                                 /* new selection */
5175
5176                                 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5177                                         _editor->set_selected_track_as_side_effect (Selection::Set);
5178                                 }
5179
5180                                 _editor->clicked_selection = _editor->selection->set (start, end);
5181                         }
5182                 }
5183
5184                 //if user is selecting a range on an automation track, bail out here before we get to the grouped stuff,
5185                 // because the grouped stuff will start working on tracks (routeTAVs), and end up removing this
5186                 AutomationTimeAxisView *atest = dynamic_cast<AutomationTimeAxisView *>(_editor->clicked_axisview);
5187                 if (atest) {
5188                         _editor->selection->add (atest);
5189                         break;
5190                 }
5191
5192                 /* select all tracks within the rectangle that we've marked out so far */
5193                 TrackViewList new_selection;
5194                 TrackViewList& all_tracks (_editor->track_views);
5195
5196                 ArdourCanvas::Coord const top = grab_y();
5197                 ArdourCanvas::Coord const bottom = current_pointer_y();
5198
5199                 if (top >= 0 && bottom >= 0) {
5200
5201                         //first, find the tracks that are covered in the y range selection
5202                         for (TrackViewList::const_iterator i = all_tracks.begin(); i != all_tracks.end(); ++i) {
5203                                 if ((*i)->covered_by_y_range (top, bottom)) {
5204                                         new_selection.push_back (*i);
5205                                 }
5206                         }
5207
5208                         //now compare our list with the current selection, and add as necessary
5209                         //( NOTE: most mouse moves don't change the selection so we can't just SET it for every mouse move; it gets clunky )
5210                         TrackViewList tracks_to_add;
5211                         TrackViewList tracks_to_remove;
5212                         for (TrackViewList::const_iterator i = new_selection.begin(); i != new_selection.end(); ++i) 
5213                                 if ( !_editor->selection->tracks.contains ( *i ) )
5214                                         tracks_to_add.push_back ( *i );
5215                         _editor->selection->add(tracks_to_add);
5216                 }
5217         }
5218         break;
5219
5220         case SelectionStartTrim:
5221
5222                 end = _editor->selection->time[_editor->clicked_selection].end;
5223
5224                 if (pending_position > end) {
5225                         start = end;
5226                 } else {
5227                         start = pending_position;
5228                 }
5229                 break;
5230
5231         case SelectionEndTrim:
5232
5233                 start = _editor->selection->time[_editor->clicked_selection].start;
5234
5235                 if (pending_position < start) {
5236                         end = start;
5237                 } else {
5238                         end = pending_position;
5239                 }
5240
5241                 break;
5242
5243         case SelectionMove:
5244
5245                 start = _editor->selection->time[_editor->clicked_selection].start;
5246                 end = _editor->selection->time[_editor->clicked_selection].end;
5247
5248                 length = end - start;
5249                 distance = pending_position - start;
5250                 start = pending_position;
5251                 _editor->snap_to (start);
5252
5253                 end = start + length;
5254
5255                 break;
5256
5257         case SelectionExtend:
5258                 break;
5259         }
5260
5261         if (start != end) {
5262                 switch (_operation) {
5263                 case SelectionMove:
5264                         if (_time_selection_at_start) {
5265                                 _editor->selection->move_time (distance);
5266                         }
5267                         break;
5268                 default:
5269                         _editor->selection->replace (_editor->clicked_selection, start, end);
5270                 }
5271         }
5272
5273         if (_operation == SelectionMove) {
5274                 show_verbose_cursor_time(start);
5275         } else {
5276                 show_verbose_cursor_time(pending_position);
5277         }
5278 }
5279
5280 void
5281 SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
5282 {
5283         Session* s = _editor->session();
5284
5285         _editor->begin_reversible_selection_op (X_("Change Time Selection"));
5286         if (movement_occurred) {
5287                 motion (event, false);
5288                 /* XXX this is not object-oriented programming at all. ick */
5289                 if (_editor->selection->time.consolidate()) {
5290                         _editor->selection->TimeChanged ();
5291                 }
5292
5293                 /* XXX what if its a music time selection? */
5294                 if (s) {
5295                         if (s->get_play_range() && s->transport_rolling()) {
5296                                 s->request_play_range (&_editor->selection->time, true);
5297                         } else if (!s->config.get_external_sync()) {
5298                                 if (UIConfiguration::instance().get_follow_edits() && !s->transport_rolling()) {
5299                                         s->request_locate (_editor->get_selection().time.start());
5300                                 }
5301                         }
5302
5303                         if (_editor->get_selection().time.length() != 0) {
5304                                 s->set_range_selection (_editor->get_selection().time.start(), _editor->get_selection().time.end_frame());
5305                         } else {
5306                                 s->clear_range_selection ();
5307                         }
5308                 }
5309
5310         } else {
5311                 /* just a click, no pointer movement.
5312                  */
5313
5314                 if (was_double_click()) {
5315                         if (UIConfiguration::instance().get_use_double_click_to_zoom_to_selection()) {
5316                                 _editor->temporal_zoom_selection (Both);
5317                                 return;
5318                         }
5319                 }
5320
5321                 if (_operation == SelectionExtend) {
5322                         if (_time_selection_at_start) {
5323                                 framepos_t pos = adjusted_current_frame (event, false);
5324                                 framepos_t start = min (pos, start_at_start);
5325                                 framepos_t end = max (pos, end_at_start);
5326                                 _editor->selection->set (start, end);
5327                         }
5328                 } else {
5329                         if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5330                                 if (_editor->clicked_selection) {
5331                                         _editor->selection->remove (_editor->clicked_selection);
5332                                 }
5333                         } else {
5334                                 if (!_editor->clicked_selection) {
5335                                         _editor->selection->clear_time();
5336                                 }
5337                         }
5338                 }
5339
5340                 if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
5341                         _editor->selection->set (_editor->clicked_axisview);
5342                 }
5343
5344                 if (s && s->get_play_range () && s->transport_rolling()) {
5345                         s->request_stop (false, false);
5346                 }
5347
5348         }
5349
5350         _editor->stop_canvas_autoscroll ();
5351         _editor->clicked_selection = 0;
5352         _editor->commit_reversible_selection_op ();
5353 }
5354
5355 void
5356 SelectionDrag::aborted (bool)
5357 {
5358         /* XXX: TODO */
5359 }
5360
5361 RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
5362         : Drag (e, i, false),
5363           _operation (o),
5364           _copy (false)
5365 {
5366         DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n");
5367
5368         _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group,
5369                                                   ArdourCanvas::Rect (0.0, 0.0, 0.0,
5370                                                                       physical_screen_height (_editor->current_toplevel()->get_window())));
5371         _drag_rect->hide ();
5372
5373         _drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag rect"));
5374         _drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag rect"));
5375 }
5376
5377 RangeMarkerBarDrag::~RangeMarkerBarDrag()
5378 {
5379         /* normal canvas items will be cleaned up when their parent group is deleted. But
5380            this item is created as the child of a long-lived parent group, and so we
5381            need to explicitly delete it.
5382         */
5383         delete _drag_rect;
5384 }
5385
5386 void
5387 RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5388 {
5389         if (_editor->session() == 0) {
5390                 return;
5391         }
5392
5393         Gdk::Cursor* cursor = MouseCursors::invalid_cursor();
5394
5395         if (!_editor->temp_location) {
5396                 _editor->temp_location = new Location (*_editor->session());
5397         }
5398
5399         switch (_operation) {
5400         case CreateSkipMarker:
5401         case CreateRangeMarker:
5402         case CreateTransportMarker:
5403         case CreateCDMarker:
5404
5405                 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) {
5406                         _copy = true;
5407                 } else {
5408                         _copy = false;
5409                 }
5410                 cursor = _editor->cursors()->selector;
5411                 break;
5412         }
5413
5414         Drag::start_grab (event, cursor);
5415
5416         show_verbose_cursor_time (adjusted_current_frame (event));
5417 }
5418
5419 void
5420 RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
5421 {
5422         framepos_t start = 0;
5423         framepos_t end = 0;
5424         ArdourCanvas::Rectangle *crect;
5425
5426         switch (_operation) {
5427         case CreateSkipMarker:
5428                 crect = _editor->range_bar_drag_rect;
5429                 break;
5430         case CreateRangeMarker:
5431                 crect = _editor->range_bar_drag_rect;
5432                 break;
5433         case CreateTransportMarker:
5434                 crect = _editor->transport_bar_drag_rect;
5435                 break;
5436         case CreateCDMarker:
5437                 crect = _editor->cd_marker_bar_drag_rect;
5438                 break;
5439         default:
5440                 error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg;
5441                 return;
5442                 break;
5443         }
5444
5445         framepos_t const pf = adjusted_current_frame (event);
5446
5447         if (_operation == CreateSkipMarker || _operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
5448                 framepos_t grab = grab_frame ();
5449                 _editor->snap_to (grab);
5450
5451                 if (pf < grab_frame()) {
5452                         start = pf;
5453                         end = grab;
5454                 } else {
5455                         end = pf;
5456                         start = grab;
5457                 }
5458
5459                 /* first drag: Either add to the selection
5460                    or create a new selection.
5461                 */
5462
5463                 if (first_move) {
5464
5465                         _editor->temp_location->set (start, end);
5466
5467                         crect->show ();
5468
5469                         update_item (_editor->temp_location);
5470                         _drag_rect->show();
5471                         //_drag_rect->raise_to_top();
5472
5473                 }
5474         }
5475
5476         if (start != end) {
5477                 _editor->temp_location->set (start, end);
5478
5479                 double x1 = _editor->sample_to_pixel (start);
5480                 double x2 = _editor->sample_to_pixel (end);
5481                 crect->set_x0 (x1);
5482                 crect->set_x1 (x2);
5483
5484                 update_item (_editor->temp_location);
5485         }
5486
5487         show_verbose_cursor_time (pf);
5488
5489 }
5490
5491 void
5492 RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
5493 {
5494         Location * newloc = 0;
5495         string rangename;
5496         int flags;
5497
5498         if (movement_occurred) {
5499                 motion (event, false);
5500                 _drag_rect->hide();
5501
5502                 switch (_operation) {
5503                 case CreateSkipMarker:
5504                 case CreateRangeMarker:
5505                 case CreateCDMarker:
5506                     {
5507                         XMLNode &before = _editor->session()->locations()->get_state();
5508                         if (_operation == CreateSkipMarker) {
5509                                 _editor->begin_reversible_command (_("new skip marker"));
5510                                 _editor->session()->locations()->next_available_name(rangename,_("skip"));
5511                                 flags = Location::IsRangeMarker | Location::IsSkip;
5512                                 _editor->range_bar_drag_rect->hide();
5513                         } else if (_operation == CreateCDMarker) {
5514                                 _editor->session()->locations()->next_available_name(rangename, _("CD"));
5515                                 _editor->begin_reversible_command (_("new CD marker"));
5516                                 flags = Location::IsRangeMarker | Location::IsCDMarker;
5517                                 _editor->cd_marker_bar_drag_rect->hide();
5518                         } else {
5519                                 _editor->begin_reversible_command (_("new skip marker"));
5520                                 _editor->session()->locations()->next_available_name(rangename, _("unnamed"));
5521                                 flags = Location::IsRangeMarker;
5522                                 _editor->range_bar_drag_rect->hide();
5523                         }
5524                         newloc = new Location (
5525                                 *_editor->session(), _editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags
5526                                 , _editor->get_grid_music_divisions (event->button.state));
5527
5528                         _editor->session()->locations()->add (newloc, true);
5529                         XMLNode &after = _editor->session()->locations()->get_state();
5530                         _editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
5531                         _editor->commit_reversible_command ();
5532                         break;
5533                     }
5534
5535                 case CreateTransportMarker:
5536                         // popup menu to pick loop or punch
5537                         _editor->new_transport_marker_context_menu (&event->button, _item);
5538                         break;
5539                 }
5540
5541         } else {
5542
5543                 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5544
5545                 if (_operation == CreateTransportMarker) {
5546
5547                         /* didn't drag, so just locate */
5548
5549                         _editor->session()->request_locate (grab_frame(), _editor->session()->transport_rolling());
5550
5551                 } else if (_operation == CreateCDMarker) {
5552
5553                         /* didn't drag, but mark is already created so do
5554                          * nothing */
5555
5556                 } else { /* operation == CreateRangeMarker || CreateSkipMarker */
5557
5558                         framepos_t start;
5559                         framepos_t end;
5560
5561                         _editor->session()->locations()->marks_either_side (grab_frame(), start, end);
5562
5563                         if (end == max_framepos) {
5564                                 end = _editor->session()->current_end_frame ();
5565                         }
5566
5567                         if (start == max_framepos) {
5568                                 start = _editor->session()->current_start_frame ();
5569                         }
5570
5571                         switch (_editor->mouse_mode) {
5572                         case MouseObject:
5573                                 /* find the two markers on either side and then make the selection from it */
5574                                 _editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set, false);
5575                                 break;
5576
5577                         case MouseRange:
5578                                 /* find the two markers on either side of the click and make the range out of it */
5579                                 _editor->selection->set (start, end);
5580                                 break;
5581
5582                         default:
5583                                 break;
5584                         }
5585                 }
5586         }
5587
5588         _editor->stop_canvas_autoscroll ();
5589 }
5590
5591 void
5592 RangeMarkerBarDrag::aborted (bool movement_occurred)
5593 {
5594         if (movement_occurred) {
5595                 _drag_rect->hide ();
5596         }
5597 }
5598
5599 void
5600 RangeMarkerBarDrag::update_item (Location* location)
5601 {
5602         double const x1 = _editor->sample_to_pixel (location->start());
5603         double const x2 = _editor->sample_to_pixel (location->end());
5604
5605         _drag_rect->set_x0 (x1);
5606         _drag_rect->set_x1 (x2);
5607 }
5608
5609 NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
5610         : Drag (e, i)
5611         , _cumulative_dx (0)
5612         , _cumulative_dy (0)
5613         , _was_selected (false)
5614 {
5615         DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n");
5616
5617         _primary = reinterpret_cast<NoteBase*> (_item->get_data ("notebase"));
5618         assert (_primary);
5619         _region = &_primary->region_view ();
5620         _note_height = _region->midi_stream_view()->note_height ();
5621 }
5622
5623 void
5624 NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
5625 {
5626         Drag::start_grab (event);
5627         setup_snap_delta (_region->source_beats_to_absolute_frames (_primary->note()->time ()));
5628
5629         if (!(_was_selected = _primary->selected())) {
5630
5631                 /* tertiary-click means extend selection - we'll do that on button release,
5632                    so don't add it here, because otherwise we make it hard to figure
5633                    out the "extend-to" range.
5634                 */
5635
5636                 bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);
5637
5638                 if (!extend) {
5639                         bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);
5640
5641                         if (add) {
5642                                 _region->note_selected (_primary, true);
5643                         } else {
5644                                 _editor->get_selection().clear_points();
5645                                 _region->unique_select (_primary);
5646                         }
5647                 }
5648         }
5649 }
5650
5651 /** @return Current total drag x change in frames */
5652 frameoffset_t
5653 NoteDrag::total_dx (const guint state) const
5654 {
5655         if (_x_constrained) {
5656                 return 0;
5657         }
5658         TempoMap& map (_editor->session()->tempo_map());
5659
5660         /* dx in frames */
5661         frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x());
5662
5663         /* primary note time */
5664         double const quarter_note_start = _region->region()->quarter_note() - _region->midi_region()->start_beats();
5665         frameoffset_t const n = map.frame_at_quarter_note (quarter_note_start + _primary->note()->time().to_double());
5666
5667         /* new time of the primary note in session frames */
5668         frameoffset_t st = n + dx + snap_delta (state);
5669
5670         framepos_t const rp = _region->region()->position ();
5671
5672         /* prevent the note being dragged earlier than the region's position */
5673         st = max (st, rp);
5674
5675         /* possibly snap and return corresponding delta */
5676
5677         bool snap = true;
5678
5679         if (ArdourKeyboard::indicates_snap (state)) {
5680                 if (_editor->snap_mode () != SnapOff) {
5681                         snap = false;
5682                 }
5683         } else {
5684                 if (_editor->snap_mode () == SnapOff) {
5685                         snap = false;
5686                         /* inverted logic here - we;re in snapoff but we've pressed the snap delta modifier */
5687                         if (ArdourKeyboard::indicates_snap_delta (state)) {
5688                                 snap = true;
5689                         }
5690                 }
5691         }
5692
5693         frameoffset_t ret;
5694         if (snap) {
5695                 bool const ensure_snap = _editor->snap_mode () != SnapMagnetic;
5696                 ret =  _region->snap_frame_to_frame (st - rp, ensure_snap) + rp - n - snap_delta (state);
5697         } else {
5698                 ret = st - n - snap_delta (state);
5699         }
5700         return ret;
5701 }
5702
5703 /** @return Current total drag y change in note number */
5704 int8_t
5705 NoteDrag::total_dy () const
5706 {
5707         if (_y_constrained) {
5708                 return 0;
5709         }
5710
5711         double const y = _region->midi_view()->y_position ();
5712         /* new current note */
5713         uint8_t n = _region->y_to_note (current_pointer_y () - y);
5714         /* clamp */
5715         MidiStreamView* msv = _region->midi_stream_view ();
5716         n = max (msv->lowest_note(), n);
5717         n = min (msv->highest_note(), n);
5718         /* and work out delta */
5719         return n - _region->y_to_note (grab_y() - y);
5720 }
5721
5722 void
5723 NoteDrag::motion (GdkEvent * event, bool)
5724 {
5725         /* Total change in x and y since the start of the drag */
5726         frameoffset_t const dx = total_dx (event->button.state);
5727         int8_t const dy = total_dy ();
5728
5729         /* Now work out what we have to do to the note canvas items to set this new drag delta */
5730         double const tdx = _x_constrained ? 0 : _editor->sample_to_pixel (dx) - _cumulative_dx;
5731         double const tdy = _y_constrained ? 0 : -dy * _note_height - _cumulative_dy;
5732
5733         if (tdx || tdy) {
5734                 _cumulative_dx += tdx;
5735                 _cumulative_dy += tdy;
5736
5737                 int8_t note_delta = total_dy();
5738
5739                 if (tdx || tdy) {
5740                         _region->move_selection (tdx, tdy, note_delta);
5741
5742                         /* the new note value may be the same as the old one, but we
5743                          * don't know what that means because the selection may have
5744                          * involved more than one note and we might be doing something
5745                          * odd with them. so show the note value anyway, always.
5746                          */
5747
5748                         uint8_t new_note = min (max (_primary->note()->note() + note_delta, 0), 127);
5749
5750                         _region->show_verbose_cursor_for_new_note_value (_primary->note(), new_note);
5751                 }
5752         }
5753 }
5754
5755 void
5756 NoteDrag::finished (GdkEvent* ev, bool moved)
5757 {
5758         if (!moved) {
5759                 /* no motion - select note */
5760
5761                 if (_editor->current_mouse_mode() == Editing::MouseContent ||
5762                     _editor->current_mouse_mode() == Editing::MouseDraw) {
5763
5764                         bool changed = false;
5765
5766                         if (_was_selected) {
5767                                 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5768                                 if (add) {
5769                                         _region->note_deselected (_primary);
5770                                         changed = true;
5771                                 } else {
5772                                         _editor->get_selection().clear_points();
5773                                         _region->unique_select (_primary);
5774                                         changed = true;
5775                                 }
5776                         } else {
5777                                 bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
5778                                 bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
5779
5780                                 if (!extend && !add && _region->selection_size() > 1) {
5781                                         _editor->get_selection().clear_points();
5782                                         _region->unique_select (_primary);
5783                                         changed = true;
5784                                 } else if (extend) {
5785                                         _region->note_selected (_primary, true, true);
5786                                         changed = true;
5787                                 } else {
5788                                         /* it was added during button press */
5789                                         changed = true;
5790
5791                                 }
5792                         }
5793
5794                         if (changed) {
5795                                 _editor->begin_reversible_selection_op(X_("Select Note Release"));
5796                                 _editor->commit_reversible_selection_op();
5797                         }
5798                 }
5799         } else {
5800                 _region->note_dropped (_primary, total_dx (ev->button.state), total_dy());
5801         }
5802 }
5803
5804 void
5805 NoteDrag::aborted (bool)
5806 {
5807         /* XXX: TODO */
5808 }
5809
5810 /** Make an AutomationRangeDrag for lines in an AutomationTimeAxisView */
5811 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView* atv, list<AudioRange> const & r)
5812         : Drag (editor, atv->base_item ())
5813         , _ranges (r)
5814         , _y_origin (atv->y_position())
5815         , _nothing_to_drag (false)
5816 {
5817         DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5818         setup (atv->lines ());
5819 }
5820
5821 /** Make an AutomationRangeDrag for region gain lines or MIDI controller regions */
5822 AutomationRangeDrag::AutomationRangeDrag (Editor* editor, RegionView* rv, list<AudioRange> const & r)
5823         : Drag (editor, rv->get_canvas_group ())
5824         , _ranges (r)
5825         , _y_origin (rv->get_time_axis_view().y_position())
5826         , _nothing_to_drag (false)
5827         , _integral (false)
5828 {
5829         DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n");
5830
5831         list<boost::shared_ptr<AutomationLine> > lines;
5832
5833         AudioRegionView*      audio_view;
5834         AutomationRegionView* automation_view;
5835         if ((audio_view = dynamic_cast<AudioRegionView*>(rv))) {
5836                 lines.push_back (audio_view->get_gain_line ());
5837         } else if ((automation_view = dynamic_cast<AutomationRegionView*>(rv))) {
5838                 lines.push_back (automation_view->line ());
5839                 _integral = true;
5840         } else {
5841                 error << _("Automation range drag created for invalid region type") << endmsg;
5842         }
5843
5844         setup (lines);
5845 }
5846
5847 /** @param lines AutomationLines to drag.
5848  *  @param offset Offset from the session start to the points in the AutomationLines.
5849  */
5850 void
5851 AutomationRangeDrag::setup (list<boost::shared_ptr<AutomationLine> > const & lines)
5852 {
5853         /* find the lines that overlap the ranges being dragged */
5854         list<boost::shared_ptr<AutomationLine> >::const_iterator i = lines.begin ();
5855         while (i != lines.end ()) {
5856                 list<boost::shared_ptr<AutomationLine> >::const_iterator j = i;
5857                 ++j;
5858
5859                 pair<framepos_t, framepos_t> r = (*i)->get_point_x_range ();
5860
5861                 /* check this range against all the AudioRanges that we are using */
5862                 list<AudioRange>::const_iterator k = _ranges.begin ();
5863                 while (k != _ranges.end()) {
5864                         if (k->coverage (r.first, r.second) != Evoral::OverlapNone) {
5865                                 break;
5866                         }
5867                         ++k;
5868                 }
5869
5870                 /* add it to our list if it overlaps at all */
5871                 if (k != _ranges.end()) {
5872                         Line n;
5873                         n.line = *i;
5874                         n.state = 0;
5875                         n.range = r;
5876                         _lines.push_back (n);
5877                 }
5878
5879                 i = j;
5880         }
5881
5882         /* Now ::lines contains the AutomationLines that somehow overlap our drag */
5883 }
5884
5885 double
5886 AutomationRangeDrag::y_fraction (boost::shared_ptr<AutomationLine> line, double global_y) const
5887 {
5888         return 1.0 - ((global_y - _y_origin) / line->height());
5889 }
5890
5891 double
5892 AutomationRangeDrag::value (boost::shared_ptr<AutomationList> list, double x) const
5893 {
5894         const double v = list->eval(x);
5895         return _integral ? rint(v) : v;
5896 }
5897
5898 void
5899 AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
5900 {
5901         Drag::start_grab (event, cursor);
5902
5903         /* Get line states before we start changing things */
5904         for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5905                 i->state = &i->line->get_state ();
5906                 i->original_fraction = y_fraction (i->line, current_pointer_y());
5907         }
5908
5909         if (_ranges.empty()) {
5910
5911                 /* No selected time ranges: drag all points */
5912                 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
5913                         uint32_t const N = i->line->npoints ();
5914                         for (uint32_t j = 0; j < N; ++j) {
5915                                 i->points.push_back (i->line->nth (j));
5916                         }
5917                 }
5918
5919         }
5920
5921         if (_nothing_to_drag) {
5922                 return;
5923         }
5924 }
5925
5926 void
5927 AutomationRangeDrag::motion (GdkEvent*, bool first_move)
5928 {
5929         if (_nothing_to_drag && !first_move) {
5930                 return;
5931         }
5932
5933         if (first_move) {
5934                 _editor->begin_reversible_command (_("automation range move"));
5935
5936                 if (!_ranges.empty()) {
5937
5938                         for (list<AudioRange>::const_iterator i = _ranges.begin(); i != _ranges.end(); ++i) {
5939
5940                                 framecnt_t const half = (i->start + i->end) / 2;
5941
5942                                 /* find the line that this audio range starts in */
5943                                 list<Line>::iterator j = _lines.begin();
5944                                 while (j != _lines.end() && (j->range.first > i->start || j->range.second < i->start)) {
5945                                         ++j;
5946                                 }
5947
5948                                 if (j != _lines.end()) {
5949                                         boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5950
5951                                 /* j is the line that this audio range starts in; fade into it;
5952                                    64 samples length plucked out of thin air.
5953                                 */
5954
5955                                         framepos_t a = i->start + 64;
5956                                         if (a > half) {
5957                                                 a = half;
5958                                         }
5959
5960                                         double const p = j->line->time_converter().from (i->start - j->line->time_converter().origin_b ());
5961                                         double const q = j->line->time_converter().from (a - j->line->time_converter().origin_b ());
5962
5963                                         XMLNode &before = the_list->get_state();
5964                                         bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5965                                         bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5966
5967                                         if (add_p || add_q) {
5968                                                 _editor->session()->add_command (
5969                                                         new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
5970                                         }
5971                                 }
5972
5973                                 /* same thing for the end */
5974
5975                                 j = _lines.begin();
5976                                 while (j != _lines.end() && (j->range.first > i->end || j->range.second < i->end)) {
5977                                         ++j;
5978                                 }
5979
5980                                 if (j != _lines.end()) {
5981                                         boost::shared_ptr<AutomationList> the_list = j->line->the_list ();
5982
5983                                         /* j is the line that this audio range starts in; fade out of it;
5984                                            64 samples length plucked out of thin air.
5985                                         */
5986
5987                                         framepos_t b = i->end - 64;
5988                                         if (b < half) {
5989                                                 b = half;
5990                                         }
5991
5992                                         double const p = j->line->time_converter().from (b - j->line->time_converter().origin_b ());
5993                                         double const q = j->line->time_converter().from (i->end - j->line->time_converter().origin_b ());
5994
5995                                         XMLNode &before = the_list->get_state();
5996                                         bool const add_p = the_list->editor_add (p, value (the_list, p), false);
5997                                         bool const add_q = the_list->editor_add (q, value (the_list, q), false);
5998
5999                                         if (add_p || add_q) {
6000                                                 _editor->session()->add_command (
6001                                                         new MementoCommand<AutomationList>(*the_list.get (), &before, &the_list->get_state()));
6002                                         }
6003                                 }
6004                         }
6005
6006                         _nothing_to_drag = true;
6007
6008                         /* Find all the points that should be dragged and put them in the relevant
6009                            points lists in the Line structs.
6010                         */
6011
6012                         for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6013
6014                                 uint32_t const N = i->line->npoints ();
6015                                 for (uint32_t j = 0; j < N; ++j) {
6016
6017                                         /* here's a control point on this line */
6018                                         ControlPoint* p = i->line->nth (j);
6019                                         double const w = i->line->time_converter().to ((*p->model())->when) + i->line->time_converter().origin_b ();
6020
6021                                         /* see if it's inside a range */
6022                                         list<AudioRange>::const_iterator k = _ranges.begin ();
6023                                         while (k != _ranges.end() && (k->start >= w || k->end <= w)) {
6024                                                 ++k;
6025                                         }
6026
6027                                         if (k != _ranges.end()) {
6028                                                 /* dragging this point */
6029                                                 _nothing_to_drag = false;
6030                                                 i->points.push_back (p);
6031                                         }
6032                                 }
6033                         }
6034                 }
6035
6036                 for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6037                         i->line->start_drag_multiple (i->points, y_fraction (i->line, current_pointer_y()), i->state);
6038                 }
6039         }
6040
6041         for (list<Line>::iterator l = _lines.begin(); l != _lines.end(); ++l) {
6042                 float const f = y_fraction (l->line, current_pointer_y());
6043                 /* we are ignoring x position for this drag, so we can just pass in anything */
6044                 pair<double, float> result;
6045                 uint32_t ignored;
6046                 result = l->line->drag_motion (0, f, true, false, ignored);
6047                 show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, result.second));
6048         }
6049 }
6050
6051 void
6052 AutomationRangeDrag::finished (GdkEvent* event, bool motion_occurred)
6053 {
6054         if (_nothing_to_drag || !motion_occurred) {
6055                 return;
6056         }
6057
6058         motion (event, false);
6059         for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6060                 i->line->end_drag (false, 0);
6061         }
6062
6063         _editor->commit_reversible_command ();
6064 }
6065
6066 void
6067 AutomationRangeDrag::aborted (bool)
6068 {
6069         for (list<Line>::iterator i = _lines.begin(); i != _lines.end(); ++i) {
6070                 i->line->reset ();
6071         }
6072 }
6073
6074 DraggingView::DraggingView (RegionView* v, RegionDrag* parent, TimeAxisView* itav)
6075         : view (v)
6076         , initial_time_axis_view (itav)
6077 {
6078         /* note that time_axis_view may be null if the regionview was created
6079          * as part of a copy operation.
6080          */
6081         time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ());
6082         layer = v->region()->layer ();
6083         initial_y = v->get_canvas_group()->position().y;
6084         initial_playlist = v->region()->playlist ();
6085         initial_position = v->region()->position ();
6086         initial_end = v->region()->position () + v->region()->length ();
6087 }
6088
6089 PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r)
6090         : Drag (e, i->canvas_item ())
6091         , _region_view (r)
6092         , _patch_change (i)
6093         , _cumulative_dx (0)
6094 {
6095         DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n",
6096                                                    _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()),
6097                                                    grab_frame()));
6098 }
6099
6100 void
6101 PatchChangeDrag::motion (GdkEvent* ev, bool)
6102 {
6103         framepos_t f = adjusted_current_frame (ev);
6104         boost::shared_ptr<Region> r = _region_view->region ();
6105         f = max (f, r->position ());
6106         f = min (f, r->last_frame ());
6107
6108         framecnt_t const dxf = f - grab_frame(); // permitted dx in frames
6109         double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units
6110         _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0));
6111         _cumulative_dx = dxu;
6112 }
6113
6114 void
6115 PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred)
6116 {
6117         if (!movement_occurred) {
6118                 if (was_double_click()) {
6119                         _region_view->edit_patch_change (_patch_change);
6120                 }
6121                 return;
6122         }
6123
6124         boost::shared_ptr<Region> r (_region_view->region ());
6125         framepos_t f = adjusted_current_frame (ev);
6126         f = max (f, r->position ());
6127         f = min (f, r->last_frame ());
6128
6129         _region_view->move_patch_change (
6130                 *_patch_change,
6131                 _region_view->region_frames_to_region_beats (f - (r->position() - r->start()))
6132                 );
6133 }
6134
6135 void
6136 PatchChangeDrag::aborted (bool)
6137 {
6138         _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0));
6139 }
6140
6141 void
6142 PatchChangeDrag::setup_pointer_frame_offset ()
6143 {
6144         boost::shared_ptr<Region> region = _region_view->region ();
6145         _pointer_frame_offset = raw_grab_frame() - _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time());
6146 }
6147
6148 MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv)
6149         : RubberbandSelectDrag (e, rv->get_canvas_group ())
6150         , _region_view (rv)
6151 {
6152
6153 }
6154
6155 void
6156 MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool /*drag_in_progress*/)
6157 {
6158         _region_view->update_drag_selection (
6159                 x1, x2, y1, y2,
6160                 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier));
6161 }
6162
6163 void
6164 MidiRubberbandSelectDrag::deselect_things ()
6165 {
6166         /* XXX */
6167 }
6168
6169 MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv)
6170         : RubberbandSelectDrag (e, rv->get_canvas_group ())
6171         , _region_view (rv)
6172 {
6173         _vertical_only = true;
6174 }
6175
6176 void
6177 MidiVerticalSelectDrag::select_things (int button_state, framepos_t /*x1*/, framepos_t /*x2*/, double y1, double y2, bool /*drag_in_progress*/)
6178 {
6179         double const y = _region_view->midi_view()->y_position ();
6180
6181         y1 = max (0.0, y1 - y);
6182         y2 = max (0.0, y2 - y);
6183
6184         _region_view->update_vertical_drag_selection (
6185                 y1,
6186                 y2,
6187                 Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier)
6188                 );
6189 }
6190
6191 void
6192 MidiVerticalSelectDrag::deselect_things ()
6193 {
6194         /* XXX */
6195 }
6196
6197 EditorRubberbandSelectDrag::EditorRubberbandSelectDrag (Editor* e, ArdourCanvas::Item* i)
6198         : RubberbandSelectDrag (e, i)
6199 {
6200
6201 }
6202
6203 void
6204 EditorRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framepos_t x2, double y1, double y2, bool drag_in_progress)
6205 {
6206         if (drag_in_progress) {
6207                 /* We just want to select things at the end of the drag, not during it */
6208                 return;
6209         }
6210
6211         Selection::Operation op = ArdourKeyboard::selection_type (button_state);
6212
6213         _editor->begin_reversible_selection_op (X_("rubberband selection"));
6214
6215         _editor->select_all_within (x1, x2 - 1, y1, y2, _editor->track_views, op, false);
6216
6217         _editor->commit_reversible_selection_op ();
6218 }
6219
6220 void
6221 EditorRubberbandSelectDrag::deselect_things ()
6222 {
6223         _editor->begin_reversible_selection_op (X_("Clear Selection (rubberband)"));
6224
6225         _editor->selection->clear_tracks();
6226         _editor->selection->clear_regions();
6227         _editor->selection->clear_points ();
6228         _editor->selection->clear_lines ();
6229         _editor->selection->clear_midi_notes ();
6230
6231         _editor->commit_reversible_selection_op();
6232 }
6233
6234 NoteCreateDrag::NoteCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6235         : Drag (e, i)
6236         , _region_view (rv)
6237         , _drag_rect (0)
6238 {
6239         _note[0] = _note[1] = 0;
6240 }
6241
6242 NoteCreateDrag::~NoteCreateDrag ()
6243 {
6244         delete _drag_rect;
6245 }
6246
6247 framecnt_t
6248 NoteCreateDrag::grid_frames (framepos_t t) const
6249 {
6250
6251         const Evoral::Beats grid_beats = _region_view->get_grid_beats (t);
6252         const Evoral::Beats t_beats = _region_view->region_frames_to_region_beats (t);
6253
6254         return _region_view->region_beats_to_region_frames (t_beats + grid_beats)
6255                 - _region_view->region_beats_to_region_frames (t_beats);
6256 }
6257
6258 void
6259 NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6260 {
6261         Drag::start_grab (event, cursor);
6262
6263         _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ());
6264         TempoMap& map (_editor->session()->tempo_map());
6265
6266         const framepos_t pf = _drags->current_pointer_frame ();
6267         const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6268
6269         const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6270
6271         double eqaf = map.exact_qn_at_frame (pf, divisions);
6272
6273         if (divisions != 0) {
6274
6275                 const double qaf = map.quarter_note_at_frame (pf);
6276
6277                 /* Hack so that we always snap to the note that we are over, instead of snapping
6278                    to the next one if we're more than halfway through the one we're over.
6279                 */
6280
6281                 const double rem = eqaf - qaf;
6282                 if (rem >= 0.0) {
6283                         eqaf -= grid_beats.to_double();
6284                 }
6285         }
6286
6287         _note[0] = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6288         /* minimum initial length is grid beats */
6289         _note[1] = map.frame_at_quarter_note (eqaf + grid_beats.to_double()) - _region_view->region()->position();
6290
6291         double const x0 = _editor->sample_to_pixel (_note[0]);
6292         double const x1 = _editor->sample_to_pixel (_note[1]);
6293         double const y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6294
6295         _drag_rect->set (ArdourCanvas::Rect (x0, y, x1, y + floor (_region_view->midi_stream_view()->note_height ())));
6296         _drag_rect->set_outline_all ();
6297         _drag_rect->set_outline_color (0xffffff99);
6298         _drag_rect->set_fill_color (0xffffff66);
6299 }
6300
6301 void
6302 NoteCreateDrag::motion (GdkEvent* event, bool)
6303 {
6304         TempoMap& map (_editor->session()->tempo_map());
6305         const framepos_t pf = _drags->current_pointer_frame ();
6306         const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6307         double eqaf = map.exact_qn_at_frame (pf, divisions);
6308
6309         if (divisions != 0) {
6310
6311                 const Evoral::Beats grid_beats = _region_view->get_grid_beats (pf);
6312
6313                 const double qaf = map.quarter_note_at_frame (pf);
6314                 /* Hack so that we always snap to the note that we are over, instead of snapping
6315                    to the next one if we're more than halfway through the one we're over.
6316                 */
6317
6318                 const double rem = eqaf - qaf;
6319                 if (rem >= 0.0) {
6320                         eqaf -= grid_beats.to_double();
6321                 }
6322
6323                 eqaf += grid_beats.to_double();
6324         }
6325         _note[1] = max ((framepos_t)0, map.frame_at_quarter_note (eqaf) - _region_view->region()->position ());
6326
6327         double const x0 = _editor->sample_to_pixel (_note[0]);
6328         double const x1 = _editor->sample_to_pixel (_note[1]);
6329         _drag_rect->set_x0 (std::min(x0, x1));
6330         _drag_rect->set_x1 (std::max(x0, x1));
6331 }
6332
6333 void
6334 NoteCreateDrag::finished (GdkEvent* ev, bool had_movement)
6335 {
6336         /* we create a note even if there was no movement */
6337         framepos_t const start = min (_note[0], _note[1]);
6338         framepos_t const start_sess_rel = start + _region_view->region()->position();
6339         framecnt_t length = max (_editor->pixel_to_sample (1.0), (framecnt_t) fabs ((double)(_note[0] - _note[1])));
6340         framecnt_t const g = grid_frames (start);
6341
6342         if (_editor->get_grid_music_divisions (ev->button.state) != 0 && length < g) {
6343                 length = g;
6344         }
6345
6346         TempoMap& map (_editor->session()->tempo_map());
6347         const double qn_length = map.quarter_notes_between_frames (start_sess_rel, start_sess_rel + length);
6348         Evoral::Beats qn_length_beats = max (Evoral::Beats::ticks(1), Evoral::Beats (qn_length));
6349
6350         _region_view->create_note_at (start, _drag_rect->y0(), qn_length_beats, ev->button.state, false);
6351 }
6352
6353 double
6354 NoteCreateDrag::y_to_region (double y) const
6355 {
6356         double x = 0;
6357         _region_view->get_canvas_group()->canvas_to_item (x, y);
6358         return y;
6359 }
6360
6361 void
6362 NoteCreateDrag::aborted (bool)
6363 {
6364
6365 }
6366
6367 HitCreateDrag::HitCreateDrag (Editor* e, ArdourCanvas::Item* i, MidiRegionView* rv)
6368         : Drag (e, i)
6369         , _region_view (rv)
6370         , _last_pos (0)
6371         , _last_y (0.0)
6372 {
6373 }
6374
6375 HitCreateDrag::~HitCreateDrag ()
6376 {
6377 }
6378
6379 void
6380 HitCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
6381 {
6382         Drag::start_grab (event, cursor);
6383
6384         TempoMap& map (_editor->session()->tempo_map());
6385
6386         const framepos_t pf = _drags->current_pointer_frame ();
6387         const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6388
6389         const double eqaf = map.exact_qn_at_frame (pf, divisions);
6390
6391         boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6392
6393         if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6394                 return;
6395         }
6396
6397         const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position();
6398         const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6399
6400         Evoral::Beats length = _region_view->get_grid_beats (pf);
6401
6402         _region_view->create_note_at (start, y, length, event->button.state, false);
6403
6404         _last_pos = start;
6405         _last_y = y;
6406 }
6407
6408 void
6409 HitCreateDrag::motion (GdkEvent* event, bool)
6410 {
6411         TempoMap& map (_editor->session()->tempo_map());
6412
6413         const framepos_t pf = _drags->current_pointer_frame ();
6414         const int32_t divisions = _editor->get_grid_music_divisions (event->button.state);
6415
6416         if (divisions == 0) {
6417                 return;
6418         }
6419
6420         const double eqaf = map.exact_qn_at_frame (pf, divisions);
6421         const framepos_t start = map.frame_at_quarter_note (eqaf) - _region_view->region()->position ();
6422         const double y = _region_view->note_to_y (_region_view->y_to_note (y_to_region (event->button.y)));
6423
6424         if (_last_pos == start && y == _last_y) {
6425                 return;
6426         }
6427
6428         Evoral::Beats length = _region_view->get_grid_beats (pf);
6429
6430         boost::shared_ptr<MidiRegion> mr = _region_view->midi_region();
6431         if (eqaf >= mr->quarter_note() + mr->length_beats()) {
6432                 return;
6433         }
6434
6435         _region_view->create_note_at (start, y, length, event->button.state, false);
6436
6437         _last_pos = start;
6438         _last_y = y;
6439 }
6440
6441 void
6442 HitCreateDrag::finished (GdkEvent* /* ev */, bool /* had_movement */)
6443 {
6444
6445 }
6446
6447 double
6448 HitCreateDrag::y_to_region (double y) const
6449 {
6450         double x = 0;
6451         _region_view->get_canvas_group()->canvas_to_item (x, y);
6452         return y;
6453 }
6454
6455 void
6456 HitCreateDrag::aborted (bool)
6457 {
6458         // umm..
6459 }
6460
6461 CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn)
6462         : Drag (e, i)
6463         , arv (rv)
6464         , start (start_yn)
6465 {
6466         std::cout << ("CrossfadeEdgeDrag is DEPRECATED.  See TrimDrag::preserve_fade_anchor") << endl;
6467 }
6468
6469 void
6470 CrossfadeEdgeDrag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
6471 {
6472         Drag::start_grab (event, cursor);
6473 }
6474
6475 void
6476 CrossfadeEdgeDrag::motion (GdkEvent*, bool)
6477 {
6478         double distance;
6479         double new_length;
6480         framecnt_t len;
6481
6482         boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6483
6484         if (start) {
6485                 distance = _drags->current_pointer_x() - grab_x();
6486                 len = ar->fade_in()->back()->when;
6487         } else {
6488                 distance = grab_x() - _drags->current_pointer_x();
6489                 len = ar->fade_out()->back()->when;
6490         }
6491
6492         /* how long should it be ? */
6493
6494         new_length = len + _editor->pixel_to_sample (distance);
6495
6496         /* now check with the region that this is legal */
6497
6498         new_length = ar->verify_xfade_bounds (new_length, start);
6499
6500         if (start) {
6501                 arv->reset_fade_in_shape_width (ar, new_length);
6502         } else {
6503                 arv->reset_fade_out_shape_width (ar, new_length);
6504         }
6505 }
6506
6507 void
6508 CrossfadeEdgeDrag::finished (GdkEvent*, bool)
6509 {
6510         double distance;
6511         double new_length;
6512         framecnt_t len;
6513
6514         boost::shared_ptr<AudioRegion> ar (arv->audio_region());
6515
6516         if (start) {
6517                 distance = _drags->current_pointer_x() - grab_x();
6518                 len = ar->fade_in()->back()->when;
6519         } else {
6520                 distance = grab_x() - _drags->current_pointer_x();
6521                 len = ar->fade_out()->back()->when;
6522         }
6523
6524         new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start);
6525
6526         _editor->begin_reversible_command ("xfade trim");
6527         ar->playlist()->clear_owned_changes ();
6528
6529         if (start) {
6530                 ar->set_fade_in_length (new_length);
6531         } else {
6532                 ar->set_fade_out_length (new_length);
6533         }
6534
6535         /* Adjusting the xfade may affect other regions in the playlist, so we need
6536            to get undo Commands from the whole playlist rather than just the
6537            region.
6538         */
6539
6540         vector<Command*> cmds;
6541         ar->playlist()->rdiff (cmds);
6542         _editor->session()->add_commands (cmds);
6543         _editor->commit_reversible_command ();
6544
6545 }
6546
6547 void
6548 CrossfadeEdgeDrag::aborted (bool)
6549 {
6550         if (start) {
6551                 // arv->redraw_start_xfade ();
6552         } else {
6553                 // arv->redraw_end_xfade ();
6554         }
6555 }
6556
6557 RegionCutDrag::RegionCutDrag (Editor* e, ArdourCanvas::Item* item, framepos_t pos)
6558         : Drag (e, item, true)
6559         , line (new EditorCursor (*e))
6560 {
6561         line->set_position (pos);
6562         line->show ();
6563         line->track_canvas_item().reparent (_editor->_drag_motion_group);
6564 }
6565
6566 RegionCutDrag::~RegionCutDrag ()
6567 {
6568         delete line;
6569 }
6570
6571 void
6572 RegionCutDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6573 {
6574         Drag::start_grab (event, c);
6575         motion (event, false);
6576 }
6577
6578 void
6579 RegionCutDrag::motion (GdkEvent* event, bool)
6580 {
6581         framepos_t pos = _drags->current_pointer_frame();
6582         _editor->snap_to_with_modifier (pos, event);
6583
6584         line->set_position (pos);
6585 }
6586
6587 void
6588 RegionCutDrag::finished (GdkEvent* event, bool)
6589 {
6590         _editor->get_track_canvas()->canvas()->re_enter();
6591
6592         framepos_t pos = _drags->current_pointer_frame();
6593         _editor->snap_to_with_modifier (pos, event);
6594
6595         line->hide ();
6596
6597         RegionSelection rs = _editor->get_regions_from_selection_and_mouse (pos);
6598
6599         if (rs.empty()) {
6600                 return;
6601         }
6602
6603         _editor->split_regions_at (pos, rs, _editor->get_grid_music_divisions (event->button.state),
6604                                    false);
6605 }
6606
6607 void
6608 RegionCutDrag::aborted (bool)
6609 {
6610 }
6611
6612 RulerZoomDrag::RulerZoomDrag (Editor* e, ArdourCanvas::Item* item)
6613         : Drag (e, item, true)
6614 {
6615 }
6616
6617 void
6618 RulerZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
6619 {
6620         Drag::start_grab (event, c);
6621
6622         framepos_t where = _editor->canvas_event_sample(event);
6623
6624         _editor->_dragging_playhead = true;
6625
6626         _editor->playhead_cursor->set_position (where);
6627 }
6628
6629 void
6630 RulerZoomDrag::motion (GdkEvent* event, bool)
6631 {
6632         framepos_t where = _editor->canvas_event_sample(event);
6633
6634         _editor->playhead_cursor->set_position (where);
6635
6636         const double movement_limit = 20.0;
6637         const double scale = 1.08;
6638         const double y_delta = last_pointer_y() - current_pointer_y();
6639
6640         if (y_delta > 0 && y_delta < movement_limit) {
6641                 _editor->temporal_zoom_step_mouse_focus_scale (true, scale);
6642         } else if (y_delta < 0 && y_delta > -movement_limit) {
6643                 _editor->temporal_zoom_step_mouse_focus_scale (false, scale);
6644         }
6645 }
6646
6647 void
6648 RulerZoomDrag::finished (GdkEvent*, bool)
6649 {
6650         _editor->_dragging_playhead = false;
6651
6652         Session* s = _editor->session ();
6653         if (s) {
6654                 s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling);
6655                 _editor->_pending_locate_request = true;
6656         }
6657
6658 }
6659
6660 void
6661 RulerZoomDrag::aborted (bool)
6662 {
6663 }