fix keyboard event handling for host-provided plugin GUIs
[ardour2.git] / gtk2_ardour / editor_mouse.cc
blob15f8b235e27b8736f0d335c56a4268a734821a46
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 (op);
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 (op);
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 (op);
462 break;
464 case GainAutomationControlPointItem:
465 case PanAutomationControlPointItem:
466 case RedirectAutomationControlPointItem:
467 set_selected_track_as_side_effect (op);
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 (op);
477 } else if (event->type == GDK_BUTTON_PRESS && mouse_mode == MouseRange) {
478 set_selected_track_as_side_effect (op);
480 break;
482 case AutomationTrackItem:
483 set_selected_track_as_side_effect (op, 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::PrimaryModifier))) {
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::SecondaryModifier)) {
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 flush_canvas ();
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 flush_canvas ();
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 flush_canvas ();
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 flush_canvas ();
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);
3749 //test to make sure that we aren't dragging near 0
3750 if (selection->regions.by_layer().size() == 1) { // If a single regionview is being dragged to zero, make sure we go all the way to zero.
3751 RegionView* rv2 = *(selection->regions.by_layer().begin());
3752 double ix1, ix2, iy1, iy2;
3753 rv2->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3754 rv2->get_canvas_group()->i2w (ix1, iy1);
3755 double pos = ix1 + horizontal_adjustment.get_value();
3756 if (-x_delta > pos) {
3757 pending_region_position = 0;
3759 } else { // If any regionview is at zero, we need to know so we can stop further leftward motion.
3761 //first find the earliest region in the selection
3762 RegionView *earliest_rv = selection->regions.by_layer().front();
3763 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
3764 RegionView* rv = (*i);
3765 if (rv->region()->position() < earliest_rv->region()->position())
3766 earliest_rv = rv;
3769 //if the earliest region is near 0, then limit the drag so it doesn't go any farther left
3770 double ix1, ix2, iy1, iy2;
3771 earliest_rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3772 earliest_rv->get_canvas_group()->i2w (ix1, iy1);
3773 double pos = ix1 + horizontal_adjustment.get_value();
3774 if (x_delta < -pos) {
3775 x_delta = -pos;
3776 pending_region_position = clicked_regionview->region()->position() - earliest_rv->region()->position();
3782 drag_info.last_frame_position = pending_region_position;
3784 } else {
3785 x_delta = 0;
3788 } else {
3789 /* threshold not passed */
3791 x_delta = 0;
3794 /*************************************************************
3795 PREPARE TO MOVE
3796 ************************************************************/
3798 if (x_delta == 0 && (pointer_y_span == 0)) {
3799 /* haven't reached next snap point, and we're not switching
3800 trackviews. nothing to do.
3802 return;
3805 /*************************************************************
3806 MOTION
3807 ************************************************************/
3808 bool do_move = true;
3809 if (drag_info.first_move) {
3810 if (!drag_info.move_threshold_passed) {
3811 do_move = false;
3815 if (do_move) {
3817 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
3818 const list<RegionView*>& layered_regions = selection->regions.by_layer();
3820 for (list<RegionView*>::const_iterator i = layered_regions.begin(); i != layered_regions.end(); ++i) {
3822 RegionView* rv = (*i);
3823 double ix1, ix2, iy1, iy2;
3824 int32_t temp_pointer_y_span = pointer_y_span;
3826 if (rv->region()->locked()) {
3827 continue;
3830 /* get item BBox, which will be relative to parent. so we have
3831 to query on a child, then convert to world coordinates using
3832 the parent.
3835 rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
3836 rv->get_canvas_group()->i2w (ix1, iy1);
3838 /* for evaluation of the track position of iy1, we have to adjust
3839 to allow for the vertical scrolling adjustment and the height of the timebars.
3842 iy1 += get_trackview_group_vertical_offset ();
3843 if (drag_info.first_move) {
3845 // hide any dependent views
3847 rv->get_time_axis_view().hide_dependent_views (*rv);
3850 reparent to a non scrolling group so that we can keep the
3851 region selection above all time axis views.
3852 reparenting means we have to move the rv as the two
3853 parent groups have different coordinates.
3856 rv->get_canvas_group()->property_y() = iy1 - 1;
3857 rv->get_canvas_group()->reparent(*_region_motion_group);
3859 rv->fake_set_opaque (true);
3862 TimeAxisView* tvp2 = trackview_by_y_position (iy1);
3863 AudioTimeAxisView* canvas_atv = dynamic_cast<AudioTimeAxisView*>(tvp2);
3864 AudioTimeAxisView* temp_atv;
3866 if ((pointer_y_span != 0) && !clamp_y_axis) {
3867 y_delta = 0;
3868 int32_t x = 0;
3869 for (j = height_list.begin(); j!= height_list.end(); j++) {
3870 if (x == canvas_atv->order) {
3871 /* we found the track the region is on */
3872 if (x != original_pointer_order) {
3873 /*this isn't from the same track we're dragging from */
3874 temp_pointer_y_span = canvas_pointer_y_span;
3876 while (temp_pointer_y_span > 0) {
3877 /* we're moving up canvas-wise,
3878 so we need to find the next track height
3880 if (j != height_list.begin()) {
3881 j--;
3883 if (x != original_pointer_order) {
3884 /* we're not from the dragged track, so ignore hidden tracks. */
3885 if ((*j) == 0) {
3886 temp_pointer_y_span++;
3889 y_delta -= (*j);
3890 temp_pointer_y_span--;
3893 while (temp_pointer_y_span < 0) {
3894 y_delta += (*j);
3895 if (x != original_pointer_order) {
3896 if ((*j) == 0) {
3897 temp_pointer_y_span--;
3901 if (j != height_list.end()) {
3902 j++;
3904 temp_pointer_y_span++;
3906 /* find out where we'll be when we move and set height accordingly */
3908 tvp2 = trackview_by_y_position (iy1 + y_delta);
3909 temp_atv = dynamic_cast<AudioTimeAxisView*>(tvp2);
3910 rv->set_height (temp_atv->current_height());
3912 /* if you un-comment the following, the region colours will follow the track colours whilst dragging,
3913 personally, i think this can confuse things, but never mind.
3916 //const GdkColor& col (temp_atv->view->get_region_color());
3917 //rv->set_color (const_cast<GdkColor&>(col));
3918 break;
3920 x++;
3924 if (drag_info.brushing) {
3925 mouse_brush_insert_region (rv, pending_region_position);
3926 } else {
3927 rv->move (x_delta, y_delta);
3930 } /* foreach region */
3932 } /* if do_move */
3934 if (drag_info.first_move && drag_info.move_threshold_passed) {
3935 cursor_group->raise_to_top();
3936 drag_info.first_move = false;
3939 if (x_delta != 0 && !drag_info.brushing) {
3940 show_verbose_time_cursor (drag_info.last_frame_position, 10);
3944 void
3945 Editor::region_drag_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
3947 bool nocommit = true;
3948 vector<RegionView*> copies;
3949 RouteTimeAxisView* source_tv;
3950 boost::shared_ptr<Diskstream> ds;
3951 boost::shared_ptr<Playlist> from_playlist;
3952 vector<RegionView*> new_selection;
3953 typedef set<boost::shared_ptr<Playlist> > PlaylistSet;
3954 PlaylistSet modified_playlists;
3955 PlaylistSet frozen_playlists;
3956 list <sigc::connection> modified_playlist_connections;
3957 pair<PlaylistSet::iterator,bool> insert_result, frozen_insert_result;
3958 nframes64_t drag_delta;
3959 bool changed_tracks, changed_position;
3961 /* first_move is set to false if the regionview has been moved in the
3962 motion handler.
3965 if (drag_info.first_move) {
3966 /* just a click */
3967 goto out;
3970 nocommit = false;
3972 if (Config->get_edit_mode() == Splice && !pre_drag_region_selection.empty()) {
3973 selection->set (pre_drag_region_selection);
3974 pre_drag_region_selection.clear ();
3977 if (drag_info.brushing) {
3978 /* all changes were made during motion event handlers */
3980 if (drag_info.copy) {
3981 for (list<RegionView*>::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) {
3982 copies.push_back (*i);
3986 goto out;
3989 char* op_string;
3991 /* reverse this here so that we have the correct logic to finalize
3992 the drag.
3995 if (Config->get_edit_mode() == Lock) {
3996 drag_info.x_constrained = !drag_info.x_constrained;
3999 cerr << "drag done, copy ? " << drag_info.copy << " x-const ? " << drag_info.x_constrained << endl;
4001 if (drag_info.copy) {
4002 if (drag_info.x_constrained) {
4003 op_string = _("fixed time region copy");
4004 } else {
4005 op_string = _("region copy");
4007 } else {
4008 if (drag_info.x_constrained) {
4009 op_string = _("fixed time region drag");
4010 } else {
4011 op_string = _("region drag");
4015 begin_reversible_command (op_string);
4016 changed_position = (drag_info.last_frame_position != (nframes64_t) (clicked_regionview->region()->position()));
4017 changed_tracks = (trackview_by_y_position (drag_info.current_pointer_y) != &clicked_regionview->get_time_axis_view());
4019 drag_delta = clicked_regionview->region()->position() - drag_info.last_frame_position;
4021 flush_canvas ();
4023 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ) {
4025 RegionView* rv = (*i);
4026 double ix1, ix2, iy1, iy2;
4027 rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
4028 rv->get_canvas_group()->i2w (ix1, iy1);
4029 iy1 += vertical_adjustment.get_value() - canvas_timebars_vsize;
4031 TimeAxisView* dest_tv = trackview_by_y_position (iy1);
4032 AudioTimeAxisView* dest_atv = dynamic_cast<AudioTimeAxisView*>(dest_tv);
4033 nframes64_t where;
4035 if (rv->region()->locked()) {
4036 ++i;
4037 continue;
4040 cerr << "drag delta = " << drag_delta << " rpos was " << rv->region()->position() << endl;
4042 if (changed_position && !drag_info.x_constrained && (mouse_mode != MouseRange)) {
4043 where = rv->region()->position() - drag_delta;
4044 } else {
4045 where = rv->region()->position();
4048 boost::shared_ptr<Region> new_region;
4050 if (drag_info.copy) {
4051 /* we already made a copy */
4052 new_region = rv->region();
4054 /* undo the previous hide_dependent_views so that xfades don't
4055 disappear on copying regions
4058 //rv->get_time_axis_view().reveal_dependent_views (*rv);
4060 } else if (changed_tracks) {
4061 new_region = RegionFactory::create (rv->region());
4064 if (changed_tracks || drag_info.copy) {
4066 boost::shared_ptr<Playlist> to_playlist = dest_atv->playlist();
4068 latest_regionviews.clear ();
4070 sigc::connection c = dest_atv->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view));
4072 insert_result = modified_playlists.insert (to_playlist);
4073 if (insert_result.second) {
4074 session->add_command (new MementoCommand<Playlist>(*to_playlist, &to_playlist->get_state(), 0));
4077 cerr << "Adding region @ " << new_region->position() << " at " << where << endl;
4079 to_playlist->add_region (new_region, where);
4081 c.disconnect ();
4083 if (!latest_regionviews.empty()) {
4084 // XXX why just the first one ? we only expect one
4085 //dest_atv->reveal_dependent_views (*latest_regionviews.front());
4086 new_selection.push_back (latest_regionviews.front());
4089 } else {
4091 motion on the same track. plonk the previously reparented region
4092 back to its original canvas group (its streamview).
4093 No need to do anything for copies as they are fake regions which will be deleted.
4096 RouteTimeAxisView* dest_rtv = dynamic_cast<RouteTimeAxisView*> (dest_atv);
4097 rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
4098 rv->get_canvas_group()->property_y() = 0;
4100 /* just change the model */
4102 boost::shared_ptr<Playlist> playlist = dest_atv->playlist();
4104 insert_result = modified_playlists.insert (playlist);
4105 if (insert_result.second) {
4106 session->add_command (new MementoCommand<Playlist>(*playlist, &playlist->get_state(), 0));
4108 /* freeze to avoid lots of relayering in the case of a multi-region drag */
4109 frozen_insert_result = frozen_playlists.insert(playlist);
4110 if (frozen_insert_result.second) {
4111 playlist->freeze();
4114 rv->region()->set_position (where, (void*) this);
4117 if (changed_tracks && !drag_info.copy) {
4119 /* get the playlist where this drag started. we can't use rv->region()->playlist()
4120 because we may have copied the region and it has not been attached to a playlist.
4123 assert ((source_tv = dynamic_cast<RouteTimeAxisView*> (&rv->get_time_axis_view())));
4124 assert ((ds = source_tv->get_diskstream()));
4125 assert ((from_playlist = ds->playlist()));
4127 /* moved to a different audio track, without copying */
4129 /* the region that used to be in the old playlist is not
4130 moved to the new one - we use a copy of it. as a result,
4131 any existing editor for the region should no longer be
4132 visible.
4135 rv->hide_region_editor();
4136 rv->fake_set_opaque (false);
4138 /* remove the region from the old playlist */
4140 insert_result = modified_playlists.insert (from_playlist);
4141 if (insert_result.second) {
4142 if (mouse_mode != MouseRange) {
4143 session->add_command (new MementoCommand<Playlist>(*from_playlist, &from_playlist->get_state(), 0));
4147 from_playlist->remove_region ((rv->region()));
4149 /* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
4150 was selected in all of them, then removing it from a playlist will have removed all
4151 trace of it from the selection (i.e. there were N regions selected, we removed 1,
4152 but since its the same playlist for N tracks, all N tracks updated themselves, removed the
4153 corresponding regionview, and the selection is now empty).
4155 this could have invalidated any and all iterators into the region selection.
4157 the heuristic we use here is: if the region selection is empty, break out of the loop
4158 here. if the region selection is not empty, then restart the loop because we know that
4159 we must have removed at least the region(view) we've just been working on as well as any
4160 that we processed on previous iterations.
4162 EXCEPT .... if we are doing a copy drag, then the selection hasn't been modified and
4163 we can just iterate.
4166 if (selection->regions.empty()) {
4167 break;
4168 } else {
4169 i = selection->regions.by_layer().begin();
4172 } else {
4173 ++i;
4176 if (drag_info.copy) {
4177 copies.push_back (rv);
4181 if (new_selection.empty()) {
4182 if (drag_info.copy) {
4183 /* the region(view)s that are selected and being dragged around
4184 are copies and do not belong to any track. remove them
4185 from the selection right here.
4187 selection->clear_regions();
4189 } else {
4190 /* this will clear any existing selection that would have been
4191 cleared in the other clause above
4193 selection->set (new_selection);
4196 for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
4197 (*p)->thaw();
4200 out:
4201 if (!nocommit) {
4202 for (set<boost::shared_ptr<Playlist> >::iterator p = modified_playlists.begin(); p != modified_playlists.end(); ++p) {
4203 session->add_command (new MementoCommand<Playlist>(*(*p), 0, &(*p)->get_state()));
4205 commit_reversible_command ();
4208 for (vector<RegionView*>::iterator x = copies.begin(); x != copies.end(); ++x) {
4209 delete *x;
4214 void
4215 Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event)
4217 /* Either add to or set the set the region selection, unless
4218 this is an alignment click (control used)
4221 if (Keyboard::modifier_state_contains (event->state, Keyboard::PrimaryModifier)) {
4222 TimeAxisView* tv = &rv.get_time_axis_view();
4223 AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(tv);
4224 double speed = 1.0;
4225 if (atv && atv->is_audio_track()) {
4226 speed = atv->get_diskstream()->speed();
4229 nframes64_t where = get_preferred_edit_position();
4231 if (where >= 0) {
4233 if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
4235 align_region (rv.region(), SyncPoint, (nframes64_t) (where * speed));
4237 } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
4239 align_region (rv.region(), End, (nframes64_t) (where * speed));
4241 } else {
4243 align_region (rv.region(), Start, (nframes64_t) (where * speed));
4249 void
4250 Editor::show_verbose_time_cursor (nframes64_t frame, double offset, double xpos, double ypos)
4252 char buf[128];
4253 SMPTE::Time smpte;
4254 BBT_Time bbt;
4255 int hours, mins;
4256 nframes64_t frame_rate;
4257 float secs;
4259 if (session == 0) {
4260 return;
4263 AudioClock::Mode m;
4265 if (Profile->get_sae() || Profile->get_small_screen()) {
4266 m = ARDOUR_UI::instance()->primary_clock.mode();
4267 } else {
4268 m = ARDOUR_UI::instance()->secondary_clock.mode();
4271 switch (m) {
4272 case AudioClock::BBT:
4273 session->bbt_time (frame, bbt);
4274 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks);
4275 break;
4277 case AudioClock::SMPTE:
4278 session->smpte_time (frame, smpte);
4279 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
4280 break;
4282 case AudioClock::MinSec:
4283 /* XXX this is copied from show_verbose_duration_cursor() */
4284 frame_rate = session->frame_rate();
4285 hours = frame / (frame_rate * 3600);
4286 frame = frame % (frame_rate * 3600);
4287 mins = frame / (frame_rate * 60);
4288 frame = frame % (frame_rate * 60);
4289 secs = (float) frame / (float) frame_rate;
4290 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
4291 break;
4293 default:
4294 snprintf (buf, sizeof(buf), "%" PRIi64, frame);
4295 break;
4298 if (xpos >= 0 && ypos >=0) {
4299 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
4301 else {
4302 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);
4304 show_verbose_canvas_cursor ();
4307 void
4308 Editor::show_verbose_duration_cursor (nframes64_t start, nframes64_t end, double offset, double xpos, double ypos)
4310 char buf[128];
4311 SMPTE::Time smpte;
4312 BBT_Time sbbt;
4313 BBT_Time ebbt;
4314 int hours, mins;
4315 nframes64_t distance, frame_rate;
4316 float secs;
4317 Meter meter_at_start(session->tempo_map().meter_at(start));
4319 if (session == 0) {
4320 return;
4323 AudioClock::Mode m;
4325 if (Profile->get_sae() || Profile->get_small_screen()) {
4326 m = ARDOUR_UI::instance()->primary_clock.mode ();
4327 } else {
4328 m = ARDOUR_UI::instance()->secondary_clock.mode ();
4331 switch (m) {
4332 case AudioClock::BBT:
4333 session->bbt_time (start, sbbt);
4334 session->bbt_time (end, ebbt);
4336 /* subtract */
4337 /* XXX this computation won't work well if the
4338 user makes a selection that spans any meter changes.
4341 ebbt.bars -= sbbt.bars;
4342 if (ebbt.beats >= sbbt.beats) {
4343 ebbt.beats -= sbbt.beats;
4344 } else {
4345 ebbt.bars--;
4346 ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats;
4348 if (ebbt.ticks >= sbbt.ticks) {
4349 ebbt.ticks -= sbbt.ticks;
4350 } else {
4351 ebbt.beats--;
4352 ebbt.ticks = int(Meter::ticks_per_beat) + ebbt.ticks - sbbt.ticks;
4355 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks);
4356 break;
4358 case AudioClock::SMPTE:
4359 session->smpte_duration (end - start, smpte);
4360 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames);
4361 break;
4363 case AudioClock::MinSec:
4364 /* XXX this stuff should be elsewhere.. */
4365 distance = end - start;
4366 frame_rate = session->frame_rate();
4367 hours = distance / (frame_rate * 3600);
4368 distance = distance % (frame_rate * 3600);
4369 mins = distance / (frame_rate * 60);
4370 distance = distance % (frame_rate * 60);
4371 secs = (float) distance / (float) frame_rate;
4372 snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", hours, mins, secs);
4373 break;
4375 default:
4376 snprintf (buf, sizeof(buf), "%" PRIi64, end - start);
4377 break;
4380 if (xpos >= 0 && ypos >=0) {
4381 set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset);
4383 else {
4384 set_verbose_canvas_cursor (buf, drag_info.current_pointer_x + offset, drag_info.current_pointer_y + offset);
4387 show_verbose_canvas_cursor ();
4390 void
4391 Editor::collect_new_region_view (RegionView* rv)
4393 latest_regionviews.push_back (rv);
4396 void
4397 Editor::start_selection_grab (ArdourCanvas::Item* item, GdkEvent* event)
4399 if (clicked_regionview == 0) {
4400 return;
4403 /* lets try to create new Region for the selection */
4405 vector<boost::shared_ptr<AudioRegion> > new_regions;
4406 create_region_from_selection (new_regions);
4408 if (new_regions.empty()) {
4409 return;
4412 cerr << "got " << new_regions.size() << " regions in selection grab\n";
4414 /* XXX fix me one day to use all new regions */
4416 boost::shared_ptr<Region> region (new_regions.front());
4418 /* add it to the current stream/playlist.
4420 tricky: the streamview for the track will add a new regionview. we will
4421 catch the signal it sends when it creates the regionview to
4422 set the regionview we want to then drag.
4425 latest_regionviews.clear();
4426 sigc::connection c = clicked_audio_trackview->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view));
4428 /* A selection grab currently creates two undo/redo operations, one for
4429 creating the new region and another for moving it.
4432 begin_reversible_command (_("selection grab"));
4434 boost::shared_ptr<Playlist> playlist = clicked_trackview->playlist();
4436 XMLNode *before = &(playlist->get_state());
4437 clicked_trackview->playlist()->add_region (region, selection->time[clicked_selection].start);
4438 XMLNode *after = &(playlist->get_state());
4439 session->add_command(new MementoCommand<Playlist>(*playlist, before, after));
4441 commit_reversible_command ();
4443 c.disconnect ();
4445 if (latest_regionviews.empty()) {
4446 /* something went wrong */
4447 return;
4450 /* we need to deselect all other regionviews, and select this one
4451 i'm ignoring undo stuff, because the region creation will take care of it
4453 selection->set (latest_regionviews);
4455 drag_info.item = latest_regionviews.front()->get_canvas_group();
4456 drag_info.data = latest_regionviews.front();
4457 drag_info.motion_callback = &Editor::region_drag_motion_callback;
4458 drag_info.finished_callback = &Editor::region_drag_finished_callback;
4460 start_grab (event);
4462 drag_info.source_trackview = clicked_trackview;
4463 drag_info.dest_trackview = drag_info.source_trackview;
4464 drag_info.last_frame_position = latest_regionviews.front()->region()->position();
4465 drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position;
4467 show_verbose_time_cursor (drag_info.last_frame_position, 10);
4470 void
4471 Editor::cancel_selection ()
4473 for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
4474 (*i)->hide_selection ();
4476 selection->clear ();
4477 clicked_selection = 0;
4480 void
4481 Editor::start_selection_op (ArdourCanvas::Item* item, GdkEvent* event, SelectionOp op)
4483 nframes64_t start = 0;
4484 nframes64_t end = 0;
4486 if (session == 0) {
4487 return;
4490 drag_info.item = item;
4491 drag_info.motion_callback = &Editor::drag_selection;
4492 drag_info.finished_callback = &Editor::end_selection_op;
4494 selection_op = op;
4496 switch (op) {
4497 case CreateSelection:
4498 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
4499 drag_info.copy = true;
4500 } else {
4501 drag_info.copy = false;
4503 start_grab (event, selector_cursor);
4504 break;
4506 case SelectionStartTrim:
4507 if (clicked_trackview) {
4508 clicked_trackview->order_selection_trims (item, true);
4510 start_grab (event, trimmer_cursor);
4511 start = selection->time[clicked_selection].start;
4512 drag_info.pointer_frame_offset = drag_info.grab_frame - start;
4513 break;
4515 case SelectionEndTrim:
4516 if (clicked_trackview) {
4517 clicked_trackview->order_selection_trims (item, false);
4519 start_grab (event, trimmer_cursor);
4520 end = selection->time[clicked_selection].end;
4521 drag_info.pointer_frame_offset = drag_info.grab_frame - end;
4522 break;
4524 case SelectionMove:
4525 start = selection->time[clicked_selection].start;
4526 start_grab (event);
4527 drag_info.pointer_frame_offset = drag_info.grab_frame - start;
4528 break;
4531 if (selection_op == SelectionMove) {
4532 show_verbose_time_cursor(start, 10);
4533 } else {
4534 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4538 void
4539 Editor::drag_selection (ArdourCanvas::Item* item, GdkEvent* event)
4541 nframes64_t start = 0;
4542 nframes64_t end = 0;
4543 nframes64_t length;
4544 nframes64_t pending_position;
4546 if (drag_info.current_pointer_frame > drag_info.pointer_frame_offset) {
4547 pending_position = drag_info.current_pointer_frame - drag_info.pointer_frame_offset;
4548 } else {
4549 pending_position = 0;
4552 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
4553 snap_to (pending_position);
4556 /* only alter selection if the current frame is
4557 different from the last frame position (adjusted)
4560 if (pending_position == drag_info.last_pointer_frame) return;
4562 switch (selection_op) {
4563 case CreateSelection:
4565 if (drag_info.first_move) {
4566 snap_to (drag_info.grab_frame);
4569 if (pending_position < drag_info.grab_frame) {
4570 start = pending_position;
4571 end = drag_info.grab_frame;
4572 } else {
4573 end = pending_position;
4574 start = drag_info.grab_frame;
4577 /* first drag: Either add to the selection
4578 or create a new selection->
4581 if (drag_info.first_move) {
4583 begin_reversible_command (_("range selection"));
4585 if (drag_info.copy) {
4586 /* adding to the selection */
4587 clicked_selection = selection->add (start, end);
4588 drag_info.copy = false;
4589 } else {
4590 /* new selection-> */
4591 clicked_selection = selection->set (clicked_trackview, start, end);
4594 break;
4596 case SelectionStartTrim:
4598 if (drag_info.first_move) {
4599 begin_reversible_command (_("trim selection start"));
4602 start = selection->time[clicked_selection].start;
4603 end = selection->time[clicked_selection].end;
4605 if (pending_position > end) {
4606 start = end;
4607 } else {
4608 start = pending_position;
4610 break;
4612 case SelectionEndTrim:
4614 if (drag_info.first_move) {
4615 begin_reversible_command (_("trim selection end"));
4618 start = selection->time[clicked_selection].start;
4619 end = selection->time[clicked_selection].end;
4621 if (pending_position < start) {
4622 end = start;
4623 } else {
4624 end = pending_position;
4627 break;
4629 case SelectionMove:
4631 if (drag_info.first_move) {
4632 begin_reversible_command (_("move selection"));
4635 start = selection->time[clicked_selection].start;
4636 end = selection->time[clicked_selection].end;
4638 length = end - start;
4640 start = pending_position;
4641 snap_to (start);
4643 end = start + length;
4645 break;
4648 if (event->button.x >= horizontal_adjustment.get_value() + canvas_width) {
4649 start_canvas_autoscroll (1, 0);
4652 if (start != end) {
4653 selection->replace (clicked_selection, start, end);
4656 drag_info.last_pointer_frame = pending_position;
4657 drag_info.first_move = false;
4659 if (selection_op == SelectionMove) {
4660 show_verbose_time_cursor(start, 10);
4661 } else {
4662 show_verbose_time_cursor(pending_position, 10);
4666 void
4667 Editor::end_selection_op (ArdourCanvas::Item* item, GdkEvent* event)
4669 if (!drag_info.first_move) {
4670 drag_selection (item, event);
4671 /* XXX this is not object-oriented programming at all. ick */
4672 if (selection->time.consolidate()) {
4673 selection->TimeChanged ();
4675 commit_reversible_command ();
4678 /* XXX what if its a music time selection? */
4679 if (Config->get_auto_play() || (session->get_play_range() && session->transport_rolling())) {
4680 session->request_play_range (&selection->time, true);
4683 } else {
4684 /* just a click, no pointer movement.*/
4686 if (Keyboard::no_modifier_keys_pressed (&event->button)) {
4688 selection->clear_time();
4692 if (session->get_play_range () && session->transport_rolling()) {
4693 session->request_stop (false, false);
4697 stop_canvas_autoscroll ();
4700 void
4701 Editor::start_trim (ArdourCanvas::Item* item, GdkEvent* event)
4703 double speed = 1.0;
4704 TimeAxisView* tvp = clicked_trackview;
4705 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4707 if (tv && tv->is_audio_track()) {
4708 speed = tv->get_diskstream()->speed();
4711 nframes64_t region_start = (nframes64_t) (clicked_regionview->region()->position() / speed);
4712 nframes64_t region_end = (nframes64_t) (clicked_regionview->region()->last_frame() / speed);
4713 nframes64_t region_length = (nframes64_t) (clicked_regionview->region()->length() / speed);
4715 //drag_info.item = clicked_regionview->get_name_highlight();
4716 drag_info.item = item;
4717 drag_info.motion_callback = &Editor::trim_motion_callback;
4718 drag_info.finished_callback = &Editor::trim_finished_callback;
4720 start_grab (event, trimmer_cursor);
4722 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
4723 trim_op = ContentsTrim;
4724 } else {
4725 /* These will get overridden for a point trim.*/
4726 if (drag_info.current_pointer_frame < (region_start + region_length/2)) {
4727 /* closer to start */
4728 trim_op = StartTrim;
4729 } else if (drag_info.current_pointer_frame > (region_end - region_length/2)) {
4730 /* closer to end */
4731 trim_op = EndTrim;
4735 switch (trim_op) {
4736 case StartTrim:
4737 show_verbose_time_cursor(region_start, 10);
4738 break;
4739 case EndTrim:
4740 show_verbose_time_cursor(region_end, 10);
4741 break;
4742 case ContentsTrim:
4743 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4744 break;
4748 void
4749 Editor::trim_motion_callback (ArdourCanvas::Item* item, GdkEvent* event)
4751 RegionView* rv = clicked_regionview;
4752 nframes64_t frame_delta = 0;
4753 bool left_direction;
4754 bool obey_snap = !Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier());
4756 /* snap modifier works differently here..
4757 its' current state has to be passed to the
4758 various trim functions in order to work properly
4761 double speed = 1.0;
4762 TimeAxisView* tvp = clicked_trackview;
4763 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
4764 pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;
4766 if (tv && tv->is_audio_track()) {
4767 speed = tv->get_diskstream()->speed();
4770 if (drag_info.last_pointer_frame > drag_info.current_pointer_frame) {
4771 left_direction = true;
4772 } else {
4773 left_direction = false;
4776 if (obey_snap) {
4777 snap_to (drag_info.current_pointer_frame);
4780 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) {
4781 return;
4784 if (drag_info.first_move) {
4786 string trim_type;
4788 switch (trim_op) {
4789 case StartTrim:
4790 trim_type = "Region start trim";
4791 break;
4792 case EndTrim:
4793 trim_type = "Region end trim";
4794 break;
4795 case ContentsTrim:
4796 trim_type = "Region content trim";
4797 break;
4800 begin_reversible_command (trim_type);
4802 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4803 (*i)->fake_set_opaque(false);
4804 (*i)->region()->freeze ();
4806 AudioRegionView* const arv = dynamic_cast<AudioRegionView*>(*i);
4807 if (arv)
4808 arv->temporarily_hide_envelope ();
4810 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
4811 insert_result = motion_frozen_playlists.insert (pl);
4812 if (insert_result.second) {
4813 session->add_command(new MementoCommand<Playlist>(*pl, &pl->get_state(), 0));
4818 if (left_direction) {
4819 frame_delta = (drag_info.last_pointer_frame - drag_info.current_pointer_frame);
4820 } else {
4821 frame_delta = (drag_info.current_pointer_frame - drag_info.last_pointer_frame);
4824 switch (trim_op) {
4825 case StartTrim:
4826 if ((left_direction == false) && (drag_info.current_pointer_frame <= rv->region()->first_frame()/speed)) {
4827 break;
4828 } else {
4829 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4830 single_start_trim (**i, frame_delta, left_direction, obey_snap);
4832 break;
4835 case EndTrim:
4836 if ((left_direction == true) && (drag_info.current_pointer_frame > (nframes64_t) (rv->region()->last_frame()/speed))) {
4837 break;
4838 } else {
4839 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i) {
4840 single_end_trim (**i, frame_delta, left_direction, obey_snap);
4842 break;
4845 case ContentsTrim:
4847 bool swap_direction = false;
4849 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
4850 swap_direction = true;
4853 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
4854 i != selection->regions.by_layer().end(); ++i)
4856 single_contents_trim (**i, frame_delta, left_direction, swap_direction, obey_snap);
4859 break;
4862 switch (trim_op) {
4863 case StartTrim:
4864 show_verbose_time_cursor((nframes64_t) (rv->region()->position()/speed), 10);
4865 break;
4866 case EndTrim:
4867 show_verbose_time_cursor((nframes64_t) (rv->region()->last_frame()/speed), 10);
4868 break;
4869 case ContentsTrim:
4870 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
4871 break;
4874 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
4875 drag_info.first_move = false;
4878 void
4879 Editor::single_contents_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool swap_direction, bool obey_snap)
4881 boost::shared_ptr<Region> region (rv.region());
4883 if (region->locked()) {
4884 return;
4887 nframes64_t new_bound;
4889 double speed = 1.0;
4890 TimeAxisView* tvp = clicked_trackview;
4891 RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
4893 if (tv && tv->is_audio_track()) {
4894 speed = tv->get_diskstream()->speed();
4897 if (left_direction) {
4898 if (swap_direction) {
4899 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4900 } else {
4901 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4903 } else {
4904 if (swap_direction) {
4905 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4906 } else {
4907 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4911 if (obey_snap) {
4912 snap_to (new_bound);
4914 region->trim_start ((nframes64_t) (new_bound * speed), this);
4915 rv.region_changed (StartChanged);
4918 void
4919 Editor::single_start_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap)
4921 boost::shared_ptr<Region> region (rv.region());
4923 if (region->locked()) {
4924 return;
4927 nframes64_t new_bound;
4929 double speed = 1.0;
4930 TimeAxisView* tvp = clicked_trackview;
4931 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4933 if (tv && tv->is_audio_track()) {
4934 speed = tv->get_diskstream()->speed();
4937 if (left_direction) {
4938 new_bound = (nframes64_t) (region->position()/speed) - frame_delta;
4939 } else {
4940 new_bound = (nframes64_t) (region->position()/speed) + frame_delta;
4943 if (obey_snap) {
4944 snap_to (new_bound, (left_direction ? 0 : 1));
4947 region->trim_front ((nframes64_t) (new_bound * speed), this);
4949 rv.region_changed (Change (LengthChanged|PositionChanged|StartChanged));
4952 void
4953 Editor::single_end_trim (RegionView& rv, nframes64_t frame_delta, bool left_direction, bool obey_snap)
4955 boost::shared_ptr<Region> region (rv.region());
4957 if (region->locked()) {
4958 return;
4961 nframes64_t new_bound;
4963 double speed = 1.0;
4964 TimeAxisView* tvp = clicked_trackview;
4965 AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp);
4967 if (tv && tv->is_audio_track()) {
4968 speed = tv->get_diskstream()->speed();
4971 if (left_direction) {
4972 new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) - frame_delta;
4973 } else {
4974 new_bound = (nframes64_t) ((region->last_frame() + 1)/speed) + frame_delta;
4977 if (obey_snap) {
4978 snap_to (new_bound);
4980 region->trim_end ((nframes64_t) (new_bound * speed), this);
4981 rv.region_changed (LengthChanged);
4984 void
4985 Editor::trim_finished_callback (ArdourCanvas::Item* item, GdkEvent* event)
4987 if (!drag_info.first_move) {
4988 trim_motion_callback (item, event);
4990 if (!selection->selected (clicked_regionview)) {
4991 thaw_region_after_trim (*clicked_regionview);
4992 } else {
4994 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
4995 i != selection->regions.by_layer().end(); ++i)
4997 thaw_region_after_trim (**i);
4998 (*i)->fake_set_opaque (true);
5002 for (set<boost::shared_ptr<Playlist> >::iterator p = motion_frozen_playlists.begin(); p != motion_frozen_playlists.end(); ++p) {
5003 //(*p)->thaw ();
5004 session->add_command (new MementoCommand<Playlist>(*(*p).get(), 0, &(*p)->get_state()));
5007 motion_frozen_playlists.clear ();
5009 commit_reversible_command();
5010 } else {
5011 /* no mouse movement */
5012 point_trim (event);
5016 void
5017 Editor::point_trim (GdkEvent* event)
5019 RegionView* rv = clicked_regionview;
5020 nframes64_t new_bound = drag_info.current_pointer_frame;
5022 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5023 snap_to (new_bound);
5026 /* Choose action dependant on which button was pressed */
5027 switch (event->button.button) {
5028 case 1:
5029 trim_op = StartTrim;
5030 begin_reversible_command (_("Start point trim"));
5032 if (selection->selected (rv)) {
5034 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin();
5035 i != selection->regions.by_layer().end(); ++i)
5037 if (!(*i)->region()->locked()) {
5038 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
5039 XMLNode &before = pl->get_state();
5040 (*i)->region()->trim_front (new_bound, this);
5041 XMLNode &after = pl->get_state();
5042 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5046 } else {
5048 if (!rv->region()->locked()) {
5049 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
5050 XMLNode &before = pl->get_state();
5051 rv->region()->trim_front (new_bound, this);
5052 XMLNode &after = pl->get_state();
5053 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5057 commit_reversible_command();
5059 break;
5060 case 2:
5061 trim_op = EndTrim;
5062 begin_reversible_command (_("End point trim"));
5064 if (selection->selected (rv)) {
5066 for (list<RegionView*>::const_iterator i = selection->regions.by_layer().begin(); i != selection->regions.by_layer().end(); ++i)
5068 if (!(*i)->region()->locked()) {
5069 boost::shared_ptr<Playlist> pl = (*i)->region()->playlist();
5070 XMLNode &before = pl->get_state();
5071 (*i)->region()->trim_end (new_bound, this);
5072 XMLNode &after = pl->get_state();
5073 session->add_command(new MementoCommand<Playlist>(*pl.get(), &before, &after));
5077 } else {
5079 if (!rv->region()->locked()) {
5080 boost::shared_ptr<Playlist> pl = rv->region()->playlist();
5081 XMLNode &before = pl->get_state();
5082 rv->region()->trim_end (new_bound, this);
5083 XMLNode &after = pl->get_state();
5084 session->add_command (new MementoCommand<Playlist>(*pl.get(), &before, &after));
5088 commit_reversible_command();
5090 break;
5091 default:
5092 break;
5096 void
5097 Editor::thaw_region_after_trim (RegionView& rv)
5099 boost::shared_ptr<Region> region (rv.region());
5101 if (region->locked()) {
5102 return;
5105 region->thaw (_("trimmed region"));
5106 XMLNode &after = region->playlist()->get_state();
5107 session->add_command (new MementoCommand<Playlist>(*(region->playlist()), 0, &after));
5109 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(&rv);
5110 if (arv)
5111 arv->unhide_envelope ();
5114 void
5115 Editor::hide_marker (ArdourCanvas::Item* item, GdkEvent* event)
5117 Marker* marker;
5118 bool is_start;
5120 if ((marker = static_cast<Marker *> (item->get_data ("marker"))) == 0) {
5121 fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg;
5122 /*NOTREACHED*/
5125 Location* location = find_location_from_marker (marker, is_start);
5126 location->set_hidden (true, this);
5130 void
5131 Editor::start_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event, RangeMarkerOp op)
5133 if (session == 0) {
5134 return;
5137 drag_info.item = item;
5138 drag_info.motion_callback = &Editor::drag_range_markerbar_op;
5139 drag_info.finished_callback = &Editor::end_range_markerbar_op;
5141 range_marker_op = op;
5143 if (!temp_location) {
5144 temp_location = new Location;
5147 switch (op) {
5148 case CreateRangeMarker:
5149 case CreateTransportMarker:
5150 case CreateCDMarker:
5152 if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
5153 drag_info.copy = true;
5154 } else {
5155 drag_info.copy = false;
5157 start_grab (event, selector_cursor);
5158 break;
5161 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
5165 void
5166 Editor::drag_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event)
5168 nframes64_t start = 0;
5169 nframes64_t end = 0;
5170 ArdourCanvas::SimpleRect *crect;
5172 switch (range_marker_op) {
5173 case CreateRangeMarker:
5174 crect = range_bar_drag_rect;
5175 break;
5176 case CreateTransportMarker:
5177 crect = transport_bar_drag_rect;
5178 break;
5179 case CreateCDMarker:
5180 crect = cd_marker_bar_drag_rect;
5181 break;
5182 default:
5183 cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
5184 return;
5185 break;
5188 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5189 snap_to (drag_info.current_pointer_frame);
5192 /* only alter selection if the current frame is
5193 different from the last frame position.
5196 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return;
5198 switch (range_marker_op) {
5199 case CreateRangeMarker:
5200 case CreateTransportMarker:
5201 case CreateCDMarker:
5202 if (drag_info.first_move) {
5203 snap_to (drag_info.grab_frame);
5206 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5207 start = drag_info.current_pointer_frame;
5208 end = drag_info.grab_frame;
5209 } else {
5210 end = drag_info.current_pointer_frame;
5211 start = drag_info.grab_frame;
5214 /* first drag: Either add to the selection
5215 or create a new selection.
5218 if (drag_info.first_move) {
5220 temp_location->set (start, end);
5222 crect->show ();
5224 update_marker_drag_item (temp_location);
5225 range_marker_drag_rect->show();
5226 //range_marker_drag_rect->raise_to_top();
5229 break;
5232 if (event->button.x >= horizontal_adjustment.get_value() + canvas_width) {
5233 start_canvas_autoscroll (1, 0);
5236 if (start != end) {
5237 temp_location->set (start, end);
5239 double x1 = frame_to_pixel (start);
5240 double x2 = frame_to_pixel (end);
5241 crect->property_x1() = x1;
5242 crect->property_x2() = x2;
5244 update_marker_drag_item (temp_location);
5247 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5248 drag_info.first_move = false;
5250 show_verbose_time_cursor(drag_info.current_pointer_frame, 10);
5254 void
5255 Editor::end_range_markerbar_op (ArdourCanvas::Item* item, GdkEvent* event)
5257 Location * newloc = 0;
5258 string rangename;
5259 int flags;
5261 if (!drag_info.first_move) {
5262 drag_range_markerbar_op (item, event);
5264 switch (range_marker_op) {
5265 case CreateRangeMarker:
5266 case CreateCDMarker:
5268 begin_reversible_command (_("new range marker"));
5269 XMLNode &before = session->locations()->get_state();
5270 session->locations()->next_available_name(rangename,"unnamed");
5271 if (range_marker_op == CreateCDMarker) {
5272 flags = Location::IsRangeMarker|Location::IsCDMarker;
5273 cd_marker_bar_drag_rect->hide();
5275 else {
5276 flags = Location::IsRangeMarker;
5277 range_bar_drag_rect->hide();
5279 newloc = new Location(temp_location->start(), temp_location->end(), rangename, (Location::Flags) flags);
5280 session->locations()->add (newloc, true);
5281 XMLNode &after = session->locations()->get_state();
5282 session->add_command(new MementoCommand<Locations>(*(session->locations()), &before, &after));
5283 commit_reversible_command ();
5285 range_marker_drag_rect->hide();
5286 break;
5289 case CreateTransportMarker:
5290 // popup menu to pick loop or punch
5291 new_transport_marker_context_menu (&event->button, item);
5293 break;
5295 } else {
5296 /* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */
5298 if (Keyboard::no_modifier_keys_pressed (&event->button) && range_marker_op != CreateCDMarker) {
5300 nframes64_t start;
5301 nframes64_t end;
5303 start = session->locations()->first_mark_before (drag_info.grab_frame);
5304 end = session->locations()->first_mark_after (drag_info.grab_frame);
5306 if (end == max_frames) {
5307 end = session->current_end_frame ();
5310 if (start == 0) {
5311 start = session->current_start_frame ();
5314 switch (mouse_mode) {
5315 case MouseObject:
5316 /* find the two markers on either side and then make the selection from it */
5317 select_all_within (start, end, 0.0f, FLT_MAX, track_views, Selection::Set);
5318 break;
5320 case MouseRange:
5321 /* find the two markers on either side of the click and make the range out of it */
5322 selection->set (0, start, end);
5323 break;
5325 default:
5326 break;
5331 stop_canvas_autoscroll ();
5336 void
5337 Editor::start_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5339 drag_info.item = item;
5340 drag_info.motion_callback = &Editor::drag_mouse_zoom;
5341 drag_info.finished_callback = &Editor::end_mouse_zoom;
5343 start_grab (event, zoom_cursor);
5345 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5348 void
5349 Editor::drag_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5351 nframes64_t start;
5352 nframes64_t end;
5354 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5355 snap_to (drag_info.current_pointer_frame);
5357 if (drag_info.first_move) {
5358 snap_to (drag_info.grab_frame);
5362 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return;
5364 /* base start and end on initial click position */
5365 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5366 start = drag_info.current_pointer_frame;
5367 end = drag_info.grab_frame;
5368 } else {
5369 end = drag_info.current_pointer_frame;
5370 start = drag_info.grab_frame;
5373 if (start != end) {
5375 if (drag_info.first_move) {
5376 zoom_rect->show();
5377 zoom_rect->raise_to_top();
5380 reposition_zoom_rect(start, end);
5382 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5383 drag_info.first_move = false;
5385 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5389 void
5390 Editor::end_mouse_zoom (ArdourCanvas::Item* item, GdkEvent* event)
5392 if (!drag_info.first_move) {
5393 drag_mouse_zoom (item, event);
5395 if (drag_info.grab_frame < drag_info.last_pointer_frame) {
5396 temporal_zoom_by_frame (drag_info.grab_frame, drag_info.last_pointer_frame, "mouse zoom");
5397 } else {
5398 temporal_zoom_by_frame (drag_info.last_pointer_frame, drag_info.grab_frame, "mouse zoom");
5400 } else {
5401 temporal_zoom_to_frame (false, drag_info.grab_frame);
5403 temporal_zoom_step (false);
5404 center_screen (drag_info.grab_frame);
5408 zoom_rect->hide();
5411 void
5412 Editor::reposition_zoom_rect (nframes64_t start, nframes64_t end)
5414 double x1 = frame_to_pixel (start);
5415 double x2 = frame_to_pixel (end);
5416 double y2 = full_canvas_height - 1.0;
5418 zoom_rect->property_x1() = x1;
5419 zoom_rect->property_y1() = 1.0;
5420 zoom_rect->property_x2() = x2;
5421 zoom_rect->property_y2() = y2;
5424 void
5425 Editor::start_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5427 drag_info.item = item;
5428 drag_info.motion_callback = &Editor::drag_rubberband_select;
5429 drag_info.finished_callback = &Editor::end_rubberband_select;
5431 start_grab (event, cross_hair_cursor);
5433 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5436 void
5437 Editor::drag_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5439 nframes64_t start;
5440 nframes64_t end;
5441 double y1;
5442 double y2;
5444 /* use a bigger drag threshold than the default */
5446 if (abs ((int) (drag_info.current_pointer_frame - drag_info.grab_frame)) < 8) {
5447 return;
5450 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) && Config->get_rubberbanding_snaps_to_grid()) {
5451 if (drag_info.first_move) {
5452 snap_to (drag_info.grab_frame);
5454 snap_to (drag_info.current_pointer_frame);
5457 /* base start and end on initial click position */
5459 if (drag_info.current_pointer_frame < drag_info.grab_frame) {
5460 start = drag_info.current_pointer_frame;
5461 end = drag_info.grab_frame;
5462 } else {
5463 end = drag_info.current_pointer_frame;
5464 start = drag_info.grab_frame;
5467 if (drag_info.current_pointer_y < drag_info.grab_y) {
5468 y1 = drag_info.current_pointer_y;
5469 y2 = drag_info.grab_y;
5470 } else {
5471 y2 = drag_info.current_pointer_y;
5472 y1 = drag_info.grab_y;
5476 if (start != end || y1 != y2) {
5478 double x1 = frame_to_pixel (start);
5479 double x2 = frame_to_pixel (end);
5481 rubberband_rect->property_x1() = x1;
5482 rubberband_rect->property_y1() = y1;
5483 rubberband_rect->property_x2() = x2;
5484 rubberband_rect->property_y2() = y2;
5486 rubberband_rect->show();
5487 rubberband_rect->raise_to_top();
5489 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5490 drag_info.first_move = false;
5492 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5496 void
5497 Editor::end_rubberband_select (ArdourCanvas::Item* item, GdkEvent* event)
5499 if (!drag_info.first_move) {
5501 drag_rubberband_select (item, event);
5503 double y1,y2;
5504 if (drag_info.current_pointer_y < drag_info.grab_y) {
5505 y1 = drag_info.current_pointer_y;
5506 y2 = drag_info.grab_y;
5507 } else {
5508 y2 = drag_info.current_pointer_y;
5509 y1 = drag_info.grab_y;
5513 Selection::Operation op = Keyboard::selection_type (event->button.state);
5514 bool commit;
5516 begin_reversible_command (_("rubberband selection"));
5518 if (drag_info.grab_frame < drag_info.last_pointer_frame) {
5519 commit = select_all_within (drag_info.grab_frame, drag_info.last_pointer_frame, y1, y2, track_views, op);
5520 } else {
5521 commit = select_all_within (drag_info.last_pointer_frame, drag_info.grab_frame, y1, y2, track_views, op);
5524 if (commit) {
5525 commit_reversible_command ();
5528 } else {
5529 if (!getenv("ARDOUR_SAE")) {
5530 selection->clear_tracks();
5532 selection->clear_regions();
5533 selection->clear_points ();
5534 selection->clear_lines ();
5537 rubberband_rect->hide();
5541 gint
5542 Editor::mouse_rename_region (ArdourCanvas::Item* item, GdkEvent* event)
5544 using namespace Gtkmm2ext;
5546 ArdourPrompter prompter (false);
5548 prompter.set_prompt (_("Name for region:"));
5549 prompter.set_initial_text (clicked_regionview->region()->name());
5550 prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
5551 prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
5552 prompter.show_all ();
5553 switch (prompter.run ()) {
5554 case Gtk::RESPONSE_ACCEPT:
5555 string str;
5556 prompter.get_result(str);
5557 if (str.length()) {
5558 clicked_regionview->region()->set_name (str);
5560 break;
5562 return true;
5565 void
5566 Editor::start_time_fx (ArdourCanvas::Item* item, GdkEvent* event)
5568 drag_info.item = item;
5569 drag_info.motion_callback = &Editor::time_fx_motion;
5570 drag_info.finished_callback = &Editor::end_time_fx;
5572 start_grab (event);
5574 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5577 void
5578 Editor::time_fx_motion (ArdourCanvas::Item *item, GdkEvent* event)
5580 RegionView* rv = clicked_regionview;
5582 if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) {
5583 snap_to (drag_info.current_pointer_frame);
5586 if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) {
5587 return;
5590 if (drag_info.current_pointer_frame > rv->region()->position()) {
5591 rv->get_time_axis_view().show_timestretch (rv->region()->position(), drag_info.current_pointer_frame);
5594 drag_info.last_pointer_frame = drag_info.current_pointer_frame;
5595 drag_info.first_move = false;
5597 show_verbose_time_cursor (drag_info.current_pointer_frame, 10);
5600 void
5601 Editor::end_time_fx (ArdourCanvas::Item* item, GdkEvent* event)
5603 clicked_regionview->get_time_axis_view().hide_timestretch ();
5605 if (drag_info.first_move) {
5606 return;
5609 if (drag_info.last_pointer_frame < clicked_regionview->region()->position()) {
5610 /* backwards drag of the left edge - not usable */
5611 return;
5614 nframes64_t newlen = drag_info.last_pointer_frame - clicked_regionview->region()->position();
5615 #ifdef USE_RUBBERBAND
5616 float percentage = (float) ((double) newlen / (double) clicked_regionview->region()->length());
5617 #else
5618 float percentage = (float) ((double) newlen - (double) clicked_regionview->region()->length()) / ((double) newlen) * 100.0f;
5619 #endif
5621 begin_reversible_command (_("timestretch"));
5623 // XXX how do timeFX on multiple regions ?
5625 RegionSelection rs;
5626 rs.add (clicked_regionview);
5628 if (time_stretch (rs, percentage) == 0) {
5629 session->commit_reversible_command ();
5633 void
5634 Editor::mouse_brush_insert_region (RegionView* rv, nframes64_t pos)
5636 /* no brushing without a useful snap setting */
5638 // FIXME
5639 AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
5640 assert(arv);
5642 switch (snap_mode) {
5643 case SnapMagnetic:
5644 return; /* can't work because it allows region to be placed anywhere */
5645 default:
5646 break; /* OK */
5649 switch (snap_type) {
5650 case SnapToMark:
5651 return;
5653 default:
5654 break;
5657 /* don't brush a copy over the original */
5659 if (pos == rv->region()->position()) {
5660 return;
5663 RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*>(&arv->get_time_axis_view());
5665 if (atv == 0 || !atv->is_audio_track()) {
5666 return;
5669 boost::shared_ptr<Playlist> playlist = atv->playlist();
5670 double speed = atv->get_diskstream()->speed();
5672 XMLNode &before = playlist->get_state();
5673 playlist->add_region (boost::dynamic_pointer_cast<AudioRegion> (RegionFactory::create (arv->audio_region())), (nframes64_t) (pos * speed));
5674 XMLNode &after = playlist->get_state();
5675 session->add_command(new MementoCommand<Playlist>(*playlist.get(), &before, &after));
5677 // playlist is frozen, so we have to update manually
5679 playlist->Modified(); /* EMIT SIGNAL */
5682 gint
5683 Editor::track_height_step_timeout ()
5685 if (get_microseconds() - last_track_height_step_timestamp < 250000) {
5686 current_stepping_trackview = 0;
5687 return false;
5689 return true;