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