fix import/embed with "sequence files" option
[ardour2.git] / gtk2_ardour / editor_mouse.cc
blobc239a98d523f4890e5ef90769eead293b4748dcf
2 /*
3 Copyright (C) 2000-2001 Paul Davis
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 #include <cassert>
22 #include <cstdlib>
23 #include <stdint.h>
24 #include <cmath>
25 #include <set>
26 #include <string>
27 #include <algorithm>
28 #include <sys/time.h>
30 #include <pbd/error.h>
31 #include <gtkmm2ext/utils.h>
32 #include <pbd/memento_command.h>
34 #include "ardour_ui.h"
35 #include "editor.h"
36 #include "time_axis_view.h"
37 #include "audio_time_axis.h"
38 #include "audio_region_view.h"
39 #include "marker.h"
40 #include "streamview.h"
41 #include "region_gain_line.h"
42 #include "automation_time_axis.h"
43 #include "prompter.h"
44 #include "utils.h"
45 #include "selection.h"
46 #include "keyboard.h"
47 #include "editing.h"
48 #include "rgb_macros.h"
50 #include <ardour/types.h>
51 #include <ardour/profile.h>
52 #include <ardour/route.h>
53 #include <ardour/audio_track.h>
54 #include <ardour/audio_diskstream.h>
55 #include <ardour/playlist.h>
56 #include <ardour/audioplaylist.h>
57 #include <ardour/audioregion.h>
58 #include <ardour/dB.h>
59 #include <ardour/utils.h>
60 #include <ardour/region_factory.h>
62 #include <bitset>
64 #include "i18n.h"
66 using namespace std;
67 using namespace ARDOUR;
68 using namespace PBD;
69 using namespace sigc;
70 using namespace Gtk;
71 using namespace Editing;
73 bool
74 Editor::mouse_frame (nframes64_t& where, bool& in_track_canvas) const
76 int x, y;
77 double wx, wy;
78 Gdk::ModifierType mask;
79 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
80 Glib::RefPtr<const Gdk::Window> pointer_window;
82 if (!canvas_window) {
83 return false;
86 pointer_window = canvas_window->get_pointer (x, y, mask);
88 if (pointer_window == track_canvas->get_bin_window()) {
89 wx = x;
90 wy = y;
91 in_track_canvas = true;
93 } else {
94 in_track_canvas = false;
95 return false;
98 GdkEvent event;
99 event.type = GDK_BUTTON_RELEASE;
100 event.button.x = wx;
101 event.button.y = wy;
103 where = event_frame (&event, 0, 0);
104 return true;
107 nframes64_t
108 Editor::event_frame (GdkEvent* event, double* pcx, double* pcy) const
110 double cx, cy;
112 if (pcx == 0) {
113 pcx = &cx;
115 if (pcy == 0) {
116 pcy = &cy;
119 *pcx = 0;
120 *pcy = 0;
122 switch (event->type) {
123 case GDK_BUTTON_RELEASE:
124 case GDK_BUTTON_PRESS:
125 case GDK_2BUTTON_PRESS:
126 case GDK_3BUTTON_PRESS:
128 *pcx = event->button.x;
129 *pcy = event->button.y;
130 _trackview_group->w2i(*pcx, *pcy);
131 break;
132 case GDK_MOTION_NOTIFY:
134 *pcx = event->motion.x;
135 *pcy = event->motion.y;
136 _trackview_group->w2i(*pcx, *pcy);
137 break;
138 case GDK_ENTER_NOTIFY:
139 case GDK_LEAVE_NOTIFY:
140 track_canvas->w2c(event->crossing.x, event->crossing.y, *pcx, *pcy);
141 break;
142 case GDK_KEY_PRESS:
143 case GDK_KEY_RELEASE:
144 // track_canvas->w2c(event->key.x, event->key.y, *pcx, *pcy);
145 break;
146 default:
147 warning << string_compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg;
148 break;
151 /* note that pixel_to_frame() never returns less than zero, so even if the pixel
152 position is negative (as can be the case with motion events in particular),
153 the frame location is always positive.
156 return pixel_to_frame (*pcx);
159 void
160 Editor::mouse_mode_toggled (MouseMode m)
162 if (ignore_mouse_mode_toggle) {
163 return;
166 switch (m) {
167 case MouseRange:
168 if (mouse_select_button.get_active()) {
169 set_mouse_mode (m);
171 break;
173 case MouseObject:
174 if (mouse_move_button.get_active()) {
175 set_mouse_mode (m);
177 break;
179 case MouseGain:
180 if (mouse_gain_button.get_active()) {
181 set_mouse_mode (m);
183 break;
185 case MouseZoom:
186 if (mouse_zoom_button.get_active()) {
187 set_mouse_mode (m);
189 break;
191 case MouseTimeFX:
192 if (mouse_timefx_button.get_active()) {
193 set_mouse_mode (m);
195 break;
197 case MouseAudition:
198 if (mouse_audition_button.get_active()) {
199 set_mouse_mode (m);
201 break;
203 default:
204 break;
208 Gdk::Cursor*
209 Editor::which_grabber_cursor ()
211 switch (_edit_point) {
212 case EditAtMouse:
213 return grabber_edit_point_cursor;
214 break;
215 default:
216 break;
218 return grabber_cursor;
221 void
222 Editor::set_canvas_cursor ()
224 switch (mouse_mode) {
225 case MouseRange:
226 current_canvas_cursor = selector_cursor;
227 break;
229 case MouseObject:
230 current_canvas_cursor = which_grabber_cursor();
231 break;
233 case MouseGain:
234 current_canvas_cursor = cross_hair_cursor;
235 break;
237 case MouseZoom:
238 current_canvas_cursor = zoom_cursor;
239 break;
241 case MouseTimeFX:
242 current_canvas_cursor = time_fx_cursor; // just use playhead
243 break;
245 case MouseAudition:
246 current_canvas_cursor = speaker_cursor;
247 break;
250 if (is_drawable()) {
251 track_canvas->get_window()->set_cursor(*current_canvas_cursor);
255 void
256 Editor::set_mouse_mode (MouseMode m, bool force)
258 if (drag_info.item) {
259 return;
262 if (!force && m == mouse_mode) {
263 return;
266 mouse_mode = m;
268 instant_save ();
270 if (mouse_mode != MouseRange) {
272 /* in all modes except range, hide the range selection,
273 show the object (region) selection.
276 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
277 (*i)->set_should_show_selection (true);
279 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
280 (*i)->hide_selection ();
283 } else {
286 in range mode,show the range selection.
289 for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) {
290 if ((*i)->get_selected()) {
291 (*i)->show_selection (selection->time);
296 /* XXX the hack of unsetting all other buttons should go
297 away once GTK2 allows us to use regular radio buttons drawn like
298 normal buttons, rather than my silly GroupedButton hack.
301 ignore_mouse_mode_toggle = true;
303 switch (mouse_mode) {
304 case MouseRange:
305 mouse_select_button.set_active (true);
306 break;
308 case MouseObject:
309 mouse_move_button.set_active (true);
310 break;
312 case MouseGain:
313 mouse_gain_button.set_active (true);
314 break;
316 case MouseZoom:
317 mouse_zoom_button.set_active (true);
318 break;
320 case MouseTimeFX:
321 mouse_timefx_button.set_active (true);
322 break;
324 case MouseAudition:
325 mouse_audition_button.set_active (true);
326 break;
329 ignore_mouse_mode_toggle = false;
331 set_canvas_cursor ();
334 void
335 Editor::step_mouse_mode (bool next)
337 switch (current_mouse_mode()) {
338 case MouseObject:
339 if (next) {
340 if (Profile->get_sae()) {
341 set_mouse_mode (MouseZoom);
342 } else {
343 set_mouse_mode (MouseRange);
345 } else {
346 set_mouse_mode (MouseTimeFX);
348 break;
350 case MouseRange:
351 if (next) set_mouse_mode (MouseZoom);
352 else set_mouse_mode (MouseObject);
353 break;
355 case MouseZoom:
356 if (next) {
357 if (Profile->get_sae()) {
358 set_mouse_mode (MouseTimeFX);
359 } else {
360 set_mouse_mode (MouseGain);
362 } else {
363 if (Profile->get_sae()) {
364 set_mouse_mode (MouseObject);
365 } else {
366 set_mouse_mode (MouseRange);
369 break;
371 case MouseGain:
372 if (next) set_mouse_mode (MouseTimeFX);
373 else set_mouse_mode (MouseZoom);
374 break;
376 case MouseTimeFX:
377 if (next) {
378 set_mouse_mode (MouseAudition);
379 } else {
380 if (Profile->get_sae()) {
381 set_mouse_mode (MouseZoom);
382 } else {
383 set_mouse_mode (MouseGain);
386 break;
388 case MouseAudition:
389 if (next) set_mouse_mode (MouseObject);
390 else set_mouse_mode (MouseTimeFX);
391 break;
395 void
396 Editor::button_selection (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
398 /* in object/audition/timefx/gain-automation mode,
399 any button press sets the selection if the object
400 can be selected. this is a bit of hack, because
401 we want to avoid this if the mouse operation is a
402 region alignment.
404 note: not dbl-click or triple-click
407 if (((mouse_mode != MouseObject) &&
408 (mouse_mode != MouseAudition || item_type != RegionItem) &&
409 (mouse_mode != MouseTimeFX || item_type != RegionItem) &&
410 (mouse_mode != MouseGain) &&
411 (mouse_mode != MouseRange)) ||
413 ((event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) || event->button.button > 3)) {
415 return;
418 if (event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE) {
420 if ((event->button.state & Keyboard::RelevantModifierKeyMask) && event->button.button != 1) {
422 /* almost no selection action on modified button-2 or button-3 events */
424 if (item_type != RegionItem && event->button.button != 2) {
425 return;
430 Selection::Operation op = Keyboard::selection_type (event->button.state);
431 bool press = (event->type == GDK_BUTTON_PRESS);
433 // begin_reversible_command (_("select on click"));
435 switch (item_type) {
436 case RegionItem:
437 if (mouse_mode != MouseRange) {
438 set_selected_regionview_from_click (press, op, true);
439 } else if (event->type == GDK_BUTTON_PRESS) {
440 set_selected_track_as_side_effect ();
442 break;
444 case RegionViewNameHighlight:
445 case RegionViewName:
446 if (mouse_mode != MouseRange) {
447 set_selected_regionview_from_click (press, op, true);
448 } else if (event->type == GDK_BUTTON_PRESS) {
449 set_selected_track_as_side_effect ();
451 break;
453 case FadeInHandleItem:
454 case FadeInItem:
455 case FadeOutHandleItem:
456 case FadeOutItem:
457 if (mouse_mode != MouseRange) {
458 set_selected_regionview_from_click (press, op, true);
459 } else if (event->type == GDK_BUTTON_PRESS) {
460 set_selected_track_as_side_effect ();
462 break;
464 case GainAutomationControlPointItem:
465 case PanAutomationControlPointItem:
466 case RedirectAutomationControlPointItem:
467 set_selected_track_as_side_effect ();
468 if (mouse_mode != MouseRange) {
469 set_selected_control_point_from_click (op, false);
471 break;
473 case StreamItem:
474 /* for context click or range selection, select track */
475 if (event->button.button == 3) {
476 set_selected_track_as_side_effect ();
477 } else if (event->type == GDK_BUTTON_PRESS && mouse_mode == MouseRange) {
478 set_selected_track_as_side_effect ();
480 break;
482 case AutomationTrackItem:
483 set_selected_track_as_side_effect (true);
484 break;
486 default:
487 break;
491 const static double ZERO_GAIN_FRACTION = gain_to_slider_position(dB_to_coefficient(0.0));
493 bool
494 Editor::button_press_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
496 Glib::RefPtr<Gdk::Window> canvas_window = const_cast<Editor*>(this)->track_canvas->get_window();
498 if (canvas_window) {
499 Glib::RefPtr<const Gdk::Window> pointer_window;
500 int x, y;
501 double wx, wy;
502 Gdk::ModifierType mask;
504 pointer_window = canvas_window->get_pointer (x, y, mask);
506 if (pointer_window == track_canvas->get_bin_window()) {
507 track_canvas->window_to_world (x, y, wx, wy);
508 allow_vertical_scroll = true;
509 } else {
510 allow_vertical_scroll = false;
514 track_canvas->grab_focus();
516 if (session && session->actively_recording()) {
517 return true;
520 button_selection (item, event, item_type);
522 //ctrl-drag or right-click-drag on a "range" ruler should start a range drag
523 if (event->type == GDK_BUTTON_PRESS) {
524 if (event->button.button == 3 || ( (event->button.button == 1) && (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier) ))) {
525 if (item_type == TransportMarkerBarItem) {
526 start_range_markerbar_op (item, event, CreateTransportMarker);
527 return true;
529 if (item_type == RangeMarkerBarItem) {
530 start_range_markerbar_op (item, event, CreateRangeMarker);
531 return true;
533 if (item_type == CdMarkerBarItem) {
534 start_range_markerbar_op (item, event, CreateCDMarker);
535 return true;
540 if (drag_info.item == 0 &&
541 (Keyboard::is_delete_event (&event->button) ||
542 Keyboard::is_context_menu_event (&event->button) ||
543 Keyboard::is_edit_event (&event->button))) {
545 /* handled by button release */
546 return true;
549 switch (event->button.button) {
550 case 1:
552 if (event->type == GDK_BUTTON_PRESS) {
554 if (drag_info.item) {
555 drag_info.item->ungrab (event->button.time);
558 /* single mouse clicks on any of these item types operate
559 independent of mouse mode, mostly because they are
560 not on the main track canvas or because we want
561 them to be modeless.
564 switch (item_type) {
565 case PlayheadCursorItem:
566 start_cursor_grab (item, event);
567 return true;
569 case MarkerItem:
570 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
571 hide_marker (item, event);
572 } else {
573 start_marker_grab (item, event);
575 return true;
577 case TempoMarkerItem:
578 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
579 start_tempo_marker_copy_grab (item, event);
580 } else {
581 start_tempo_marker_grab (item, event);
583 return true;
585 case MeterMarkerItem:
586 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
587 start_meter_marker_copy_grab (item, event);
588 } else {
589 start_meter_marker_grab (item, event);
591 return true;
593 case MarkerBarItem:
594 case TempoBarItem:
595 case MeterBarItem:
596 case TransportMarkerBarItem:
597 case RangeMarkerBarItem:
598 case CdMarkerBarItem:
599 if (!Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
600 start_cursor_grab_no_stop(&playhead_cursor->canvas_item, event);
602 return true;
603 break;
605 default:
606 break;
610 switch (mouse_mode) {
611 case MouseRange:
612 switch (item_type) {
613 case StartSelectionTrimItem:
614 start_selection_op (item, event, SelectionStartTrim);
615 break;
617 case EndSelectionTrimItem:
618 start_selection_op (item, event, SelectionEndTrim);
619 break;
621 case SelectionItem:
622 if (Keyboard::modifier_state_contains
623 (event->button.state, Keyboard::ModifierMask(Keyboard::SecondaryModifier))) {
624 // contains and not equals because I can't use alt as a modifier alone.
625 start_selection_grab (item, event);
626 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
627 /* grab selection for moving */
628 start_selection_op (item, event, SelectionMove);
629 } else {
630 /* this was debated, but decided the more common action was to
631 make a new selection */
632 start_selection_op (item, event, CreateSelection);
634 break;
636 default:
637 start_selection_op (item, event, CreateSelection);
639 return true;
640 break;
642 case MouseObject:
643 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) &&
644 event->type == GDK_BUTTON_PRESS) {
646 start_rubberband_select (item, event);
648 } else if (event->type == GDK_BUTTON_PRESS) {
650 switch (item_type) {
651 case FadeInHandleItem:
652 start_fade_in_grab (item, event);
653 return true;
655 case FadeOutHandleItem:
656 start_fade_out_grab (item, event);
657 return true;
659 case RegionItem:
660 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
661 start_region_copy_grab (item, event);
662 } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) {
663 start_region_brush_grab (item, event);
664 } else {
665 start_region_grab (item, event);
667 break;
669 case RegionViewNameHighlight:
670 start_trim (item, event);
671 return true;
672 break;
674 case RegionViewName:
675 /* rename happens on edit clicks */
676 start_trim (clicked_regionview->get_name_highlight(), event);
677 return true;
678 break;
680 case GainAutomationControlPointItem:
681 case PanAutomationControlPointItem:
682 case RedirectAutomationControlPointItem:
683 start_control_point_grab (item, event);
684 return true;
685 break;
687 case GainAutomationLineItem:
688 case PanAutomationLineItem:
689 case RedirectAutomationLineItem:
690 start_line_grab_from_line (item, event);
691 return true;
692 break;
694 case StreamItem:
695 case AutomationTrackItem:
696 start_rubberband_select (item, event);
697 break;
699 /* <CMT Additions> */
700 case ImageFrameHandleStartItem:
701 imageframe_start_handle_op(item, event) ;
702 return(true) ;
703 break ;
704 case ImageFrameHandleEndItem:
705 imageframe_end_handle_op(item, event) ;
706 return(true) ;
707 break ;
708 case MarkerViewHandleStartItem:
709 markerview_item_start_handle_op(item, event) ;
710 return(true) ;
711 break ;
712 case MarkerViewHandleEndItem:
713 markerview_item_end_handle_op(item, event) ;
714 return(true) ;
715 break ;
716 /* </CMT Additions> */
718 /* <CMT Additions> */
719 case MarkerViewItem:
720 start_markerview_grab(item, event) ;
721 break ;
722 case ImageFrameItem:
723 start_imageframe_grab(item, event) ;
724 break ;
725 /* </CMT Additions> */
727 case MarkerBarItem:
729 break;
731 default:
732 break;
735 return true;
736 break;
738 case MouseGain:
739 switch (item_type) {
740 case RegionItem:
741 /* start a grab so that if we finish after moving
742 we can tell what happened.
744 drag_info.item = item;
745 drag_info.motion_callback = &Editor::region_gain_motion_callback;
746 drag_info.finished_callback = 0;
747 start_grab (event, current_canvas_cursor);
748 break;
750 case GainControlPointItem:
751 start_control_point_grab (item, event);
752 return true;
754 case GainLineItem:
755 start_line_grab_from_line (item, event);
756 return true;
758 case GainAutomationControlPointItem:
759 case PanAutomationControlPointItem:
760 case RedirectAutomationControlPointItem:
761 start_control_point_grab (item, event);
762 return true;
763 break;
765 default:
766 break;
768 return true;
769 break;
771 switch (item_type) {
772 case GainAutomationControlPointItem:
773 case PanAutomationControlPointItem:
774 case RedirectAutomationControlPointItem:
775 start_control_point_grab (item, event);
776 break;
778 case GainAutomationLineItem:
779 case PanAutomationLineItem:
780 case RedirectAutomationLineItem:
781 start_line_grab_from_line (item, event);
782 break;
784 case RegionItem:
785 // XXX need automation mode to identify which
786 // line to use
787 // start_line_grab_from_regionview (item, event);
788 break;
790 default:
791 break;
793 return true;
794 break;
796 case MouseZoom:
797 if (event->type == GDK_BUTTON_PRESS) {
798 start_mouse_zoom (item, event);
801 return true;
802 break;
804 case MouseTimeFX:
805 if (item_type == RegionItem) {
806 start_time_fx (item, event);
808 break;
810 case MouseAudition:
811 _scrubbing = true;
812 scrub_reversals = 0;
813 scrub_reverse_distance = 0;
814 last_scrub_x = event->button.x;
815 scrubbing_direction = 0;
816 track_canvas->get_window()->set_cursor (*transparent_cursor);
817 /* rest handled in motion & release */
818 break;
820 default:
821 break;
823 break;
825 case 2:
826 switch (mouse_mode) {
827 case MouseObject:
828 if (event->type == GDK_BUTTON_PRESS) {
829 switch (item_type) {
830 case RegionItem:
831 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::CopyModifier)) {
832 start_region_copy_grab (item, event);
833 } else {
834 start_region_grab (item, event);
836 return true;
837 break;
839 case GainAutomationControlPointItem:
840 case PanAutomationControlPointItem:
841 case RedirectAutomationControlPointItem:
842 start_control_point_grab (item, event);
843 return true;
844 break;
846 default:
847 break;
852 switch (item_type) {
853 case RegionViewNameHighlight:
854 start_trim (item, event);
855 return true;
856 break;
858 case RegionViewName:
859 start_trim (clicked_regionview->get_name_highlight(), event);
860 return true;
861 break;
863 default:
864 break;
867 break;
869 case MouseRange:
870 if (event->type == GDK_BUTTON_PRESS) {
871 /* relax till release */
873 return true;
874 break;
877 case MouseZoom:
878 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
879 temporal_zoom_session();
880 } else {
881 temporal_zoom_to_frame (true, event_frame(event));
883 return true;
884 break;
886 default:
887 break;
890 break;
892 case 3:
893 break;
895 default:
896 break;
900 return false;
903 bool
904 Editor::button_release_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
906 nframes64_t where = event_frame (event, 0, 0);
907 AutomationTimeAxisView* atv = 0;
909 /* no action if we're recording */
911 if (session && session->actively_recording()) {
912 return true;
915 /* first, see if we're finishing a drag ... */
917 if (drag_info.item) {
918 if (end_grab (item, event)) {
919 /* grab dragged, so do nothing else */
920 return true;
924 button_selection (item, event, item_type);
925 update_region_layering_order_editor (where);
927 /* edit events get handled here */
929 if (drag_info.item == 0 && Keyboard::is_edit_event (&event->button)) {
930 switch (item_type) {
931 case RegionItem:
932 edit_region ();
933 break;
935 case TempoMarkerItem:
936 edit_tempo_marker (item);
937 break;
939 case MeterMarkerItem:
940 edit_meter_marker (item);
941 break;
943 case RegionViewName:
944 if (clicked_regionview->name_active()) {
945 return mouse_rename_region (item, event);
947 break;
949 default:
950 break;
952 return true;
955 /* context menu events get handled here */
957 if (Keyboard::is_context_menu_event (&event->button)) {
959 if (drag_info.item == 0) {
961 /* no matter which button pops up the context menu, tell the menu
962 widget to use button 1 to drive menu selection.
965 switch (item_type) {
966 case FadeInItem:
967 case FadeInHandleItem:
968 case FadeOutItem:
969 case FadeOutHandleItem:
970 popup_fade_context_menu (1, event->button.time, item, item_type);
971 break;
973 case StreamItem:
974 popup_track_context_menu (1, event->button.time, item_type, false, where);
975 break;
977 case RegionItem:
978 case RegionViewNameHighlight:
979 case RegionViewName:
980 popup_track_context_menu (1, event->button.time, item_type, false, where);
981 break;
983 case SelectionItem:
984 popup_track_context_menu (1, event->button.time, item_type, true, where);
985 break;
987 case AutomationTrackItem:
988 popup_track_context_menu (1, event->button.time, item_type, false, where);
989 break;
991 case MarkerBarItem:
992 case RangeMarkerBarItem:
993 case TransportMarkerBarItem:
994 case CdMarkerBarItem:
995 case TempoBarItem:
996 case MeterBarItem:
997 popup_ruler_menu (where, item_type);
998 break;
1000 case MarkerItem:
1001 marker_context_menu (&event->button, item);
1002 break;
1004 case TempoMarkerItem:
1005 tm_marker_context_menu (&event->button, item);
1006 break;
1008 case MeterMarkerItem:
1009 tm_marker_context_menu (&event->button, item);
1010 break;
1012 case CrossfadeViewItem:
1013 popup_track_context_menu (1, event->button.time, item_type, false, where);
1014 break;
1016 /* <CMT Additions> */
1017 case ImageFrameItem:
1018 popup_imageframe_edit_menu(1, event->button.time, item, true) ;
1019 break ;
1020 case ImageFrameTimeAxisItem:
1021 popup_imageframe_edit_menu(1, event->button.time, item, false) ;
1022 break ;
1023 case MarkerViewItem:
1024 popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ;
1025 break ;
1026 case MarkerTimeAxisItem:
1027 popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ;
1028 break ;
1029 /* <CMT Additions> */
1032 default:
1033 break;
1036 return true;
1040 /* delete events get handled here */
1042 if (drag_info.item == 0 && Keyboard::is_delete_event (&event->button)) {
1044 switch (item_type) {
1045 case TempoMarkerItem:
1046 remove_tempo_marker (item);
1047 break;
1049 case MeterMarkerItem:
1050 remove_meter_marker (item);
1051 break;
1053 case MarkerItem:
1054 remove_marker (*item, event);
1055 break;
1057 case RegionItem:
1058 if (mouse_mode == MouseObject) {
1059 remove_clicked_region ();
1061 break;
1063 case GainControlPointItem:
1064 if (mouse_mode == MouseGain) {
1065 remove_gain_control_point (item, event);
1067 break;
1069 case GainAutomationControlPointItem:
1070 case PanAutomationControlPointItem:
1071 case RedirectAutomationControlPointItem:
1072 remove_control_point (item, event);
1073 break;
1075 default:
1076 break;
1078 return true;
1081 switch (event->button.button) {
1082 case 1:
1084 switch (item_type) {
1085 /* see comments in button_press_handler */
1086 case PlayheadCursorItem:
1087 case MarkerItem:
1088 case GainLineItem:
1089 case GainAutomationLineItem:
1090 case PanAutomationLineItem:
1091 case RedirectAutomationLineItem:
1092 case StartSelectionTrimItem:
1093 case EndSelectionTrimItem:
1094 return true;
1096 case MarkerBarItem:
1097 if (!_dragging_playhead) {
1098 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
1099 snap_to (where, 0, true);
1101 mouse_add_new_marker (where);
1103 return true;
1105 case CdMarkerBarItem:
1106 if (!_dragging_playhead) {
1107 // if we get here then a dragged range wasn't done
1108 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
1109 snap_to (where, 0, true);
1111 mouse_add_new_marker (where, true);
1113 return true;
1115 case TempoBarItem:
1116 if (!_dragging_playhead) {
1117 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
1118 snap_to (where);
1120 mouse_add_new_tempo_event (where);
1122 return true;
1124 case MeterBarItem:
1125 if (!_dragging_playhead) {
1126 mouse_add_new_meter_event (pixel_to_frame (event->button.x));
1128 return true;
1129 break;
1131 default:
1132 break;
1135 switch (mouse_mode) {
1136 case MouseObject:
1137 switch (item_type) {
1138 case AutomationTrackItem:
1139 atv = dynamic_cast<AutomationTimeAxisView*>(clicked_trackview);
1140 if (atv) {
1141 atv->add_automation_event (item, event, where, event->button.y);
1143 return true;
1145 break;
1147 default:
1148 break;
1150 break;
1152 case MouseGain:
1153 // Gain only makes sense for audio regions
1155 if (!dynamic_cast<AudioRegionView*>(clicked_regionview)) {
1156 break;
1159 switch (item_type) {
1160 case RegionItem:
1161 /* check that we didn't drag before releasing, since
1162 its really annoying to create new control
1163 points when doing this.
1165 if (drag_info.first_move) {
1166 dynamic_cast<AudioRegionView*>(clicked_regionview)->add_gain_point_event (item, event);
1168 return true;
1169 break;
1171 case AutomationTrackItem:
1172 dynamic_cast<AutomationTimeAxisView*>(clicked_trackview)->
1173 add_automation_event (item, event, where, event->button.y);
1174 return true;
1175 break;
1176 default:
1177 break;
1179 break;
1181 case MouseAudition:
1182 _scrubbing = false;
1183 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1184 if (scrubbing_direction == 0) {
1185 /* no drag, just a click */
1186 switch (item_type) {
1187 case RegionItem:
1188 play_selected_region ();
1189 break;
1190 default:
1191 break;
1193 } else {
1194 /* make sure we stop */
1195 session->request_stop ();
1197 break;
1199 default:
1200 break;
1204 return true;
1205 break;
1208 case 2:
1209 switch (mouse_mode) {
1211 case MouseObject:
1212 switch (item_type) {
1213 case RegionItem:
1214 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
1215 raise_region ();
1216 } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::TertiaryModifier|Keyboard::SecondaryModifier))) {
1217 lower_region ();
1218 } else {
1219 // Button2 click is unused
1221 return true;
1223 break;
1225 default:
1226 break;
1228 break;
1230 case MouseRange:
1232 // x_style_paste (where, 1.0);
1233 return true;
1234 break;
1236 default:
1237 break;
1240 break;
1242 case 3:
1243 break;
1245 default:
1246 break;
1248 return false;
1251 bool
1252 Editor::enter_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1254 ControlPoint* cp;
1255 Marker * marker;
1256 double fraction;
1258 if (last_item_entered != item) {
1259 last_item_entered = item;
1260 last_item_entered_n = 0;
1263 switch (item_type) {
1264 case GainControlPointItem:
1265 if (mouse_mode == MouseGain) {
1266 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1267 cp->set_visible (true);
1269 double at_x, at_y;
1270 at_x = cp->get_x();
1271 at_y = cp->get_y ();
1272 cp->item->i2w (at_x, at_y);
1273 at_x += 10.0;
1274 at_y += 10.0;
1276 fraction = 1.0 - (cp->get_y() / cp->line.height());
1278 if (is_drawable() && !_scrubbing) {
1279 track_canvas->get_window()->set_cursor (*fader_cursor);
1282 last_item_entered_n++;
1283 set_verbose_canvas_cursor (cp->line.get_verbose_cursor_string (fraction), at_x, at_y);
1284 if (last_item_entered_n < 10) {
1285 show_verbose_canvas_cursor ();
1288 break;
1290 case GainAutomationControlPointItem:
1291 case PanAutomationControlPointItem:
1292 case RedirectAutomationControlPointItem:
1293 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1294 cp = static_cast<ControlPoint*>(item->get_data ("control_point"));
1295 cp->set_visible (true);
1297 double at_x, at_y;
1298 at_x = cp->get_x();
1299 at_y = cp->get_y ();
1300 cp->item->i2w (at_x, at_y);
1301 at_x += 10.0;
1302 at_y += 10.0;
1304 fraction = 1.0 - (cp->get_y() / cp->line.height());
1306 set_verbose_canvas_cursor (cp->line.get_verbose_cursor_string (fraction), at_x, at_y);
1307 show_verbose_canvas_cursor ();
1309 if (is_drawable()) {
1310 track_canvas->get_window()->set_cursor (*fader_cursor);
1313 break;
1315 case GainLineItem:
1316 if (mouse_mode == MouseGain) {
1317 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1318 if (line)
1319 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredGainLine.get();
1320 if (is_drawable()) {
1321 track_canvas->get_window()->set_cursor (*fader_cursor);
1324 break;
1326 case GainAutomationLineItem:
1327 case RedirectAutomationLineItem:
1328 case PanAutomationLineItem:
1329 if (mouse_mode == MouseGain || mouse_mode == MouseObject) {
1331 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1332 if (line)
1333 line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredAutomationLine.get();
1335 if (is_drawable()) {
1336 track_canvas->get_window()->set_cursor (*fader_cursor);
1339 break;
1341 case RegionViewNameHighlight:
1342 if (is_drawable() && mouse_mode == MouseObject) {
1343 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1345 break;
1347 case StartSelectionTrimItem:
1348 case EndSelectionTrimItem:
1349 /* <CMT Additions> */
1350 case ImageFrameHandleStartItem:
1351 case ImageFrameHandleEndItem:
1352 case MarkerViewHandleStartItem:
1353 case MarkerViewHandleEndItem:
1354 /* </CMT Additions> */
1356 if (is_drawable()) {
1357 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1359 break;
1361 case PlayheadCursorItem:
1362 if (is_drawable()) {
1363 switch (_edit_point) {
1364 case EditAtMouse:
1365 track_canvas->get_window()->set_cursor (*grabber_edit_point_cursor);
1366 break;
1367 default:
1368 track_canvas->get_window()->set_cursor (*grabber_cursor);
1369 break;
1372 break;
1374 case RegionViewName:
1376 /* when the name is not an active item, the entire name highlight is for trimming */
1378 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1379 if (mouse_mode == MouseObject && is_drawable()) {
1380 track_canvas->get_window()->set_cursor (*trimmer_cursor);
1383 break;
1386 case AutomationTrackItem:
1387 if (is_drawable()) {
1388 Gdk::Cursor *cursor;
1389 switch (mouse_mode) {
1390 case MouseRange:
1391 cursor = selector_cursor;
1392 break;
1393 case MouseZoom:
1394 cursor = zoom_cursor;
1395 break;
1396 default:
1397 cursor = cross_hair_cursor;
1398 break;
1401 track_canvas->get_window()->set_cursor (*cursor);
1403 AutomationTimeAxisView* atv;
1404 if ((atv = static_cast<AutomationTimeAxisView*>(item->get_data ("trackview"))) != 0) {
1405 clear_entered_track = false;
1406 set_entered_track (atv);
1409 break;
1411 case MarkerBarItem:
1412 case RangeMarkerBarItem:
1413 case TransportMarkerBarItem:
1414 case CdMarkerBarItem:
1415 case MeterBarItem:
1416 case TempoBarItem:
1417 if (is_drawable()) {
1418 track_canvas->get_window()->set_cursor (*timebar_cursor);
1420 break;
1422 case MarkerItem:
1423 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1424 break;
1426 entered_marker = marker;
1427 marker->set_color_rgba (ARDOUR_UI::config()->canvasvar_EnteredMarker.get());
1428 // fall through
1429 case MeterMarkerItem:
1430 case TempoMarkerItem:
1431 if (is_drawable()) {
1432 track_canvas->get_window()->set_cursor (*timebar_cursor);
1434 break;
1435 case FadeInHandleItem:
1436 case FadeOutHandleItem:
1437 if (mouse_mode == MouseObject) {
1438 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1439 if (rect) {
1440 rect->property_fill_color_rgba() = 0;
1441 rect->property_outline_pixels() = 1;
1444 break;
1446 default:
1447 break;
1450 /* second pass to handle entered track status in a comprehensible way.
1453 switch (item_type) {
1454 case GainLineItem:
1455 case GainAutomationLineItem:
1456 case RedirectAutomationLineItem:
1457 case PanAutomationLineItem:
1458 case GainControlPointItem:
1459 case GainAutomationControlPointItem:
1460 case PanAutomationControlPointItem:
1461 case RedirectAutomationControlPointItem:
1462 /* these do not affect the current entered track state */
1463 clear_entered_track = false;
1464 break;
1466 case AutomationTrackItem:
1467 /* handled above already */
1468 break;
1470 default:
1471 set_entered_track (0);
1472 break;
1475 return false;
1478 bool
1479 Editor::leave_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type)
1481 AutomationLine* al;
1482 ControlPoint* cp;
1483 Marker *marker;
1484 Location *loc;
1485 RegionView* rv;
1486 bool is_start;
1488 switch (item_type) {
1489 case GainControlPointItem:
1490 case GainAutomationControlPointItem:
1491 case PanAutomationControlPointItem:
1492 case RedirectAutomationControlPointItem:
1493 cp = reinterpret_cast<ControlPoint*>(item->get_data ("control_point"));
1494 if (cp->line.npoints() > 1) {
1495 if (!cp->selected) {
1496 cp->set_visible (false);
1500 if (is_drawable()) {
1501 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1504 hide_verbose_canvas_cursor ();
1505 break;
1507 case RegionViewNameHighlight:
1508 case StartSelectionTrimItem:
1509 case EndSelectionTrimItem:
1510 case PlayheadCursorItem:
1511 /* <CMT Additions> */
1512 case ImageFrameHandleStartItem:
1513 case ImageFrameHandleEndItem:
1514 case MarkerViewHandleStartItem:
1515 case MarkerViewHandleEndItem:
1516 /* </CMT Additions> */
1517 if (is_drawable()) {
1518 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1520 break;
1522 case GainLineItem:
1523 case GainAutomationLineItem:
1524 case RedirectAutomationLineItem:
1525 case PanAutomationLineItem:
1526 al = reinterpret_cast<AutomationLine*> (item->get_data ("line"));
1528 ArdourCanvas::Line *line = dynamic_cast<ArdourCanvas::Line *> (item);
1529 if (line)
1530 line->property_fill_color_rgba() = al->get_line_color();
1532 if (is_drawable()) {
1533 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1535 break;
1537 case RegionViewName:
1538 /* see enter_handler() for notes */
1539 if (!reinterpret_cast<RegionView *> (item->get_data ("regionview"))->name_active()) {
1540 if (is_drawable() && mouse_mode == MouseObject) {
1541 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1544 break;
1546 case RangeMarkerBarItem:
1547 case TransportMarkerBarItem:
1548 case CdMarkerBarItem:
1549 case MeterBarItem:
1550 case TempoBarItem:
1551 case MarkerBarItem:
1552 if (is_drawable()) {
1553 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1555 break;
1557 case MarkerItem:
1558 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
1559 break;
1561 entered_marker = 0;
1562 if ((loc = find_location_from_marker (marker, is_start)) != 0) {
1563 location_flags_changed (loc, this);
1565 // fall through
1566 case MeterMarkerItem:
1567 case TempoMarkerItem:
1569 if (is_drawable()) {
1570 track_canvas->get_window()->set_cursor (*timebar_cursor);
1573 break;
1575 case FadeInHandleItem:
1576 case FadeOutHandleItem:
1577 rv = static_cast<RegionView*>(item->get_data ("regionview"));
1579 ArdourCanvas::SimpleRect *rect = dynamic_cast<ArdourCanvas::SimpleRect *> (item);
1580 if (rect) {
1581 rect->property_fill_color_rgba() = rv->get_fill_color();
1582 rect->property_outline_pixels() = 0;
1585 break;
1587 case AutomationTrackItem:
1588 if (is_drawable()) {
1589 track_canvas->get_window()->set_cursor (*current_canvas_cursor);
1590 clear_entered_track = true;
1591 Glib::signal_idle().connect (mem_fun(*this, &Editor::left_automation_track));
1593 break;
1595 default:
1596 break;
1599 return false;
1602 gint
1603 Editor::left_automation_track ()
1605 if (clear_entered_track) {
1606 set_entered_track (0);
1607 clear_entered_track = false;
1609 return false;
1612 void
1613 Editor::scrub ()
1615 double delta;
1617 if (scrubbing_direction == 0) {
1618 /* first move */
1619 session->request_locate (drag_info.current_pointer_frame, false);
1620 session->request_transport_speed (0.1);
1621 scrubbing_direction = 1;
1623 } else {
1625 if (last_scrub_x > drag_info.current_pointer_x) {
1627 /* pointer moved to the left */
1629 if (scrubbing_direction > 0) {
1631 /* we reversed direction to go backwards */
1633 scrub_reversals++;
1634 scrub_reverse_distance += (int) (last_scrub_x - drag_info.current_pointer_x);
1636 } else {
1638 /* still moving to the left (backwards) */
1640 scrub_reversals = 0;
1641 scrub_reverse_distance = 0;
1643 delta = 0.01 * (last_scrub_x - drag_info.current_pointer_x);
1644 session->request_transport_speed (session->transport_speed() - delta);
1647 } else {
1648 /* pointer moved to the right */
1650 if (scrubbing_direction < 0) {
1651 /* we reversed direction to go forward */
1653 scrub_reversals++;
1654 scrub_reverse_distance += (int) (drag_info.current_pointer_x - last_scrub_x);
1656 } else {
1657 /* still moving to the right */
1659 scrub_reversals = 0;
1660 scrub_reverse_distance = 0;
1662 delta = 0.01 * (drag_info.current_pointer_x - last_scrub_x);
1663 session->request_transport_speed (session->transport_speed() + delta);
1667 /* if there have been more than 2 opposite motion moves detected, or one that moves
1668 back more than 10 pixels, reverse direction
1671 if (scrub_reversals >= 2 || scrub_reverse_distance > 10) {
1673 if (scrubbing_direction > 0) {
1674 /* was forwards, go backwards */
1675 session->request_transport_speed (-0.1);
1676 scrubbing_direction = -1;
1677 } else {
1678 /* was backwards, go forwards */
1679 session->request_transport_speed (0.1);
1680 scrubbing_direction = 1;
1683 scrub_reverse_distance = 0;
1684 scrub_reversals = 0;
1688 last_scrub_x = drag_info.current_pointer_x;
1691 bool
1692 Editor::motion_handler (ArdourCanvas::Item* item, GdkEvent* event, ItemType item_type, bool from_autoscroll)
1694 if (event->motion.is_hint) {
1695 gint x, y;
1697 /* We call this so that MOTION_NOTIFY events continue to be
1698 delivered to the canvas. We need to do this because we set
1699 Gdk::POINTER_MOTION_HINT_MASK on the canvas. This reduces
1700 the density of the events, at the expense of a round-trip
1701 to the server. Given that this will mostly occur on cases
1702 where DISPLAY = :0.0, and given the cost of what the motion
1703 event might do, its a good tradeoff.
1706 track_canvas->get_pointer (x, y);
1709 if (current_stepping_trackview) {
1710 /* don't keep the persistent stepped trackview if the mouse moves */
1711 current_stepping_trackview = 0;
1712 step_timeout.disconnect ();
1715 if (session && session->actively_recording()) {
1716 /* Sorry. no dragging stuff around while we record */
1717 return true;
1720 drag_info.item_type = item_type;
1721 drag_info.last_pointer_x = drag_info.current_pointer_x;
1722 drag_info.last_pointer_y = drag_info.current_pointer_y;
1723 drag_info.current_pointer_frame = event_frame (event, &drag_info.current_pointer_x,
1724 &drag_info.current_pointer_y);
1727 switch (mouse_mode) {
1728 case MouseAudition:
1729 if (_scrubbing) {
1730 scrub ();
1732 break;
1734 default:
1735 break;
1738 if (!from_autoscroll && drag_info.item) {
1739 /* item != 0 is the best test i can think of for dragging.
1741 if (!drag_info.move_threshold_passed) {
1743 bool x_threshold_passed = (::llabs ((nframes64_t) (drag_info.current_pointer_x - drag_info.grab_x)) > 4LL);
1744 bool y_threshold_passed = (::llabs ((nframes64_t) (drag_info.current_pointer_y - drag_info.grab_y)) > 4LL);
1746 drag_info.move_threshold_passed = (x_threshold_passed || y_threshold_passed);
1748 // and change the initial grab loc/frame if this drag info wants us to
1750 if (drag_info.want_move_threshold && drag_info.move_threshold_passed) {
1751 drag_info.grab_frame = drag_info.current_pointer_frame;
1752 drag_info.grab_x = drag_info.current_pointer_x;
1753 drag_info.grab_y = drag_info.current_pointer_y;
1754 drag_info.last_pointer_frame = drag_info.grab_frame;
1755 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
1760 switch (item_type) {
1761 case PlayheadCursorItem:
1762 case MarkerItem:
1763 case MarkerBarItem:
1764 case TempoBarItem:
1765 case MeterBarItem:
1766 case RangeMarkerBarItem:
1767 case TransportMarkerBarItem:
1768 case CdMarkerBarItem:
1769 case GainControlPointItem:
1770 case RedirectAutomationControlPointItem:
1771 case GainAutomationControlPointItem:
1772 case PanAutomationControlPointItem:
1773 case TempoMarkerItem:
1774 case MeterMarkerItem:
1775 case RegionViewNameHighlight:
1776 case StartSelectionTrimItem:
1777 case EndSelectionTrimItem:
1778 case SelectionItem:
1779 case GainLineItem:
1780 case RedirectAutomationLineItem:
1781 case GainAutomationLineItem:
1782 case PanAutomationLineItem:
1783 case FadeInHandleItem:
1784 case FadeOutHandleItem:
1785 /* <CMT Additions> */
1786 case ImageFrameHandleStartItem:
1787 case ImageFrameHandleEndItem:
1788 case MarkerViewHandleStartItem:
1789 case MarkerViewHandleEndItem:
1790 /* </CMT Additions> */
1791 if (drag_info.item && (event->motion.state & Gdk::BUTTON1_MASK ||
1792 (event->motion.state & Gdk::BUTTON3_MASK) ||
1793 (event->motion.state & Gdk::BUTTON2_MASK))) {
1794 if (!from_autoscroll) {
1795 maybe_autoscroll_horizontally (&event->motion);
1797 if (drag_info.motion_callback) {
1798 (this->*(drag_info.motion_callback)) (item, event);
1800 goto handled;
1802 goto not_handled;
1803 break;
1804 default:
1805 break;
1808 switch (mouse_mode) {
1809 case MouseGain:
1810 if (item_type == RegionItem) {
1811 if (drag_info.item && drag_info.motion_callback) {
1812 (this->*(drag_info.motion_callback)) (item, event);
1814 goto handled;
1816 break;
1818 case MouseObject:
1819 case MouseRange:
1820 case MouseZoom:
1821 case MouseTimeFX:
1822 if (drag_info.item && (event->motion.state & GDK_BUTTON1_MASK ||
1823 (event->motion.state & GDK_BUTTON2_MASK))) {
1824 if (!from_autoscroll) {
1825 maybe_autoscroll (&event->motion);
1827 if (drag_info.motion_callback) {
1828 (this->*(drag_info.motion_callback)) (item, event);
1830 goto handled;
1832 goto not_handled;
1833 break;
1835 default:
1836 break;
1839 handled:
1840 track_canvas_motion (event);
1841 // drag_info.last_pointer_frame = drag_info.current_pointer_frame;
1842 return true;
1844 not_handled:
1845 return false;
1848 void
1849 Editor::break_drag ()
1851 stop_canvas_autoscroll ();
1852 hide_verbose_canvas_cursor ();
1854 if (drag_info.item) {
1855 drag_info.item->ungrab (0);
1857 /* put it back where it came from */
1859 double cxw, cyw;
1860 cxw = 0;
1861 cyw = 0;
1862 drag_info.item->i2w (cxw, cyw);
1863 drag_info.item->move (drag_info.original_x - cxw, drag_info.original_y - cyw);
1866 finalize_drag ();
1869 void
1870 Editor::finalize_drag ()
1872 drag_info.item = 0;
1873 drag_info.copy = false;
1874 drag_info.motion_callback = 0;
1875 drag_info.finished_callback = 0;
1876 drag_info.dest_trackview = 0;
1877 drag_info.source_trackview = 0;
1878 drag_info.last_frame_position = 0;
1879 drag_info.grab_frame = 0;
1880 drag_info.last_pointer_frame = 0;
1881 drag_info.current_pointer_frame = 0;
1882 drag_info.brushing = false;
1883 range_marker_drag_rect->hide();
1884 drag_info.clear_copied_locations ();
1887 void
1888 Editor::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
1890 if (drag_info.item == 0) {
1891 fatal << _("programming error: start_grab called without drag item") << endmsg;
1892 /*NOTREACHED*/
1893 return;
1896 if (cursor == 0) {
1897 cursor = which_grabber_cursor ();
1900 // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained
1902 if (Keyboard::is_button2_event (&event->button)) {
1903 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
1904 drag_info.y_constrained = true;
1905 drag_info.x_constrained = false;
1906 } else {
1907 drag_info.y_constrained = false;
1908 drag_info.x_constrained = true;
1910 } else {
1911 drag_info.x_constrained = false;
1912 drag_info.y_constrained = false;
1915 drag_info.grab_frame = event_frame (event, &drag_info.grab_x, &drag_info.grab_y);
1916 drag_info.last_pointer_frame = drag_info.grab_frame;
1917 drag_info.current_pointer_frame = drag_info.grab_frame;
1918 drag_info.current_pointer_x = drag_info.grab_x;
1919 drag_info.current_pointer_y = drag_info.grab_y;
1920 drag_info.last_pointer_x = drag_info.current_pointer_x;
1921 drag_info.last_pointer_y = drag_info.current_pointer_y;
1922 drag_info.cumulative_x_drag = 0;
1923 drag_info.cumulative_y_drag = 0;
1924 drag_info.first_move = true;
1925 drag_info.move_threshold_passed = false;
1926 drag_info.want_move_threshold = false;
1927 drag_info.pointer_frame_offset = 0;
1928 drag_info.brushing = false;
1929 drag_info.clear_copied_locations ();
1931 drag_info.original_x = 0;
1932 drag_info.original_y = 0;
1933 drag_info.item->i2w (drag_info.original_x, drag_info.original_y);
1935 drag_info.item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
1936 *cursor,
1937 event->button.time);
1939 if (session && session->transport_rolling()) {
1940 drag_info.was_rolling = true;
1941 } else {
1942 drag_info.was_rolling = false;
1945 switch (snap_type) {
1946 case SnapToRegionStart:
1947 case SnapToRegionEnd:
1948 case SnapToRegionSync:
1949 case SnapToRegionBoundary:
1950 build_region_boundary_cache ();
1951 break;
1952 default:
1953 break;
1957 void
1958 Editor::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time)
1960 drag_info.item->ungrab (0);
1961 drag_info.item = new_item;
1963 if (cursor == 0) {
1964 cursor = which_grabber_cursor ();
1967 drag_info.item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK, *cursor, time);
1970 bool
1971 Editor::end_grab (ArdourCanvas::Item* item, GdkEvent* event)
1973 bool did_drag = false;
1975 stop_canvas_autoscroll ();
1977 if (drag_info.item == 0) {
1978 return false;
1981 drag_info.item->ungrab (event ? event->button.time : 0);
1983 if (drag_info.finished_callback && event) {
1984 drag_info.last_pointer_x = drag_info.current_pointer_x;
1985 drag_info.last_pointer_y = drag_info.current_pointer_y;
1986 (this->*(drag_info.finished_callback)) (item, event);
1989 did_drag = !drag_info.first_move;
1991 hide_verbose_canvas_cursor();
1993 finalize_drag ();
1995 return did_drag;
1998 void
1999 Editor::region_gain_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2001 if (drag_info.first_move && drag_info.move_threshold_passed) {
2002 drag_info.first_move = false;
2006 void
2007 Editor::start_fade_in_grab (ArdourCanvas::Item* item, GdkEvent* event)
2009 drag_info.item = item;
2010 drag_info.motion_callback = &Editor::fade_in_drag_motion_callback;
2011 drag_info.finished_callback = &Editor::fade_in_drag_finished_callback;
2013 start_grab (event);
2015 if ((drag_info.data = (item->get_data ("regionview"))) == 0) {
2016 fatal << _("programming error: fade in canvas item has no regionview data pointer!") << endmsg;
2017 /*NOTREACHED*/
2020 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2022 drag_info.pointer_frame_offset = drag_info.grab_frame - ((nframes64_t) arv->audio_region()->fade_in().back()->when + arv->region()->position());
2025 void
2026 Editor::fade_in_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2028 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2029 nframes64_t pos;
2030 nframes64_t fade_length;
2032 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2033 pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2035 else {
2036 pos = 0;
2039 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2040 snap_to (pos);
2043 if (pos < (arv->region()->position() + 64)) {
2044 fade_length = 64; // this should be a minimum defined somewhere
2045 } else if (pos > arv->region()->last_frame()) {
2046 fade_length = arv->region()->length();
2047 } else {
2048 fade_length = pos - arv->region()->position();
2050 /* mapover the region selection */
2052 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
2054 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
2056 if (!tmp) {
2057 continue;
2060 tmp->reset_fade_in_shape_width (fade_length);
2063 show_verbose_duration_cursor (arv->region()->position(), arv->region()->position() + fade_length, 10);
2065 drag_info.first_move = false;
2068 void
2069 Editor::fade_in_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2071 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2072 nframes64_t pos;
2073 nframes64_t fade_length;
2075 if (drag_info.first_move) return;
2077 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2078 pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2079 } else {
2080 pos = 0;
2083 if (pos < (arv->region()->position() + 64)) {
2084 fade_length = 64; // this should be a minimum defined somewhere
2085 } else if (pos > arv->region()->last_frame()) {
2086 fade_length = arv->region()->length();
2087 } else {
2088 fade_length = pos - arv->region()->position();
2091 begin_reversible_command (_("change fade in length"));
2093 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
2095 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
2097 if (!tmp) {
2098 continue;
2101 AutomationList& alist = tmp->audio_region()->fade_in();
2102 XMLNode &before = alist.get_state();
2104 tmp->audio_region()->set_fade_in_length (fade_length);
2105 tmp->audio_region()->set_fade_in_active (true);
2107 XMLNode &after = alist.get_state();
2108 session->add_command(new MementoCommand<AutomationList>(alist, &before, &after));
2111 commit_reversible_command ();
2114 void
2115 Editor::start_fade_out_grab (ArdourCanvas::Item* item, GdkEvent* event)
2117 drag_info.item = item;
2118 drag_info.motion_callback = &Editor::fade_out_drag_motion_callback;
2119 drag_info.finished_callback = &Editor::fade_out_drag_finished_callback;
2121 start_grab (event);
2123 if ((drag_info.data = (item->get_data ("regionview"))) == 0) {
2124 fatal << _("programming error: fade out canvas item has no regionview data pointer!") << endmsg;
2125 /*NOTREACHED*/
2128 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2130 drag_info.pointer_frame_offset = drag_info.grab_frame - (arv->region()->length() - (nframes64_t) arv->audio_region()->fade_out().back()->when + arv->region()->position());
2133 void
2134 Editor::fade_out_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2136 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2137 nframes64_t pos;
2138 nframes64_t fade_length;
2140 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2141 pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2142 } else {
2143 pos = 0;
2146 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2147 snap_to (pos);
2150 if (pos > (arv->region()->last_frame() - 64)) {
2151 fade_length = 64; // this should really be a minimum fade defined somewhere
2153 else if (pos < arv->region()->position()) {
2154 fade_length = arv->region()->length();
2156 else {
2157 fade_length = arv->region()->last_frame() - pos;
2160 /* mapover the region selection */
2162 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
2164 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
2166 if (!tmp) {
2167 continue;
2170 tmp->reset_fade_out_shape_width (fade_length);
2173 show_verbose_duration_cursor (arv->region()->last_frame() - fade_length, arv->region()->last_frame(), 10);
2175 drag_info.first_move = false;
2178 void
2179 Editor::fade_out_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2181 if (drag_info.first_move) return;
2183 AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data);
2184 nframes64_t pos;
2185 nframes64_t fade_length;
2187 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2188 pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2190 else {
2191 pos = 0;
2194 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2195 snap_to (pos);
2198 if (pos > (arv->region()->last_frame() - 64)) {
2199 fade_length = 64; // this should really be a minimum fade defined somewhere
2201 else if (pos < arv->region()->position()) {
2202 fade_length = arv->region()->length();
2204 else {
2205 fade_length = arv->region()->last_frame() - pos;
2208 begin_reversible_command (_("change fade out length"));
2210 for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
2212 AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (*i);
2214 if (!tmp) {
2215 continue;
2218 AutomationList& alist = tmp->audio_region()->fade_out();
2219 XMLNode &before = alist.get_state();
2221 tmp->audio_region()->set_fade_out_length (fade_length);
2222 tmp->audio_region()->set_fade_out_active (true);
2224 XMLNode &after = alist.get_state();
2225 session->add_command(new MementoCommand<AutomationList>(alist, &before, &after));
2228 commit_reversible_command ();
2231 void
2232 Editor::start_cursor_grab (ArdourCanvas::Item* item, GdkEvent* event)
2234 drag_info.item = item;
2235 drag_info.motion_callback = &Editor::cursor_drag_motion_callback;
2236 drag_info.finished_callback = &Editor::cursor_drag_finished_callback;
2238 start_grab (event);
2240 if ((drag_info.data = (item->get_data ("cursor"))) == 0) {
2241 fatal << _("programming error: cursor canvas item has no cursor data pointer!") << endmsg;
2242 /*NOTREACHED*/
2245 Cursor* cursor = (Cursor *) drag_info.data;
2247 if (cursor == playhead_cursor) {
2248 _dragging_playhead = true;
2250 if (session && drag_info.was_rolling) {
2251 session->request_stop (false, true);
2254 if (session && session->is_auditioning()) {
2255 session->cancel_audition ();
2259 drag_info.pointer_frame_offset = drag_info.grab_frame - cursor->current_frame;
2261 show_verbose_time_cursor (cursor->current_frame, 10);
2264 void
2265 Editor::start_cursor_grab_no_stop (ArdourCanvas::Item* item, GdkEvent* event)
2267 drag_info.item = item;
2268 drag_info.motion_callback = &Editor::cursor_drag_motion_callback;
2269 drag_info.finished_callback = &Editor::cursor_drag_finished_ensure_locate_callback;
2271 start_grab (event);
2273 if ((drag_info.data = (item->get_data ("cursor"))) == 0) {
2274 fatal << _("programming error: cursor canvas item has no cursor data pointer!") << endmsg;
2275 /*NOTREACHED*/
2278 Cursor* cursor = (Cursor *) drag_info.data;
2279 nframes64_t where = event_frame (event, 0, 0);
2281 snap_to(where);
2282 playhead_cursor->set_position (where);
2284 if (cursor == playhead_cursor) {
2285 _dragging_playhead = true;
2287 if (session && session->is_auditioning()) {
2288 session->cancel_audition ();
2292 drag_info.pointer_frame_offset = drag_info.grab_frame - cursor->current_frame;
2294 show_verbose_time_cursor (cursor->current_frame, 10);
2297 void
2298 Editor::cursor_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2300 Cursor* cursor = (Cursor *) drag_info.data;
2301 nframes64_t adjusted_frame;
2303 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2304 adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2306 else {
2307 adjusted_frame = 0;
2310 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2311 if (cursor == playhead_cursor) {
2312 snap_to (adjusted_frame);
2316 if (adjusted_frame == drag_info.last_pointer_frame) return;
2318 cursor->set_position (adjusted_frame);
2320 show_verbose_time_cursor (cursor->current_frame, 10);
2322 #ifdef GTKOSX
2323 track_canvas->update_now ();
2324 #endif
2325 UpdateAllTransportClocks (cursor->current_frame);
2327 drag_info.last_pointer_frame = adjusted_frame;
2328 drag_info.first_move = false;
2331 void
2332 Editor::cursor_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2334 _dragging_playhead = false;
2336 if (drag_info.first_move) {
2337 return;
2340 cursor_drag_motion_callback (item, event);
2342 if (item == &playhead_cursor->canvas_item) {
2343 if (session) {
2344 session->request_locate (playhead_cursor->current_frame, drag_info.was_rolling);
2349 void
2350 Editor::cursor_drag_finished_ensure_locate_callback (ArdourCanvas::Item* item, GdkEvent* event)
2352 _dragging_playhead = false;
2354 cursor_drag_motion_callback (item, event);
2356 if (item == &playhead_cursor->canvas_item) {
2357 if (session) {
2358 session->request_locate (playhead_cursor->current_frame, drag_info.was_rolling);
2363 void
2364 Editor::update_marker_drag_item (Location *location)
2366 double x1 = frame_to_pixel (location->start());
2367 double x2 = frame_to_pixel (location->end());
2369 if (location->is_mark()) {
2370 marker_drag_line_points.front().set_x(x1);
2371 marker_drag_line_points.back().set_x(x1);
2372 marker_drag_line->property_points() = marker_drag_line_points;
2373 } else {
2374 range_marker_drag_rect->property_x1() = x1;
2375 range_marker_drag_rect->property_x2() = x2;
2379 void
2380 Editor::start_marker_grab (ArdourCanvas::Item* item, GdkEvent* event)
2382 Marker* marker;
2384 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
2385 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
2386 /*NOTREACHED*/
2389 bool is_start;
2391 Location *location = find_location_from_marker (marker, is_start);
2393 drag_info.item = item;
2394 drag_info.data = marker;
2395 drag_info.motion_callback = &Editor::marker_drag_motion_callback;
2396 drag_info.finished_callback = &Editor::marker_drag_finished_callback;
2398 start_grab (event);
2400 _dragging_edit_point = true;
2402 drag_info.pointer_frame_offset = drag_info.grab_frame - (is_start ? location->start() : location->end());
2404 update_marker_drag_item (location);
2406 if (location->is_mark()) {
2407 // marker_drag_line->show();
2408 // marker_drag_line->raise_to_top();
2409 } else {
2410 range_marker_drag_rect->show();
2411 //range_marker_drag_rect->raise_to_top();
2414 if (is_start) {
2415 show_verbose_time_cursor (location->start(), 10);
2416 } else {
2417 show_verbose_time_cursor (location->end(), 10);
2420 Selection::Operation op = Keyboard::selection_type (event->button.state);
2422 switch (op) {
2423 case Selection::Toggle:
2424 selection->toggle (marker);
2425 break;
2426 case Selection::Set:
2427 if (!selection->selected (marker)) {
2428 selection->set (marker);
2430 break;
2431 case Selection::Extend:
2433 Locations::LocationList ll;
2434 list<Marker*> to_add;
2435 nframes64_t s, e;
2436 selection->markers.range (s, e);
2437 s = min (marker->position(), s);
2438 e = max (marker->position(), e);
2439 s = min (s, e);
2440 e = max (s, e);
2441 if (e < max_frames) {
2442 ++e;
2444 session->locations()->find_all_between (s, e, ll, Location::Flags (0));
2445 for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
2446 LocationMarkers* lm = find_location_markers (*i);
2447 if (lm) {
2448 if (lm->start) {
2449 to_add.push_back (lm->start);
2451 if (lm->end) {
2452 to_add.push_back (lm->end);
2456 if (!to_add.empty()) {
2457 selection->add (to_add);
2459 break;
2461 case Selection::Add:
2462 selection->add (marker);
2463 break;
2466 /* set up copies for us to manipulate during the drag */
2468 drag_info.clear_copied_locations ();
2470 for (MarkerSelection::iterator i = selection->markers.begin(); i != selection->markers.end(); ++i) {
2471 Location *l = find_location_from_marker (*i, is_start);
2472 drag_info.copied_locations.push_back (new Location (*l));
2476 void
2477 Editor::marker_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2479 nframes64_t f_delta = 0;
2480 nframes64_t newframe;
2481 bool is_start;
2482 bool move_both = false;
2483 Marker* dragged_marker = (Marker*) drag_info.data;
2484 Marker* marker;
2485 Location *real_location;
2486 Location *copy_location;
2488 if (drag_info.pointer_frame_offset <= drag_info.current_pointer_frame) {
2489 newframe = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2490 } else {
2491 newframe = 0;
2494 nframes64_t next = newframe;
2496 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2497 snap_to (newframe, 0, true);
2500 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) {
2501 return;
2504 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
2505 move_both = true;
2508 MarkerSelection::iterator i;
2509 list<Location*>::iterator x;
2511 /* find the marker we're dragging, and compute the delta */
2513 for (i = selection->markers.begin(), x = drag_info.copied_locations.begin();
2514 x != drag_info.copied_locations.end() && i != selection->markers.end();
2515 ++i, ++x) {
2517 copy_location = *x;
2518 marker = *i;
2520 if (marker == dragged_marker) {
2522 if ((real_location = find_location_from_marker (marker, is_start)) == 0) {
2523 /* que pasa ?? */
2524 return;
2527 if (real_location->is_mark()) {
2528 f_delta = newframe - copy_location->start();
2529 } else {
2532 switch (marker->type()) {
2533 case Marker::Start:
2534 case Marker::LoopStart:
2535 case Marker::PunchIn:
2536 f_delta = newframe - copy_location->start();
2537 break;
2539 case Marker::End:
2540 case Marker::LoopEnd:
2541 case Marker::PunchOut:
2542 f_delta = newframe - copy_location->end();
2543 break;
2544 default:
2545 /* what kind of marker is this ? */
2546 return;
2549 break;
2553 if (i == selection->markers.end()) {
2554 /* hmm, impossible - we didn't find the dragged marker */
2555 return;
2558 /* now move them all */
2560 for (i = selection->markers.begin(), x = drag_info.copied_locations.begin();
2561 x != drag_info.copied_locations.end() && i != selection->markers.end();
2562 ++i, ++x) {
2564 copy_location = *x;
2565 marker = *i;
2567 /* call this to find out if its the start or end */
2569 if ((real_location = find_location_from_marker (marker, is_start)) == 0) {
2570 continue;
2573 if (real_location->locked()) {
2574 continue;
2577 if (copy_location->is_mark()) {
2579 /* just move it */
2581 copy_location->set_start (copy_location->start() + f_delta);
2583 } else {
2585 nframes64_t new_start = copy_location->start() + f_delta;
2586 nframes64_t new_end = copy_location->end() + f_delta;
2588 if (is_start) { // start-of-range marker
2590 if (move_both) {
2591 copy_location->set_start (new_start);
2592 copy_location->set_end (new_end);
2593 } else if (new_start < copy_location->end()) {
2594 copy_location->set_start (new_start);
2595 } else {
2596 snap_to (next, 1, true);
2597 copy_location->set_end (next);
2598 copy_location->set_start (newframe);
2601 } else { // end marker
2603 if (move_both) {
2604 copy_location->set_end (new_end);
2605 copy_location->set_start (new_start);
2606 } else if (new_end > copy_location->start()) {
2607 copy_location->set_end (new_end);
2608 } else if (newframe > 0) {
2609 snap_to (next, -1, true);
2610 copy_location->set_start (next);
2611 copy_location->set_end (newframe);
2615 update_marker_drag_item (copy_location);
2617 LocationMarkers* lm = find_location_markers (real_location);
2619 if (lm) {
2620 lm->set_position (copy_location->start(), copy_location->end());
2624 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
2625 drag_info.first_move = false;
2627 if (drag_info.copied_locations.empty()) {
2628 abort();
2631 if (Profile->get_sae()) {
2632 edit_point_clock.set (drag_info.copied_locations.front()->start());
2634 show_verbose_time_cursor (newframe, 10);
2636 #ifdef GTKOSX
2637 track_canvas->update_now ();
2638 #endif
2641 void
2642 Editor::marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2644 if (drag_info.first_move) {
2646 /* just a click, do nothing but finish
2647 off the selection process
2650 Selection::Operation op = Keyboard::selection_type (event->button.state);
2651 Marker* marker = (Marker *) drag_info.data;
2653 switch (op) {
2654 case Selection::Set:
2655 if (selection->selected (marker) && selection->markers.size() > 1) {
2656 selection->set (marker);
2658 break;
2660 case Selection::Toggle:
2661 case Selection::Extend:
2662 case Selection::Add:
2663 break;
2666 return;
2669 _dragging_edit_point = false;
2672 begin_reversible_command ( _("move marker") );
2673 XMLNode &before = session->locations()->get_state();
2675 MarkerSelection::iterator i;
2676 list<Location*>::iterator x;
2677 bool is_start;
2679 for (i = selection->markers.begin(), x = drag_info.copied_locations.begin();
2680 x != drag_info.copied_locations.end() && i != selection->markers.end();
2681 ++i, ++x) {
2683 Location * location = find_location_from_marker ((*i), is_start);
2685 if (location) {
2687 if (location->locked()) {
2688 return;
2691 if (location->is_mark()) {
2692 location->set_start ((*x)->start());
2693 } else {
2694 location->set ((*x)->start(), (*x)->end());
2699 XMLNode &after = session->locations()->get_state();
2700 session->add_command(new MementoCommand<Locations>(*(session->locations()), &before, &after));
2701 commit_reversible_command ();
2703 marker_drag_line->hide();
2704 range_marker_drag_rect->hide();
2707 void
2708 Editor::start_meter_marker_grab (ArdourCanvas::Item* item, GdkEvent* event)
2710 Marker* marker;
2711 MeterMarker* meter_marker;
2713 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
2714 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
2715 /*NOTREACHED*/
2718 meter_marker = dynamic_cast<MeterMarker*> (marker);
2720 MetricSection& section (meter_marker->meter());
2722 if (!section.movable()) {
2723 return;
2726 drag_info.item = item;
2727 drag_info.copy = false;
2728 drag_info.data = marker;
2729 drag_info.motion_callback = &Editor::meter_marker_drag_motion_callback;
2730 drag_info.finished_callback = &Editor::meter_marker_drag_finished_callback;
2732 start_grab (event);
2734 drag_info.pointer_frame_offset = drag_info.grab_frame - meter_marker->meter().frame();
2736 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
2739 void
2740 Editor::start_meter_marker_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
2742 Marker* marker;
2743 MeterMarker* meter_marker;
2745 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
2746 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
2747 /*NOTREACHED*/
2750 meter_marker = dynamic_cast<MeterMarker*> (marker);
2752 // create a dummy marker for visual representation of moving the copy.
2753 // The actual copying is not done before we reach the finish callback.
2754 char name[64];
2755 snprintf (name, sizeof(name), "%g/%g", meter_marker->meter().beats_per_bar(), meter_marker->meter().note_divisor ());
2756 MeterMarker* new_marker = new MeterMarker(*this, *meter_group, ARDOUR_UI::config()->canvasvar_MeterMarker.get(), name,
2757 *new MeterSection(meter_marker->meter()));
2759 drag_info.item = &new_marker->the_item();
2760 drag_info.copy = true;
2761 drag_info.data = new_marker;
2762 drag_info.motion_callback = &Editor::meter_marker_drag_motion_callback;
2763 drag_info.finished_callback = &Editor::meter_marker_drag_finished_callback;
2765 start_grab (event);
2767 drag_info.pointer_frame_offset = drag_info.grab_frame - meter_marker->meter().frame();
2769 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
2772 void
2773 Editor::meter_marker_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2775 MeterMarker* marker = (MeterMarker *) drag_info.data;
2776 nframes64_t adjusted_frame;
2778 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2779 adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2781 else {
2782 adjusted_frame = 0;
2785 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2786 snap_to (adjusted_frame);
2789 if (adjusted_frame == drag_info.last_pointer_frame) return;
2791 marker->set_position (adjusted_frame);
2794 drag_info.last_pointer_frame = adjusted_frame;
2795 drag_info.first_move = false;
2797 show_verbose_time_cursor (adjusted_frame, 10);
2800 void
2801 Editor::meter_marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2803 if (drag_info.first_move) return;
2805 meter_marker_drag_motion_callback (drag_info.item, event);
2807 MeterMarker* marker = (MeterMarker *) drag_info.data;
2808 BBT_Time when;
2810 TempoMap& map (session->tempo_map());
2811 map.bbt_time (drag_info.last_pointer_frame, when);
2813 if (drag_info.copy == true) {
2814 begin_reversible_command (_("copy meter mark"));
2815 XMLNode &before = map.get_state();
2816 map.add_meter (marker->meter(), when);
2817 XMLNode &after = map.get_state();
2818 session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
2819 commit_reversible_command ();
2821 // delete the dummy marker we used for visual representation of copying.
2822 // a new visual marker will show up automatically.
2823 delete marker;
2824 } else {
2825 begin_reversible_command (_("move meter mark"));
2826 XMLNode &before = map.get_state();
2827 map.move_meter (marker->meter(), when);
2828 XMLNode &after = map.get_state();
2829 session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
2830 commit_reversible_command ();
2834 void
2835 Editor::start_tempo_marker_grab (ArdourCanvas::Item* item, GdkEvent* event)
2837 Marker* marker;
2838 TempoMarker* tempo_marker;
2840 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
2841 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
2842 /*NOTREACHED*/
2845 if ((tempo_marker = dynamic_cast<TempoMarker *> (marker)) == 0) {
2846 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
2847 /*NOTREACHED*/
2850 MetricSection& section (tempo_marker->tempo());
2852 if (!section.movable()) {
2853 return;
2856 drag_info.item = item;
2857 drag_info.copy = false;
2858 drag_info.data = marker;
2859 drag_info.motion_callback = &Editor::tempo_marker_drag_motion_callback;
2860 drag_info.finished_callback = &Editor::tempo_marker_drag_finished_callback;
2862 start_grab (event);
2864 drag_info.pointer_frame_offset = drag_info.grab_frame - tempo_marker->tempo().frame();
2865 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
2868 void
2869 Editor::start_tempo_marker_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
2871 Marker* marker;
2872 TempoMarker* tempo_marker;
2874 if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
2875 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
2876 /*NOTREACHED*/
2879 if ((tempo_marker = dynamic_cast<TempoMarker *> (marker)) == 0) {
2880 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
2881 /*NOTREACHED*/
2884 // create a dummy marker for visual representation of moving the copy.
2885 // The actual copying is not done before we reach the finish callback.
2886 char name[64];
2887 snprintf (name, sizeof (name), "%.2f", tempo_marker->tempo().beats_per_minute());
2888 TempoMarker* new_marker = new TempoMarker(*this, *tempo_group, ARDOUR_UI::config()->canvasvar_TempoMarker.get(), name,
2889 *new TempoSection(tempo_marker->tempo()));
2891 drag_info.item = &new_marker->the_item();
2892 drag_info.copy = true;
2893 drag_info.data = new_marker;
2894 drag_info.motion_callback = &Editor::tempo_marker_drag_motion_callback;
2895 drag_info.finished_callback = &Editor::tempo_marker_drag_finished_callback;
2897 start_grab (event);
2899 drag_info.pointer_frame_offset = drag_info.grab_frame - tempo_marker->tempo().frame();
2901 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
2904 void
2905 Editor::tempo_marker_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
2907 TempoMarker* marker = (TempoMarker *) drag_info.data;
2908 nframes64_t adjusted_frame;
2910 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
2911 adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
2913 else {
2914 adjusted_frame = 0;
2917 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
2918 snap_to (adjusted_frame);
2921 if (adjusted_frame == drag_info.last_pointer_frame) return;
2923 /* OK, we've moved far enough to make it worth actually move the thing. */
2925 marker->set_position (adjusted_frame);
2927 show_verbose_time_cursor (adjusted_frame, 10);
2929 drag_info.last_pointer_frame = adjusted_frame;
2930 drag_info.first_move = false;
2933 void
2934 Editor::tempo_marker_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
2936 if (drag_info.first_move) return;
2938 tempo_marker_drag_motion_callback (drag_info.item, event);
2940 TempoMarker* marker = (TempoMarker *) drag_info.data;
2941 BBT_Time when;
2943 TempoMap& map (session->tempo_map());
2944 map.bbt_time (drag_info.last_pointer_frame, when);
2946 if (drag_info.copy == true) {
2947 begin_reversible_command (_("copy tempo mark"));
2948 XMLNode &before = map.get_state();
2949 map.add_tempo (marker->tempo(), when);
2950 XMLNode &after = map.get_state();
2951 session->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2952 commit_reversible_command ();
2954 // delete the dummy marker we used for visual representation of copying.
2955 // a new visual marker will show up automatically.
2956 delete marker;
2957 } else {
2958 begin_reversible_command (_("move tempo mark"));
2959 XMLNode &before = map.get_state();
2960 map.move_tempo (marker->tempo(), when);
2961 XMLNode &after = map.get_state();
2962 session->add_command (new MementoCommand<TempoMap>(map, &before, &after));
2963 commit_reversible_command ();
2967 void
2968 Editor::remove_gain_control_point (ArdourCanvas::Item*item, GdkEvent* event)
2970 ControlPoint* control_point;
2972 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2973 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2974 /*NOTREACHED*/
2977 // We shouldn't remove the first or last gain point
2978 if (control_point->line.is_last_point(*control_point) ||
2979 control_point->line.is_first_point(*control_point)) {
2980 return;
2983 control_point->line.remove_point (*control_point);
2986 void
2987 Editor::remove_control_point (ArdourCanvas::Item*item, GdkEvent* event)
2989 ControlPoint* control_point;
2991 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
2992 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
2993 /*NOTREACHED*/
2996 control_point->line.remove_point (*control_point);
2999 void
3000 Editor::start_control_point_grab (ArdourCanvas::Item* item, GdkEvent* event)
3002 ControlPoint* control_point;
3004 if ((control_point = reinterpret_cast<ControlPoint *> (item->get_data ("control_point"))) == 0) {
3005 fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg;
3006 /*NOTREACHED*/
3009 drag_info.item = item;
3010 drag_info.data = control_point;
3011 drag_info.motion_callback = &Editor::control_point_drag_motion_callback;
3012 drag_info.finished_callback = &Editor::control_point_drag_finished_callback;
3014 start_grab (event, fader_cursor);
3016 // start the grab at the center of the control point so
3017 // the point doesn't 'jump' to the mouse after the first drag
3018 drag_info.grab_x = control_point->get_x();
3019 drag_info.grab_y = control_point->get_y();
3020 control_point->line.parent_group().i2w(drag_info.grab_x, drag_info.grab_y);
3021 track_canvas->w2c(drag_info.grab_x, drag_info.grab_y, drag_info.grab_x, drag_info.grab_y);
3023 drag_info.grab_frame = pixel_to_frame(drag_info.grab_x);
3025 control_point->line.start_drag (control_point, drag_info.grab_frame, 0);
3027 float fraction = 1.0 - (control_point->get_y() / control_point->line.height());
3028 set_verbose_canvas_cursor (control_point->line.get_verbose_cursor_string (fraction),
3029 drag_info.current_pointer_x + 10, drag_info.current_pointer_y + 10);
3031 show_verbose_canvas_cursor ();
3034 void
3035 Editor::control_point_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
3037 ControlPoint* cp = reinterpret_cast<ControlPoint *> (drag_info.data);
3039 double dx = drag_info.current_pointer_x - drag_info.last_pointer_x;
3040 double dy = drag_info.current_pointer_y - drag_info.last_pointer_y;
3042 if (event->button.state & Keyboard::SecondaryModifier) {
3043 dx *= 0.1;
3044 dy *= 0.1;
3047 double cx = drag_info.grab_x + drag_info.cumulative_x_drag + dx;
3048 double cy = drag_info.grab_y + drag_info.cumulative_y_drag + dy;
3050 // calculate zero crossing point. back off by .01 to stay on the
3051 // positive side of zero
3052 double _unused = 0;
3053 double zero_gain_y = (1.0 - ZERO_GAIN_FRACTION) * cp->line.height() - .01;
3054 cp->line.parent_group().i2w(_unused, zero_gain_y);
3056 // make sure we hit zero when passing through
3057 if ((cy < zero_gain_y and (cy - dy) > zero_gain_y)
3058 or (cy > zero_gain_y and (cy - dy) < zero_gain_y)) {
3059 cy = zero_gain_y;
3062 if (drag_info.x_constrained) {
3063 cx = drag_info.grab_x;
3065 if (drag_info.y_constrained) {
3066 cy = drag_info.grab_y;
3069 drag_info.cumulative_x_drag = cx - drag_info.grab_x;
3070 drag_info.cumulative_y_drag = cy - drag_info.grab_y;
3072 cp->line.parent_group().w2i (cx, cy);
3074 cx = max (0.0, cx);
3075 cy = max (0.0, cy);
3076 cy = min ((double) cp->line.height(), cy);
3078 //translate cx to frames
3079 nframes64_t cx_frames = unit_to_frame (cx);
3081 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) && !drag_info.x_constrained) {
3082 snap_to (cx_frames);
3085 float fraction = 1.0 - (cy / cp->line.height());
3087 bool push;
3089 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
3090 push = true;
3091 } else {
3092 push = false;
3095 cp->line.point_drag (*cp, cx_frames , fraction, push);
3097 set_verbose_canvas_cursor_text (cp->line.get_verbose_cursor_string (fraction));
3099 drag_info.first_move = false;
3102 void
3103 Editor::control_point_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3105 ControlPoint* cp = reinterpret_cast<ControlPoint *> (drag_info.data);
3107 if (drag_info.first_move) {
3109 /* just a click */
3111 if ((event->type == GDK_BUTTON_RELEASE) && (event->button.button == 1) && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
3112 reset_point_selection ();
3115 } else {
3116 control_point_drag_motion_callback (item, event);
3118 cp->line.end_drag (cp);
3121 void
3122 Editor::start_line_grab_from_regionview (ArdourCanvas::Item* item, GdkEvent* event)
3124 switch (mouse_mode) {
3125 case MouseGain:
3126 assert(dynamic_cast<AudioRegionView*>(clicked_regionview));
3127 start_line_grab (dynamic_cast<AudioRegionView*>(clicked_regionview)->get_gain_line(), event);
3128 break;
3129 default:
3130 break;
3134 void
3135 Editor::start_line_grab_from_line (ArdourCanvas::Item* item, GdkEvent* event)
3137 AutomationLine* al;
3139 if ((al = reinterpret_cast<AutomationLine*> (item->get_data ("line"))) == 0) {
3140 fatal << _("programming error: line canvas item has no line pointer!") << endmsg;
3141 /*NOTREACHED*/
3144 start_line_grab (al, event);
3147 void
3148 Editor::start_line_grab (AutomationLine* line, GdkEvent* event)
3150 double cx;
3151 double cy;
3152 nframes64_t frame_within_region;
3154 /* need to get x coordinate in terms of parent (TimeAxisItemView)
3155 origin, and ditto for y.
3158 cx = event->button.x;
3159 cy = event->button.y;
3161 line->parent_group().w2i (cx, cy);
3163 frame_within_region = (nframes64_t) floor (cx * frames_per_unit);
3165 if (!line->control_points_adjacent (frame_within_region, current_line_drag_info.before,
3166 current_line_drag_info.after)) {
3167 /* no adjacent points */
3168 return;
3171 drag_info.item = &line->grab_item();
3172 drag_info.data = line;
3173 drag_info.motion_callback = &Editor::line_drag_motion_callback;
3174 drag_info.finished_callback = &Editor::line_drag_finished_callback;
3176 start_grab (event, fader_cursor);
3178 /* store grab start in parent frame */
3180 drag_info.grab_x = cx;
3181 drag_info.grab_y = cy;
3183 double fraction = 1.0 - (cy / line->height());
3185 line->start_drag (0, drag_info.grab_frame, fraction);
3187 set_verbose_canvas_cursor (line->get_verbose_cursor_string (fraction),
3188 drag_info.current_pointer_x + 10, drag_info.current_pointer_y + 10);
3189 show_verbose_canvas_cursor ();
3192 void
3193 Editor::line_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
3195 AutomationLine* line = reinterpret_cast<AutomationLine *> (drag_info.data);
3197 double dy = drag_info.current_pointer_y - drag_info.last_pointer_y;
3199 if (event->button.state & Keyboard::SecondaryModifier) {
3200 dy *= 0.1;
3203 double cy = drag_info.grab_y + drag_info.cumulative_y_drag + dy;
3205 drag_info.cumulative_y_drag = cy - drag_info.grab_y;
3207 cy = max (0.0, cy);
3208 cy = min ((double) line->height(), cy);
3210 double fraction = 1.0 - (cy / line->height());
3212 bool push;
3214 if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
3215 push = false;
3216 } else {
3217 push = true;
3220 line->line_drag (current_line_drag_info.before, current_line_drag_info.after, fraction, push);
3222 set_verbose_canvas_cursor_text (line->get_verbose_cursor_string (fraction));
3225 void
3226 Editor::line_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3228 AutomationLine* line = reinterpret_cast<AutomationLine *> (drag_info.data);
3229 line_drag_motion_callback (item, event);
3230 line->end_drag (0);
3233 void
3234 Editor::start_region_grab (ArdourCanvas::Item* item, GdkEvent* event)
3236 if (selection->regions.empty() || clicked_regionview == 0) {
3237 return;
3240 drag_info.copy = false;
3241 drag_info.item = item;
3242 drag_info.data = clicked_regionview;
3244 if (Config->get_edit_mode() == Splice) {
3245 drag_info.motion_callback = &Editor::region_drag_splice_motion_callback;
3246 drag_info.finished_callback = &Editor::region_drag_splice_finished_callback;
3247 } else {
3248 drag_info.motion_callback = &Editor::region_drag_motion_callback;
3249 drag_info.finished_callback = &Editor::region_drag_finished_callback;
3252 start_grab (event);
3254 double speed = 1.0;
3255 TimeAxisView* tvp = clicked_trackview;
3256 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
3258 if (tv && tv->is_audio_track()) {
3259 speed = tv->get_diskstream()->speed();
3262 drag_info.last_frame_position = (nframes64_t) (clicked_regionview->region()->position() / speed);
3263 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
3264 drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
3265 drag_info.dest_trackview = drag_info.source_trackview;
3266 // we want a move threshold
3267 drag_info.want_move_threshold = true;
3269 show_verbose_time_cursor (drag_info.last_frame_position, 10);
3271 begin_reversible_command (_("move region(s)"));
3273 _region_motion_group->raise_to_top ();
3275 /* sync the canvas to what we think is its current state */
3276 track_canvas->update_now();
3279 void
3280 Editor::start_region_copy_grab (ArdourCanvas::Item* item, GdkEvent* event)
3282 if (selection->regions.empty() || clicked_regionview == 0) {
3283 return;
3286 drag_info.copy = true;
3287 drag_info.item = item;
3288 drag_info.data = clicked_regionview;
3290 start_grab(event);
3292 TimeAxisView* tv = &clicked_regionview->get_time_axis_view();
3293 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*>(tv);
3294 double speed = 1.0;
3296 if (atv && atv->is_audio_track()) {
3297 speed = atv->get_diskstream()->speed();
3300 drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
3301 drag_info.dest_trackview = drag_info.source_trackview;
3302 drag_info.last_frame_position = (nframes64_t) (clicked_regionview->region()->position() / speed);
3303 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
3304 // we want a move threshold
3305 drag_info.want_move_threshold = true;
3306 drag_info.motion_callback = &Editor::region_drag_motion_callback;
3307 drag_info.finished_callback = &Editor::region_drag_finished_callback;
3308 show_verbose_time_cursor (drag_info.last_frame_position, 10);
3309 _region_motion_group->raise_to_top ();
3312 void
3313 Editor::start_region_brush_grab (ArdourCanvas::Item* item, GdkEvent* event)
3315 if (selection->regions.empty() || clicked_regionview == 0 || Config->get_edit_mode() == Splice) {
3316 return;
3319 drag_info.copy = false;
3320 drag_info.item = item;
3321 drag_info.data = clicked_regionview;
3322 drag_info.motion_callback = &Editor::region_drag_motion_callback;
3323 drag_info.finished_callback = &Editor::region_drag_finished_callback;
3325 start_grab (event);
3327 double speed = 1.0;
3328 TimeAxisView* tvp = clicked_trackview;
3329 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
3331 if (tv && tv->is_audio_track()) {
3332 speed = tv->get_diskstream()->speed();
3335 drag_info.last_frame_position = (nframes64_t) (clicked_regionview->region()->position() / speed);
3336 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
3337 drag_info.source_trackview = &clicked_regionview->get_time_axis_view();
3338 drag_info.dest_trackview = drag_info.source_trackview;
3339 // we want a move threshold
3340 drag_info.want_move_threshold = true;
3341 drag_info.brushing = true;
3343 begin_reversible_command (_("Drag region brush"));
3346 void
3347 Editor::possibly_copy_regions_during_grab (GdkEvent* event)
3349 if (drag_info.copy && drag_info.move_threshold_passed && drag_info.want_move_threshold) {
3351 drag_info.want_move_threshold = false; // don't copy again
3353 /* duplicate the regionview(s) and region(s) */
3355 vector<RegionView*> new_regionviews;
3357 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
3358 RegionView* nrv;
3359 AudioRegionView* arv;
3361 if ((arv = dynamic_cast<AudioRegionView*>(*i)) == 0) {
3362 /* XXX handle MIDI here */
3363 continue;
3366 const boost::shared_ptr<const Region> original = arv->region();
3367 boost::shared_ptr<Region> region_copy = RegionFactory::create (original);
3368 boost::shared_ptr<AudioRegion> ar = boost::dynamic_pointer_cast<AudioRegion> (region_copy);
3370 nrv = new AudioRegionView (*arv, ar);
3371 nrv->get_canvas_group()->show ();
3373 new_regionviews.push_back (nrv);
3376 if (new_regionviews.empty()) {
3377 return;
3380 /* reset selection to new regionviews. This will not set selection visual status for
3381 these regionviews since they don't belong to a track, so do that by hand too.
3384 selection->set (new_regionviews);
3386 for (vector<RegionView*>::iterator i = new_regionviews.begin(); i != new_regionviews.end(); ++i) {
3387 (*i)->set_selected (true);
3390 /* reset drag_info data to reflect the fact that we are dragging the copies */
3392 drag_info.data = new_regionviews.front();
3394 swap_grab (new_regionviews.front()->get_canvas_group (), 0, event->motion.time);
3396 sync the canvas to what we think is its current state
3397 without it, the canvas seems to
3398 "forget" to update properly after the upcoming reparent()
3399 ..only if the mouse is in rapid motion at the time of the grab.
3400 something to do with regionview creation raking so long?
3402 track_canvas->update_now();
3406 bool
3407 Editor::check_region_drag_possible (AudioTimeAxisView** tv)
3409 /* Which trackview is this ? */
3411 TimeAxisView* tvp = trackview_by_y_position (drag_info.current_pointer_y);
3412 (*tv) = dynamic_cast<AudioTimeAxisView*>(tvp);
3414 /* The region motion is only processed if the pointer is over
3415 an audio track.
3418 if (!(*tv) || !(*tv)->is_audio_track()) {
3419 /* To make sure we hide the verbose canvas cursor when the mouse is
3420 not held over and audiotrack.
3422 hide_verbose_canvas_cursor ();
3423 return false;
3426 return true;
3429 struct RegionSelectionByPosition {
3430 bool operator() (RegionView*a, RegionView* b) {
3431 return a->region()->position () < b->region()->position();
3435 void
3436 Editor::region_drag_splice_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
3438 AudioTimeAxisView* tv;
3440 if (!check_region_drag_possible (&tv)) {
3441 return;
3444 if (!drag_info.move_threshold_passed) {
3445 return;
3448 int dir;
3450 if (drag_info.current_pointer_x - drag_info.grab_x > 0) {
3451 dir = 1;
3452 } else {
3453 dir = -1;
3456 RegionSelection copy (selection->regions);
3458 RegionSelectionByPosition cmp;
3459 copy.sort (cmp);
3461 for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {
3463 AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*> (&(*i)->get_time_axis_view());
3465 if (!atv) {
3466 continue;
3469 boost::shared_ptr<Playlist> playlist;
3471 if ((playlist = atv->playlist()) == 0) {
3472 continue;
3475 if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
3476 continue;
3479 if (dir > 0) {
3480 if (drag_info.current_pointer_frame < (*i)->region()->last_frame() + 1) {
3481 continue;
3483 } else {
3484 if (drag_info.current_pointer_frame > (*i)->region()->first_frame()) {
3485 continue;
3490 playlist->shuffle ((*i)->region(), dir);
3492 drag_info.grab_x = drag_info.current_pointer_x;
3496 void
3497 Editor::region_drag_splice_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3501 void
3502 Editor::region_drag_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
3504 double x_delta;
3505 double y_delta = 0;
3506 nframes64_t pending_region_position = 0;
3507 int32_t pointer_y_span = 0, canvas_pointer_y_span = 0, original_pointer_order;
3508 int32_t visible_y_high = 0, visible_y_low = 512; //high meaning higher numbered.. not the height on the screen
3509 bool clamp_y_axis = false;
3510 vector<int32_t> height_list(512) ;
3511 vector<int32_t>::iterator j;
3512 AudioTimeAxisView* tv;
3514 possibly_copy_regions_during_grab (event);
3516 if (!check_region_drag_possible (&tv)) {
3517 return;
3520 original_pointer_order = drag_info.dest_trackview->order;
3522 /************************************************************
3523 Y-Delta Computation
3524 ************************************************************/
3526 if (drag_info.brushing) {
3527 clamp_y_axis = true;
3528 pointer_y_span = 0;
3529 goto y_axis_done;
3532 if ((pointer_y_span = (drag_info.dest_trackview->order - tv->order)) != 0) {
3534 int32_t children = 0, numtracks = 0;
3535 // XXX hard coding track limit, oh my, so very very bad
3536 bitset <1024> tracks (0x00ul);
3537 /* get a bitmask representing the visible tracks */
3539 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
3540 TimeAxisView *tracklist_timeview;
3541 tracklist_timeview = (*i);
3542 AudioTimeAxisView* atv2 = dynamic_cast<AudioTimeAxisView*>(tracklist_timeview);
3543 list<TimeAxisView*> children_list;
3545 /* zeroes are audio tracks. ones are other types. */
3547 if (!atv2->hidden()) {
3549 if (visible_y_high < atv2->order) {
3550 visible_y_high = atv2->order;
3552 if (visible_y_low > atv2->order) {
3553 visible_y_low = atv2->order;
3556 if (!atv2->is_audio_track()) {
3557 tracks = tracks |= (0x01 << atv2->order);
3560 height_list[atv2->order] = (*i)->current_height();
3561 children = 1;
3562 if ((children_list = atv2->get_child_list()).size() > 0) {
3563 for (list<TimeAxisView*>::iterator j = children_list.begin(); j != children_list.end(); ++j) {
3564 tracks = tracks |= (0x01 << (atv2->order + children));
3565 height_list[atv2->order + children] = (*j)->current_height();
3566 numtracks++;
3567 children++;
3570 numtracks++;
3573 /* find the actual span according to the canvas */
3575 canvas_pointer_y_span = pointer_y_span;
3576 if (drag_info.dest_trackview->order >= tv->order) {
3577 int32_t y;
3578 for (y = tv->order; y < drag_info.dest_trackview->order; y++) {
3579 if (height_list[y] == 0 ) {
3580 canvas_pointer_y_span--;
3583 } else {
3584 int32_t y;
3585 for (y = drag_info.dest_trackview->order;y <= tv->order; y++) {
3586 if ( height_list[y] == 0 ) {
3587 canvas_pointer_y_span++;
3592 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
3593 RegionView* rv2 = (*i);
3594 double ix1, ix2, iy1, iy2;
3595 int32_t n = 0;
3597 if (rv2->region()->locked()) {
3598 continue;
3601 rv2->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3602 rv2->get_canvas_group()->i2w (ix1, iy1);
3603 iy1 += vertical_adjustment.get_value() - canvas_timebars_vsize;
3605 TimeAxisView* tvp2 = trackview_by_y_position (iy1);
3606 RouteTimeAxisView* atv2 = dynamic_cast<RouteTimeAxisView*>(tvp2);
3608 if (atv2->order != original_pointer_order) {
3609 /* this isn't the pointer track */
3611 if (canvas_pointer_y_span > 0) {
3613 /* moving up the canvas */
3614 if ((atv2->order - canvas_pointer_y_span) >= visible_y_low) {
3616 int32_t visible_tracks = 0;
3617 while (visible_tracks < canvas_pointer_y_span ) {
3618 visible_tracks++;
3620 while (height_list[atv2->order - (visible_tracks - n)] == 0) {
3621 /* we're passing through a hidden track */
3622 n--;
3626 if (tracks[atv2->order - (canvas_pointer_y_span - n)] != 0x00) {
3627 clamp_y_axis = true;
3630 } else {
3631 clamp_y_axis = true;
3634 } else if (canvas_pointer_y_span < 0) {
3636 /*moving down the canvas*/
3638 if ((atv2->order - (canvas_pointer_y_span - n)) <= visible_y_high) { // we will overflow
3641 int32_t visible_tracks = 0;
3643 while (visible_tracks > canvas_pointer_y_span ) {
3644 visible_tracks--;
3646 while (height_list[atv2->order - (visible_tracks - n)] == 0) {
3647 n++;
3650 if ( tracks[atv2->order - ( canvas_pointer_y_span - n)] != 0x00) {
3651 clamp_y_axis = true;
3654 } else {
3656 clamp_y_axis = true;
3660 } else {
3662 /* this is the pointer's track */
3663 if ((atv2->order - pointer_y_span) > visible_y_high) { // we will overflow
3664 clamp_y_axis = true;
3665 } else if ((atv2->order - pointer_y_span) < visible_y_low) { // we will underflow
3666 clamp_y_axis = true;
3669 if (clamp_y_axis) {
3670 break;
3674 } else if (drag_info.dest_trackview == tv) {
3675 clamp_y_axis = true;
3678 y_axis_done:
3679 if (!clamp_y_axis) {
3680 drag_info.dest_trackview = tv;
3683 /************************************************************
3684 X DELTA COMPUTATION
3685 ************************************************************/
3687 /* compute the amount of pointer motion in frames, and where
3688 the region would be if we moved it by that much.
3690 if ( drag_info.move_threshold_passed ) {
3692 if (drag_info.current_pointer_frame >= drag_info.pointer_frame_offset) {
3694 nframes64_t sync_frame;
3695 nframes64_t sync_offset;
3696 int32_t sync_dir;
3698 pending_region_position = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
3700 sync_offset = clicked_regionview->region()->sync_offset (sync_dir);
3702 /* we don't handle a sync point that lies before zero.
3704 if (sync_dir >= 0 || (sync_dir < 0 && pending_region_position >= sync_offset)) {
3705 sync_frame = pending_region_position + (sync_dir*sync_offset);
3707 /* we snap if the snap modifier is not enabled.
3710 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
3711 snap_to (sync_frame);
3714 pending_region_position = clicked_regionview->region()->adjust_to_sync (sync_frame);
3716 } else {
3717 pending_region_position = drag_info.last_frame_position;
3720 } else {
3721 pending_region_position = 0;
3724 if (pending_region_position > max_frames - clicked_regionview->region()->length()) {
3725 pending_region_position = drag_info.last_frame_position;
3728 // printf ("3: pending_region_position= %lu %lu\n", pending_region_position, drag_info.last_frame_position );
3730 bool x_move_allowed;
3732 if (Config->get_edit_mode() == Lock) {
3733 x_move_allowed = drag_info.x_constrained;
3734 } else {
3735 x_move_allowed = !drag_info.x_constrained;
3738 if (( pending_region_position != drag_info.last_frame_position) && x_move_allowed ) {
3740 /* now compute the canvas unit distance we need to move the regionview
3741 to make it appear at the new location.
3744 if (pending_region_position > drag_info.last_frame_position) {
3745 x_delta = ((double) (pending_region_position - drag_info.last_frame_position) / frames_per_unit);
3746 } else {
3747 x_delta = -((double) (drag_info.last_frame_position - pending_region_position) / frames_per_unit);
3748 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
3750 RegionView* rv2 = (*i);
3752 // If any regionview is at zero, we need to know so we can stop further leftward motion.
3754 double ix1, ix2, iy1, iy2;
3755 rv2->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3756 rv2->get_canvas_group()->i2w (ix1, iy1);
3758 if (-x_delta > ix1 + horizontal_adjustment.get_value()) {
3759 x_delta = 0;
3760 pending_region_position = drag_info.last_frame_position;
3761 break;
3767 drag_info.last_frame_position = pending_region_position;
3769 } else {
3770 x_delta = 0;
3773 } else {
3774 /* threshold not passed */
3776 x_delta = 0;
3779 /*************************************************************
3780 PREPARE TO MOVE
3781 ************************************************************/
3783 if (x_delta == 0 && (pointer_y_span == 0)) {
3784 /* haven't reached next snap point, and we're not switching
3785 trackviews. nothing to do.
3787 return;
3790 /*************************************************************
3791 MOTION
3792 ************************************************************/
3793 bool do_move = true;
3794 if (drag_info.first_move) {
3795 if (!drag_info.move_threshold_passed) {
3796 do_move = false;
3800 if (do_move) {
3802 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
3803 const list<RegionView*>& layered_regions = selection->regions.by_layer();
3805 for (list<RegionView*>::const_iterator i = layered_regions.begin(); i != layered_regions.end(); ++i) {
3807 RegionView* rv = (*i);
3808 double ix1, ix2, iy1, iy2;
3809 int32_t temp_pointer_y_span = pointer_y_span;
3811 if (rv->region()->locked()) {
3812 continue;
3815 /* get item BBox, which will be relative to parent. so we have
3816 to query on a child, then convert to world coordinates using
3817 the parent.
3820 rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3821 rv->get_canvas_group()->i2w (ix1, iy1);
3823 /* for evaluation of the track position of iy1, we have to adjust
3824 to allow for the vertical scrolling adjustment and the height of the timebars.
3827 iy1 += get_trackview_group_vertical_offset ();
3828 if (drag_info.first_move) {
3830 // hide any dependent views
3832 rv->get_time_axis_view().hide_dependent_views (*rv);
3835 reparent to a non scrolling group so that we can keep the
3836 region selection above all time axis views.
3837 reparenting means we have to move the rv as the two
3838 parent groups have different coordinates.
3841 rv->get_canvas_group()->property_y() = iy1 - 1;
3842 rv->get_canvas_group()->reparent(*_region_motion_group);
3844 rv->fake_set_opaque (true);
3847 TimeAxisView* tvp2 = trackview_by_y_position (iy1);
3848 AudioTimeAxisView* canvas_atv = dynamic_cast<AudioTimeAxisView*>(tvp2);
3849 AudioTimeAxisView* temp_atv;
3851 if ((pointer_y_span != 0) && !clamp_y_axis) {
3852 y_delta = 0;
3853 int32_t x = 0;
3854 for (j = height_list.begin(); j!= height_list.end(); j++) {
3855 if (x == canvas_atv->order) {
3856 /* we found the track the region is on */
3857 if (x != original_pointer_order) {
3858 /*this isn't from the same track we're dragging from */
3859 temp_pointer_y_span = canvas_pointer_y_span;
3861 while (temp_pointer_y_span > 0) {
3862 /* we're moving up canvas-wise,
3863 so we need to find the next track height
3865 if (j != height_list.begin()) {
3866 j--;
3868 if (x != original_pointer_order) {
3869 /* we're not from the dragged track, so ignore hidden tracks. */
3870 if ((*j) == 0) {
3871 temp_pointer_y_span++;
3874 y_delta -= (*j);
3875 temp_pointer_y_span--;
3878 while (temp_pointer_y_span < 0) {
3879 y_delta += (*j);
3880 if (x != original_pointer_order) {
3881 if ((*j) == 0) {
3882 temp_pointer_y_span--;
3886 if (j != height_list.end()) {
3887 j++;
3889 temp_pointer_y_span++;
3891 /* find out where we'll be when we move and set height accordingly */
3893 tvp2 = trackview_by_y_position (iy1 + y_delta);
3894 temp_atv = dynamic_cast<AudioTimeAxisView*>(tvp2);
3895 rv->set_height (temp_atv->current_height());
3897 /* if you un-comment the following, the region colours will follow the track colours whilst dragging,
3898 personally, i think this can confuse things, but never mind.
3901 //const GdkColor& col (temp_atv->view->get_region_color());
3902 //rv->set_color (const_cast<GdkColor&>(col));
3903 break;
3905 x++;
3909 if (drag_info.brushing) {
3910 mouse_brush_insert_region (rv, pending_region_position);
3911 } else {
3912 rv->move (x_delta, y_delta);
3915 } /* foreach region */
3917 } /* if do_move */
3919 if (drag_info.first_move && drag_info.move_threshold_passed) {
3920 cursor_group->raise_to_top();
3921 drag_info.first_move = false;
3924 if (x_delta != 0 && !drag_info.brushing) {
3925 show_verbose_time_cursor (drag_info.last_frame_position, 10);
3929 void
3930 Editor::region_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3932 bool nocommit = true;
3933 vector<RegionView*> copies;
3934 RouteTimeAxisView* source_tv;
3935 boost::shared_ptr<Diskstream> ds;
3936 boost::shared_ptr<Playlist> from_playlist;
3937 vector<RegionView*> new_selection;
3938 typedef set<boost::shared_ptr<Playlist> > PlaylistSet;
3939 PlaylistSet modified_playlists;
3940 PlaylistSet frozen_playlists;
3941 list <sigc::connection> modified_playlist_connections;
3942 pair<PlaylistSet::iterator,bool> insert_result, frozen_insert_result;
3943 nframes64_t drag_delta;
3944 bool changed_tracks, changed_position;
3946 /* first_move is set to false if the regionview has been moved in the
3947 motion handler.
3950 if (drag_info.first_move) {
3951 /* just a click */
3952 goto out;
3955 nocommit = false;
3957 if (Config->get_edit_mode() == Splice && !pre_drag_region_selection.empty()) {
3958 selection->set (pre_drag_region_selection);
3959 pre_drag_region_selection.clear ();
3962 if (drag_info.brushing) {
3963 /* all changes were made during motion event handlers */
3965 if (drag_info.copy) {
3966 for (list<RegionView*>::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
3967 copies.push_back (*i);
3971 goto out;
3974 char* op_string;
3976 /* reverse this here so that we have the correct logic to finalize
3977 the drag.
3980 if (Config->get_edit_mode() == Lock) {
3981 drag_info.x_constrained = !drag_info.x_constrained;
3984 if (drag_info.copy) {
3985 if (drag_info.x_constrained) {
3986 op_string = _("fixed time region copy");
3987 } else {
3988 op_string = _("region copy");
3990 } else {
3991 if (drag_info.x_constrained) {
3992 op_string = _("fixed time region drag");
3993 } else {
3994 op_string = _("region drag");
3998 begin_reversible_command (op_string);
3999 changed_position = (drag_info.last_frame_position != (nframes64_t) (clicked_regionview->region()->position()));
4000 changed_tracks = (trackview_by_y_position (drag_info.current_pointer_y) != &clicked_regionview->get_time_axis_view());
4002 drag_delta = clicked_regionview->region()->position() - drag_info.last_frame_position;
4004 track_canvas->update_now ();
4006 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ) {
4008 RegionView* rv = (*i);
4009 double ix1, ix2, iy1, iy2;
4010 rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
4011 rv->get_canvas_group()->i2w (ix1, iy1);
4012 iy1 += vertical_adjustment.get_value() - canvas_timebars_vsize;
4014 TimeAxisView* dest_tv = trackview_by_y_position (iy1);
4015 AudioTimeAxisView* dest_atv = dynamic_cast<AudioTimeAxisView*>(dest_tv);
4016 nframes64_t where;
4018 if (rv->region()->locked()) {
4019 ++i;
4020 continue;
4023 if (changed_position && !drag_info.x_constrained) {
4024 where = rv->region()->position() - drag_delta;
4025 } else {
4026 where = rv->region()->position();
4029 boost::shared_ptr<Region> new_region;
4031 if (drag_info.copy) {
4032 /* we already made a copy */
4033 new_region = rv->region();
4035 /* undo the previous hide_dependent_views so that xfades don't
4036 disappear on copying regions
4039 //rv->get_time_axis_view().reveal_dependent_views (*rv);
4041 } else if (changed_tracks) {
4042 new_region = RegionFactory::create (rv->region());
4045 if (changed_tracks || drag_info.copy) {
4047 boost::shared_ptr<Playlist> to_playlist = dest_atv->playlist();
4049 latest_regionviews.clear ();
4051 sigc::connection c = dest_atv->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view));
4053 insert_result = modified_playlists.insert (to_playlist);
4054 if (insert_result.second) {
4055 session->add_command (new MementoCommand<Playlist>(*to_playlist, &to_playlist->get_state(), 0));
4058 to_playlist->add_region (new_region, where);
4060 c.disconnect ();
4062 if (!latest_regionviews.empty()) {
4063 // XXX why just the first one ? we only expect one
4064 //dest_atv->reveal_dependent_views (*latest_regionviews.front());
4065 new_selection.push_back (latest_regionviews.front());
4068 } else {
4070 motion on the same track. plonk the previously reparented region
4071 back to its original canvas group (its streamview).
4072 No need to do anything for copies as they are fake regions which will be deleted.
4075 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (dest_atv);
4076 rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
4077 rv->get_canvas_group()->property_y() = 0;
4079 /* just change the model */
4081 boost::shared_ptr<Playlist> playlist = dest_atv->playlist();
4083 insert_result = modified_playlists.insert (playlist);
4084 if (insert_result.second) {
4085 session->add_command (new MementoCommand<Playlist>(*playlist, &playlist->get_state(), 0));
4087 /* freeze to avoid lots of relayering in the case of a multi-region drag */
4088 frozen_insert_result = frozen_playlists.insert(playlist);
4089 if (frozen_insert_result.second) {
4090 playlist->freeze();
4093 rv->region()->set_position (where, (void*) this);
4096 if (changed_tracks && !drag_info.copy) {
4098 /* get the playlist where this drag started. we can't use rv->region()->playlist()
4099 because we may have copied the region and it has not been attached to a playlist.
4102 assert ((source_tv = dynamic_cast<RouteTimeAxisView*> (&rv->get_time_axis_view())));
4103 assert ((ds = source_tv->get_diskstream()));
4104 assert ((from_playlist = ds->playlist()));
4106 /* moved to a different audio track, without copying */
4108 /* the region that used to be in the old playlist is not
4109 moved to the new one - we use a copy of it. as a result,
4110 any existing editor for the region should no longer be
4111 visible.
4114 rv->hide_region_editor();
4115 rv->fake_set_opaque (false);
4117 /* remove the region from the old playlist */
4119 insert_result = modified_playlists.insert (from_playlist);
4120 if (insert_result.second) {
4121 session->add_command (new MementoCommand<Playlist>(*from_playlist, &from_playlist->get_state(), 0));
4124 from_playlist->remove_region ((rv->region()));
4126 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
4127 was selected in all of them, then removing it from a playlist will have removed all
4128 trace of it from the selection (i.e. there were N regions selected, we removed 1,
4129 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
4130 corresponding regionview, and the selection is now empty).
4132 this could have invalidated any and all iterators into the region selection.
4134 the heuristic we use here is: if the region selection is empty, break out of the loop
4135 here. if the region selection is not empty, then restart the loop because we know that
4136 we must have removed at least the region(view) we've just been working on as well as any
4137 that we processed on previous iterations.
4139 EXCEPT .... if we are doing a copy drag, then the selection hasn't been modified and
4140 we can just iterate.
4143 if (selection->regions.empty()) {
4144 break;
4145 } else {
4146 i = selection->regions.by_layer().begin();
4149 } else {
4150 ++i;
4153 if (drag_info.copy) {
4154 copies.push_back (rv);
4158 if (new_selection.empty()) {
4159 if (drag_info.copy) {
4160 /* the region(view)s that are selected and being dragged around
4161 are copies and do not belong to any track. remove them
4162 from the selection right here.
4164 selection->clear_regions();
4166 } else {
4167 /* this will clear any existing selection that would have been
4168 cleared in the other clause above
4170 selection->set (new_selection);
4173 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
4174 (*p)->thaw();
4177 out:
4178 if (!nocommit) {
4179 for (set<boost::shared_ptr<Playlist> >::iterator p = modified_playlists.begin(); p != modified_playlists.end(); ++p) {
4180 session->add_command (new MementoCommand<Playlist>(*(*p), 0, &(*p)->get_state()));
4182 commit_reversible_command ();
4185 for (vector<RegionView*>::iterator x = copies.begin(); x != copies.end(); ++x) {
4186 delete *x;
4191 void
4192 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
4194 /* Either add to or set the set the region selection, unless
4195 this is an alignment click (control used)
4198 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
4199 TimeAxisView* tv = &rv.get_time_axis_view();
4200 AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(tv);
4201 double speed = 1.0;
4202 if (atv && atv->is_audio_track()) {
4203 speed = atv->get_diskstream()->speed();
4206 nframes64_t where = get_preferred_edit_position();
4208 if (where >= 0) {
4210 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
4212 align_region (rv.region(), SyncPoint, (nframes64_t) (where * speed));
4214 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
4216 align_region (rv.region(), End, (nframes64_t) (where * speed));
4218 } else {
4220 align_region (rv.region(), Start, (nframes64_t) (where * speed));
4226 void
4227 Editor::show_verbose_time_cursor (nframes64_t frame, double offset, double xpos, double ypos)
4229 char buf[128];
4230 SMPTE::Time smpte;
4231 BBT_Time bbt;
4232 int hours, mins;
4233 nframes64_t frame_rate;
4234 float secs;
4236 if (session == 0) {
4237 return;
4240 AudioClock::Mode m;
4242 if (Profile->get_sae() || Profile->get_small_screen()) {
4243 m = ARDOUR_UI::instance()->primary_clock.mode();
4244 } else {
4245 m = ARDOUR_UI::instance()->secondary_clock.mode();
4248 switch (m) {
4249 case AudioClock::BBT:
4250 session->bbt_time (frame, bbt);
4251 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
4252 break;
4254 case AudioClock::SMPTE:
4255 session->smpte_time (frame, smpte);
4256 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
4257 break;
4259 case AudioClock::MinSec:
4260 /* XXX this is copied from show_verbose_duration_cursor() */
4261 frame_rate = session->frame_rate();
4262 hours = frame / (frame_rate * 3600);
4263 frame = frame % (frame_rate * 3600);
4264 mins = frame / (frame_rate * 60);
4265 frame = frame % (frame_rate * 60);
4266 secs = (float) frame / (float) frame_rate;
4267 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
4268 break;
4270 default:
4271 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
4272 break;
4275 if (xpos >= 0 && ypos >=0) {
4276 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
4278 else {
4279 set_verbose_canvas_cursor (buf, drag_info.current_pointer_x + offset - horizontal_adjustment.get_value(), drag_info.current_pointer_y + offset - vertical_adjustment.get_value() + canvas_timebars_vsize);
4281 show_verbose_canvas_cursor ();
4284 void
4285 Editor::show_verbose_duration_cursor (nframes64_t start, nframes64_t end, double offset, double xpos, double ypos)
4287 char buf[128];
4288 SMPTE::Time smpte;
4289 BBT_Time sbbt;
4290 BBT_Time ebbt;
4291 int hours, mins;
4292 nframes64_t distance, frame_rate;
4293 float secs;
4294 Meter meter_at_start(session->tempo_map().meter_at(start));
4296 if (session == 0) {
4297 return;
4300 AudioClock::Mode m;
4302 if (Profile->get_sae() || Profile->get_small_screen()) {
4303 m = ARDOUR_UI::instance()->primary_clock.mode ();
4304 } else {
4305 m = ARDOUR_UI::instance()->secondary_clock.mode ();
4308 switch (m) {
4309 case AudioClock::BBT:
4310 session->bbt_time (start, sbbt);
4311 session->bbt_time (end, ebbt);
4313 /* subtract */
4314 /* XXX this computation won't work well if the
4315 user makes a selection that spans any meter changes.
4318 ebbt.bars -= sbbt.bars;
4319 if (ebbt.beats >= sbbt.beats) {
4320 ebbt.beats -= sbbt.beats;
4321 } else {
4322 ebbt.bars--;
4323 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
4325 if (ebbt.ticks >= sbbt.ticks) {
4326 ebbt.ticks -= sbbt.ticks;
4327 } else {
4328 ebbt.beats--;
4329 ebbt.ticks = int(Meter::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
4332 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
4333 break;
4335 case AudioClock::SMPTE:
4336 session->smpte_duration (end - start, smpte);
4337 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
4338 break;
4340 case AudioClock::MinSec:
4341 /* XXX this stuff should be elsewhere.. */
4342 distance = end - start;
4343 frame_rate = session->frame_rate();
4344 hours = distance / (frame_rate * 3600);
4345 distance = distance % (frame_rate * 3600);
4346 mins = distance / (frame_rate * 60);
4347 distance = distance % (frame_rate * 60);
4348 secs = (float) distance / (float) frame_rate;
4349 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
4350 break;
4352 default:
4353 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
4354 break;
4357 if (xpos >= 0 && ypos >=0) {
4358 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
4360 else {
4361 set_verbose_canvas_cursor (buf, drag_info.current_pointer_x + offset, drag_info.current_pointer_y + offset);
4364 show_verbose_canvas_cursor ();
4367 void
4368 Editor::collect_new_region_view (RegionView* rv)
4370 latest_regionviews.push_back (rv);
4373 void
4374 Editor::start_selection_grab (ArdourCanvas::Item* item, GdkEvent* event)
4376 if (clicked_regionview == 0) {
4377 return;
4380 /* lets try to create new Region for the selection */
4382 vector<boost::shared_ptr<AudioRegion> > new_regions;
4383 create_region_from_selection (new_regions);
4385 if (new_regions.empty()) {
4386 return;
4389 /* XXX fix me one day to use all new regions */
4391 boost::shared_ptr<Region> region (new_regions.front());
4393 /* add it to the current stream/playlist.
4395 tricky: the streamview for the track will add a new regionview. we will
4396 catch the signal it sends when it creates the regionview to
4397 set the regionview we want to then drag.
4400 latest_regionviews.clear();
4401 sigc::connection c = clicked_audio_trackview->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view));
4403 /* A selection grab currently creates two undo/redo operations, one for
4404 creating the new region and another for moving it.
4407 begin_reversible_command (_("selection grab"));
4409 boost::shared_ptr<Playlist> playlist = clicked_trackview->playlist();
4411 XMLNode *before = &(playlist->get_state());
4412 clicked_trackview->playlist()->add_region (region, selection->time[clicked_selection].start);
4413 XMLNode *after = &(playlist->get_state());
4414 session->add_command(new MementoCommand<Playlist>(*playlist, before, after));
4416 commit_reversible_command ();
4418 c.disconnect ();
4420 if (latest_regionviews.empty()) {
4421 /* something went wrong */
4422 return;
4425 /* we need to deselect all other regionviews, and select this one
4426 i'm ignoring undo stuff, because the region creation will take care of it
4428 selection->set (latest_regionviews);
4430 drag_info.item = latest_regionviews.front()->get_canvas_group();
4431 drag_info.data = latest_regionviews.front();
4432 drag_info.motion_callback = &Editor::region_drag_motion_callback;
4433 drag_info.finished_callback = &Editor::region_drag_finished_callback;
4435 start_grab (event);
4437 drag_info.source_trackview = clicked_trackview;
4438 drag_info.dest_trackview = drag_info.source_trackview;
4439 drag_info.last_frame_position = latest_regionviews.front()->region()->position();
4440 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
4442 show_verbose_time_cursor (drag_info.last_frame_position, 10);
4445 void
4446 Editor::cancel_selection ()
4448 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
4449 (*i)->hide_selection ();
4451 selection->clear ();
4452 clicked_selection = 0;
4455 void
4456 Editor::start_selection_op (ArdourCanvas::Item* item, GdkEvent* event, SelectionOp op)
4458 nframes64_t start = 0;
4459 nframes64_t end = 0;
4461 if (session == 0) {
4462 return;
4465 drag_info.item = item;
4466 drag_info.motion_callback = &Editor::drag_selection;
4467 drag_info.finished_callback = &Editor::end_selection_op;
4469 selection_op = op;
4471 switch (op) {
4472 case CreateSelection:
4473 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
4474 drag_info.copy = true;
4475 } else {
4476 drag_info.copy = false;
4478 start_grab (event, selector_cursor);
4479 break;
4481 case SelectionStartTrim:
4482 if (clicked_trackview) {
4483 clicked_trackview->order_selection_trims (item, true);
4485 start_grab (event, trimmer_cursor);
4486 start = selection->time[clicked_selection].start;
4487 drag_info.pointer_frame_offset = drag_info.grab_frame - start;
4488 break;
4490 case SelectionEndTrim:
4491 if (clicked_trackview) {
4492 clicked_trackview->order_selection_trims (item, false);
4494 start_grab (event, trimmer_cursor);
4495 end = selection->time[clicked_selection].end;
4496 drag_info.pointer_frame_offset = drag_info.grab_frame - end;
4497 break;
4499 case SelectionMove:
4500 start = selection->time[clicked_selection].start;
4501 start_grab (event);
4502 drag_info.pointer_frame_offset = drag_info.grab_frame - start;
4503 break;
4506 if (selection_op == SelectionMove) {
4507 show_verbose_time_cursor(start, 10);
4508 } else {
4509 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4513 void
4514 Editor::drag_selection (ArdourCanvas::Item* item, GdkEvent* event)
4516 nframes64_t start = 0;
4517 nframes64_t end = 0;
4518 nframes64_t length;
4519 nframes64_t pending_position;
4521 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
4522 pending_position = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
4523 } else {
4524 pending_position = 0;
4527 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
4528 snap_to (pending_position);
4531 /* only alter selection if the current frame is
4532 different from the last frame position (adjusted)
4535 if (pending_position == drag_info.last_pointer_frame) return;
4537 switch (selection_op) {
4538 case CreateSelection:
4540 if (drag_info.first_move) {
4541 snap_to (drag_info.grab_frame);
4544 if (pending_position < drag_info.grab_frame) {
4545 start = pending_position;
4546 end = drag_info.grab_frame;
4547 } else {
4548 end = pending_position;
4549 start = drag_info.grab_frame;
4552 /* first drag: Either add to the selection
4553 or create a new selection->
4556 if (drag_info.first_move) {
4558 begin_reversible_command (_("range selection"));
4560 if (drag_info.copy) {
4561 /* adding to the selection */
4562 clicked_selection = selection->add (start, end);
4563 drag_info.copy = false;
4564 } else {
4565 /* new selection-> */
4566 clicked_selection = selection->set (clicked_trackview, start, end);
4569 break;
4571 case SelectionStartTrim:
4573 if (drag_info.first_move) {
4574 begin_reversible_command (_("trim selection start"));
4577 start = selection->time[clicked_selection].start;
4578 end = selection->time[clicked_selection].end;
4580 if (pending_position > end) {
4581 start = end;
4582 } else {
4583 start = pending_position;
4585 break;
4587 case SelectionEndTrim:
4589 if (drag_info.first_move) {
4590 begin_reversible_command (_("trim selection end"));
4593 start = selection->time[clicked_selection].start;
4594 end = selection->time[clicked_selection].end;
4596 if (pending_position < start) {
4597 end = start;
4598 } else {
4599 end = pending_position;
4602 break;
4604 case SelectionMove:
4606 if (drag_info.first_move) {
4607 begin_reversible_command (_("move selection"));
4610 start = selection->time[clicked_selection].start;
4611 end = selection->time[clicked_selection].end;
4613 length = end - start;
4615 start = pending_position;
4616 snap_to (start);
4618 end = start + length;
4620 break;
4623 if (event->button.x >= horizontal_adjustment.get_value() + canvas_width) {
4624 start_canvas_autoscroll (1, 0);
4627 if (start != end) {
4628 selection->replace (clicked_selection, start, end);
4631 drag_info.last_pointer_frame = pending_position;
4632 drag_info.first_move = false;
4634 if (selection_op == SelectionMove) {
4635 show_verbose_time_cursor(start, 10);
4636 } else {
4637 show_verbose_time_cursor(pending_position, 10);
4641 void
4642 Editor::end_selection_op (ArdourCanvas::Item* item, GdkEvent* event)
4644 if (!drag_info.first_move) {
4645 drag_selection (item, event);
4646 /* XXX this is not object-oriented programming at all. ick */
4647 if (selection->time.consolidate()) {
4648 selection->TimeChanged ();
4650 commit_reversible_command ();
4653 /* XXX what if its a music time selection? */
4654 if (Config->get_auto_play() || (session->get_play_range() && session->transport_rolling())) {
4655 session->request_play_range (&selection->time, true);
4658 } else {
4659 /* just a click, no pointer movement.*/
4661 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
4663 selection->clear_time();
4667 if (session->get_play_range () && session->transport_rolling()) {
4668 session->request_stop (false, false);
4672 stop_canvas_autoscroll ();
4675 void
4676 Editor::start_trim (ArdourCanvas::Item* item, GdkEvent* event)
4678 double speed = 1.0;
4679 TimeAxisView* tvp = clicked_trackview;
4680 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4682 if (tv && tv->is_audio_track()) {
4683 speed = tv->get_diskstream()->speed();
4686 nframes64_t region_start = (nframes64_t) (clicked_regionview->region()->position() / speed);
4687 nframes64_t region_end = (nframes64_t) (clicked_regionview->region()->last_frame() / speed);
4688 nframes64_t region_length = (nframes64_t) (clicked_regionview->region()->length() / speed);
4690 //drag_info.item = clicked_regionview->get_name_highlight();
4691 drag_info.item = item;
4692 drag_info.motion_callback = &Editor::trim_motion_callback;
4693 drag_info.finished_callback = &Editor::trim_finished_callback;
4695 start_grab (event, trimmer_cursor);
4697 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
4698 trim_op = ContentsTrim;
4699 } else {
4700 /* These will get overridden for a point trim.*/
4701 if (drag_info.current_pointer_frame < (region_start + region_length/2)) {
4702 /* closer to start */
4703 trim_op = StartTrim;
4704 } else if (drag_info.current_pointer_frame > (region_end - region_length/2)) {
4705 /* closer to end */
4706 trim_op = EndTrim;
4710 switch (trim_op) {
4711 case StartTrim:
4712 show_verbose_time_cursor(region_start, 10);
4713 break;
4714 case EndTrim:
4715 show_verbose_time_cursor(region_end, 10);
4716 break;
4717 case ContentsTrim:
4718 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4719 break;
4723 void
4724 Editor::trim_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
4726 RegionView* rv = clicked_regionview;
4727 nframes64_t frame_delta = 0;
4728 bool left_direction;
4729 bool obey_snap = !Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier());
4731 /* snap modifier works differently here..
4732 its' current state has to be passed to the
4733 various trim functions in order to work properly
4736 double speed = 1.0;
4737 TimeAxisView* tvp = clicked_trackview;
4738 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
4739 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
4741 if (tv && tv->is_audio_track()) {
4742 speed = tv->get_diskstream()->speed();
4745 if (drag_info.last_pointer_frame > drag_info.current_pointer_frame) {
4746 left_direction = true;
4747 } else {
4748 left_direction = false;
4751 if (obey_snap) {
4752 snap_to (drag_info.current_pointer_frame);
4755 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) {
4756 return;
4759 if (drag_info.first_move) {
4761 string trim_type;
4763 switch (trim_op) {
4764 case StartTrim:
4765 trim_type = "Region start trim";
4766 break;
4767 case EndTrim:
4768 trim_type = "Region end trim";
4769 break;
4770 case ContentsTrim:
4771 trim_type = "Region content trim";
4772 break;
4775 begin_reversible_command (trim_type);
4777 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4778 (*i)->fake_set_opaque(false);
4779 (*i)->region()->freeze ();
4781 AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*i);
4782 if (arv)
4783 arv->temporarily_hide_envelope ();
4785 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
4786 insert_result = motion_frozen_playlists.insert (pl);
4787 if (insert_result.second) {
4788 session->add_command(new MementoCommand<Playlist>(*pl, &pl->get_state(), 0));
4793 if (left_direction) {
4794 frame_delta = (drag_info.last_pointer_frame - drag_info.current_pointer_frame);
4795 } else {
4796 frame_delta = (drag_info.current_pointer_frame - drag_info.last_pointer_frame);
4799 switch (trim_op) {
4800 case StartTrim:
4801 if ((left_direction == false) && (drag_info.current_pointer_frame <= rv->region()->first_frame()/speed)) {
4802 break;
4803 } else {
4804 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4805 single_start_trim (**i, frame_delta, left_direction, obey_snap);
4807 break;
4810 case EndTrim:
4811 if ((left_direction == true) && (drag_info.current_pointer_frame > (nframes64_t) (rv->region()->last_frame()/speed))) {
4812 break;
4813 } else {
4814 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4815 single_end_trim (**i, frame_delta, left_direction, obey_snap);
4817 break;
4820 case ContentsTrim:
4822 bool swap_direction = false;
4824 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
4825 swap_direction = true;
4828 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
4829 i != selection->regions.by_layer().end(); ++i)
4831 single_contents_trim (**i, frame_delta, left_direction, swap_direction, obey_snap);
4834 break;
4837 switch (trim_op) {
4838 case StartTrim:
4839 show_verbose_time_cursor((nframes64_t) (rv->region()->position()/speed), 10);
4840 break;
4841 case EndTrim:
4842 show_verbose_time_cursor((nframes64_t) (rv->region()->last_frame()/speed), 10);
4843 break;
4844 case ContentsTrim:
4845 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4846 break;
4849 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
4850 drag_info.first_move = false;
4853 void
4854 Editor::single_contents_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool swap_direction, bool obey_snap)
4856 boost::shared_ptr<Region> region (rv.region());
4858 if (region->locked()) {
4859 return;
4862 nframes64_t new_bound;
4864 double speed = 1.0;
4865 TimeAxisView* tvp = clicked_trackview;
4866 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
4868 if (tv && tv->is_audio_track()) {
4869 speed = tv->get_diskstream()->speed();
4872 if (left_direction) {
4873 if (swap_direction) {
4874 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4875 } else {
4876 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4878 } else {
4879 if (swap_direction) {
4880 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4881 } else {
4882 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4886 if (obey_snap) {
4887 snap_to (new_bound);
4889 region->trim_start ((nframes64_t) (new_bound * speed), this);
4890 rv.region_changed (StartChanged);
4893 void
4894 Editor::single_start_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap)
4896 boost::shared_ptr<Region> region (rv.region());
4898 if (region->locked()) {
4899 return;
4902 nframes64_t new_bound;
4904 double speed = 1.0;
4905 TimeAxisView* tvp = clicked_trackview;
4906 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4908 if (tv && tv->is_audio_track()) {
4909 speed = tv->get_diskstream()->speed();
4912 if (left_direction) {
4913 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4914 } else {
4915 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4918 if (obey_snap) {
4919 snap_to (new_bound, (left_direction ? 0 : 1));
4922 region->trim_front ((nframes64_t) (new_bound * speed), this);
4924 rv.region_changed (Change (LengthChanged|PositionChanged|StartChanged));
4927 void
4928 Editor::single_end_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap)
4930 boost::shared_ptr<Region> region (rv.region());
4932 if (region->locked()) {
4933 return;
4936 nframes64_t new_bound;
4938 double speed = 1.0;
4939 TimeAxisView* tvp = clicked_trackview;
4940 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4942 if (tv && tv->is_audio_track()) {
4943 speed = tv->get_diskstream()->speed();
4946 if (left_direction) {
4947 new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) - frame_delta;
4948 } else {
4949 new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) + frame_delta;
4952 if (obey_snap) {
4953 snap_to (new_bound);
4955 region->trim_end ((nframes64_t) (new_bound * speed), this);
4956 rv.region_changed (LengthChanged);
4959 void
4960 Editor::trim_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
4962 if (!drag_info.first_move) {
4963 trim_motion_callback (item, event);
4965 if (!selection->selected (clicked_regionview)) {
4966 thaw_region_after_trim (*clicked_regionview);
4967 } else {
4969 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
4970 i != selection->regions.by_layer().end(); ++i)
4972 thaw_region_after_trim (**i);
4973 (*i)->fake_set_opaque (true);
4977 for (set<boost::shared_ptr<Playlist> >::iterator p = motion_frozen_playlists.begin(); p != motion_frozen_playlists.end(); ++p) {
4978 //(*p)->thaw ();
4979 session->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
4982 motion_frozen_playlists.clear ();
4984 commit_reversible_command();
4985 } else {
4986 /* no mouse movement */
4987 point_trim (event);
4991 void
4992 Editor::point_trim (GdkEvent* event)
4994 RegionView* rv = clicked_regionview;
4995 nframes64_t new_bound = drag_info.current_pointer_frame;
4997 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
4998 snap_to (new_bound);
5001 /* Choose action dependant on which button was pressed */
5002 switch (event->button.button) {
5003 case 1:
5004 trim_op = StartTrim;
5005 begin_reversible_command (_("Start point trim"));
5007 if (selection->selected (rv)) {
5009 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
5010 i != selection->regions.by_layer().end(); ++i)
5012 if (!(*i)->region()->locked()) {
5013 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
5014 XMLNode &before = pl->get_state();
5015 (*i)->region()->trim_front (new_bound, this);
5016 XMLNode &after = pl->get_state();
5017 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5021 } else {
5023 if (!rv->region()->locked()) {
5024 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
5025 XMLNode &before = pl->get_state();
5026 rv->region()->trim_front (new_bound, this);
5027 XMLNode &after = pl->get_state();
5028 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5032 commit_reversible_command();
5034 break;
5035 case 2:
5036 trim_op = EndTrim;
5037 begin_reversible_command (_("End point trim"));
5039 if (selection->selected (rv)) {
5041 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
5043 if (!(*i)->region()->locked()) {
5044 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
5045 XMLNode &before = pl->get_state();
5046 (*i)->region()->trim_end (new_bound, this);
5047 XMLNode &after = pl->get_state();
5048 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5052 } else {
5054 if (!rv->region()->locked()) {
5055 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
5056 XMLNode &before = pl->get_state();
5057 rv->region()->trim_end (new_bound, this);
5058 XMLNode &after = pl->get_state();
5059 session->add_command (new MementoCommand<Playlist>(*pl.get(), &before, &after));
5063 commit_reversible_command();
5065 break;
5066 default:
5067 break;
5071 void
5072 Editor::thaw_region_after_trim (RegionView& rv)
5074 boost::shared_ptr<Region> region (rv.region());
5076 if (region->locked()) {
5077 return;
5080 region->thaw (_("trimmed region"));
5081 XMLNode &after = region->playlist()->get_state();
5082 session->add_command (new MementoCommand<Playlist>(*(region->playlist()), 0, &after));
5084 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(&rv);
5085 if (arv)
5086 arv->unhide_envelope ();
5089 void
5090 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* event)
5092 Marker* marker;
5093 bool is_start;
5095 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
5096 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
5097 /*NOTREACHED*/
5100 Location* location = find_location_from_marker (marker, is_start);
5101 location->set_hidden (true, this);
5105 void
5106 Editor::start_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event, RangeMarkerOp op)
5108 if (session == 0) {
5109 return;
5112 drag_info.item = item;
5113 drag_info.motion_callback = &Editor::drag_range_markerbar_op;
5114 drag_info.finished_callback = &Editor::end_range_markerbar_op;
5116 range_marker_op = op;
5118 if (!temp_location) {
5119 temp_location = new Location;
5122 switch (op) {
5123 case CreateRangeMarker:
5124 case CreateTransportMarker:
5125 case CreateCDMarker:
5127 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
5128 drag_info.copy = true;
5129 } else {
5130 drag_info.copy = false;
5132 start_grab (event, selector_cursor);
5133 break;
5136 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
5140 void
5141 Editor::drag_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event)
5143 nframes64_t start = 0;
5144 nframes64_t end = 0;
5145 ArdourCanvas::SimpleRect *crect;
5147 switch (range_marker_op) {
5148 case CreateRangeMarker:
5149 crect = range_bar_drag_rect;
5150 break;
5151 case CreateTransportMarker:
5152 crect = transport_bar_drag_rect;
5153 break;
5154 case CreateCDMarker:
5155 crect = cd_marker_bar_drag_rect;
5156 break;
5157 default:
5158 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
5159 return;
5160 break;
5163 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5164 snap_to (drag_info.current_pointer_frame);
5167 /* only alter selection if the current frame is
5168 different from the last frame position.
5171 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return;
5173 switch (range_marker_op) {
5174 case CreateRangeMarker:
5175 case CreateTransportMarker:
5176 case CreateCDMarker:
5177 if (drag_info.first_move) {
5178 snap_to (drag_info.grab_frame);
5181 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5182 start = drag_info.current_pointer_frame;
5183 end = drag_info.grab_frame;
5184 } else {
5185 end = drag_info.current_pointer_frame;
5186 start = drag_info.grab_frame;
5189 /* first drag: Either add to the selection
5190 or create a new selection.
5193 if (drag_info.first_move) {
5195 temp_location->set (start, end);
5197 crect->show ();
5199 update_marker_drag_item (temp_location);
5200 range_marker_drag_rect->show();
5201 //range_marker_drag_rect->raise_to_top();
5204 break;
5207 if (event->button.x >= horizontal_adjustment.get_value() + canvas_width) {
5208 start_canvas_autoscroll (1, 0);
5211 if (start != end) {
5212 temp_location->set (start, end);
5214 double x1 = frame_to_pixel (start);
5215 double x2 = frame_to_pixel (end);
5216 crect->property_x1() = x1;
5217 crect->property_x2() = x2;
5219 update_marker_drag_item (temp_location);
5222 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5223 drag_info.first_move = false;
5225 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
5229 void
5230 Editor::end_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event)
5232 Location * newloc = 0;
5233 string rangename;
5234 int flags;
5236 if (!drag_info.first_move) {
5237 drag_range_markerbar_op (item, event);
5239 switch (range_marker_op) {
5240 case CreateRangeMarker:
5241 case CreateCDMarker:
5243 begin_reversible_command (_("new range marker"));
5244 XMLNode &before = session->locations()->get_state();
5245 session->locations()->next_available_name(rangename,"unnamed");
5246 if (range_marker_op == CreateCDMarker) {
5247 flags = Location::IsRangeMarker|Location::IsCDMarker;
5248 cd_marker_bar_drag_rect->hide();
5250 else {
5251 flags = Location::IsRangeMarker;
5252 range_bar_drag_rect->hide();
5254 newloc = new Location(temp_location->start(), temp_location->end(), rangename, (Location::Flags) flags);
5255 session->locations()->add (newloc, true);
5256 XMLNode &after = session->locations()->get_state();
5257 session->add_command(new MementoCommand<Locations>(*(session->locations()), &before, &after));
5258 commit_reversible_command ();
5260 range_marker_drag_rect->hide();
5261 break;
5264 case CreateTransportMarker:
5265 // popup menu to pick loop or punch
5266 new_transport_marker_context_menu (&event->button, item);
5268 break;
5270 } else {
5271 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5273 if (Keyboard::no_modifier_keys_pressed (&event->button) && range_marker_op != CreateCDMarker) {
5275 nframes64_t start;
5276 nframes64_t end;
5278 start = session->locations()->first_mark_before (drag_info.grab_frame);
5279 end = session->locations()->first_mark_after (drag_info.grab_frame);
5281 if (end == max_frames) {
5282 end = session->current_end_frame ();
5285 if (start == 0) {
5286 start = session->current_start_frame ();
5289 switch (mouse_mode) {
5290 case MouseObject:
5291 /* find the two markers on either side and then make the selection from it */
5292 select_all_within (start, end, 0.0f, FLT_MAX, track_views, Selection::Set);
5293 break;
5295 case MouseRange:
5296 /* find the two markers on either side of the click and make the range out of it */
5297 selection->set (0, start, end);
5298 break;
5300 default:
5301 break;
5306 stop_canvas_autoscroll ();
5311 void
5312 Editor::start_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5314 drag_info.item = item;
5315 drag_info.motion_callback = &Editor::drag_mouse_zoom;
5316 drag_info.finished_callback = &Editor::end_mouse_zoom;
5318 start_grab (event, zoom_cursor);
5320 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5323 void
5324 Editor::drag_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5326 nframes64_t start;
5327 nframes64_t end;
5329 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5330 snap_to (drag_info.current_pointer_frame);
5332 if (drag_info.first_move) {
5333 snap_to (drag_info.grab_frame);
5337 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return;
5339 /* base start and end on initial click position */
5340 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5341 start = drag_info.current_pointer_frame;
5342 end = drag_info.grab_frame;
5343 } else {
5344 end = drag_info.current_pointer_frame;
5345 start = drag_info.grab_frame;
5348 if (start != end) {
5350 if (drag_info.first_move) {
5351 zoom_rect->show();
5352 zoom_rect->raise_to_top();
5355 reposition_zoom_rect(start, end);
5357 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5358 drag_info.first_move = false;
5360 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5364 void
5365 Editor::end_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5367 if (!drag_info.first_move) {
5368 drag_mouse_zoom (item, event);
5370 if (drag_info.grab_frame < drag_info.last_pointer_frame) {
5371 temporal_zoom_by_frame (drag_info.grab_frame, drag_info.last_pointer_frame, "mouse zoom");
5372 } else {
5373 temporal_zoom_by_frame (drag_info.last_pointer_frame, drag_info.grab_frame, "mouse zoom");
5375 } else {
5376 temporal_zoom_to_frame (false, drag_info.grab_frame);
5378 temporal_zoom_step (false);
5379 center_screen (drag_info.grab_frame);
5383 zoom_rect->hide();
5386 void
5387 Editor::reposition_zoom_rect (nframes64_t start, nframes64_t end)
5389 double x1 = frame_to_pixel (start);
5390 double x2 = frame_to_pixel (end);
5391 double y2 = full_canvas_height - 1.0;
5393 zoom_rect->property_x1() = x1;
5394 zoom_rect->property_y1() = 1.0;
5395 zoom_rect->property_x2() = x2;
5396 zoom_rect->property_y2() = y2;
5399 void
5400 Editor::start_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5402 drag_info.item = item;
5403 drag_info.motion_callback = &Editor::drag_rubberband_select;
5404 drag_info.finished_callback = &Editor::end_rubberband_select;
5406 start_grab (event, cross_hair_cursor);
5408 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5411 void
5412 Editor::drag_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5414 nframes64_t start;
5415 nframes64_t end;
5416 double y1;
5417 double y2;
5419 /* use a bigger drag threshold than the default */
5421 if (abs ((int) (drag_info.current_pointer_frame - drag_info.grab_frame)) < 8) {
5422 return;
5425 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) && Config->get_rubberbanding_snaps_to_grid()) {
5426 if (drag_info.first_move) {
5427 snap_to (drag_info.grab_frame);
5429 snap_to (drag_info.current_pointer_frame);
5432 /* base start and end on initial click position */
5434 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5435 start = drag_info.current_pointer_frame;
5436 end = drag_info.grab_frame;
5437 } else {
5438 end = drag_info.current_pointer_frame;
5439 start = drag_info.grab_frame;
5442 if (drag_info.current_pointer_y < drag_info.grab_y) {
5443 y1 = drag_info.current_pointer_y;
5444 y2 = drag_info.grab_y;
5445 } else {
5446 y2 = drag_info.current_pointer_y;
5447 y1 = drag_info.grab_y;
5451 if (start != end || y1 != y2) {
5453 double x1 = frame_to_pixel (start);
5454 double x2 = frame_to_pixel (end);
5456 rubberband_rect->property_x1() = x1;
5457 rubberband_rect->property_y1() = y1;
5458 rubberband_rect->property_x2() = x2;
5459 rubberband_rect->property_y2() = y2;
5461 rubberband_rect->show();
5462 rubberband_rect->raise_to_top();
5464 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5465 drag_info.first_move = false;
5467 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5471 void
5472 Editor::end_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5474 if (!drag_info.first_move) {
5476 drag_rubberband_select (item, event);
5478 double y1,y2;
5479 if (drag_info.current_pointer_y < drag_info.grab_y) {
5480 y1 = drag_info.current_pointer_y;
5481 y2 = drag_info.grab_y;
5482 } else {
5483 y2 = drag_info.current_pointer_y;
5484 y1 = drag_info.grab_y;
5488 Selection::Operation op = Keyboard::selection_type (event->button.state);
5489 bool commit;
5491 begin_reversible_command (_("rubberband selection"));
5493 if (drag_info.grab_frame < drag_info.last_pointer_frame) {
5494 commit = select_all_within (drag_info.grab_frame, drag_info.last_pointer_frame, y1, y2, track_views, op);
5495 } else {
5496 commit = select_all_within (drag_info.last_pointer_frame, drag_info.grab_frame, y1, y2, track_views, op);
5499 if (commit) {
5500 commit_reversible_command ();
5503 } else {
5504 if (!getenv("ARDOUR_SAE")) {
5505 selection->clear_tracks();
5507 selection->clear_regions();
5508 selection->clear_points ();
5509 selection->clear_lines ();
5512 rubberband_rect->hide();
5516 gint
5517 Editor::mouse_rename_region (ArdourCanvas::Item* item, GdkEvent* event)
5519 using namespace Gtkmm2ext;
5521 ArdourPrompter prompter (false);
5523 prompter.set_prompt (_("Name for region:"));
5524 prompter.set_initial_text (clicked_regionview->region()->name());
5525 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
5526 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
5527 prompter.show_all ();
5528 switch (prompter.run ()) {
5529 case Gtk::RESPONSE_ACCEPT:
5530 string str;
5531 prompter.get_result(str);
5532 if (str.length()) {
5533 clicked_regionview->region()->set_name (str);
5535 break;
5537 return true;
5540 void
5541 Editor::start_time_fx (ArdourCanvas::Item* item, GdkEvent* event)
5543 drag_info.item = item;
5544 drag_info.motion_callback = &Editor::time_fx_motion;
5545 drag_info.finished_callback = &Editor::end_time_fx;
5547 start_grab (event);
5549 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5552 void
5553 Editor::time_fx_motion (ArdourCanvas::Item *item, GdkEvent* event)
5555 RegionView* rv = clicked_regionview;
5557 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5558 snap_to (drag_info.current_pointer_frame);
5561 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) {
5562 return;
5565 if (drag_info.current_pointer_frame > rv->region()->position()) {
5566 rv->get_time_axis_view().show_timestretch (rv->region()->position(), drag_info.current_pointer_frame);
5569 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5570 drag_info.first_move = false;
5572 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5575 void
5576 Editor::end_time_fx (ArdourCanvas::Item* item, GdkEvent* event)
5578 clicked_regionview->get_time_axis_view().hide_timestretch ();
5580 if (drag_info.first_move) {
5581 return;
5584 if (drag_info.last_pointer_frame < clicked_regionview->region()->position()) {
5585 /* backwards drag of the left edge - not usable */
5586 return;
5589 nframes64_t newlen = drag_info.last_pointer_frame - clicked_regionview->region()->position();
5590 #ifdef USE_RUBBERBAND
5591 float percentage = (float) ((double) newlen / (double) clicked_regionview->region()->length());
5592 #else
5593 float percentage = (float) ((double) newlen - (double) clicked_regionview->region()->length()) / ((double) newlen) * 100.0f;
5594 #endif
5596 begin_reversible_command (_("timestretch"));
5598 // XXX how do timeFX on multiple regions ?
5600 RegionSelection rs;
5601 rs.add (clicked_regionview);
5603 if (time_stretch (rs, percentage) == 0) {
5604 session->commit_reversible_command ();
5608 void
5609 Editor::mouse_brush_insert_region (RegionView* rv, nframes64_t pos)
5611 /* no brushing without a useful snap setting */
5613 // FIXME
5614 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
5615 assert(arv);
5617 switch (snap_mode) {
5618 case SnapMagnetic:
5619 return; /* can't work because it allows region to be placed anywhere */
5620 default:
5621 break; /* OK */
5624 switch (snap_type) {
5625 case SnapToMark:
5626 return;
5628 default:
5629 break;
5632 /* don't brush a copy over the original */
5634 if (pos == rv->region()->position()) {
5635 return;
5638 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*>(&arv->get_time_axis_view());
5640 if (atv == 0 || !atv->is_audio_track()) {
5641 return;
5644 boost::shared_ptr<Playlist> playlist = atv->playlist();
5645 double speed = atv->get_diskstream()->speed();
5647 XMLNode &before = playlist->get_state();
5648 playlist->add_region (boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (arv->audio_region())), (nframes64_t) (pos * speed));
5649 XMLNode &after = playlist->get_state();
5650 session->add_command(new MementoCommand<Playlist>(*playlist.get(), &before, &after));
5652 // playlist is frozen, so we have to update manually
5654 playlist->Modified(); /* EMIT SIGNAL */
5657 gint
5658 Editor::track_height_step_timeout ()
5660 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
5661 current_stepping_trackview = 0;
5662 return false;
5664 return true;