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